diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b7249ced4e..7507b2dd8d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -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' diff --git a/poetry.lock b/poetry.lock index 503d0e2098..7a4bbc3b9c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2507,7 +2507,7 @@ requests = ">=2.0.0" [[package]] name = "notifications-utils" -version = "52.0.9" +version = "52.0.11" description = "Shared python code for Notification - Provides logging utils etc." category = "main" optional = false @@ -2541,8 +2541,8 @@ werkzeug = "2.3.7" [package.source] type = "git" url = "https://github.com/cds-snc/notifier-utils.git" -reference = "52.0.9" -resolved_reference = "f63ebcc2868839ee0a7b3bb7c4f2b77164130c35" +reference = "52.0.11" +resolved_reference = "08ba5512ae392390b6c991042858f3cbe4e8aa95" [[package]] name = "ordered-set" @@ -4222,4 +4222,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "~3.10.9" -content-hash = "c5d80e8de8bd8407d90deea154d50a229799b287b0d7f0539c3b5b3d87a1437c" +content-hash = "6099fb4abd4ea8c67a00145154745a44cbe1f82fb80c262ddeca3ea915e5ca1f" diff --git a/pyproject.toml b/pyproject.toml index 93424a39a8..85295a66ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/scripts/soak_test/README.md b/scripts/soak_test/README.md index 5d24c31baf..a9e2ecb50e 100644 --- a/scripts/soak_test/README.md +++ b/scripts/soak_test/README.md @@ -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 @@ -22,7 +26,7 @@ __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 @@ -30,25 +34,39 @@ 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 @@ -73,3 +91,4 @@ stats as ( ) select * from stats ``` + diff --git a/scripts/soak_test/locust.conf b/scripts/soak_test/locust.conf index d88ebcd001..44c0e7a6ad 100644 --- a/scripts/soak_test/locust.conf +++ b/scripts/soak_test/locust.conf @@ -1,3 +1,3 @@ users=1 stop-timeout=10 -host=https://api.staging.notification.cdssandbox.xyz +host=https://staging.notification.cdssandbox.xyz diff --git a/scripts/soak_test/soak_test_all_servers.py b/scripts/soak_test/soak_test_all_servers.py new file mode 100644 index 0000000000..089ef07b77 --- /dev/null +++ b/scripts/soak_test/soak_test_all_servers.py @@ -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] diff --git a/scripts/soak_test/soak-test.py b/scripts/soak_test/soak_test_send_email.py similarity index 91% rename from scripts/soak_test/soak-test.py rename to scripts/soak_test/soak_test_send_email.py index d761d08f85..f9cefd88d1 100644 --- a/scripts/soak_test/soak-test.py +++ b/scripts/soak_test/soak_test_send_email.py @@ -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() @@ -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 = "success@simulator.amazonses.com" diff --git a/scripts/soak_test/soak_utils.py b/scripts/soak_test/soak_utils.py new file mode 100644 index 0000000000..3a62cf115d --- /dev/null +++ b/scripts/soak_test/soak_utils.py @@ -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()