Skip to content

Commit

Permalink
Merge branch 'main' into repo-sync/site-reliability-engineering/default
Browse files Browse the repository at this point in the history
  • Loading branch information
jzbahrai authored Oct 3, 2023
2 parents fdb10a0 + 89a13a1 commit 8b7963a
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
run: |
cp -f .env.example .env
- name: Checks for new endpoints against AWS WAF rules
uses: cds-snc/notification-utils/.github/actions/waffles@415cd22db72ea1bcc56b7904f984cc7de369b7df # 52.0.6
uses: cds-snc/notification-utils/.github/actions/waffles@08ba5512ae392390b6c991042858f3cbe4e8aa95 # 52.0.11
with:
app-loc: '/github/workspace'
app-libs: '/github/workspace/env/site-packages'
Expand Down
8 changes: 4 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Werkzeug = "2.3.7"
MarkupSafe = "2.1.3"
# REVIEW: v2 is using sha512 instead of sha1 by default (in v1)
itsdangerous = "2.1.2"
notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", rev = "52.0.9" }
notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", rev = "52.0.11" }
# rsa = "4.9 # awscli 1.22.38 depends on rsa<4.8
typing-extensions = "4.7.1"
greenlet = "2.0.2"
Expand Down
33 changes: 26 additions & 7 deletions scripts/soak_test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@

The goal of this code is to do a soak test of api while we make significant application or infrastructure changes.

There are two soak tests here:
- `soak_test_send_email.py` will POST an email to api every second.
- `soak_test_all_servers.py` will do a GET to all our servers (admin, api, dd-api, api-k8s, documentation), on average hitting each server once a second

## How to configure

Run the setup.sh to install the python pre-requisites or run in the repo devcontainer.

Default configuration is in the `locust.conf` file.
Default configuration is in the `locust.conf` file. Note that the `host` is the base address of the system you are testing, for example `https://staging.notification.cdssandbox.xyz` **not** `https://api.staging.notification.cdssandbox.xyz`. The "api" prefix will be added in the code.

The python file `soak_test.py` requires environment variables `API_KEY` and `EMAIL_TEMPLATE_ID`. The template should have no variables.
The python file `soak_test_send_email.py` requires environment variables `API_KEY` and `EMAIL_TEMPLATE_ID`. The template should have no variables.

```
API_KEY=gcntfy-notAKey-f6c7cc49-b5b7-4e67-a8ff-24f34be34523-f6c7cc49-b5b7-4e67-a8ff-24f34be34523
Expand All @@ -22,33 +26,47 @@ __See Last Pass note "Soak Test Staging API Key and Template" in Shared-New-Noti

Note that the default configuration in `locust.conf` is to send one email per second.

You should supply a `--ref` option that will set the notification's `client_reference`. This is useful in testing that all POSTs were processed successfully.
You can supply a `--ref` option to `soak_test_send_email.py` that will set the notification's `client_reference`. This is useful in testing that all POSTs were processed successfully.

## How to run

There are two ways to run Locust, with the UI or headless.

### With the UI

Locally, simply run:
Locally you can run the email soak test with:

```shell
locust -f ./soak-test.py --ref=soak-2023-05-30-A
locust -f ./soak_test_send_email.py --ref=soak-2023-05-30-A
```

Follow the localhost address that the console will display to get to the UI. It will ask you how many total users and spawned users you want configured. Once setup, you can manually start the tests via the UI and follow the summary data and charts visually.

The server soak test can be run with

```shell
locust -f ./soak_test_all_servers.py
```

### Headless, via the command line

You can pass the necessary parameters to the command line to run in the headless mode. For example:

```shell
locust -f ./soak-test.py --headless --ref=soak-2023-05-30-A
locust -f ./soak_test_send_email.py --headless --ref=soak-2023-05-30-A
```

The defaults in `locust.conf` may be overridden by command line options

To check whether all the POSTs from locust made it into the database, run the "Soak test" query on blazer. The query is already in staging, or you can run:
The server soak test can be run with

```shell
locust -f ./soak_test_all_servers.py --headless
```

## Checking if all emails were sent

To check whether all the POSTs from `soak_test_send_email.py` made it into the database, run the "Soak test" query on blazer. The query is already in staging, or you can run:

```sql
WITH
Expand All @@ -73,3 +91,4 @@ stats as (
)
select * from stats
```

2 changes: 1 addition & 1 deletion scripts/soak_test/locust.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
users=1
stop-timeout=10
host=https://api.staging.notification.cdssandbox.xyz
host=https://staging.notification.cdssandbox.xyz
60 changes: 60 additions & 0 deletions scripts/soak_test/soak_test_all_servers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from dotenv import load_dotenv
from locust import HttpUser, TaskSet, constant_pacing, task
from locust.clients import HttpSession
from soak_utils import url_with_prefix

load_dotenv()


class MultipleHostsUser(HttpUser):
abstract = True

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self.admin_client = HttpSession(
base_url=self.host, request_event=self.client.request_event, user=self
)

self.api_client = HttpSession(
base_url=url_with_prefix(self.host, "api"), request_event=self.client.request_event, user=self
)

self.api_k8s_client = HttpSession(
base_url=url_with_prefix(self.host, "api-k8s"), request_event=self.client.request_event, user=self
)

self.dd_api_client = HttpSession(
base_url=url_with_prefix(self.host, "api.document"), request_event=self.client.request_event, user=self
)

self.documentation_client = HttpSession(
base_url=url_with_prefix(self.host, "documentation"), request_event=self.client.request_event, user=self
)


class UserTasks(TaskSet):
@task
def test_admin(self):
self.user.admin_client.get("/_status?simple=true", name=f"{self.user.admin_client.base_url}/_status?simple=true")

@task
def test_api(self):
self.user.api_client.get("/_status?status=true", name=f"{self.user.api_client.base_url}/_status?simple=true")

@task
def test_api_k8s(self):
self.user.api_k8s_client.get("/_status?status=true", name=f"{self.user.api_k8s_client.base_url}/_status?simple=true")

@task
def test_dd_api(self):
self.user.dd_api_client.get("/_status?simple=true", name=f"{self.user.dd_api_client.base_url}/_status?simple=true")

@task
def test_documentation(self):
self.user.documentation_client.get("/", name=f"{self.user.documentation_client.base_url}/")


class WebsiteUser(MultipleHostsUser):
wait_time = constant_pacing(0.2) # 5 GETs a second, so each server every second on average
tasks = [UserTasks]
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from dotenv import load_dotenv
from locust import HttpUser, constant_pacing, events, task
from soak_utils import url_with_prefix

load_dotenv()

Expand All @@ -15,6 +16,8 @@ class NotifyApiUser(HttpUser):
wait_time = constant_pacing(1) # each user makes one post per second

def __init__(self, *args, **kwargs):
self.host = url_with_prefix(self.host, "api")

super(NotifyApiUser, self).__init__(*args, **kwargs)
self.headers = {"Authorization": f"apikey-v1 {os.getenv('API_KEY')}"}
self.email_address = "[email protected]"
Expand Down
6 changes: 6 additions & 0 deletions scripts/soak_test/soak_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from urllib.parse import urlparse


def url_with_prefix(url: str, prefix: str) -> str:
parsed_url = urlparse(url)
return parsed_url._replace(netloc=f"{prefix}.{parsed_url.netloc}").geturl()

0 comments on commit 8b7963a

Please sign in to comment.