Compare commits
	
		
			39 Commits
		
	
	
		
			enterprise
			...
			benchmarks
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cde4e395e9 | |||
| d19c692f81 | |||
| d5d2be5672 | |||
| 8597db59f5 | |||
| 74fb9492bc | |||
| defbafb55e | |||
| e2ed7391bc | |||
| 8dcd0dcaa9 | |||
| 18eee1b722 | |||
| d0f6c815c3 | |||
| b13eba3b0a | |||
| 77fe4e9fe2 | |||
| 71fe8b4fb3 | |||
| b14cb832b2 | |||
| 24b5296d88 | |||
| 41b7e50bc6 | |||
| 6b750d7c59 | |||
| d268c28934 | |||
| 688404b6a5 | |||
| cbd2425a5f | |||
| 877c264d59 | |||
| 2575b540fa | |||
| 0e0b76a62e | |||
| 6d625fd1d7 | |||
| bd0630e300 | |||
| ffb7d44024 | |||
| 7589b11f98 | |||
| ad21dfa2bc | |||
| 95692f5a7c | |||
| 1f4ed1defa | |||
| 334b183465 | |||
| 1f789dd4c5 | |||
| 057e5747c9 | |||
| 8717a3aaab | |||
| 527173236a | |||
| 3e6eb6f248 | |||
| 6babf0f1c4 | |||
| ca7cc30504 | |||
| a7cb808cad | 
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -209,3 +209,10 @@ source_docs/ | ||||
|  | ||||
| ### Golang ### | ||||
| /vendor/ | ||||
|  | ||||
| ### Benchmark ### | ||||
| tests/benchmark/k6 | ||||
| tests/benchmark/prometheus | ||||
| tests/benchmark/**/*.json | ||||
| tests/benchmark/**/*.ndjson | ||||
| tests/benchmark/**/*.html | ||||
|  | ||||
							
								
								
									
										17
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								Makefile
									
									
									
									
									
								
							| @ -278,3 +278,20 @@ ci-bandit: ci--meta-debug | ||||
|  | ||||
| ci-pending-migrations: ci--meta-debug | ||||
| 	ak makemigrations --check | ||||
|  | ||||
| ######################### | ||||
| ## Benchmark | ||||
| ######################### | ||||
|  | ||||
| benchmark-fixtures-create: | ||||
| 	tests/benchmark/fixtures.py create | ||||
|  | ||||
| benchmark-run: | ||||
| 	docker compose -f tests/benchmark/docker-compose.yml up -d | ||||
| 	sleep 5 | ||||
| 	tests/benchmark/run.sh | ||||
|  | ||||
| benchmark-fixtures-delete: | ||||
| 	tests/benchmark/fixtures.py delete | ||||
|  | ||||
| benchmark: benchmark-fixtures-create benchmark-run benchmark-fixtures-delete | ||||
|  | ||||
| @ -4,6 +4,7 @@ services: | ||||
|   postgresql: | ||||
|     container_name: postgres | ||||
|     image: docker.io/library/postgres:16 | ||||
|     command: "-c max_connections=500" | ||||
|     volumes: | ||||
|       - db-data:/var/lib/postgresql/data | ||||
|     environment: | ||||
|  | ||||
							
								
								
									
										0
									
								
								tests/benchmark/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/benchmark/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										30
									
								
								tests/benchmark/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								tests/benchmark/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| --- | ||||
| services: | ||||
|   prometheus: | ||||
|     image: quay.io/prometheus/prometheus:latest | ||||
|     restart: unless-stopped | ||||
|     command: | ||||
|       - --enable-feature=native-histograms | ||||
|       - --web.enable-remote-write-receiver | ||||
|       - --config.file=/etc/prometheus/prometheus.yml | ||||
|       - --storage.tsdb.path=/prometheus | ||||
|       - --web.console.libraries=/usr/share/prometheus/console_libraries | ||||
|       - --web.console.templates=/usr/share/prometheus/consoles | ||||
|     ports: | ||||
|       - 127.0.0.1:9090:9090 | ||||
|     volumes: | ||||
|       - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro | ||||
|       - ./prometheus:/prometheus | ||||
|     user: root | ||||
|  | ||||
|   grafana: | ||||
|     image: grafana/grafana:latest | ||||
|     restart: unless-stopped | ||||
|     environment: | ||||
|       GF_AUTH_ANONYMOUS_ENABLED: "true" | ||||
|       GF_AUTH_ANONYMOUS_ORG_ROLE: Admin | ||||
|     ports: | ||||
|       - 127.0.0.1:3000:3000 | ||||
|     volumes: | ||||
|       - ./grafana/provisioning:/etc/grafana/provisioning:ro | ||||
|       - ./grafana/dashboards:/var/lib/grafana/dashboards:ro | ||||
							
								
								
									
										79
									
								
								tests/benchmark/event_list.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								tests/benchmark/event_list.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | ||||
