-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add locust for testing * add tahmo scenario test * update the user scenario tests * fix lint * remove unused docker-compose
- Loading branch information
1 parent
8c88362
commit 23c946e
Showing
14 changed files
with
719 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,3 +29,6 @@ docs/mkdocs.yml | |
|
||
# ignore .env in root project for vscode | ||
.env | ||
|
||
# locust auth files | ||
locust_auth.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# Tomorrow Now GAP Load Testing Using Locust | ||
|
||
## Description | ||
|
||
Load test using Locust. | ||
|
||
- Python based class | ||
- Easy to generate scenario test using python | ||
- Nice UI and charts that updates in real time | ||
|
||
|
||
## Authentication Config | ||
|
||
Create a json file under locust directory called `locust_auth.json`. | ||
Below is the sample: | ||
|
||
``` | ||
[ | ||
{ | ||
"username": "YOUR_USERNAME", | ||
"password": "YOUR_PASSWORD", | ||
"wait_time_start": null, | ||
"wait_time_end": null | ||
} | ||
] | ||
``` | ||
|
||
We can configure `wait_time_start` and `wait_time_end` for each user. If it is null, then the wait_time by default is a constant 1 second. | ||
|
||
|
||
## Usage: Virtual env | ||
|
||
1. Create virtual environment | ||
``` | ||
mkvirtualenv tn_locust | ||
``` | ||
|
||
Or activate existing virtual environment | ||
``` | ||
workon tn_locust | ||
``` | ||
|
||
2. Install locust | ||
``` | ||
pip3 install locust | ||
``` | ||
|
||
3. Run locust master | ||
``` | ||
locust -f weather --class-picker | ||
``` | ||
|
||
There are currently 4 task types: | ||
- `rand_var`: Random attributes length | ||
- `rand_out`: Random output_type | ||
- `rand_date`: Random date range | ||
- `rand_all`: Random all | ||
|
||
These types are represented as task tag, so we can filter out the task that we only want to run by using parameter in the command line. | ||
|
||
For example, we want to run task with random attributes length: | ||
``` | ||
locust -f weather --class-picker --tags rand_var | ||
``` | ||
|
||
The tags can also be configured in web ui for each UserClass. | ||
Web UI is available on http://localhost:8089/ | ||
|
||
|
||
## Usage: Docker Compose | ||
|
||
TODO: docker compose for running locust | ||
|
||
|
||
## Using Locust Web UI | ||
|
||
TODO: add screenshots. | ||
|
||
To start a new test: | ||
1. Pick one or more the User class | ||
2. (Optional) Configure tags in User class | ||
3. Set number of users | ||
4. Set ramp up | ||
5. Set the host | ||
6. (Advanced Options) Set maximum run time | ||
7. Click Start |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
# coding=utf-8 | ||
""" | ||
Tomorrow Now GAP. | ||
.. note:: API Class for Locust Load Testing | ||
""" | ||
|
||
|
||
class ApiTaskTag: | ||
"""Represent the tag for a task.""" | ||
|
||
RANDOM_VAR = 'rand_var' | ||
RANDOM_OUTPUT = 'rand_out' | ||
RANDOM_DATE = 'rand_date' | ||
RANDOM_ALL = 'rand_all' | ||
|
||
|
||
class ApiWeatherGroupMode: | ||
"""Represents how to group the API requests.""" | ||
|
||
BY_PRODUCT_TYPE = 1 | ||
BY_OUTPUT_TYPE = 2 | ||
BY_ATTRIBUTE_LENGTH = 3 | ||
BY_DATE_COUNT = 4 | ||
BY_QUERY_TYPE = 5 | ||
|
||
@staticmethod | ||
def as_list(): | ||
"""Return the enum as list.""" | ||
return [ | ||
ApiWeatherGroupMode.BY_PRODUCT_TYPE, | ||
ApiWeatherGroupMode.BY_OUTPUT_TYPE, | ||
ApiWeatherGroupMode.BY_ATTRIBUTE_LENGTH, | ||
ApiWeatherGroupMode.BY_DATE_COUNT, | ||
ApiWeatherGroupMode.BY_QUERY_TYPE, | ||
] | ||
|
||
|
||
class Api: | ||
"""Provides api call to TNGAP.""" | ||
|
||
def __init__(self, client, user): | ||
"""Initialize the class.""" | ||
self.client = client | ||
self.user = user | ||
|
||
def get_weather_request_name( | ||
self, group_modes, product_type, output_type, attributes, | ||
start_date, end_date, lat=None, lon=None, bbox=None, | ||
location_name=None, default_name=None): | ||
"""Return request name.""" | ||
names = [] | ||
for mode in ApiWeatherGroupMode.as_list(): | ||
if mode not in group_modes: | ||
continue | ||
|
||
name = '' | ||
if mode == ApiWeatherGroupMode.BY_PRODUCT_TYPE: | ||
name = product_type | ||
elif mode == ApiWeatherGroupMode.BY_OUTPUT_TYPE: | ||
name = output_type | ||
elif mode == ApiWeatherGroupMode.BY_ATTRIBUTE_LENGTH: | ||
name = f'ATTR{len(attributes)}' | ||
elif mode == ApiWeatherGroupMode.BY_DATE_COUNT: | ||
name = f'DT{(end_date - start_date).days}' | ||
elif mode == ApiWeatherGroupMode.BY_QUERY_TYPE: | ||
name = 'point' | ||
if bbox is not None: | ||
name = 'bbox' | ||
elif location_name is not None: | ||
name = 'loc' | ||
|
||
if name: | ||
names.append(name) | ||
|
||
return default_name if len(names) == 0 else '_'.join(names) | ||
|
||
def weather( | ||
self, product_type, output_type, attributes, start_date, end_date, | ||
lat=None, lon=None, bbox=None, location_name=None, group_modes=None | ||
): | ||
"""Call weather API.""" | ||
if group_modes is None: | ||
group_modes = [ | ||
ApiWeatherGroupMode.BY_PRODUCT_TYPE, | ||
ApiWeatherGroupMode.BY_OUTPUT_TYPE | ||
] | ||
request_name = self.get_weather_request_name( | ||
group_modes, product_type, output_type, attributes, | ||
start_date, end_date, lat=lat, lon=lon, bbox=bbox, | ||
location_name=location_name, default_name='weather' | ||
) | ||
attributes_str = ','.join(attributes) | ||
url = ( | ||
f'/api/v1/measurement/?lat={lat}&lon={lon}&bbox={bbox}&' + | ||
f'location_name={location_name}&attributes={attributes_str}&' + | ||
f'start_date={start_date}&end_date={end_date}&' + | ||
f'product={product_type}&output_type={output_type}' | ||
) | ||
|
||
headers = { | ||
'Authorization': self.user['auth'], | ||
'user-agent': ( | ||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' + | ||
'(KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36' | ||
) | ||
} | ||
|
||
self.client.get(url, headers=headers, name=request_name) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# coding=utf-8 | ||
""" | ||
Tomorrow Now GAP. | ||
.. note:: Auth for Locust Load Testing | ||
""" | ||
|
||
import json | ||
import random | ||
from base64 import b64encode | ||
from locust import between, constant | ||
|
||
|
||
def basic_auth(username, password): | ||
"""Encode username and password as basic auth.""" | ||
token = b64encode( | ||
f"{username}:{password}".encode('utf-8')).decode("ascii") | ||
return f'Basic {token}' | ||
|
||
|
||
class AuthConfig: | ||
"""Auth users from config json file.""" | ||
|
||
DEFAULT_WAIT_TIME = 1 # 1 second | ||
|
||
def __init__(self, file_path='/mnt/locust/locust_auth.json'): | ||
"""Initialize the class.""" | ||
with open(file_path, 'r') as json_file: | ||
self.users = json.load(json_file) | ||
|
||
def get_user(self): | ||
"""Get random user.""" | ||
user = random.choice(self.users) | ||
wait_time = constant(self.DEFAULT_WAIT_TIME) | ||
if user['wait_time_start'] and user['wait_time_end']: | ||
wait_time = between( | ||
user['wait_time_start'], user['wait_time_end']) | ||
return { | ||
'auth': basic_auth(user['username'], user['password']), | ||
'wait_time': wait_time | ||
} | ||
|
||
|
||
auth_config = AuthConfig('locust_auth.json') |
Oops, something went wrong.