| import exec from "k6/execution"; | ||||
| import http from "k6/http"; | ||||
| import { check } from "k6"; | ||||
|  | ||||
| const host = __ENV.BENCH_HOST ? __ENV.BENCH_HOST : "localhost"; | ||||
| const VUs = __ENV.VUS ? __ENV.VUS : 8; | ||||
|  | ||||
| export const options = { | ||||
|     discardResponseBodies: true, | ||||
|     scenarios: Object.fromEntries( | ||||
|         [ | ||||
|             // Number of events, page size | ||||
|             [1000, 100], | ||||
|             [10000, 20], | ||||
|             [10000, 100], | ||||
|             [100000, 100], | ||||
|             [1000000, 100], | ||||
|         ].map((obj, i) => [ | ||||
|             `${obj[0]}_${obj[1]}`, | ||||
|             { | ||||
|                 executor: "constant-vus", | ||||
|                 vus: VUs, | ||||
|                 duration: "150s", | ||||
|                 startTime: `${165 * i}s`, | ||||
|                 env: { | ||||
|                     EVENT_COUNT: `${obj[0]}`, | ||||
|                     PAGE_SIZE: `${obj[1]}`, | ||||
|                 }, | ||||
|                 tags: { | ||||
|                     testid: `event_list_${obj[0]}_${obj[1]}`, | ||||
|                     event_count: `${obj[0]}`, | ||||
|                     page_size: `${obj[1]}`, | ||||
|                 }, | ||||
|             }, | ||||
|         ]), | ||||
|     ), | ||||
| }; | ||||
|  | ||||
| export default function () { | ||||
|     const event_count = Number(__ENV.EVENT_COUNT); | ||||
|     const domain = `event-list-${event_count}.${host}:9000`; | ||||
|     const page_size = Number(__ENV.PAGE_SIZE); | ||||
|     const pages = Math.round(event_count / page_size); | ||||
|     const params = { | ||||
|         headers: { | ||||
|             Authorization: "Bearer akadmin", | ||||
|             "Content-Type": "application/json", | ||||
|             Accept: "*/*", | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
|     if (pages <= 10) { | ||||
|         for (let page = 1; page <= pages; page++) { | ||||
|             let res = http.get( | ||||
|                 http.url`http://${domain}/api/v3/events/events/?page=${page}&page_size=${page_size}`, | ||||
|                 params, | ||||
|             ); | ||||
|             check(res, { | ||||
|                 "status is 200": (res) => res.status === 200, | ||||
|             }); | ||||
|         } | ||||
|     } else { | ||||
|         let requests = []; | ||||
|         for (let page = 1; page <= pages; page++) { | ||||
|             requests.push([ | ||||
|                 "GET", | ||||
|                 http.url`http://${domain}/api/v3/events/events/?page=${page}&page_size=${page_size}`, | ||||
|                 null, | ||||
|                 params, | ||||
|             ]); | ||||
|         } | ||||
|         const responses = http.batch(requests); | ||||
|         for (let page = 1; page <= pages; page++) { | ||||
|             check(responses[page - 1], { | ||||
|                 "status is 200": (res) => res.status === 200, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										345
									
								
								tests/benchmark/fixtures.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										345
									
								
								tests/benchmark/fixtures.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,345 @@ | ||||
| #!/usr/bin/env python3 | ||||
| import random | ||||
| import sys | ||||
| from collections.abc import Iterable | ||||
| from multiprocessing import Process | ||||
| from os import environ | ||||
| from uuid import uuid4 | ||||
|  | ||||
| import django | ||||
|  | ||||
| environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings") | ||||
| environ.setdefault("AUTHENTIK_BOOTSTRAP_PASSWORD", "akadmin") | ||||
| environ.setdefault("AUTHENTIK_BOOTSTRAP_TOKEN", "akadmin") | ||||
| environ.setdefault("AUTHENTIK_BOOTSTRAP_EMAIL", "akadmin@authentik.test") | ||||
| django.setup() | ||||
|  | ||||
| from django.conf import settings | ||||
|  | ||||
| from authentik.core.models import Application, Group, User | ||||
| from authentik.crypto.models import CertificateKeyPair | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.flows.models import Flow | ||||
| from authentik.policies.expression.models import ExpressionPolicy | ||||
| from authentik.policies.models import PolicyBinding | ||||
| from authentik.providers.oauth2.models import OAuth2Provider | ||||
| from authentik.stages.authenticator_static.models import StaticToken | ||||
| from authentik.tenants.models import Domain, Tenant | ||||
|  | ||||
| settings.CELERY["task_always_eager"] = True | ||||
|  | ||||
| host = environ.get("BENCH_HOST", "localhost") | ||||
|  | ||||
|  | ||||
| class TestSuite: | ||||
|     TEST_NAME: str | ||||
|     TEST_CASES: Iterable[Iterable[int | str | bool]] | ||||
|  | ||||
|     @classmethod | ||||
|     def get_testcases(cls): | ||||
|         return [cls(params) for params in cls.TEST_CASES] | ||||
|  | ||||
|     def __init__(self, params: Iterable[int | str | bool]): | ||||
|         self.params = params | ||||
|  | ||||
|     def __str__(self): | ||||
|         return ( | ||||
|             "-".join([self.TEST_NAME] + [str(param) for param in self.params]) | ||||
|             .replace("_", "-") | ||||
|             .lower() | ||||
|         ) | ||||
|  | ||||
|     @property | ||||
|     def schema_name(self): | ||||
|         return f"t_{str(self).replace('-', '_')}" | ||||
|  | ||||
|     @property | ||||
|     def domain_name(self): | ||||
|         return f"{str(self)}.{host}" | ||||
|  | ||||
|     def create(self): | ||||
|         created = False | ||||
|         t = Tenant.objects.filter(schema_name=self.schema_name).first() | ||||
|         if not t: | ||||
|             created = True | ||||
|             t = Tenant.objects.create(schema_name=self.schema_name, name=uuid4()) | ||||
|         Domain.objects.get_or_create(tenant=t, domain=self.domain_name) | ||||
|         if created: | ||||
|             with t: | ||||
|                 self.create_data(*self.params) | ||||
|  | ||||
|     def create_data(self): | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def delete(self): | ||||
|         Tenant.objects.filter(schema_name=self.schema_name).delete() | ||||
|  | ||||
|  | ||||
| class UserList(TestSuite): | ||||
|     TEST_NAME = "user-list" | ||||
|     TEST_CASES = [ | ||||
|         (1000, 0, 0), | ||||
|         (10000, 0, 0), | ||||
|         (1000, 3, 0), | ||||
|         (10000, 3, 0), | ||||
|         (1000, 20, 0), | ||||
|         (10000, 20, 0), | ||||
|         (1000, 20, 3), | ||||
|         (10000, 20, 3), | ||||
|     ] | ||||
|  | ||||
|     def create_data(self, user_count: int, groups_per_user: int, parents_per_group: int): | ||||
|         Group.objects.bulk_create([Group(name=uuid4()) for _ in range(groups_per_user * 5)]) | ||||
|         for group in Group.objects.exclude(name="authentik Admins"): | ||||
|             for _ in range(parents_per_group): | ||||
|                 new_group = Group.objects.create(name=uuid4()) | ||||
|                 group.parent = new_group | ||||
|                 group.save() | ||||
|                 group = new_group | ||||
|         User.objects.bulk_create( | ||||
|             [ | ||||
|                 User( | ||||
|                     username=uuid4(), | ||||
|                     name=uuid4(), | ||||
|                 ) | ||||
|                 for _ in range(user_count) | ||||
|             ] | ||||
|         ) | ||||
|         if groups_per_user: | ||||
|             for user in User.objects.exclude_anonymous().exclude(username="akadmin"): | ||||
|                 user.ak_groups.set( | ||||
|                     Group.objects.exclude(name="authentik Admins").order_by("?")[:groups_per_user] | ||||
|                 ) | ||||
|  | ||||
|  | ||||
| class GroupList(TestSuite): | ||||
|     TEST_NAME = "group-list" | ||||
|     TEST_CASES = [ | ||||
|         (1000, 0, False), | ||||
|         (10000, 0, False), | ||||
|         (1000, 1000, False), | ||||
|         (1000, 10000, False), | ||||
|         (1000, 0, True), | ||||
|         (10000, 0, True), | ||||
|     ] | ||||
|  | ||||
|     def create_data(self, group_count, users_per_group, with_parent): | ||||
|         User.objects.bulk_create( | ||||
|             [ | ||||
|                 User( | ||||
|                     username=uuid4(), | ||||
|                     name=uuid4(), | ||||
|                 ) | ||||
|                 for _ in range(users_per_group * 5) | ||||
|             ] | ||||
|         ) | ||||
|         if with_parent: | ||||
|             parents = Group.objects.bulk_create([Group(name=uuid4()) for _ in range(group_count)]) | ||||
|         groups = Group.objects.bulk_create( | ||||
|             [ | ||||
|                 Group(name=uuid4(), parent=(parents[i] if with_parent else None)) | ||||
|                 for i in range(group_count) | ||||
|             ] | ||||
|         ) | ||||
|         if users_per_group: | ||||
|             for group in groups: | ||||
|                 group.users.set( | ||||
|                     User.objects.exclude_anonymous() | ||||
|                     .exclude(username="akadmin") | ||||
|                     .order_by("?")[:users_per_group] | ||||
|                 ) | ||||
|  | ||||
|  | ||||
| class Login(TestSuite): | ||||
|     TEST_NAME = "login" | ||||
|     TEST_CASES = [ | ||||
|         ("no-mfa",), | ||||
|         ("with-mfa",), | ||||
|     ] | ||||
|  | ||||
|     def create_data(self, mfa: str): | ||||
|         user = User(username="test", name=uuid4()) | ||||
|         user.set_password("verySecurePassword") | ||||
|         user.save() | ||||
|  | ||||
|         if mfa == "with-mfa": | ||||
|             device = user.staticdevice_set.create() | ||||
|             # Multiple token with same token for all the iterations in the test | ||||
|             device.token_set.bulk_create( | ||||
|                 [StaticToken(device=device, token=f"staticToken") for _ in range(1_000_000)] | ||||
|             ) | ||||
|  | ||||
|  | ||||
| class ProviderOauth2(TestSuite): | ||||
|     TEST_NAME = "provider-oauth2" | ||||
|     TEST_CASES = [ | ||||
|         (2, 50, 2), | ||||
|         (0, 0, 0), | ||||
|         (10, 0, 0), | ||||
|         (100, 0, 0), | ||||
|         (0, 10, 0), | ||||
|         (0, 100, 0), | ||||
|         (0, 0, 10), | ||||
|         (0, 0, 100), | ||||
|         (10, 10, 10), | ||||
|         (100, 100, 100), | ||||
|     ] | ||||
|  | ||||
|     def create_data( | ||||
|         self, user_policies_count: int, group_policies_count: int, expression_policies_count: int | ||||
|     ): | ||||
|         user = User(username="test", name=uuid4()) | ||||
|         user.set_password("verySecurePassword") | ||||
|         user.save() | ||||
|  | ||||
|         provider = OAuth2Provider.objects.create( | ||||
|             name="test", | ||||
|             authorization_flow=Flow.objects.get( | ||||
|                 slug="default-provider-authorization-implicit-consent" | ||||
|             ), | ||||
|             signing_key=CertificateKeyPair.objects.get(name="authentik Self-signed Certificate"), | ||||
|             redirect_uris="http://test.localhost", | ||||
|             client_id="123456", | ||||
|             client_secret="123456", | ||||
|         ) | ||||
|         application = Application.objects.create(slug="test", name="test", provider=provider) | ||||
|  | ||||
|         User.objects.bulk_create( | ||||
|             [ | ||||
|                 User( | ||||
|                     username=uuid4(), | ||||
|                     name=uuid4(), | ||||
|                 ) | ||||
|                 for _ in range(user_policies_count) | ||||
|             ] | ||||
|         ) | ||||
|         PolicyBinding.objects.bulk_create( | ||||
|             [ | ||||
|                 PolicyBinding( | ||||
|                     user=user, | ||||
|                     target=application, | ||||
|                     order=random.randint(1, 1_000_000), | ||||
|                 ) | ||||
|                 for user in User.objects.exclude(username="akadmin").exclude_anonymous() | ||||
|             ] | ||||
|         ) | ||||
|  | ||||
|         Group.objects.bulk_create([Group(name=uuid4()) for _ in range(group_policies_count)]) | ||||
|         PolicyBinding.objects.bulk_create( | ||||
|             [ | ||||
|                 PolicyBinding( | ||||
|                     group=group, | ||||
|                     target=application, | ||||
|                     order=random.randint(1, 1_000_000), | ||||
|                 ) | ||||
|                 for group in Group.objects.exclude(name="authentik Admins") | ||||
|             ] | ||||
|         ) | ||||
|         user.ak_groups.set(Group.objects.exclude(name="authentik Admins").order_by("?")[:1]) | ||||
|  | ||||
|         [ | ||||
|             ExpressionPolicy( | ||||
|                 name=f"test-{uuid4()}", | ||||
|                 expression="return True", | ||||
|             ).save() | ||||
|             for _ in range(expression_policies_count) | ||||
|         ] | ||||
|         PolicyBinding.objects.bulk_create( | ||||
|             [ | ||||
|                 PolicyBinding( | ||||
|                     policy=policy, | ||||
|                     target=application, | ||||
|                     order=random.randint(1, 1_000_000), | ||||
|                 ) | ||||
|                 for policy in ExpressionPolicy.objects.filter(name__startswith="test-") | ||||
|             ] | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class EventList(TestSuite): | ||||
|     TEST_NAME = "event-list" | ||||
|     TEST_CASES = [ | ||||
|         (1_000,), | ||||
|         (10_000,), | ||||
|         (100_000,), | ||||
|         (1_000_000,), | ||||
|     ] | ||||
|  | ||||
|     def create_data(self, event_count: int): | ||||
|         for _ in range(event_count // 1000): | ||||
|             Event.objects.bulk_create( | ||||
|                 [ | ||||
|                     Event( | ||||
|                         user={ | ||||
|                             "pk": str(uuid4()), | ||||
|                             "name": str(uuid4()), | ||||
|                             "username": str(uuid4()), | ||||
|                             "email": f"{uuid4()}@example.org", | ||||
|                         }, | ||||
|                         action="custom_benchmark", | ||||
|                         app="tests_benchmarks", | ||||
|                         context={ | ||||
|                             str(uuid4()): str(uuid4()), | ||||
|                             str(uuid4()): str(uuid4()), | ||||
|                             str(uuid4()): str(uuid4()), | ||||
|                             str(uuid4()): str(uuid4()), | ||||
|                             str(uuid4()): str(uuid4()), | ||||
|                         }, | ||||
|                         client_ip="192.0.2.42", | ||||
|                     ) | ||||
|                     for _ in range(1000) | ||||
|                 ] | ||||
|             ) | ||||
|  | ||||
|  | ||||
| class UserGroupCreate(TestSuite): | ||||
|     TEST_NAME = "user-group-create" | ||||
|     TEST_CASES = [ | ||||
|         (), | ||||
|     ] | ||||
|  | ||||
|     def create_data(self): | ||||
|         pass | ||||
|  | ||||
|  | ||||
| def main(action: str, selected_suite: str | None = None): | ||||
|     testsuites = TestSuite.__subclasses__() | ||||
|     testcases = [] | ||||
|     for testsuite in testsuites: | ||||
|         testcases += testsuite.get_testcases() | ||||
|  | ||||
|     match action: | ||||
|         case "create": | ||||
|             to_create = [] | ||||
|             for testcase in testcases: | ||||
|                 if selected_suite and testcase.TEST_NAME != selected_suite: | ||||
|                     continue | ||||
|                 testcase.create() | ||||
|             #     to_create.append(testcase) | ||||
|             # processes = [Process(target=testcase.create) for testcase in to_create] | ||||
|             # for p in processes: | ||||
|             #     p.start() | ||||
|             # for p in processes: | ||||
|             #     p.join() | ||||
|         case "list": | ||||
|             print(*[testsuite.TEST_NAME for testsuite in testsuites], sep="\n") | ||||
|         case "delete": | ||||
|             for testcase in testcases: | ||||
|                 if selected_suite and testcase.TEST_NAME != selected_suite: | ||||
|                     continue | ||||
|                 testcase.delete() | ||||
|         case _: | ||||
|             print("Unknown action. Should be create, list or delete") | ||||
|             exit(1) | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     if len(sys.argv) < 2: | ||||
|         action = "create" | ||||
|     else: | ||||
|         action = sys.argv[1] | ||||
|     if len(sys.argv) < 3: | ||||
|         testsuite = None | ||||
|     else: | ||||
|         testsuite = sys.argv[2] | ||||
|     main(action, testsuite) | ||||
| @ -0,0 +1,8 @@ | ||||
| --- | ||||
| apiVersion: 1 | ||||
| providers: | ||||
|   - name: default | ||||
|     folder: k6 | ||||
|     type: file | ||||
|     options: | ||||
|       path: /var/lib/grafana/dashboards | ||||
| @ -0,0 +1,11 @@ | ||||
| --- | ||||
| apiVersion: 1 | ||||
| datasources: | ||||
|   - name: Prometheus | ||||
|     type: prometheus | ||||
|     access: proxy | ||||
|     orgId: 1 | ||||
|     uid: prometheus | ||||
|     url: http://prometheus:9090 | ||||
|     jsonData: | ||||
|       timeInterval: 1s | ||||
							
								
								
									
										93
									
								
								tests/benchmark/group_list.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								tests/benchmark/group_list.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| import exec from "k6/execution"; | ||||
| import http from "k6/http"; | ||||
| import { check } from "k6"; | ||||
|  | ||||
| const host = __ENV.BENCH_HOST ? __ENV.BENCH_HOST : "localhost"; | ||||
| const VUs = __ENV.VUS ? __ENV.VUS : 8; | ||||
|  | ||||
| export const options = { | ||||
|     discardResponseBodies: true, | ||||
|     scenarios: Object.fromEntries( | ||||
|         [ | ||||
|             // Number of groups, number of users per group, with parent, page size, include users | ||||
|             [1000, 0, false, 20, false], | ||||
|             [10000, 0, false, 20, false], | ||||
|             [1000, 0, false, 100, false], | ||||
|             [10000, 0, false, 100, false], | ||||
|             [1000, 1000, false, 100, false], | ||||
|             [1000, 10000, false, 100, false], | ||||
|             [1000, 1000, false, 100, true], | ||||
|             [1000, 10000, false, 100, true], | ||||
|             [1000, 0, true, 100, false], | ||||
|             [10000, 0, true, 100, false], | ||||
|         ].map((obj, i) => [ | ||||
|             `${obj[0]}_${obj[1]}_${obj[2] ? "with_parents" : "without_parents"}_${obj[3]}_${obj[4] ? "with_users" : "without_users"}`, | ||||
|             { | ||||
|                 executor: "constant-vus", | ||||
|                 vus: VUs, | ||||
|                 duration: "150s", | ||||
|                 startTime: `${165 * i}s`, | ||||
|                 env: { | ||||
|                     GROUP_COUNT: `${obj[0]}`, | ||||
|                     USERS_PER_GROUP: `${obj[1]}`, | ||||
|                     WITH_PARENTS: `${obj[2]}`, | ||||
|                     PAGE_SIZE: `${obj[3]}`, | ||||
|                     WITH_USERS: `${obj[4] ? "true" : "false"}`, | ||||
|                 }, | ||||
|                 tags: { | ||||
|                     testid: `group_list_${obj[0]}_${obj[1]}_${obj[2] ? "with_parents" : "without_parents"}_${obj[3]}_${obj[4] ? "with_users" : "without_users"}`, | ||||
|                     group_count: `${obj[0]}`, | ||||
|                     users_per_group: `${obj[1]}`, | ||||
|                     with_parents: `${obj[2]}`, | ||||
|                     page_size: `${obj[3]}`, | ||||
|                     with_users: `${obj[4] ? "true" : "false"}`, | ||||
|                 }, | ||||
|             }, | ||||
|         ]), | ||||
|     ), | ||||
| }; | ||||
|  | ||||
| export default function () { | ||||
|     const group_count = Number(__ENV.GROUP_COUNT); | ||||
|     const users_per_group = Number(__ENV.USERS_PER_GROUP); | ||||
|     const with_parents = __ENV.WITH_PARENTS; | ||||
|     const with_users = __ENV.WITH_USERS; | ||||
|     const domain = `group-list-${group_count}-${users_per_group}-${with_parents}.${host}:9000`; | ||||
|     const page_size = Number(__ENV.PAGE_SIZE); | ||||
|     const pages = Math.round(group_count / page_size); | ||||
|     const params = { | ||||
|         headers: { | ||||
|             Authorization: "Bearer akadmin", | ||||
|             "Content-Type": "application/json", | ||||
|             Accept: "*/*", | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
|     if (pages <= 10) { | ||||
|         for (let page = 1; page <= pages; page++) { | ||||
|             let res = http.get( | ||||
|                 http.url`http://${domain}/api/v3/core/groups/?page=${page}&page_size=${page_size}&include_users=${with_users}`, | ||||
|                 params, | ||||
|             ); | ||||
|             check(res, { | ||||
|                 "status is 200": (res) => res.status === 200, | ||||
|             }); | ||||
|         } | ||||
|     } else { | ||||
|         let requests = []; | ||||
|         for (let page = 1; page <= pages; page++) { | ||||
|             requests.push([ | ||||
|                 "GET", | ||||
|                 http.url`http://${domain}/api/v3/core/groups/?page=${page}&page_size=${page_size}&include_users=${with_users}`, | ||||
|                 null, | ||||
|                 params, | ||||
|             ]); | ||||
|         } | ||||
|         const responses = http.batch(requests); | ||||
|         for (let page = 1; page <= pages; page++) { | ||||
|             check(responses[page - 1], { | ||||
|                 "status is 200": (res) => res.status === 200, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										78
									
								
								tests/benchmark/login.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								tests/benchmark/login.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| import http from "k6/http"; | ||||
| import { check, fail } from "k6"; | ||||
|  | ||||
| const host = __ENV.BENCH_HOST ? __ENV.BENCH_HOST : "localhost"; | ||||
| const VUs = __ENV.VUS ? __ENV.VUS : 8; | ||||
|  | ||||
| export const options = { | ||||
|     scenarios: Object.fromEntries( | ||||
|         ["no-mfa", "with-mfa"].map((obj, i) => [ | ||||
|             obj, | ||||
|             { | ||||
|                 executor: "constant-vus", | ||||
|                 vus: VUs, | ||||
|                 duration: "150s", | ||||
|                 startTime: `${165 * i}s`, | ||||
|                 env: { | ||||
|                     DOMAIN: `login-${obj}`, | ||||
|                 }, | ||||
|                 tags: { | ||||
|                     testid: `login-${obj}`, | ||||
|                 }, | ||||
|             }, | ||||
|         ]), | ||||
|     ), | ||||
| }; | ||||
|  | ||||
| export default function () { | ||||
|     const domain = __ENV.DOMAIN; | ||||
|     const url = http.url`http://${domain}.${host}:9000/api/v3/flows/executor/default-authentication-flow/`; | ||||
|     const cookieJar = new http.CookieJar(); | ||||
|     const params = { | ||||
|         jar: cookieJar, | ||||
|         headers: { | ||||
|             "Content-Type": "application/json", | ||||
|             Accept: "*/*", | ||||
|         }, | ||||
|     }; | ||||
|     let res = http.get(url, params); | ||||
|     let i = 0; | ||||
|     while (true) { | ||||
|         if (i > 10) { | ||||
|             fail("Test made more than 10 queries."); | ||||
|             break; | ||||
|         } | ||||
|         check(res, { | ||||
|             "status is 200": (res) => res.status === 200, | ||||
|         }); | ||||
|         if (res.status !== 200) { | ||||
|             fail("Endpoint did not return 200."); | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         const component = res.json()["component"]; | ||||
|         let payload = {}; | ||||
|         if (component === "ak-stage-identification") { | ||||
|             payload = { | ||||
|                 uid_field: "test", | ||||
|             }; | ||||
|         } else if (component === "ak-stage-password") { | ||||
|             payload = { | ||||
|                 password: "verySecurePassword", | ||||
|             }; | ||||
|         } else if (component === "ak-stage-authenticator-validate") { | ||||
|             payload = { | ||||
|                 code: "staticToken", | ||||
|             }; | ||||
|         } else if (component === "xak-flow-redirect") { | ||||
|             break; | ||||
|         } else { | ||||
|             console.log(`Unknown component type: ${component}`); | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         payload["component"] = component; | ||||
|         res = http.post(url, JSON.stringify(payload), params); | ||||
|         i++; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										13
									
								
								tests/benchmark/prometheus.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								tests/benchmark/prometheus.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| --- | ||||
| global: | ||||
|   scrape_interval: 15s | ||||
|   evaluation_interval: 15s | ||||
|   external_labels: | ||||
|     cluster: benchmarks | ||||
|     prometheus: benchmarks | ||||
|     prometheus_replica: "0" | ||||
|  | ||||
| scrape_configs: | ||||
|   - job_name: prometheus | ||||
|     static_configs: | ||||
|       - targets: ["localhost:9090"] | ||||
							
								
								
									
										179
									
								
								tests/benchmark/provider_oauth2.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								tests/benchmark/provider_oauth2.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,179 @@ | ||||
| import crypto from "k6/crypto"; | ||||
| import exec from "k6/execution"; | ||||
| import http from "k6/http"; | ||||
| import { check, fail } from "k6"; | ||||
|  | ||||
| const host = __ENV.BENCH_HOST ? __ENV.BENCH_HOST : "localhost"; | ||||
| const VUs = __ENV.VUS ? __ENV.VUS : 8; | ||||
|  | ||||
| const testcases = [ | ||||
|     [2, 50, 2], | ||||
|     [0, 0, 0], | ||||
|     [10, 0, 0], | ||||
|     [100, 0, 0], | ||||
|     [0, 10, 0], | ||||
|     [0, 100, 0], | ||||
|     [0, 0, 10], | ||||
|     [0, 0, 100], | ||||
|     [10, 10, 10], | ||||
|     [100, 100, 100], | ||||
| ]; | ||||
|  | ||||
| export const options = { | ||||
|     setupTimeout: "10m", | ||||
|     scenarios: Object.fromEntries( | ||||
|         testcases.map((obj, i) => [ | ||||
|             `${obj[0]}_${obj[1]}_${obj[2]}`, | ||||
|             { | ||||
|                 executor: "constant-vus", | ||||
|                 vus: VUs, | ||||
|                 duration: "150s", | ||||
|                 startTime: `${165 * i}s`, | ||||
|                 env: { | ||||
|                     USER_POLICIES_COUNT: `${obj[0]}`, | ||||
|                     GROUP_POLICIES_COUNT: `${obj[1]}`, | ||||
|                     EXPRESSION_POLICIES_COUNT: `${obj[2]}`, | ||||
|                 }, | ||||
|                 tags: { | ||||
|                     testid: `provider-oauth2-${obj[0]}_${obj[1]}_${obj[2]}`, | ||||
|                     user_policies_count: `${obj[0]}`, | ||||
|                     group_policies_count: `${obj[1]}`, | ||||
|                     expression_policies_count: `${obj[2]}`, | ||||
|                 }, | ||||
|             }, | ||||
|         ]), | ||||
|     ), | ||||
| }; | ||||
|  | ||||
| export function setup() { | ||||
|     let cookies = {}; | ||||
|     for (let vu = 0; vu < VUs; vu++) { | ||||
|         cookies[vu] = {}; | ||||
|         for (const testcase of testcases) { | ||||
|             const user_policies_count = testcase[0]; | ||||
|             const group_policies_count = testcase[1]; | ||||
|             const expression_policies_count = testcase[2]; | ||||
|             const domain = `provider-oauth2-${user_policies_count}-${group_policies_count}-${expression_policies_count}.${host}:9000`; | ||||
|             const url = http.url`http://${domain}/api/v3/flows/executor/default-authentication-flow/`; | ||||
|             const params = { | ||||
|                 headers: { | ||||
|                     "Content-Type": "application/json", | ||||
|                     Accept: "*/*", | ||||
|                 }, | ||||
|             }; | ||||
|             http.cookieJar().clear(`http://${domain}`); | ||||
|             let res = http.get(url, params); | ||||
|             let i = 0; | ||||
|             while (true) { | ||||
|                 if (i > 10) { | ||||
|                     fail("Test made more than 10 queries."); | ||||
|                     break; | ||||
|                 } | ||||
|                 check(res, { | ||||
|                     "status is 200": (res) => res.status === 200, | ||||
|                 }); | ||||
|                 if (res.status !== 200) { | ||||
|                     fail("Endpoint did not return 200."); | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 const component = res.json()["component"]; | ||||
|                 let payload = {}; | ||||
|                 if (component === "ak-stage-identification") { | ||||
|                     payload = { | ||||
|                         uid_field: "test", | ||||
|                     }; | ||||
|                 } else if (component === "ak-stage-password") { | ||||
|                     payload = { | ||||
|                         password: "verySecurePassword", | ||||
|                     }; | ||||
|                 } else if (component === "xak-flow-redirect") { | ||||
|                     break; | ||||
|                 } else { | ||||
|                     fail(`Unknown component type: ${component}`); | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 payload["component"] = component; | ||||
|                 res = http.post(url, JSON.stringify(payload), params); | ||||
|                 i++; | ||||
|             } | ||||
|             cookies[vu][domain] = http | ||||
|                 .cookieJar() | ||||
|                 .cookiesForURL(`http://${domain}`); | ||||
|         } | ||||
|     } | ||||
|     return { cookies }; | ||||
| } | ||||
|  | ||||
| export default function (data) { | ||||
|     // Restore cookies | ||||
|     let jar = http.cookieJar(); | ||||
|     const vu = exec.vu.idInTest % VUs; | ||||
|     Object.keys(data.cookies[vu]).forEach((domain) => { | ||||
|         Object.keys(data.cookies[vu][domain]).forEach((key) => { | ||||
|             jar.set(`http://${domain}`, key, data.cookies[vu][domain][key][0]); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     const user_policies_count = Number(__ENV.USER_POLICIES_COUNT); | ||||
|     const group_policies_count = Number(__ENV.GROUP_POLICIES_COUNT); | ||||
|     const expression_policies_count = Number(__ENV.EXPRESSION_POLICIES_COUNT); | ||||
|     const domain = `provider-oauth2-${user_policies_count}-${group_policies_count}-${expression_policies_count}.${host}:9000`; | ||||
|     const params = { | ||||
|         headers: { | ||||
|             "Content-Type": "application/json", | ||||
|             Accept: "*/*", | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
|     const random = (length = 32) => { | ||||
|         let chars = | ||||
|             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | ||||
|         let str = ""; | ||||
|         for (let i = 0; i < length; i++) { | ||||
|             str += chars.charAt(Math.floor(Math.random() * chars.length)); | ||||
|         } | ||||
|         return str; | ||||
|     }; | ||||
|  | ||||
|     const state = random(32); | ||||
|     const nonce = random(32); | ||||
|     const code_verifier = random(64); | ||||
|     const code_challenge = crypto.sha256(code_verifier, "base64"); | ||||
|     const urlParams = { | ||||
|         response_type: "code", | ||||
|         scope: "openid profile email", | ||||
|         client_id: "123456", | ||||
|         redirect_uri: "http://test.localhost", | ||||
|         state: state, | ||||
|         nonce: nonce, | ||||
|         code_challenge: code_challenge, | ||||
|         code_challenge_method: "S256", | ||||
|     }; | ||||
|  | ||||
|     let url = http.url`http://${domain}/application/o/authorize/?${Object.entries( | ||||
|         urlParams, | ||||
|     ) | ||||
|         .map((kv) => kv.map(encodeURIComponent).join("=")) | ||||
|         .join("&")}`; | ||||
|     let res = http.get(url, params); | ||||
|     check(res, { | ||||
|         "status is 200": (res) => res.status === 200, | ||||
|     }); | ||||
|     if (res.status !== 200) { | ||||
|         fail("Endpoint did not return 200."); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     url = http.url`http://${domain}/api/v3/flows/executor/default-provider-authorization-implicit-consent/`; | ||||
|     res = http.get(url, params); | ||||
|     check(res, { | ||||
|         "status is 200": (res) => res.status === 200, | ||||
|         "last redirect is present": (res) => res.json()["type"] === "redirect", | ||||
|     }); | ||||
|     if (res.status !== 200) { | ||||
|         fail("Endpoint did not return 200."); | ||||
|         return; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										32
									
								
								tests/benchmark/run.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										32
									
								
								tests/benchmark/run.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,32 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| set -euo pipefail | ||||
|  | ||||
| BASE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)" | ||||
|  | ||||
| function _k6 { | ||||
|     local filename="${1}" | ||||
|  | ||||
|     K6_PROMETHEUS_RW_SERVER_URL=${PROMETHEUS_REMOTE_WRITE_ENDPOINT:-http://localhost:9090/api/v1/write} \ | ||||
|     K6_PROMETHEUS_RW_TREND_AS_NATIVE_HISTOGRAM=true \ | ||||
|     K6_PROMETHEUS_RW_PUSH_INTERVAL=1s \ | ||||
|     k6 run \ | ||||
|         --out experimental-prometheus-rw \ | ||||
|         --out "json=${filename%.*}.json" \ | ||||
|         "${@}" | ||||
| } | ||||
|  | ||||
| filename="" | ||||
| if [ "${#}" -ge 1 ]; then | ||||
|     filename="${1:-}" | ||||
|     shift | ||||
| fi | ||||
|  | ||||
|  | ||||
| if [ -f "${filename}" ]; then | ||||
|     _k6 "${filename}" "${@}" | ||||
| else | ||||
|     find "${BASE_DIR}" -name '*.js' | while read -r f; do | ||||
|         _k6 "${f}" "${@}" | ||||
|     done | ||||
| fi | ||||
							
								
								
									
										68
									
								
								tests/benchmark/user_group_create.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								tests/benchmark/user_group_create.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | ||||
| import exec from "k6/execution"; | ||||
| import http from "k6/http"; | ||||
| import { check } from "k6"; | ||||
|  | ||||
| const host = __ENV.BENCH_HOST ? __ENV.BENCH_HOST : "localhost"; | ||||
| const VUs = __ENV.VUS ? __ENV.VUS : 8; | ||||
|  | ||||
| export const options = { | ||||
|     vus: VUs, | ||||
|     duration: "150s", | ||||
|     tags: { | ||||
|         testid: `user-group-create`, | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| export default function () { | ||||
|     const domain = `user-group-create.${host}:9000`; | ||||
|     const params = { | ||||
|         headers: { | ||||
|             Authorization: "Bearer akadmin", | ||||
|             "Content-Type": "application/json", | ||||
|             Accept: "*/*", | ||||
|         }, | ||||
|     }; | ||||
|     const random = (length = 32) => { | ||||
|         let chars = | ||||
|             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | ||||
|         let str = ""; | ||||
|         for (let i = 0; i < length; i++) { | ||||
|             str += chars.charAt(Math.floor(Math.random() * chars.length)); | ||||
|         } | ||||
|         return str; | ||||
|     }; | ||||
|  | ||||
|     let user_res = http.post( | ||||
|         http.url`http://${domain}/api/v3/core/users/`, | ||||
|         JSON.stringify({ | ||||
|             username: random(16), | ||||
|             name: random(16), | ||||
|         }), | ||||
|         params, | ||||
|     ); | ||||
|     check(user_res, { | ||||
|         "user status is 201": (res) => res.status === 201, | ||||
|     }); | ||||
|  | ||||
|     let group_res = http.post( | ||||
|         http.url`http://${domain}/api/v3/core/groups/`, | ||||
|         JSON.stringify({ | ||||
|             name: random(16), | ||||
|         }), | ||||
|         params, | ||||
|     ); | ||||
|     check(group_res, { | ||||
|         "group status is 201": (res) => res.status === 201, | ||||
|     }); | ||||
|  | ||||
|     let user_group_res = http.post( | ||||
|         http.url`http://${domain}/api/v3/core/groups/${group_res.json()["pk"]}/add_user/`, | ||||
|         JSON.stringify({ | ||||
|             pk: user_res.json()["pk"], | ||||
|         }), | ||||
|         params, | ||||
|     ); | ||||
|     check(user_group_res, { | ||||
|         "user group status is 204": (res) => res.status === 204, | ||||
|     }); | ||||
| } | ||||
							
								
								
									
										99
									
								
								tests/benchmark/user_list.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								tests/benchmark/user_list.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,99 @@ | ||||
| import exec from "k6/execution"; | ||||
| import http from "k6/http"; | ||||
| import { check } from "k6"; | ||||
|  | ||||
| const host = __ENV.BENCH_HOST ? __ENV.BENCH_HOST : "localhost"; | ||||
| const VUs = __ENV.VUS ? __ENV.VUS : 8; | ||||
|  | ||||
| export const options = { | ||||
|     discardResponseBodies: true, | ||||
|     scenarios: Object.fromEntries( | ||||
|         [ | ||||
|             // Number of users, number of groups per user, number of parents per group, page size, with groups | ||||
|             [1000, 0, 0, 20, true], | ||||
|             [10000, 0, 0, 20, true], | ||||
|             [1000, 0, 0, 20, false], | ||||
|             [10000, 0, 0, 20, false], | ||||
|             [1000, 0, 0, 100, true], | ||||
|             [10000, 0, 0, 100, true], | ||||
|             [1000, 3, 0, 20, true], | ||||
|             [10000, 3, 0, 20, true], | ||||
|             [1000, 20, 0, 20, true], | ||||
|             [10000, 20, 0, 20, true], | ||||
|             [1000, 20, 3, 20, true], | ||||
|             [10000, 20, 3, 20, true], | ||||
|             [1000, 20, 0, 20, false], | ||||
|             [10000, 20, 0, 20, false], | ||||
|             [1000, 20, 3, 20, false], | ||||
|             [10000, 20, 3, 20, false], | ||||
|         ].map((obj, i) => [ | ||||
|             `${obj[0]}_${obj[1]}_${obj[2]}_${obj[3]}_${obj[4] ? "with_groups" : "without_groups"}`, | ||||
|             { | ||||
|                 executor: "constant-vus", | ||||
|                 vus: VUs, | ||||
|                 duration: "150s", | ||||
|                 startTime: `${165 * i}s`, | ||||
|                 env: { | ||||
|                     USER_COUNT: `${obj[0]}`, | ||||
|                     GROUPS_PER_USER: `${obj[1]}`, | ||||
|                     PARENTS_PER_GROUP: `${obj[2]}`, | ||||
|                     PAGE_SIZE: `${obj[3]}`, | ||||
|                     WITH_GROUPS: `${obj[4] ? "true" : "false"}`, | ||||
|                 }, | ||||
|                 tags: { | ||||
|                     testid: `user_list_${obj[0]}_${obj[1]}_${obj[2]}_${obj[3]}_${obj[4] ? "with_groups" : "without_groups"}`, | ||||
|                     user_count: `${obj[0]}`, | ||||
|                     groups_per_user: `${obj[1]}`, | ||||
|                     parents_per_group: `${obj[2]}`, | ||||
|                     page_size: `${obj[3]}`, | ||||
|                     with_groups: `${obj[4] ? "true" : "false"}`, | ||||
|                 }, | ||||
|             }, | ||||
|         ]), | ||||
|     ), | ||||
| }; | ||||
|  | ||||
| export default function () { | ||||
|     const user_count = Number(__ENV.USER_COUNT); | ||||
|     const groups_per_user = Number(__ENV.GROUPS_PER_USER); | ||||
|     const parents_per_group = Number(__ENV.PARENTS_PER_GROUP); | ||||
|     const with_groups = __ENV.WITH_GROUPS; | ||||
|     const domain = `user-list-${user_count}-${groups_per_user}-${parents_per_group}.${host}:9000`; | ||||
|     const page_size = Number(__ENV.PAGE_SIZE); | ||||
|     const pages = Math.round(user_count / page_size); | ||||
|     const params = { | ||||
|         headers: { | ||||
|             Authorization: "Bearer akadmin", | ||||
|             "Content-Type": "application/json", | ||||
|             Accept: "*/*", | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
|     if (pages <= 10) { | ||||
|         for (let page = 1; page <= pages; page++) { | ||||
|             let res = http.get( | ||||
|                 http.url`http://${domain}/api/v3/core/users/?page=${page}&page_size=${page_size}&include_groups=${with_groups}`, | ||||
|                 params, | ||||
|             ); | ||||
|             check(res, { | ||||
|                 "status is 100": (res) => res.status === 200, | ||||
|             }); | ||||
|         } | ||||
|     } else { | ||||
|         let requests = []; | ||||
|         for (let page = 1; page <= pages; page++) { | ||||
|             requests.push([ | ||||
|                 "GET", | ||||
|                 http.url`http://${domain}/api/v3/core/users/?page=${page}&page_size=${page_size}&include_groups=${with_groups}`, | ||||
|                 null, | ||||
|                 params, | ||||
|             ]); | ||||
|         } | ||||
|         const responses = http.batch(requests); | ||||
|         for (let page = 1; page <= pages; page++) { | ||||
|             check(responses[page - 1], { | ||||
|                 "status is 200": (res) => res.status === 200, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	