From e4c8abe0174903b8176c98f85128937a0d6f2140 Mon Sep 17 00:00:00 2001 From: Andrei Pashkevich Date: Mon, 30 Mar 2026 11:00:18 +0300 Subject: [PATCH 1/2] 53619 IDV Performance reivew --- .gitignore | 9 +- idv/README.md | 234 ++ idv/docker-compose.yaml | 320 +++ idv/environment/.env | 8 + idv/environment/config.yaml | 238 ++ idv/environment/config/docreader/config.yaml | 53 + idv/environment/config/faceapi/config.yaml | 84 + .../data/localization/general_kyc_locale.yaml | 419 ++++ .../data/workflows/general_kyc.yaml | 1692 +++++++++++++ .../provisioning/dashboards/locust-test.json | 2215 +++++++++++++++++ .../provisioning/dashboards/locust-test.yml | 12 + .../provisioning/datasources/prometheus.yml | 16 + idv/environment/prometheus/config.yml | 28 + idv/locust-docker-compose.yml | 50 + idv/locust-environment/.env | 10 + idv/locust-test/locustfile.py | 170 ++ idv/locust-test/request-data/auth.json | 6 + idv/locust-test/request-data/search.json | 23 + 18 files changed, 5586 insertions(+), 1 deletion(-) create mode 100644 idv/README.md create mode 100644 idv/docker-compose.yaml create mode 100644 idv/environment/.env create mode 100644 idv/environment/config.yaml create mode 100644 idv/environment/config/docreader/config.yaml create mode 100644 idv/environment/config/faceapi/config.yaml create mode 100644 idv/environment/data/localization/general_kyc_locale.yaml create mode 100644 idv/environment/data/workflows/general_kyc.yaml create mode 100644 idv/environment/grafana/provisioning/dashboards/locust-test.json create mode 100644 idv/environment/grafana/provisioning/dashboards/locust-test.yml create mode 100644 idv/environment/grafana/provisioning/datasources/prometheus.yml create mode 100644 idv/environment/prometheus/config.yml create mode 100644 idv/locust-docker-compose.yml create mode 100644 idv/locust-environment/.env create mode 100644 idv/locust-test/locustfile.py create mode 100644 idv/locust-test/request-data/auth.json create mode 100644 idv/locust-test/request-data/search.json diff --git a/.gitignore b/.gitignore index ec16161..992fe50 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,11 @@ faceapi/environment/regula.license __pycache__ **/.idea -**/volumes \ No newline at end of file +**/volumes + + +/idv/environment/data/assets_data/ +/idv/environment/data/person_data/ +/idv/environment/data/user_data/ +/idv/environment/data/user_files/ +/idv/environment/regula.license diff --git a/idv/README.md b/idv/README.md new file mode 100644 index 0000000..d7e7e89 --- /dev/null +++ b/idv/README.md @@ -0,0 +1,234 @@ +# IDV Coordinator Performance Tests + +## Environment + +### Start configuration + +Env file: [./environment/.env](./environment/.env) + +Before starting services, place your `regula.license` file at `./environment/regula.license`. + +| Environment variable | Value | Info | +|---|---|---| +| IDV_TAG | latest | IDV Coordinator image tag | +| FACEAPI_TAG | latest | Face API image tag | +| DOCREADER_TAG | latest | DocReader image tag | +| DOCKER_PLATFORM | linux/amd64 | Docker platform for IDV-related services | +| MONGODB_TAG | 8.0 | MongoDB image tag | +| OPENSEARCH_TAG | 3 | OpenSearch image tag | +| RABBITMQ_TAG | 3.13-management | RabbitMQ image tag | + +### Run services + +```bash +docker compose --env-file ./environment/.env up -d +``` + +### Stop services + +```bash +docker compose --env-file ./environment/.env down --remove-orphans +``` + +### Stop services and delete volumes + +```shell +docker compose --env-file ./environment/.env down -v --remove-orphans +``` + +### Services + +| Service | Port | Credentials | +|---|---|---| +| IDV API | 8000 | User: regula-idv / Password: t3stP@ss | +| Face API | 41101 | - | +| DocReader | 8080 | - | +| MongoDB | 27017 | - | +| OpenSearch | 9200 | User: admin / Password: t3stP@ss!Pwd#2025 | +| RabbitMQ | 5672 / 15672 | User: admin / Password: admin | +| Prometheus | 9090 | - | +| Grafana | 9091 | User: admin / Password: admin | + + +## Tests + +### Start configuration + +Env file: [./locust-environment/.env](./locust-environment/.env) + +| Environment variable | Value | Info | +|---|---|---| +| IDV_API_URL | http://webserver:8000 | URL where IDV service is running | +| NUMBER_LOCUST_USERS | 1 | Peak number of concurrent Locust users | +| SCENARIO | IDVViewGetByIdPerf | Performance test scenario | +| VIEW_NAMESPACE | active-views-3 | View namespace used for session discovery | + +### Run tests + +```shell +docker compose -f locust-docker-compose.yml --env-file ./locust-environment/.env up -d +``` + + + +`locust` starts the scenario automatically (`--autostart`) and exposes UI at `http://localhost:8089`. + +To run with a different user count, edit `NUMBER_LOCUST_USERS` in `./locust-environment/.env` or override inline: + +```shell +NUMBER_LOCUST_USERS=5 docker compose -f locust-docker-compose.yml --env-file ./locust-environment/.env up -d --force-recreate +``` + +```shell +NUMBER_LOCUST_USERS=10 docker compose -f locust-docker-compose.yml --env-file ./locust-environment/.env up -d --force-recreate +``` + +### Headless perf runs (5 minutes) with artifacts + +Run `1` user for `5m`: + +```shell +docker compose -f ./locust-docker-compose.yml --env-file ./locust-environment/.env run --rm --no-deps locust \ + -f /mnt/locust/locustfile.py IDVViewGetByIdPerf \ + -H http://webserver:8000 \ + --headless -u 1 -r 1 --run-time 5m \ + --csv=/mnt/locust/artifacts/u1/stats \ + --html=/mnt/locust/artifacts/u1/report.html +``` + +Run `5` users for `5m`: + +```shell +docker compose -f ./locust-docker-compose.yml --env-file ./locust-environment/.env run --rm --no-deps locust \ + -f /mnt/locust/locustfile.py IDVViewGetByIdPerf \ + -H http://webserver:8000 \ + --headless -u 5 -r 1 --run-time 5m \ + --csv=/mnt/locust/artifacts/u5/stats \ + --html=/mnt/locust/artifacts/u5/report.html +``` + +Run `10` users for `5m`: + +```shell +docker compose -f ./locust-docker-compose.yml --env-file ./locust-environment/.env run --rm --no-deps locust \ + -f /mnt/locust/locustfile.py IDVViewGetByIdPerf \ + -H http://webserver:8000 \ + --headless -u 10 -r 1 --run-time 5m \ + --csv=/mnt/locust/artifacts/u10/stats \ + --html=/mnt/locust/artifacts/u10/report.html +``` + +Artifacts will be stored in: + +- `./locust-test/artifacts/u1` +- `./locust-test/artifacts/u5` +- `./locust-test/artifacts/u10` + +### Stop tests + +```shell +docker compose -f locust-docker-compose.yml --env-file ./locust-environment/.env down +``` + +### How metrics get to Grafana + +1. `locust` generates load and runtime stats at `http://locust:8089`. +2. `locust-metrics-exporter` reads Locust stats and exposes Prometheus metrics at `:9646`. +3. Prometheus scrapes `locust-metrics-exporter:9646` (job `locust-monitoring`). +4. Grafana visualizes those Prometheus metrics at `http://localhost:9091`. + +### Quick checks + +```shell +docker compose -f locust-docker-compose.yml --env-file ./locust-environment/.env ps +``` + +```shell +docker compose -f locust-docker-compose.yml --env-file ./locust-environment/.env logs --tail=100 locust +``` + +```shell +curl -s http://localhost:9646/metrics | head +``` + +All statistics on the service and locust tests are available in Grafana at `http://localhost:9091` (`admin/admin`). + +## Local Performance Testing + +Run performance test locally without Docker: + +```shell +cd locust-test && locust -f locustfile.py --host=http://localhost:8000 +``` + +With parameters: + +```shell +locust -f locustfile.py IDVViewGetByIdPerf --host=http://localhost:8000 -u 50 -r 10 --run-time 10m +``` + +### Available test scenario + +- **IDVViewGetByIdPerf** (`view_search_and_get_by_id`) - calls `/api/views/namespaces/{namespace}`, builds a search body from view definition, runs `POST /api/sessions/search?limit=100`, and then loads each returned session via `/api/sessions/{session_id}` + +### Nightly dataset precondition (100 sessions) + +Before test run, export sessions from https://nightly-idv.regula.app/ and place archive (for example, `sessions-*.zip`) into `./environment/data/import`. +The DB is initialized from that archive by `setup` service (`idv session import -i /app/import`). +The scenario does not enforce an exact count in code and processes all sessions returned by `/api/sessions/search`. +If you need to reinitialize the environment from nightly archive, restart stack with volume cleanup: + +```shell +docker compose --env-file ./environment/.env down -v --remove-orphans +docker compose --env-file ./environment/.env up -d +``` + +Web UI available at `http://localhost:8089` + +## Monitoring Metrics + +### Stack Overview + +``` +Locust Test (port 8089) + ↓ +Locust Metrics Exporter (port 9646) + ↓ +Prometheus (port 9090) + ↓ +Grafana (port 9091) +``` + +### Dashboards + +Grafana dashboard "Locust Prometheus Monitoring Modern" shows: +- Requests Per Second (RPS) +- Response Time percentiles +- Total Requests +- Total Failures + +Access at `http://localhost:9091` (admin/admin) + +### Troubleshooting + +If Grafana dashboard is empty: + +1. Check if Locust is running and generating data: + ```shell + curl http://localhost:8089/stats/requests + ``` + +2. Check if Metrics Exporter is connected: + ```shell + curl http://localhost:9646/metrics | head -20 + ``` + +3. Check Prometheus targets: + ```shell + curl http://localhost:9090/api/v1/targets + ``` + +4. Check if data exists in Prometheus: + ```shell + curl 'http://localhost:9090/api/v1/query?query=locust_requests_current_rps' + ``` diff --git a/idv/docker-compose.yaml b/idv/docker-compose.yaml new file mode 100644 index 0000000..d2ea5e3 --- /dev/null +++ b/idv/docker-compose.yaml @@ -0,0 +1,320 @@ +x-idv-volumes: &idv-volumes + - ./environment/regula.license:/app/extBin/unix/regula.license + - ./environment/config.yaml:/app/config.yaml:ro + + +services: + faceapi: + container_name: faceapi + platform: "linux/amd64" + image: regulaforensics/face-api:${FACEAPI_TAG:-latest} + # deploy: + # resources: + # reservations: + # devices: + # - driver: ${GPU_DRIVER:-nvidia} + # count: ${GPU_COUNT:-0} + # capabilities: [gpu] + networks: + - idv-network + volumes: + - ./environment/regula.license:/app/extBin/unix/regula.license + - ./environment/config/faceapi/config.yaml:/app/config.yaml + healthcheck: + test: curl -f http://127.0.0.1:41101/api/ping + interval: 60s + start_period: 60s + timeout: 30s + retries: 5 + ports: + - "41101:41101" + depends_on: + opensearch: + condition: service_healthy + + docreader: + container_name: docreader + platform: "linux/amd64" + image: regulaforensics/docreader:${DOCREADER_TAG:-latest} + networks: + - idv-network + volumes: + - ./environment/regula.license:/app/extBin/unix/regula.license + - ./environment/config/docreader/config.yaml:/app/config.yaml + healthcheck: + test: curl -f http://127.0.0.1:8080/api/ping + interval: 60s + start_period: 60s + timeout: 30s + retries: 5 + ports: + - "8080:8080" + + mongo: + container_name: mongo + image: mongo:${MONGODB_TAG:-8.0} + restart: always + ports: + - "27017:27017" + networks: + - idv-network + volumes: + - ./environment/volumes/mongo:/data/db + healthcheck: + test: ["CMD", "sh", "-c", "timeout 5 bash -c ' + sh -c " + idv user delete --name regula-idv + echo \"Creating 'regula-idv' user\" && + USER_INFO=$(idv user create -n regula-idv -p t3stP@ss -r admin -e regula-idv@regula.us); + echo \"User info: $$USER_INFO\"; + USER_ID=$(echo \"$$USER_INFO\" | grep 'User ID:' | awk '{print $3}') && + echo \"User ID is $$USER_ID\" && + echo \"Creating General KYC view\" && + idv view create --key general-kyc-key --namespace active-views-3 --title 'General KYC' --workflows edda0192-b890-11ef-a348-17da751a6a22 --fields status --properties FULL_NAME,DOCUMENT_NUMBER,AGE,RFID_STATUS,DATE_OF_BIRTH --userId $$USER_ID && + echo \"General KYC view is created\" && + idv session import -i /app/import && + echo \"Sessions import finished\" && + idv session reindex && + echo \"Sessions reindex finished\" + " + + minio: + container_name: minio + image: minio/minio:RELEASE.2025-09-07T16-13-09Z + command: server --console-address ":9001" /data + restart: unless-stopped + ports: + - "9000:9000" + - "9001:9001" + environment: + MINIO_ACCESS_KEY: minioadmin + MINIO_SECRET_KEY: minioadmin + volumes: + - ./environment/volumes/minio:/data + networks: + - idv-network + healthcheck: + test: [ "CMD", "curl", "-f", "http://127.0.0.1:9000/minio/health/live" ] + interval: 30s + timeout: 20s + retries: 3 + + minio-cfg: + container_name: miniocfg + image: minio/mc:RELEASE.2025-08-13T08-35-41Z + volumes: + - ./environment/data:/data + networks: + - idv-network + depends_on: + - minio + entrypoint: > + /bin/sh -c " + sleep 5; + /usr/bin/mc alias set myminio http://minio:9000 minioadmin minioadmin; + /usr/bin/mc mb myminio/idv-development-316487603304/; + /usr/bin/mc cp --recursive /data/workflows/ myminio/idv-development-316487603304/workflows; + /usr/bin/mc cp --recursive /data/localization/ myminio/idv-development-316487603304/localization; + sleep 60; + " + + scheduler: + container_name: scheduler + platform: ${DOCKER_PLATFORM:-linux/amd64} + user: "1001" + image: regulaforensics/idv-coordinator:${IDV_TAG:-latest} + networks: + - idv-network + volumes: *idv-volumes + depends_on: + - setup + - minio + command: [ "idv", "scheduler", "start" ] + + webserver: + container_name: webserver + platform: ${DOCKER_PLATFORM:-linux/amd64} + user: "1001" + image: regulaforensics/idv-coordinator:${IDV_TAG:-latest} + networks: + - idv-network + volumes: *idv-volumes + ports: + - "8000:8000" + depends_on: + - scheduler + - minio + command: ["idv", "webserver", "start"] + + audit: + container_name: audit + platform: ${DOCKER_PLATFORM:-linux/amd64} + user: "1001" + image: regulaforensics/idv-coordinator:${IDV_TAG:-latest} + networks: + - idv-network + volumes: *idv-volumes + depends_on: + - scheduler + - minio + command: ["idv", "audit", "start"] + + workflow: + container_name: workflow + platform: ${DOCKER_PLATFORM:-linux/amd64} + user: "1001" + image: regulaforensics/idv-coordinator:${IDV_TAG:-latest} + networks: + - idv-network + volumes: *idv-volumes + depends_on: + - scheduler + - minio + command: ["idv", "workflow", "start"] + + + indexer: + container_name: indexer + platform: ${DOCKER_PLATFORM:-linux/amd64} + user: "1001" + image: regulaforensics/idv-coordinator:${IDV_TAG:-latest} + networks: + - idv-network + volumes: *idv-volumes + depends_on: + - scheduler + - minio + command: ["idv", "indexer", "start"] + + + prometheus: + container_name: prometheus + image: ubuntu/prometheus:latest + volumes: + - ./environment/prometheus/config.yml:/etc/prometheus/prometheus.yml + - ./environment/volumes/prometheus:/prometheus + networks: + - idv-network + ports: + - "9090:9090" + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--storage.tsdb.retention.time=30d' + + node-exporter: + image: prom/node-exporter:latest + container_name: node-exporter + restart: unless-stopped + volumes: + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /:/rootfs:ro + command: + - '--path.procfs=/host/proc' + - '--path.rootfs=/rootfs' + - '--path.sysfs=/host/sys' + - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)' + expose: + - 9100 + networks: + - idv-network + + grafana: + container_name: grafana + image: grafana/grafana:latest + volumes: + - ./environment/volumes/grafana:/var/lib/grafana + - ./environment/grafana/provisioning:/etc/grafana/provisioning + ports: + - "9091:3000" + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_USERS_ALLOW_SIGN_UP=false + depends_on: + - prometheus + networks: + - idv-network + + +networks: + idv-network: + driver: bridge diff --git a/idv/environment/.env b/idv/environment/.env new file mode 100644 index 0000000..ea3a10c --- /dev/null +++ b/idv/environment/.env @@ -0,0 +1,8 @@ +# image tags +FACEAPI_TAG=latest +DOCREADER_TAG=latest +MONGODB_TAG=8.0 +OPENSEARCH_TAG=3 +RABBITMQ_TAG=3.13-management +IDV_TAG=3.4.0 +DOCKER_PLATFORM=linux/amd64 diff --git a/idv/environment/config.yaml b/idv/environment/config.yaml new file mode 100644 index 0000000..440548e --- /dev/null +++ b/idv/environment/config.yaml @@ -0,0 +1,238 @@ +logging: + level: INFO + formatter: "%(asctime)s.%(msecs)03d - %(name)s - %(levelname)s - %(message)s" + console: true + file: false + path: logs + maxFileSize: 10048576 + filesCount: 10 + +mode: cluster +fernetKey: "z82-gpAZjBkCmwE8GJjB-Lt5kJZi9ARAI2uXTRGtEfQ=" +identifier: "PQT" +baseUrl: "http://localhost:8000" + +mobile: + apple: + bundleId: H6WR54S268.regula.DocumentReader + appId: "1001303920" + android: + bundleId: "com.regula.documentreader" + sha256: + - "60:92:AB:C1:E3:F1:53:5D:94:A8:CA:E5:40:85:8C:9B:3F:4A:30:99:44:88:08:1A:11:94:A0:71:6F:34:90:51" + +mongo: + url: mongodb://mongo:27017/idv + tempFiles: + ttl: 86400 + +storage: + type: s3 + s3: + endpoint: "http://minio:9000" + accessKey: "minioadmin" + accessSecret: "minioadmin" + region: master + secure: false + fs: + path: "" + az: + storageAccount: "devstoreaccount1" + connectionString: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1;" + gcs: + anonymous: true + endpoint: "http://localhost:4443" + verifySsl: false + sessions: + location: + container: "idv-development-316487603304" + bucket: "idv-development-316487603304" + prefix: "sessions" + folder: "user_data" + + persons: + location: + container: "idv-development-316487603304" + bucket: "idv-development-316487603304" + prefix: "persons" + folder: "person_data" + + workflows: + location: + container: "idv-development-316487603304" + bucket: "idv-development-316487603304" + prefix: "workflows" + folder: "config" + + userFiles: + location: + container: "idv-development-316487603304" + bucket: "idv-development-316487603304" + prefix: "files" + folder: "user_files" + + tempFiles: + location: + container: "idv-development-316487603304" + bucket: "idv-development-316487603304" + prefix: "tmp" + folder: "user_files" + + locales: + location: + container: "idv-development-316487603304" + bucket: "idv-development-316487603304" + prefix: "localization" + folder: "config" + + assets: + location: + container: "idv-development-316487603304" + bucket: "idv-development-316487603304" + prefix: "assets" + folder: "assets_data" + +smtp: + enabled: false + host: "" + port: 587 + username: "" + password: "" + tls: true + +faceSearch: + enabled: true + limit: 1000 + threshold: 0.75 + database: + type: opensearch + opensearch: + host: "localhost" + port: "9200" + useSsl: false + verifyCerts: false + username: admin + password: "" + dimension: 512 + method: hnsw + awsAuth: + enabled: false + region: "" + +textSearch: + enabled: true + limit: 1000 + database: + type: opensearch + opensearch: + host: "localhost" + port: "9200" + useSsl: false + verifyCerts: false + username: admin + password: "" + awsAuth: + enabled: false + region: "" + +services: + docreader: + enabled: true + prefix: "drapi" + url: "http://docreader:8080" + + faceapi: + enabled: true + prefix: "faceapi" + url: "http://faceapi:41101" + + api: + enabled: true + openapi: true + port: 8000 + host: "0.0.0.0" + workers: 2 + threads: auto + keepalive: 120 + timeout: 30 + maxBodySize: 64Mi + cors: + enabled: true + origins: "*" + methods: "POST,PUT,GET,DELETE,PATCH,HEAD" + headers: "Content-Type,X-Client-Key" + maxAge: 0 + + audit: + enabled: true + wsEnabled: true + user: + keepFor: "90d" + api: + keepFor: "90d" + + indexer: + enabled: true + timeout: 60 + maxBatchSize: 1000 + + scheduler: + enabled: true + jobs: + expireSessions: + cron: "* 0/10 * * * *" + reloadWorkflows: + cron: "*/15 * * * * *" + cleanSessions: + cron: "* 0/10 * * * *" + keepFor: "3365d" + expireDeviceLogs: + cron: "*/30 * * * *" + keepFor: "30d" + reloadLocales: + cron: "* 0/10 * * * *" + cronWorkflow: + cron: "* 0/10 * * * *" + tempFilesCleanup: + cron: "* */1 * * *" + keepFor: "1d" + + workflow: + enabled: true + workers: auto + + ip2location: + enabled: false + type: regula + regula: + url: https://lic.regulaforensics.com + timeout: 3 + + livekit: + enabled: false + url: http://localhost:7880 + apiKey: devkey + apiSecret: secret + + +messageBroker: + url: amqp://admin:admin@rabbitmq:5672/ + +metrics: + statsd: + enabled: false + host: localhost + port: 9125 + prefix: idv + database: + enabled: true + alerts: + enabled: true + source: prometheus + prometheus: + url: http://prometheus:9090 + filter: + groups: [ "idv.rules" ] + +basicAuth: + enabled: True diff --git a/idv/environment/config/docreader/config.yaml b/idv/environment/config/docreader/config.yaml new file mode 100644 index 0000000..0350e38 --- /dev/null +++ b/idv/environment/config/docreader/config.yaml @@ -0,0 +1,53 @@ +sdk: + systemInfo: + returnSystemInfo: false +service: + database: + connectionString: mongodb://mongo:27017/doc + processing: + enabled: true + results: + audit: false + location: + bucket: docreader-processing + container: docreader-processing + folder: output +# saveResult: true + sessionApi: + enabled: true + transactions: + location: + bucket: docreader-transactions + container: docreader-transactions + folder: docreader-transactions + storage: + type: fs + webServer: + cors: + headers: Content-Type,X-Client-Key + methods: POST,PUT,GET,DELETE,PATCH,HEAD + origins: '*' + demoApp: + enabled: true + webComponent: + enabled: false + logging: + access: + console: true + format: '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' + path: logs/access/docreader-access.log + app: + console: true + path: logs/app/docreader-app.log + formatter: text + level: INFO + metrics: + enabled: true + path: metrics + port: 8080 + ssl: + cert: certs/tls.crt + enabled: false + key: certs/tls.key + tlsVersion: 1.2 + timeout: 30 diff --git a/idv/environment/config/faceapi/config.yaml b/idv/environment/config/faceapi/config.yaml new file mode 100644 index 0000000..5ddacf8 --- /dev/null +++ b/idv/environment/config/faceapi/config.yaml @@ -0,0 +1,84 @@ +sdk: + compare: + limitPerImageTypes: 2 + logging: + level: INFO +service: + webServer: + port: 41101 + workers: 1 + timeout: 60 + + demoApp: + enabled: false + + cors: + origins: "*" + headers: "Content-Type" + methods: "POST,PUT,GET,DELETE,PATCH,HEAD" + + ssl: + enabled: false + + logging: + level: INFO + formatter: text + + access: + console: true + path: logs/faceapi-access.log + + app: + console: true + path: logs/app/facesdk-reader-app.log + + metrics: + enabled: true + path: metrics + + storage: + type: fs + + database: + connectionString: sqlite:///regula.face.db + + liveness: + mode: development + exposeData: + portrait: true + video: true + enabled: true + sessions: + location: + bucket: faceapi + prefix: liveness + + search: + enabled: true + persons: + location: + bucket: faceapi-persons + container: faceapi-persons + folder: tmp/faceapi-persons + results: + audit: true + location: + bucket: faceapi-search + container: faceapi-search + folder: faceapi-search + threshold: 1.0 + vectorDatabase: + opensearch: + awsAuth: + accessKey: null + enabled: false + region: null + secretKey: null + dimension: 512 + host: opensearch + password: t3stP@ss!Pwd#2025 + port: '9200' + useSsl: false + username: admin + verifyCerts: false + type: opensearch \ No newline at end of file diff --git a/idv/environment/data/localization/general_kyc_locale.yaml b/idv/environment/data/localization/general_kyc_locale.yaml new file mode 100644 index 0000000..16799c7 --- /dev/null +++ b/idv/environment/data/localization/general_kyc_locale.yaml @@ -0,0 +1,419 @@ +- key: "GENERAL_KYC_VERIFY_IDENTITY" + code: "en-us" + value: "Verify your identity" + +- key: "GENERAL_KYC_SCAN_ID" + code: "en-us" + value: "Scan your ID" + +- key: "GENERAL_KYC_SCAN_ID_DESC" + code: "en-us" + value: "Place the identity document within the frame, and it will be automatically detected, cropped, and processed." + +- key: "GENERAL_KYC_READ_RFID" + code: "en-us" + value: "Read RFID chip" + +- key: "GENERAL_KYC_READ_RFID_DESC" + code: "en-us" + value: "Place your phone on the document, wait for a vibration signal, and then hold steady until processing is done." + +- key: "GENERAL_KYC_TAKE_SELFIE" + code: "en-us" + value: "Take a selfie" + +- key: "GENERAL_KYC_TAKE_SELFIE_DESC" + code: "en-us" + value: "Position yourself within the frame, follow the instructions, and the selfie will be taken automatically." + +- key: "GENERAL_KYC_START_VERIFICATION" + code: "en-us" + value: "Start verification" + +- key: "GENERAL_KYC_CONTINUE" + code: "en-us" + value: "Continue" + +- key: "GENERAL_KYC_SCAN_ID_EXTRA_DESC" + code: "en-us" + value: "Place the identity document within the frame, and it will be automatically detected, cropped, and processed.\n\nYou can use any valid passport or ID card." + +- key: "GENERAL_KYC_SKIP" + code: "en-us" + value: "Skip" + +- key: "GENERAL_KYC_VERIFICATION_CANCELLED" + code: "en-us" + value: "Verification cancelled" + +- key: "GENERAL_KYC_VERIFICATION_WAITED" + code: "en-us" + value: "Operator confirmation required, please wait for session to continue" + +- key: "GENERAL_KYC_CLOSE" + code: "en-us" + value: "Close" + +- key: "GENERAL_KYC_DATA_SUBMITTED" + code: "en-us" + value: "Your data has been submitted for verification. Thank you!" + +- key: "GENERAL_KYC_VERIFICATION_FAILED" + code: "en-us" + value: "Verification failed. Please try again" + +- key: "GENERAL_KYC_ERROR_READING_ID" + code: "en-us" + value: "Something went wrong while reading your ID" + +- key: "GENERAL_KYC_CLOSE_VERIFICATION" + code: "en-us" + value: "Close verification" + +- key: "GENERAL_KYC_ERROR_READING_RFID" + code: "en-us" + value: "Something went wrong while reading RFID" + +- key: "GENERAL_KYC_TRY_AGAIN" + code: "en-us" + value: "Try again" + +- key: "GENERAL_KYC_SKIP_RFID_VERIFICATION" + code: "en-us" + value: "Skip RFID Verification" + +- key: "GENERAL_KYC_SKIP_RFID_VERIFICATION" + code: "ru-ru" + value: "Пропустить проверку RFID" + +- key: "GENERAL_KYC_VERIFY_IDENTITY" + code: "ru-ru" + value: "Подтвердите свою личность" + +- key: "GENERAL_KYC_SCAN_ID" + code: "ru-ru" + value: "Отсканируйте ваш документ" + +- key: "GENERAL_KYC_SCAN_ID_DESC" + code: "ru-ru" + value: "Поместите документ в рамку, он будет автоматически распознан, обрезан и обработан." + +- key: "GENERAL_KYC_READ_RFID" + code: "ru-ru" + value: "Считайте RFID-чип" + +- key: "GENERAL_KYC_READ_RFID_DESC" + code: "ru-ru" + value: "Приложите телефон к документу, дождитесь вибрации и держите до окончания обработки." + +- key: "GENERAL_KYC_TAKE_SELFIE" + code: "ru-ru" + value: "Сделайте селфи" + +- key: "GENERAL_KYC_CONTINUE" + code: "ru-ru" + value: "Продолжить" + +- key: "GENERAL_KYC_TAKE_SELFIE_DESC" + code: "ru-ru" + value: "Разместите себя в рамке, следуйте инструкциям – селфи будет сделано автоматически." + +- key: "GENERAL_KYC_START_VERIFICATION" + code: "ru-ru" + value: "Начать проверку" + +- key: "GENERAL_KYC_SCAN_ID_EXTRA_DESC" + code: "ru-ru" + value: "Поместите документ в рамку — он будет автоматически распознан, обрезан и обработан.\n\nВы можете использовать любой действительный паспорт или удостоверение личности." + +- key: "GENERAL_KYC_SKIP" + code: "ru-ru" + value: "Пропустить" + +- key: "GENERAL_KYC_VERIFICATION_CANCELLED" + code: "ru-ru" + value: "Проверка отменена" + +- key: "GENERAL_KYC_VERIFICATION_WAITED" + code: "ru-ru" + value: "Необходимо подтверждение оператора, ожидайте продолжения сессии" + +- key: "GENERAL_KYC_CLOSE" + code: "ru-ru" + value: "Закрыть" + +- key: "GENERAL_KYC_DATA_SUBMITTED" + code: "ru-ru" + value: "Ваши данные отправлены на проверку. Спасибо!" + +- key: "GENERAL_KYC_VERIFICATION_FAILED" + code: "ru-ru" + value: "Проверка не удалась. Пожалуйста, попробуйте снова" + +- key: "GENERAL_KYC_ERROR_READING_ID" + code: "ru-ru" + value: "Что-то пошло не так при чтении вашего документа" + +- key: "GENERAL_KYC_CLOSE_VERIFICATION" + code: "ru-ru" + value: "Закрыть проверку" + +- key: "GENERAL_KYC_ERROR_READING_RFID" + code: "ru-ru" + value: "Что-то пошло не так при чтении RFID" + +- key: "GENERAL_KYC_TRY_AGAIN" + code: "ru-ru" + value: "Попробовать снова" + +- key: "GENERAL_KYC_VERIFY_IDENTITY" + code: "es-cl" + value: "Verifica tu identidad" + +- key: "GENERAL_KYC_SCAN_ID" + code: "es-cl" + value: "Escanee su ID" + +- key: "GENERAL_KYC_SCAN_ID_DESC" + code: "es-cl" + value: "Ubica tu documento de identidad en el recuadro, será automáticamente detectado, recortado y procesado." + +- key: "GENERAL_KYC_READ_RFID" + code: "es-cl" + value: "Lectura de chip RFID" + +- key: "GENERAL_KYC_READ_RFID_DESC" + code: "es-cl" + value: "Coloca tu teléfono sobre el documento, espera la señal de vibración y mantenlo quieto hasta que se complete el procesamiento." + +- key: "GENERAL_KYC_TAKE_SELFIE" + code: "es-cl" + value: "Tómate una selfie" + +- key: "GENERAL_KYC_TAKE_SELFIE_DESC" + code: "es-cl" + value: "Posiciónate dentro del marco, sigue las instrucciones y la selfie se tomará automáticamente." + +- key: "GENERAL_KYC_START_VERIFICATION" + code: "es-cl" + value: "Iniciar verificación" + +- key: "GENERAL_KYC_CONTINUE" + code: "es-cl" + value: "Continuar" + +- key: "GENERAL_KYC_SCAN_ID_EXTRA_DESC" + code: "es-cl" + value: "Coloca el documento de identidad dentro del marco; será detectado, recortado y procesado automáticamente.\n\nPuedes usar cualquier pasaporte o tarjeta de identificación válida." + +- key: "GENERAL_KYC_SKIP" + code: "es-cl" + value: "Omitir" + +- key: "GENERAL_KYC_VERIFICATION_CANCELLED" + code: "es-cl" + value: "Verificación cancelada" + +- key: "GENERAL_KYC_VERIFICATION_WAITED" + code: "es-cl" + value: "Se requiere confirmación del operador, espere a que la sesión continúe" + +- key: "GENERAL_KYC_CLOSE" + code: "es-cl" + value: "Cerrar" + +- key: "GENERAL_KYC_DATA_SUBMITTED" + code: "es-cl" + value: "Tus datos han sido enviados para verificación. ¡Gracias!" + +- key: "GENERAL_KYC_VERIFICATION_FAILED" + code: "es-cl" + value: "Verificación fallida. Por favor, inténtelo de nuevo" + +- key: "GENERAL_KYC_ERROR_READING_ID" + code: "es-cl" + value: "Algo salió mal al leer tu documento de identidad" + +- key: "GENERAL_KYC_CLOSE_VERIFICATION" + code: "es-cl" + value: "Cerrar verificación" + +- key: "GENERAL_KYC_ERROR_READING_RFID" + code: "es-cl" + value: "Algo salió mal al leer el RFID" + +- key: "GENERAL_KYC_TRY_AGAIN" + code: "es-cl" + value: "Inténtalo de nuevo" + +- key: "GENERAL_KYC_SKIP_RFID_VERIFICATION" + code: "es-cl" + value: "Omitir verificación de RFID" + +- key: "GENERAL_KYC_VERIFY_IDENTITY" + code: "pt-br" + value: "Verifique sua identidade" + +- key: "GENERAL_KYC_SCAN_ID" + code: "pt-br" + value: "Escaneie seu documento de identidade" + +- key: "GENERAL_KYC_SCAN_ID_DESC" + code: "pt-br" + value: "Coloque o documento de identidade dentro da moldura. Ele será detectado, recortado e processado automaticamente." + +- key: "GENERAL_KYC_READ_RFID" + code: "pt-br" + value: "Leia o chip RFID" + +- key: "GENERAL_KYC_READ_RFID_DESC" + code: "pt-br" + value: "Coloque o seu celular sobre o documento, aguarde o sinal de vibração e mantenha-o parado até que o processamento seja concluído." + +- key: "GENERAL_KYC_TAKE_SELFIE" + code: "pt-br" + value: "Tire uma selfie" + +- key: "GENERAL_KYC_TAKE_SELFIE_DESC" + code: "pt-br" + value: "Posicione-se dentro da moldura, siga as instruções e a selfie será tirada automaticamente." + +- key: "GENERAL_KYC_START_VERIFICATION" + code: "pt-br" + value: "Iniciar verificação" + +- key: "GENERAL_KYC_CONTINUE" + code: "pt-br" + value: "Continuar" + +- key: "GENERAL_KYC_SCAN_ID_EXTRA_DESC" + code: "pt-br" + value: "Coloque o documento de identidade dentro da moldura; ele será detectado, recortado e processado automaticamente.\n\nVocê pode usar qualquer passaporte ou carteira de identidade válida." + +- key: "GENERAL_KYC_SKIP" + code: "pt-br" + value: "Pular" + +- key: "GENERAL_KYC_VERIFICATION_CANCELLED" + code: "pt-br" + value: "Verificação cancelada" + +- key: "GENERAL_KYC_CLOSE" + code: "pt-br" + value: "Fechar" + +- key: "GENERAL_KYC_DATA_SUBMITTED" + code: "pt-br" + value: "Seus dados foram enviados para verificação. Obrigado!" + +- key: "GENERAL_KYC_VERIFICATION_FAILED" + code: "pt-br" + value: "Verificação falhou. Por favor, tente novamente." + +- key: "GENERAL_KYC_ERROR_READING_ID" + code: "pt-br" + value: "Algo deu errado ao ler seu documento de identidade." + +- key: "GENERAL_KYC_CLOSE_VERIFICATION" + code: "pt-br" + value: "Fechar verificação" + +- key: "GENERAL_KYC_ERROR_READING_RFID" + code: "pt-br" + value: "Algo deu errado ao ler o RFID" + +- key: "GENERAL_KYC_TRY_AGAIN" + code: "pt-br" + value: "Tente novamente" + +- key: "GENERAL_KYC_SKIP_RFID_VERIFICATION" + code: "pt-br" + value: "Pular verificação de RFID" + +- key: "GENERAL_KYC_TRY_AGAIN" + code: "pt-br" + value: "Tente novamente" + +- key: "GENERAL_KYC_SKIP_RFID_VERIFICATION" + code: "pt-br" + value: "Pular verificação de RFID" + +- key: "GENERAL_KYC_VERIFY_IDENTITY" + code: "ar-bh" + value: "تحقق من هويتك" + +- key: "GENERAL_KYC_SCAN_ID" + code: "ar-bh" + value: "امسح بطاقة الهوية" + +- key: "GENERAL_KYC_SCAN_ID_DESC" + code: "ar-bh" + value: "ضع بطاقة الهوية داخل الإطار، وسيتم تحديدها وقصها ومعالجتها تلقائيًا" + +- key: "GENERAL_KYC_READ_RFID" + code: "ar-bh" + value: "RFID قراءة شريحة" + +- key: "GENERAL_KYC_READ_RFID_DESC" + code: "ar-bh" + value: "ضع هاتفك على الوثيقة، انتظر إشارة الاهتزاز، ثم ثبت الهاتف حتى تكتمل عملية المعالجة" + +- key: "GENERAL_KYC_TAKE_SELFIE" + code: "ar-bh" + value: "التقط صورة لنفسك" + +- key: "GENERAL_KYC_TAKE_SELFIE_DESC" + code: "ar-bh" + value: "ضع نفسك داخل الإطار، اتبع التعليمات، وسيتم التقاط الصورة تلقائيًا" + +- key: "GENERAL_KYC_START_VERIFICATION" + code: "ar-bh" + value: "بدء التحقق" + +- key: "GENERAL_KYC_CONTINUE" + code: "ar-bh" + value: "تابع" + +- key: "GENERAL_KYC_SCAN_ID_EXTRA_DESC" + code: "ar-bh" + value: "ضع وثيقة الهوية داخل الإطار، وسيتم تحديدها وقصها ومعالجتها تلقائيًا./n/n يمكنك استخدام جواز السفر أو بطاقة هوية سارية المفعول" + +- key: "GENERAL_KYC_SKIP" + code: "ar-bh" + value: "تخطي" + +- key: "GENERAL_KYC_VERIFICATION_CANCELLED" + code: "ar-bh" + value: "تم إلغاء التحقق" + +- key: "GENERAL_KYC_CLOSE" + code: "ar-bh" + value: "إغلاق" + +- key: "GENERAL_KYC_DATA_SUBMITTED" + code: "ar-bh" + value: "!تم إرسال بياناتك للتحقق. شكرًا لك" + +- key: "GENERAL_KYC_VERIFICATION_FAILED" + code: "ar-bh" + value: "فشل التحقق. يرجى المحاولة مرة أخرى" + +- key: "GENERAL_KYC_ERROR_READING_ID" + code: "ar-bh" + value: "حدث خطأ أثناء قراءة بطاقة الهوية الخاصة بك." + +- key: "GENERAL_KYC_CLOSE_VERIFICATION" + code: "ar-bh" + value: "إغلاق التحقق" + +- key: "GENERAL_KYC_ERROR_READING_RFID" + code: "ar-bh" + value: "RFID حدث خطأ أثناء قراءة شريحة" + +- key: "GENERAL_KYC_TRY_AGAIN" + code: "ar-bh" + value: "حاول مرة اخرى" + +- key: "GENERAL_KYC_SKIP_RFID_VERIFICATION" + code: "ar-bh" + value: "RFID تخطى التحقق عبر" diff --git a/idv/environment/data/workflows/general_kyc.yaml b/idv/environment/data/workflows/general_kyc.yaml new file mode 100644 index 0000000..20bf92d --- /dev/null +++ b/idv/environment/data/workflows/general_kyc.yaml @@ -0,0 +1,1692 @@ +id: edda0192-b890-11ef-a348-17da751a6a22 +name: General KYC +version: latest +description: General KYC demo flow +lifetime: 1000 + +defaultLocale: en-us +locales: + - code: en-us + value: English + - code: ru-ru + value: Russian + - code: es-cl + value: Spanish + - code: pt-br + value: Portuguese + - code: ar-bh + value: Arabic +client: + layout: + info_screen: + backgroundColor: '#F5F5F7' + info_details_view: + backgroundColor: '#FFFFFF' + cornerRadius: 16 + info_H1: + fontName: SF Pro Display + fontSize: 28 + fontColor: '#202020' + alignment: LEFT + info_H2: + fontName: SF Pro Display + fontSize: 17 + fontColor: '#202020' + info_H3: + fontName: SF Pro Display + fontSize: 15 + fontColor: '#202020' + info_description: + fontName: SF Pro Display + fontSize: 20 + fontColor: '#202020' + info_main_button_title: + fontName: SF Pro + fontSize: 17 + fontColor: '#FFFFFF' + alignment: CENTER + info_secondary_button_title: + fontName: SF Pro + fontSize: 17 + fontColor: '#7E57C5' + alignment: CENTER + status_title: + fontName: SF Pro Display + fontSize: 24 + fontColor: '#202020' + alignment: CENTER + progress_title: + fontName: SF Pro + fontSize: 16 + fontColor: '#202020' + alignment: CENTER + info_details_image: + contentMode: FILL + cornerRadius: 16 + info_image: + contentMode: ASPECT_FILL + cornerRadius: 9 + status_image: + contentMode: FILL + info_signature: + signature: + imageId: signature + contentMode: ASPECT_FILL + info_main_button: + backgroundColor: '#7E57C5' + highlightColor: '#7E57C580' + cornerRadius: 16 + info_secondary_button: + backgroundColor: '#F5F5F7' + highlightColor: '#F5F5F780' + cornerRadius: 16 + borderWidth: 1 + borderColor: '#7E57C5' + progress_indicator: + indicatorColor: '#7E57C5' + info_common_header: + closeButton: + image: + imageId: close_icon + responseBody: + continue: false + info_common_skip_header: + closeButton: + image: + imageId: close_icon + responseBody: + continue: false + skip: false + info_common_footer: + signature: + imageId: signature + steps: + - templateId: PROGRESS + templateLayout: + progressView: + title: + text: Processing + layout: + - progress_title + layout: + - progress_indicator + footer: + layout: + - info_common_footer + layout: + - info_screen + android: + dbPath: + type: assets + path: Regula/db.dat + databseId: Full + licensePath: + type: assets + path: Regula/regula.license + ios: + dbPath: + type: mainBundle + path: db.dat + databseId: Full + licensePath: + type: mainBundle + path: regula.license +steps: + - id: ONBOARDING + name: ONBOARDING + fileId: is_continue_file + cancelLifetimeTimer: false + type: DATA + client: + templateId: INFO_DETAILS + templateLayout: + header: + layout: + - info_common_header + title: + text: "{{ locale.GENERAL_KYC_VERIFY_IDENTITY }}" + layout: + - info_H1 + alignment: CENTER + details: + items: + - title: + text: "{{ locale.GENERAL_KYC_SCAN_ID }}" + layout: + - info_H2 + description: + text: "{{ locale.GENERAL_KYC_SCAN_ID_DESC }}" + layout: + - info_H3 + image: + imageURL: '{{ config.baseUrl }}/api/v1/assets/onboardingScanDoc/content' + layout: + - info_details_image + - title: + text: "{{ locale.GENERAL_KYC_READ_RFID }}" + layout: + - info_H2 + description: + text: "{{ locale.GENERAL_KYC_READ_RFID_DESC }}" + layout: + - info_H3 + image: + imageURL: '{{ config.baseUrl }}/api/v1/assets/onboardingRfid/content' + layout: + - info_details_image + - title: + text: "{{ locale.GENERAL_KYC_TAKE_SELFIE }}" + layout: + - info_H2 + description: + text: "{{ locale.GENERAL_KYC_TAKE_SELFIE_DESC }}" + layout: + - info_H3 + image: + imageURL: '{{ config.baseUrl }}/api/v1/assets/onboardingLiveness/content' + layout: + - info_details_image + layout: + - info_details_view + buttons: + - title: + text: "{{ locale.GENERAL_KYC_START_VERIFICATION }}" + layout: + - info_main_button_title + responseBody: + continue: true + layout: + - info_main_button + footer: + layout: + - info_common_footer + layout: + - info_screen + transformers: + - id: CONTINUE_TRANSFORMER + conditions: + - name: Prepare OK + transit: + step: PREPARE_DOCUMENT + rules: + - id: continue_rule + decision: true + - name: Prepare NOT OK + transit: + step: ABORT + status: NOT_COMPLETED + rules: + - id: continue_rule + decision: false + - id: PREPARE_DOCUMENT + name: PREPARE_DOCUMENT + fileId: is_continue_file + cancelLifetimeTimer: false + type: DATA + client: + templateId: INFO + templateLayout: + header: + layout: + - info_common_header + image: + imageURL: '{{ config.baseUrl }}/api/v1/assets/prepareScanDoc/content' + layout: + - info_image + title: + text: "{{ locale.GENERAL_KYC_SCAN_ID }}" + layout: + - info_H1 + description: + text: "{{ locale.GENERAL_KYC_SCAN_ID_DESC }}" + layout: + - info_description + buttons: + - title: + text: "{{ locale.GENERAL_KYC_CONTINUE }}" + layout: + - info_main_button_title + responseBody: + continue: true + layout: + - info_main_button + footer: + layout: + - info_common_footer + layout: + - info_screen + transformers: + - id: CONTINUE_TRANSFORMER + conditions: + - name: Prepare passport OK + transit: + step: DOC_READER + rules: + - id: continue_rule + decision: true + - name: Prepare passport NOT OK + transit: + step: ABORT + status: NOT_COMPLETED + rules: + - id: continue_rule + decision: false + attempts: + count: 3 + transit: + step: KYC_FAILED + status: NOT_VERIFIED + - id: DOC_READER + name: DOC_READER + fileId: doc_reader + cancelLifetimeTimer: false + type: DATA + client: + templateId: DOC_READER + locale: '{{ session.locale }}' + processParam: + multipageProcessing: true + scenario: FullProcess + internalScenario: MrzAndLocate + backendProcessing: + serviceURL: '{{ config.baseUrl }}/{{ config.services.docreader.prefix }}' + rfidServerSideChipVerification: false + functionality: + recordScanningProcess: true + showSkipNextPageButton: false + showCaptureButton: false + showCaptureButtonDelayFromDetect: 0 + showCaptureButtonDelayFromStart: 0 + orientation: + android: 1 + ios: 2 + rfidTimeout: 60 + transformers: + - id: DOC_READER_TRANSFORMER + - id: CANCEL_TRANSFORMER + conditions: + - name: Doc cancel + transit: + step: PREPARE_DOCUMENT + rules: + - id: cancel_rule + decision: true + - name: Doc has no type + transit: + step: ERROR_DOC + rules: + - id: doc_has_type + decision: false + - name: Valid doc with rfid + transit: + step: PREPARE_RFID + rules: + - id: rfid_flow + decision: true + - name: Valid doc without rfid + transit: + step: PREPARE_LIVENESS + rules: + - id: rfid_flow + decision: false + actions: + - id: DOCREADER_SCENARIO + - id: GET_DOCREADER_RESULTS + async: true + - id: GET_DOC_VIDEO + async: true + normalizers: + - id: document + attempts: + count: 3 + transit: + step: KYC_FAILED + status: NOT_VERIFIED + - id: PREPARE_RFID + name: PREPARE_RFID + fileId: is_continue_or_skip_file + cancelLifetimeTimer: false + type: DATA + client: + templateId: INFO + templateLayout: + header: + layout: + - info_common_skip_header + image: + imageURL: '{{ config.baseUrl }}/api/v1/assets/prepareRFID/content' + layout: + - info_image + title: + text: "{{ locale.GENERAL_KYC_READ_RFID }}" + layout: + - info_H1 + description: + text: "{{ locale.GENERAL_KYC_READ_RFID_DESC }}" + layout: + - info_description + buttons: + - title: + text: "{{ locale.GENERAL_KYC_CONTINUE }}" + layout: + - info_main_button_title + responseBody: + continue: true + skip: false + layout: + - info_main_button + - title: + text: "{{ locale.GENERAL_KYC_SKIP }}" + layout: + - info_secondary_button_title + responseBody: + continue: false + skip: true + layout: + - info_secondary_button + footer: + layout: + - info_common_footer + layout: + - info_screen + transformers: + - id: CONTINUE_OR_SKIP_TRANSFORMER + conditions: + - name: Prepare rfid + transit: + step: SCAN_RFID + rules: + - id: continue_rule + decision: true + - name: Skip step + transit: + step: PREPARE_LIVENESS + rules: + - id: skip_rule + decision: true + - name: Prepare rfid not ok + transit: + step: ABORT + status: NOT_COMPLETED + rules: + - id: continue_rule + decision: false + attempts: + count: 3 + transit: + step: PREPARE_LIVENESS + - id: SCAN_RFID + name: SCAN_RFID + fileId: scan_rfid + cancelLifetimeTimer: false + type: DATA + client: + templateId: SCAN_RFID + locale: '{{ session.locale }}' + processParam: + scenario: FullProcess + backendProcessing: + serviceURL: '{{ config.baseUrl }}/{{ config.services.docreader.prefix }}' + rfidServerSideChipVerification: false + transformers: + - id: SCAN_RFID_TRANSFORMER + - id: CANCEL_TRANSFORMER + conditions: + - name: Rfid cancel + transit: + step: PREPARE_RFID + rules: + - id: cancel_rule + decision: true + - name: Valid rfid + transit: + step: PREPARE_LIVENESS + rules: + - id: valid_rfid + decision: true + - name: Invalid rfid transaction + transit: + step: ERROR_RFID + operator: any + rules: + - id: invalid_rfid_transaction + decision: true + - name: Not valid rfid + transit: + step: ERROR_RFID + operator: any + rules: + - id: valid_rfid + decision: false + actions: + - id: DOCREADER_SCENARIO + - id: GET_DOCREADER_RESULTS + normalizers: + - id: document + attempts: + count: 3 + transit: + step: PREPARE_LIVENESS + - id: PREPARE_LIVENESS + name: PREPARE_LIVENESS + fileId: is_continue_file + cancelLifetimeTimer: false + type: DATA + client: + templateId: INFO + templateLayout: + header: + layout: + - info_common_header + image: + imageURL: '{{ config.baseUrl }}/api/v1/assets/prepareLiveness/content' + layout: + - info_image + title: + text: '{{ locale.GENERAL_KYC_TAKE_SELFIE }}' + layout: + - info_H1 + description: + text: '{{ locale.GENERAL_KYC_TAKE_SELFIE_DESC }}' + layout: + - info_description + buttons: + - title: + text: '{{ locale.GENERAL_KYC_CONTINUE }}' + layout: + - info_main_button_title + responseBody: + continue: true + layout: + - info_main_button + footer: + layout: + - info_common_footer + layout: + - info_screen + transformers: + - id: CONTINUE_TRANSFORMER + conditions: + - name: Liveness is ok + transit: + step: LIVENESS + rules: + - id: continue_rule + decision: true + - name: Liveness not ok + transit: + step: ABORT + status: NOT_COMPLETED + rules: + - id: continue_rule + decision: false + attempts: + transit: + step: KYC_FAILED + status: NOT_VERIFIED + count: 3 + + - id: LIVENESS + name: LIVENESS + fileId: liveness + cancelLifetimeTimer: false + type: DATA + client: + templateId: LIVENESS + serviceURL: '{{ config.baseUrl }}/{{ config.services.faceapi.prefix }}' + locale: '{{ session.locale }}' + config: + skipSteps: + - SUCCESS_STEP + attemptsCount: 3 + recordingProcess: SYNCHRONOUS_UPLOAD + transformers: + - id: LIVENESS_TRANSFORMER + - id: CANCEL_TRANSFORMER + actions: + - id: LIVENESS_STATUS + - id: MATCH_DOC_AND_LIVENESS + - id: GET_LIVENESS_PORTRAIT + - id: GET_LIVENESS_VIDEO + conditions: + - name: Liveness cancel + transit: + step: PREPARE_LIVENESS + operator: all + rules: + - id: cancel_rule + decision: true + - name: Continue + transit: + step: PERFORM_DATA_EXTRACTION + operator: all + rules: + - id: cancel_rule + decision: false + - id: PERFORM_DATA_EXTRACTION + name: PERFORM_DATA_EXTRACTION + cancelLifetimeTimer: false + type: PROCESSING + conditions: + - name: Next + transit: + step: PERFORM_MATCH + rules: + - id: constant_true_rule + decision: true + actions: + - id: LIVENESS_STATUS + async: true + - id: GET_LIVENESS_VIDEO + async: true + - id: GET_LIVENESS_PORTRAIT + async: true + - id: PERFORM_MATCH + name: PERFORM_MATCH + cancelLifetimeTimer: false + type: PROCESSING + conditions: + - name: Next + transit: + step: PERFORM_CHECKS + rules: + - id: constant_true_rule + decision: true + actions: + - id: MATCH_DOC_AND_LIVENESS + - id: PERFORM_CHECKS + name: PERFORM_CHECKS + cancelLifetimeTimer: false + type: PROCESSING + conditions: + - name: All Good + transit: + step: KYC_COMPLETED + status: VERIFIED + operator: all + rules: + - id: valid_document + decision: true + - id: valid_document_type + decision: true + - id: valid_age + decision: true + - id: compliant_liveness_status + decision: true + - id: valid_similarity + decision: true + - name: Not Good + transit: + step: KYC_COMPLETED + status: NOT_VERIFIED + operator: any + rules: + - id: valid_document + decision: false + - id: valid_document_type + decision: false + - id: valid_age + decision: false + - id: compliant_liveness_status + decision: false + - id: valid_similarity + decision: false + normalizers: + - id: liveness + - id: face_match + - id: document + - id: ABORT + name: ABORT + cancelLifetimeTimer: false + final: true + type: DATA + client: + templateId: STATUS + templateLayout: + image: + imageURL: '{{ config.baseUrl }}/api/v1/assets/workflowErrorLarge/content' + layout: + - status_image + title: + text: '{{ locale.GENERAL_KYC_VERIFICATION_CANCELLED }}' + layout: + - status_title + buttons: + - title: + text: '{{ locale.GENERAL_KYC_CLOSE }}' + layout: + - info_main_button_title + responseBody: + continue: true + layout: + - info_main_button + footer: + layout: + - info_common_footer + layout: + - info_screen + - id: ERROR + name: ERROR + cancelLifetimeTimer: false + final: true + type: DATA + - id: KYC_COMPLETED + name: KYC_COMPLETED + cancelLifetimeTimer: false + final: true + type: DATA + client: + templateId: STATUS + templateLayout: + image: + imageURL: '{{ config.baseUrl }}/api/v1/assets/workflowVerifiedLarge/content' + layout: + - status_image + title: + text: '{{ locale.GENERAL_KYC_DATA_SUBMITTED }}' + layout: + - status_title + buttons: + - title: + text: '{{ locale.GENERAL_KYC_CLOSE }}' + layout: + - info_main_button_title + responseBody: + continue: true + layout: + - info_main_button + footer: + layout: + - info_common_footer + layout: + - info_screen + - id: KYC_FAILED + name: KYC_FAILED + cancelLifetimeTimer: false + final: true + type: DATA + client: + templateId: STATUS + templateLayout: + image: + imageURL: '{{ config.baseUrl }}/api/v1/assets/workflowErrorLarge/content' + layout: + - status_image + title: + text: '{{ locale.GENERAL_KYC_VERIFICATION_FAILED }}' + layout: + - status_title + buttons: + - title: + text: '{{ locale.GENERAL_KYC_CLOSE }}' + layout: + - info_main_button_title + responseBody: + continue: true + layout: + - info_main_button + footer: + layout: + - info_common_footer + layout: + - info_screen + - id: ERROR_DOC + name: ERROR_DOC + fileId: is_continue_file + cancelLifetimeTimer: false + type: DATA + client: + templateId: STATUS + templateLayout: + image: + imageURL: '{{ config.baseUrl }}/api/v1/assets/workflowErrorLarge/content' + layout: + - status_image + title: + text: '{{ locale.GENERAL_KYC_ERROR_READING_ID }}' + layout: + - status_title + buttons: + - title: + text: '{{ locale.GENERAL_KYC_TRY_AGAIN }}' + layout: + - info_main_button_title + responseBody: + continue: true + layout: + - info_main_button + - title: + text: '{{ locale.GENERAL_KYC_CLOSE_VERIFICATION }}' + layout: + - info_secondary_button_title + responseBody: + continue: false + layout: + - info_secondary_button + footer: + layout: + - info_common_footer + layout: + - info_screen + transformers: + - id: CONTINUE_TRANSFORMER + conditions: + - name: Continue + transit: + step: PREPARE_DOCUMENT + rules: + - id: continue_rule + decision: true + - name: Abort + transit: + step: ABORT + status: NOT_COMPLETED + rules: + - id: continue_rule + decision: false + normalizers: + - id: document + attempts: + count: 2 + transit: + step: KYC_FAILED + status: NOT_VERIFIED + - id: ERROR_RFID + name: ERROR_RFID + fileId: is_continue_or_skip_file + cancelLifetimeTimer: false + type: DATA + client: + templateId: STATUS + templateLayout: + image: + imageURL: '{{ config.baseUrl }}/api/v1/assets/workflowErrorLarge/content' + layout: + - status_image + title: + text: '{{ locale.GENERAL_KYC_ERROR_READING_RFID }}' + layout: + - status_title + buttons: + - title: + text: '{{ locale.GENERAL_KYC_TRY_AGAIN }}' + layout: + - info_main_button_title + responseBody: + continue: true + skip: false + layout: + - info_main_button + - title: + text: '{{ locale.GENERAL_KYC_CLOSE_VERIFICATION }}' + layout: + - info_secondary_button_title + responseBody: + continue: false + skip: false + layout: + - info_secondary_button + - title: + text: '{{ locale.GENERAL_KYC_SKIP_RFID_VERIFICATION }}' + layout: + - info_secondary_button_title + responseBody: + continue: false + skip: true + layout: + - info_secondary_button + footer: + layout: + - info_common_footer + layout: + - info_screen + transformers: + - id: CONTINUE_OR_SKIP_TRANSFORMER + conditions: + - name: Continue + transit: + step: PREPARE_RFID + rules: + - id: continue_rule + decision: true + - name: Skip step + transit: + step: PREPARE_LIVENESS + rules: + - id: skip_rule + decision: true + - name: Abort + transit: + step: ABORT + status: NOT_COMPLETED + rules: + - id: continue_rule + decision: false + attempts: + count: 2 + transit: + step: PREPARE_LIVENESS + status: NOT_VERIFIED +rules: + - id: invalid_rfid_transaction + name: Is empty rfid transaction + precondition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: SCAN_RFID_TRANSACTION_ID + op: $exists + condition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: SCAN_RFID_TRANSACTION_ID + op: $eq + value: '' + - id: constant_true_rule + name: Constant True + condition: + type: script + script: 1 == 1 + - id: continue_rule + name: Continue + precondition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: CONTINUE + op: $exists + condition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: CONTINUE + op: $eq + value: 'true' + - id: skip_rule + name: Skip + precondition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: SKIP + op: $exists + condition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: SKIP + op: $eq + value: 'true' + - id: cancel_rule + name: Cancel + precondition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: CANCEL + op: $exists + condition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: CANCEL + op: $eq + value: 'true' + - id: valid_document + name: Valid Document + precondition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: TRANSACTION_ID + op: $exists + - property: FULL_AUTH_OVERALL_STATUS + op: $exists + condition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: FULL_AUTH_OVERALL_STATUS + op: $eq + value: 1 + - id: valid_document_type + name: Allowed document type + precondition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: DOCTYPE + op: $exists + condition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: DOCTYPE + op: $in + value: + - 11 + - 12 + - 215 + - id: doc_has_type + name: Has document type + precondition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: GET_DOCREADER_RESULTS_completed + op: $exists + condition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: DOCTYPE + op: $exists + - id: valid_age + name: Age verification + precondition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: AGE + op: $exists + condition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: AGE + op: $gte + value: 18 + - id: compliant_liveness_status + name: Liveness check + condition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: LIVENESS_STATUS + op: $eq + value: 0 + - id: valid_similarity + name: Document vs Selfie match + condition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: LIVENESS_DOC_SIMILARITY + op: $gte + value: 0.8 + - id: rfid_flow + name: Go to RFID Flow + precondition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: CHIP_PAGE + op: $exists + - property: HAS_NFC + op: $exists + condition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: CHIP_PAGE + op: $ne + value: 0 + - property: HAS_NFC + op: $eq + value: 'true' + - id: valid_rfid + name: Validated RFID + precondition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: RFID_STATUS + op: $exists + condition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: RFID_STATUS + op: $eq + value: 1 + - id: valid_mrz_status + name: MRZ validity + precondition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: MRZ_STATUS + op: $exists + condition: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: MRZ_STATUS + op: $eq + value: 1 +events: + onError: + transit: + step: ERROR + status: not_verified + onTimeout: + transit: + step: ABORT + status: not_completed +properties: + - id: FULL_NAME + type: str + - id: FIRST_NAME + type: str + - id: SURNAME + type: str + - id: DOCUMENT_NUMBER + type: str + - id: LIVENESS_STATUS + type: int + - id: LIVENESS_PORTRAIT + type: binary + - id: LIVENESS_VIDEO + type: binary + - id: DOC_VIDEO + type: binary + - id: TRANSACTION_ID + type: str + - id: LIVENESS_TRANSACTION_ID + type: str + - id: SCAN_RFID_TRANSACTION_ID + type: str + - id: HAS_NFC + type: bool + - id: LIVENESS_DOC_SIMILARITY + type: decimal + - id: AGE + type: int + - id: RFID_STATUS + type: int + - id: CHIP_PAGE + type: int + - id: RFID_PHOTO + type: binary + - id: DOC_PHOTO + type: binary + - id: DOCUMENT_CLASS_CODE + type: str + - id: FULL_AUTH_OVERALL_STATUS + type: int + - id: DOCTYPE + type: int + - id: MRZ_STATUS + type: int + - id: ONECANDIDATE + type: str + - id: CONTINUE + type: bool + - id: SKIP + type: bool + - id: CANCEL + type: bool + - id: NATIONALITY + type: str + - id: DATE_OF_BIRTH + type: str +transformers: + - id: GET_DOC_READER_RESULTS + name: GET_DOC_READER_RESULTS + type: easyjson + extracts: + - id: DOCTYPE + rule: + key: dType + path: ContainerList.List.OneCandidate.FDSIDList + - id: CHIP_PAGE + rule: + key: ChipPage + - id: AGE + rule: + key: value + path: ContainerList.List.Text.fieldList + filters: + fieldList: + fieldType: 185 + lcid: 0 + - id: FULL_AUTH_OVERALL_STATUS + rule: + key: overallStatus + path: ContainerList.List.Status + - id: RFID_STATUS + rule: + key: Status + path: ContainerList.List.TDocBinaryInfo.RFID_BINARY_DATA.RFID_Session_Data + - id: MRZ_STATUS + rule: + key: mrz + path: ContainerList.List.Status.detailsOptical + - id: FULL_NAME + rule: + key: value + path: ContainerList.List.Text.fieldList + filters: + fieldList: + fieldType: 25 + lcid: 0 + - id: FIRST_NAME + rule: + key: value + path: ContainerList.List.Text.fieldList + filters: + fieldList: + fieldType: 9 + lcid: 0 + - id: SURNAME + rule: + key: value + path: ContainerList.List.Text.fieldList + filters: + fieldList: + fieldType: 8 + lcid: 0 + - id: DOCUMENT_NUMBER + rule: + key: value + path: ContainerList.List.Text.fieldList + filters: + fieldList: + fieldType: 2 + lcid: 0 + - id: DOC_PHOTO + rule: + key: value + path: ContainerList.List.Images.fieldList.valueList + filters: + fieldList: + fieldType: 201 + valueList: + containerType: 6 + - id: DOC_PHOTO + rule: + key: value + path: ContainerList.List.Images.fieldList.valueList + filters: + fieldList: + fieldType: 201 + valueList: + containerType: 103 + - id: NATIONALITY + rule: + key: value + path: ContainerList.List.Text.fieldList + filters: + fieldList: + fieldType: 11 + lcid: 0 + - id: DATE_OF_BIRTH + rule: + key: value + path: ContainerList.List.Text.fieldList + filters: + fieldList: + fieldType: 5 + lcid: 0 + - id: MATCH_DOC_AND_LIVENESS_TRANSFORMER + name: MATCH_DOC_AND_LIVENESS_TRANSFORMER + type: json + extracts: + - id: LIVENESS_DOC_SIMILARITY + rule: $..results.[0].similarity + - id: LIVENESS_STATUS_TRANSFORMER + name: LIVENESS_STATUS_TRANSFORMER + type: json + extracts: + - id: LIVENESS_STATUS + rule: $..status + - id: LIVENESS_PORTRAIT_TRANSFORMER + name: LIVENESS_PORTRAIT_TRANSFORMER + type: binary + extracts: + - id: LIVENESS_PORTRAIT + - id: LIVENESS_VIDEO_TRANSFORMER + name: LIVENESS_VIDEO_TRANSFORMER + type: binary + extracts: + - id: LIVENESS_VIDEO + - id: DOC_VIDEO_TRANSFORMER + name: DOC_VIDEO_TRANSFORMER + type: binary + extracts: + - id: DOC_VIDEO + - id: LIVENESS_TRANSFORMER + name: LIVENESS_TRANSFORMER + type: json + extracts: + - id: LIVENESS_TRANSACTION_ID + rule: $..transactionId + - id: DOC_READER_TRANSFORMER + name: DOC_READER_TRANSFORMER + type: json + extracts: + - id: TRANSACTION_ID + rule: $..transactionId + - id: HAS_NFC + rule: $..has_nfc + - id: SCAN_RFID_TRANSFORMER + name: SCAN_RFID_TRANSFORMER + type: json + extracts: + - id: SCAN_RFID_TRANSACTION_ID + rule: $..transactionId + - id: CANCEL_TRANSFORMER + name: CANCEL_TRANSFORMER + type: json + extracts: + - id: CANCEL + rule: $..cancel + - id: CONTINUE_TRANSFORMER + name: CONTINUE_TRANSFORMER + type: json + extracts: + - id: CONTINUE + rule: $..continue + - id: CONTINUE_OR_SKIP_TRANSFORMER + name: CONTINUE_OR_SKIP_TRANSFORMER + type: json + extracts: + - id: CONTINUE + rule: $..continue + - id: SKIP + rule: $..skip +actions: + - id: MATCH_DOC_AND_LIVENESS + name: MATCH_DOC_AND_LIVENESS + type: http + fileId: match_doc_and_liveness + transformers: + - id: MATCH_DOC_AND_LIVENESS_TRANSFORMER + options: + url: '{{ config.services.faceapi.url }}/api/match?logRequest=false' + method: post + headers: null + successCodes: + - 200 + body: | + { + "images": [ + { + "index": 0, + "data": "{{ DOC_PHOTO | loadFromStorage | b64encode }}", + "type": 1 + }, + { + "index": 1, + "data": "{{ LIVENESS_PORTRAIT | loadFromStorage | b64encode }}", + "type": 3 + } + ] + } + response: {} + timeout: 25 + when: + type: statement + op: $and + groups: + - op: $and + conditions: + - property: LIVENESS_PORTRAIT + op: $exists + - id: DOCREADER_SCENARIO + name: DOCREADER_SCENARIO + type: http + options: + url: >- + {{ config.services.docreader.url }}/api/v2/transaction/{{ TRANSACTION_ID + }}/process?useCache=true + method: post + headers: null + successCodes: + - 200 + body: | + { + "processParam": { + "scenario": "FullProcess", + "useFaceApi": true, + "faceApi": { + "serviceTimeout": 10000, + "url": "{{ config.services.faceapi.url }}" + }, + "log": true, + "authParams": { + "livenessParams": { + "checkED": false + } + } + } + } + response: {} + retry: {} + - id: GET_DOCREADER_RESULTS + name: GET_DOCREADER_RESULTS + type: http + fileId: doc_reader_results + transformers: + - id: GET_DOC_READER_RESULTS + options: + url: >- + {{ config.services.docreader.url }}/api/v2/transaction/{{ TRANSACTION_ID + }}/results?withImages=true + method: get + headers: null + successCodes: + - 200 + body: null + response: {} + - id: LIVENESS_STATUS + name: LIVENESS_STATUS + type: http + fileId: liveness_status + transformers: + - id: LIVENESS_STATUS_TRANSFORMER + options: + url: >- + {{ config.services.faceapi.url }}/api/v2/liveness?transactionId={{ + LIVENESS_TRANSACTION_ID }}&logRequest=false + method: get + headers: null + successCodes: + - 200 + body: null + response: {} + timeout: 20 + - id: GET_LIVENESS_PORTRAIT + name: LIVENESS_PORTRAIT + type: http + transformers: + - id: LIVENESS_PORTRAIT_TRANSFORMER + options: + url: >- + {{ config.services.faceapi.url + }}/api/v2/liveness/portrait?transactionId={{ LIVENESS_TRANSACTION_ID + }}&logRequest=false + method: get + headers: null + successCodes: + - 200 + - 404 + body: null + response: {} + timeout: 20 + - id: GET_LIVENESS_VIDEO + name: LIVENESS_VIDEO + type: http + transformers: + - id: LIVENESS_VIDEO_TRANSFORMER + options: + url: >- + {{ config.services.faceapi.url }}/api/v2/liveness/video?transactionId={{ + LIVENESS_TRANSACTION_ID }}&logRequest=false + method: get + headers: null + successCodes: + - 200 + - 404 + body: null + response: {} + timeout: 20 + - id: GET_DOC_VIDEO + name: GET_DOC_VIDEO + type: http + transformers: + - id: DOC_VIDEO_TRANSFORMER + options: + url: >- + {{ config.services.docreader.url }}/api/v2/transaction/{{ TRANSACTION_ID + }}/file?name=in/video.mp4&logRequest=false + method: get + headers: null + successCodes: + - 200 + - 404 + body: null + response: {} + timeout: 20 +files: + - id: match_doc_and_liveness + type: REGULA_FACE_MATCH + contentType: application/json + - id: liveness_status + type: UNKNOWN + contentType: application/json + - id: is_continue_file + type: UNKNOWN + contentType: application/json + jsonSchema: | + { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "continue": { + "type": "boolean" + } + }, + "required": [ + "continue" + ] + } + - id: is_continue_or_skip_file + type: UNKNOWN + contentType: application/json + jsonSchema: | + { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "continue": { + "type": "boolean" + }, + "skip": { + "type": "boolean" + } + }, + "required": [ + "continue", + "skip" + ] + } + - id: prepare_document + type: UNKNOWN + contentType: application/json + jsonSchema: | + { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "continue": { + "type": "boolean" + }, + "has_nfc": { + "type": "boolean" + } + }, + "required": [ + "continue", + "has_nfc" + ] + } + - id: doc_reader_results + type: REGULA_DOC_READER + contentType: application/json + - id: doc_reader + type: UNKNOWN + contentType: application/json + jsonSchema: | + { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "transactionId": { + "type": "string" + }, + "cancel": { + "type": "boolean" + }, + "has_nfc": { + "type": "boolean" + } + }, + "required": [ + "transactionId", + "cancel", + "has_nfc" + ] + } + - id: liveness + type: UNKNOWN + contentType: application/json + jsonSchema: | + { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "transactionId": { + "type": "string" + }, + "cancel": { + "type": "boolean" + } + }, + "required": [ + "transactionId", + "cancel" + ] + } + - id: scan_rfid + type: UNKNOWN + contentType: application/json + jsonSchema: | + { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "transactionId": { + "type": "string" + }, + "cancel": { + "type": "boolean" + } + }, + "required": [ + "transactionId", + "cancel" + ] + } +view: + title: + propertyIds: + - FULL_NAME + photo: + propertyIds: + - DOC_PHOTO + fields: + - propertyId: DATE_OF_BIRTH + name: Date of Birth + - propertyId: NATIONALITY + name: Nationality + - propertyId: DOCUMENT_NUMBER + name: Document number +normalizers: + - id: document + name: Document status + type: DOCUMENT + rules: + - id: valid_document + decision: true + - id: valid_document_type + decision: true + - id: valid_age + decision: true + identifier: + property: TRANSACTION_ID + group: Documents + options: + documentFile: + actionId: GET_DOCREADER_RESULTS + videoFile: + property: DOC_VIDEO + - id: liveness + name: Liveness + type: LIVENESS + rules: + - id: compliant_liveness_status + decision: true + group: Biometrics + options: + livenessPortrait: + property: LIVENESS_PORTRAIT + livenessVideo: + property: LIVENESS_VIDEO + - id: face_match + name: Selfie match + type: FACE_MATCH + rules: + - id: valid_similarity + decision: true + group: Biometrics + options: + results: + - name: Selfie + similarity: null + file: + actionId: GET_LIVENESS_PORTRAIT + - name: Passport + similarity: + property: LIVENESS_DOC_SIMILARITY + file: + property: DOC_PHOTO \ No newline at end of file diff --git a/idv/environment/grafana/provisioning/dashboards/locust-test.json b/idv/environment/grafana/provisioning/dashboards/locust-test.json new file mode 100644 index 0000000..7044d76 --- /dev/null +++ b/idv/environment/grafana/provisioning/dashboards/locust-test.json @@ -0,0 +1,2215 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "limit": 100, + "name": "Annotations & Alerts", + "showIn": 0, + "type": "dashboard" + } + ] + }, + "description": "Locust Dashboard for load test analysis", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 20462, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 24, + "panels": [], + "title": "Main", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "red", + "index": 2, + "text": "Stopped" + }, + "1": { + "color": "dark-green", + "index": 0, + "text": "Running" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 0 + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 2 + } + ] + }, + "unit": "none", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 4, + "x": 0, + "y": 1 + }, + "id": 1, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "none", + "wideLayout": true + }, + "pluginVersion": "10.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "exemplar": false, + "expr": "locustexporter_build_info", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "__auto", + "metric": "", + "range": false, + "refId": "A", + "step": 20 + } + ], + "title": "Locust Status", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgb(31, 120, 193)", + "mode": "fixed" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 4, + "y": 1 + }, + "id": 2, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "locust_users", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "range": true, + "refId": "A", + "step": 20 + } + ], + "title": "Swarmed users", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 8, + "y": 1 + }, + "id": 4, + "links": [], + "options": { + "colorMode": "none", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "locust_requests_current_rps{name=\"Aggregated\"}", + "intervalFactor": 2, + "range": true, + "refId": "A", + "step": 20 + } + ], + "title": "Current RPS", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 14, + "y": 1 + }, + "id": 18, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "sum(locust_requests_num_requests{method=~\".+\"})", + "interval": "", + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Requests", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "semi-dark-red", + "value": null + } + ] + }, + "unit": "percentunit", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 1 + }, + "id": 21, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto", + "text": {} + }, + "pluginVersion": "10.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "locust_fail_ratio", + "hide": false, + "interval": "", + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Fails", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgb(31, 120, 193)", + "mode": "fixed" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 4, + "y": 5 + }, + "id": 3, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "exemplar": false, + "expr": "locust_workers_running_count", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "range": true, + "refId": "A", + "step": 20 + } + ], + "title": "Connected workers", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + } + ] + }, + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 14, + "y": 5 + }, + "id": 19, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "sum(locust_requests_num_failures{method=~\".+\"})", + "hide": false, + "interval": "", + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Failures", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false, + "minWidth": 50 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unitScale": true + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "displayName", + "value": "Time" + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "method" + }, + "properties": [ + { + "id": "displayName", + "value": "Method" + }, + { + "id": "unit", + "value": "short" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "name" + }, + "properties": [ + { + "id": "displayName", + "value": "URL" + }, + { + "id": "unit", + "value": "short" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #A" + }, + "properties": [ + { + "id": "displayName", + "value": "MIN RT" + }, + { + "id": "unit", + "value": "ms" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #B" + }, + "properties": [ + { + "id": "displayName", + "value": "Errors" + }, + { + "id": "unit", + "value": "none" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #C" + }, + "properties": [ + { + "id": "displayName", + "value": "MAX RT" + }, + { + "id": "unit", + "value": "ms" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #D" + }, + "properties": [ + { + "id": "displayName", + "value": "MEDIAN RT" + }, + { + "id": "unit", + "value": "ms" + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #E" + }, + "properties": [ + { + "id": "displayName", + "value": "AVG RT" + }, + { + "id": "unit", + "value": "ms" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #H" + }, + "properties": [ + { + "id": "displayName", + "value": "Errors Ratio" + }, + { + "id": "unit", + "value": "reqps" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #F" + }, + "properties": [ + { + "id": "displayName", + "value": "Requests" + }, + { + "id": "unit", + "value": "none" + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #G" + }, + "properties": [ + { + "id": "displayName", + "value": "RPS" + }, + { + "id": "unit", + "value": "reqps" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #I" + }, + "properties": [ + { + "id": "displayName", + "value": "Content" + }, + { + "id": "unit", + "value": "bytes" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #J" + }, + "properties": [ + { + "id": "displayName", + "value": "90 %ile" + }, + { + "id": "unit", + "value": "ms" + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #K" + }, + "properties": [ + { + "id": "displayName", + "value": "99 %ile" + }, + { + "id": "unit", + "value": "ms" + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #L" + }, + "properties": [ + { + "id": "displayName", + "value": "Error %" + }, + { + "id": "unit", + "value": "percentunit" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Method" + }, + "properties": [ + { + "id": "custom.width", + "value": 85 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "custom.width", + "value": 317 + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 14, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "10.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(locust_requests_ninetieth_response_time{method=~\".+\"}) by (method, name)", + "format": "table", + "hide": false, + "instant": true, + "legendFormat": "", + "range": false, + "refId": "J" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(locust_requests_ninety_ninth_response_time{method=~\".+\"}) by (method, name)", + "format": "table", + "hide": false, + "instant": true, + "legendFormat": "", + "range": false, + "refId": "K" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "sum(locust_requests_min_response_time{method=~\".+\"}) by (method, name)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "expr": "sum(locust_requests_max_response_time{method=~\".+\"}) by (method, name)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "expr": "sum(locust_requests_avg_response_time{method=~\".+\"}) by (method, name)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "E" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "expr": "sum(locust_requests_median_response_time{method=~\".+\"}) by (method, name)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "sum(locust_requests_num_failures{method=~\".+\"}) by (method, name)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "expr": "sum(locust_requests_current_fail_per_sec{method=~\".+\"}) by (method, name)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "H" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "exemplar": false, + "expr": "(sum(locust_requests_num_failures{method=~\".+\"}) by (method, name))/(sum(locust_requests_num_requests{method=~\".+\"}) by (method, name))", + "format": "table", + "hide": false, + "instant": true, + "legendFormat": "", + "range": false, + "refId": "L" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "sum(locust_requests_num_requests{method=~\".+\"}) by (method, name)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "F" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "sum(locust_requests_avg_content_length{method=~\".+\"}) by (method, name)", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "I" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "sum(locust_requests_current_rps{method=~\".+\"}) by (method, name)", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "G" + } + ], + "title": "Aggregate Report", + "transformations": [ + { + "id": "merge", + "options": { + "reducers": [] + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "includeByName": {}, + "indexByName": {}, + "renameByName": {} + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps", + "unitScale": true + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "fail_ratio [%]" + }, + "properties": [ + { + "id": "unit", + "value": "percent" + }, + { + "id": "min", + "value": 0 + } + ] + } + ] + }, + "gridPos": { + "h": 16, + "w": 24, + "x": 0, + "y": 14 + }, + "id": 7, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "locust_requests_current_rps{method=~\".+\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{ method}} - {{name}}", + "range": true, + "refId": "A", + "step": 2 + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "sum(locust_requests_current_rps{method=~\".+\"})", + "format": "time_series", + "hide": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "total", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "locust_fail_ratio * 100", + "format": "time_series", + "hide": true, + "interval": "", + "intervalFactor": 2, + "legendFormat": "fail_ratio [%]", + "range": true, + "refId": "B", + "step": 2 + } + ], + "title": "RPS (endpoints)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none", + "unitScale": true + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Users" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 0 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 31, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "exemplar": false, + "expr": "locust_users", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "Users", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "locust_requests_current_rps{name=\"Aggregated\"}", + "hide": false, + "instant": false, + "legendFormat": "Current RPS", + "range": true, + "refId": "B" + } + ], + "title": "Users and Total RPS", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 38 + }, + "id": 23, + "panels": [], + "title": "Errors", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 39 + }, + "id": 22, + "options": { + "legend": { + "calcs": [ + "max", + "last" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Max", + "sortDesc": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(locust_requests_current_fail_per_sec{method=~\".+\"}) by (method, name)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{method}} - {{name}}", + "range": true, + "refId": "A" + } + ], + "title": "Errors Per Second", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "decimals": 2, + "displayName": "", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short", + "unitScale": true + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "displayName", + "value": "Time" + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value" + }, + "properties": [ + { + "id": "displayName", + "value": "Requests" + }, + { + "id": "unit", + "value": "none" + }, + { + "id": "custom.align" + }, + { + "id": "custom.width", + "value": 80 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "method" + }, + "properties": [ + { + "id": "displayName", + "value": "Method" + }, + { + "id": "unit", + "value": "short" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "name" + }, + "properties": [ + { + "id": "displayName", + "value": "URL" + }, + { + "id": "unit", + "value": "short" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "error" + }, + "properties": [ + { + "id": "displayName", + "value": "Error" + }, + { + "id": "unit", + "value": "short" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Method" + }, + "properties": [ + { + "id": "custom.width", + "value": 70 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "custom.width", + "value": 365 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 46 + }, + "id": 16, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "10.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "expr": "sum(locust_errors) by (method, name, error)", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + } + ], + "title": "Errors", + "transformations": [ + { + "id": "merge", + "options": { + "reducers": [] + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "includeByName": {}, + "indexByName": { + "Time": 2, + "Value": 4, + "error": 3, + "method": 0, + "name": 1 + }, + "renameByName": {} + } + } + ], + "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 54 + }, + "id": 26, + "panels": [], + "title": "Response Times", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 55 + }, + "id": 15, + "interval": "", + "links": [], + "options": { + "legend": { + "calcs": [ + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "(locust_requests_current_response_time_percentile_95 > 0) or on() locust_requests_max_response_time{method=\"\",name=\"Aggregated\"}", + "interval": "", + "intervalFactor": 2, + "legendFormat": "P95", + "range": true, + "refId": "D", + "step": 2 + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "(locust_requests_current_response_time_percentile_50 > 0) or on() locust_requests_median_response_time{method=\"\",name=\"Aggregated\"}", + "interval": "", + "legendFormat": "P50", + "range": true, + "refId": "A" + } + ], + "title": "Response Times", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ms", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 63 + }, + "id": 6, + "links": [], + "options": { + "legend": { + "calcs": [ + "last" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "avg(locust_requests_max_response_time{method=~\".+\"})", + "hide": false, + "intervalFactor": 2, + "legendFormat": "AVG MAX", + "range": true, + "refId": "A", + "step": 2 + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "avg(locust_requests_min_response_time{method=~\".+\"})", + "intervalFactor": 2, + "legendFormat": "AVG MIN", + "metric": "", + "range": true, + "refId": "B", + "step": 2 + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "exemplar": false, + "expr": "avg(locust_requests_avg_response_time{method=~\".+\"})", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "AVG AVG", + "metric": "", + "range": true, + "refId": "C", + "step": 2 + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "avg(locust_requests_median_response_time{method=~\".+\"})", + "hide": false, + "intervalFactor": 2, + "legendFormat": "AVG MEDIAN", + "range": true, + "refId": "D", + "step": 2 + } + ], + "title": "Response Times (avarage for all requests)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ms", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 71 + }, + "id": 27, + "links": [], + "options": { + "legend": { + "calcs": [ + "max", + "last" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Max", + "sortDesc": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "b3a95192-0c55-4e25-8bb4-815fff10d074" + }, + "editorMode": "code", + "expr": "locust_requests_avg_response_time{method=~\".+\"}", + "hide": false, + "intervalFactor": 2, + "legendFormat": "{{method}} - {{name}}", + "range": true, + "refId": "A", + "step": 2 + } + ], + "title": "Avarage Response Time", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [ + "locust", + "prometheus" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "Locust Prometheus Monitoring Modern", + "uid": "120224", + "version": 12, + "weekStart": "" +} \ No newline at end of file diff --git a/idv/environment/grafana/provisioning/dashboards/locust-test.yml b/idv/environment/grafana/provisioning/dashboards/locust-test.yml new file mode 100644 index 0000000..60f60be --- /dev/null +++ b/idv/environment/grafana/provisioning/dashboards/locust-test.yml @@ -0,0 +1,12 @@ +apiVersion: 1 + +providers: + - name: 'Locust Dashboards' + orgId: 1 + type: file + disableDeletion: false + editable: true + allowUiUpdates: true + updateIntervalSeconds: 60 + options: + path: /etc/grafana/provisioning/dashboards \ No newline at end of file diff --git a/idv/environment/grafana/provisioning/datasources/prometheus.yml b/idv/environment/grafana/provisioning/datasources/prometheus.yml new file mode 100644 index 0000000..056f9ba --- /dev/null +++ b/idv/environment/grafana/provisioning/datasources/prometheus.yml @@ -0,0 +1,16 @@ +apiVersion: 1 + +deleteDatasources: + - name: Prometheus + orgId: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + orgId: 1 + uid: b3a95192-0c55-4e25-8bb4-815fff10d074 + url: http://prometheus:9090 + editable: true + isDefault: true + diff --git a/idv/environment/prometheus/config.yml b/idv/environment/prometheus/config.yml new file mode 100644 index 0000000..d532f66 --- /dev/null +++ b/idv/environment/prometheus/config.yml @@ -0,0 +1,28 @@ +global: + scrape_interval: 10s + external_labels: + monitor: "idv-coordinator" + +rule_files: + +scrape_configs: + - job_name: "idv-api" + static_configs: + - targets: ["webserver:8000"] + + - job_name: 'node' + static_configs: + - targets: [ 'node-exporter:9100' ] + + - job_name: "mongodb" + static_configs: + - targets: [ "mongo:27017" ] + + - job_name: "opensearch" + static_configs: + - targets: [ "opensearch:9200" ] + + - job_name: "locust-monitoring" + static_configs: + - targets: [ "locust-metrics-exporter:9646" ] + diff --git a/idv/locust-docker-compose.yml b/idv/locust-docker-compose.yml new file mode 100644 index 0000000..b921467 --- /dev/null +++ b/idv/locust-docker-compose.yml @@ -0,0 +1,50 @@ +services: + locust: + image: locustio/locust:latest + container_name: locust + restart: unless-stopped + ports: + - "8089:8089" + networks: + - idv-network + volumes: + - ./locust-test/:/mnt/locust + command: > + -f /mnt/locust/locustfile.py + ${SCENARIO} + -H ${IDV_API_URL} + -u ${NUMBER_LOCUST_USERS} + -r 1 + --web-host 0.0.0.0 + --loglevel INFO + --autostart + healthcheck: + test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8089/', timeout=3)\""] + interval: 10s + timeout: 5s + retries: 10 + start_period: 20s + + locust-metrics-exporter: + image: containersol/locust_exporter:latest + container_name: locust-metrics-exporter + restart: unless-stopped + platform: "linux/amd64" + ports: + - "9646:9646" + networks: + - idv-network + environment: + - LOCUST_EXPORTER_URI=http://locust:8089 + depends_on: + locust: + condition: service_healthy + +networks: + idv-network: + external: true + name: idv_idv-network + + + + diff --git a/idv/locust-environment/.env b/idv/locust-environment/.env new file mode 100644 index 0000000..f4162aa --- /dev/null +++ b/idv/locust-environment/.env @@ -0,0 +1,10 @@ +# IDV API settings +IDV_API_URL=http://webserver:8000 +NUMBER_LOCUST_USERS=1 +SCENARIO=IDVViewGetByIdPerf + +# Test data paths +AUTH_CREDENTIALS_PATH=/mnt/locust/request-data/auth.json +VIEW_NAMESPACE=active-views-3 + + diff --git a/idv/locust-test/locustfile.py b/idv/locust-test/locustfile.py new file mode 100644 index 0000000..f988b37 --- /dev/null +++ b/idv/locust-test/locustfile.py @@ -0,0 +1,170 @@ +"""IDV perf scenario: view search and get each session by ID.""" + +import json +import os + +from locust import HttpUser, between, task + + +auth_credentials_path = os.getenv("AUTH_CREDENTIALS_PATH", "/mnt/locust/request-data/auth.json") +view_namespace = os.getenv("VIEW_NAMESPACE", "active-views-3") + +try: + with open(auth_credentials_path, "r") as file: + auth_data = json.load(file) + USER_ID = auth_data.get("user_id", "regula-idv") + PASSWORD = auth_data.get("password", "t3stP@ss") +except (FileNotFoundError, json.JSONDecodeError): + USER_ID = "regula-idv" + PASSWORD = "t3stP@ss" + + +def extract_session_ids(payload): + """Extract session IDs from sessions search response.""" + if isinstance(payload, dict): + candidates = payload.get("items") or payload.get("records") or payload.get("data") or [] + elif isinstance(payload, list): + candidates = payload + else: + candidates = [] + + ids = [] + for item in candidates: + if not isinstance(item, dict): + continue + session_id = item.get("id") or item.get("sessionId") or item.get("session_id") + if session_id: + ids.append(session_id) + + return list(dict.fromkeys(ids)) + + +def build_search_payload(view_payload): + """Build /api/sessions/search payload from the first namespace view item.""" + if not isinstance(view_payload, dict): + return None + + items = view_payload.get("items") or [] + if not items or not isinstance(items[0], dict): + return None + + view_item = items[0] + view_value = view_item.get("value") + if not isinstance(view_value, dict): + return None + + workflows = view_value.get("workflows") or [] + if not isinstance(workflows, list): + workflows = [] + + return { + "id": view_value.get("id") or view_item.get("key"), + "title": view_value.get("title"), + "description": view_value.get("description"), + "workflows": workflows, + "columns": view_value.get("columns") or [], + "sort": [], + "filter": { + "op": "$and", + "groups": [ + { + "op": "$and", + "conditions": [ + { + "field": "workflow.id", + "op": "$in", + "value": workflows, + } + ], + } + ], + }, + "groupIds": view_value.get("groupIds") or view_item.get("groupIds") or [], + "userIds": view_value.get("userIds") or view_item.get("userIds") or [], + } + + +class IDVViewGetByIdPerf(HttpUser): + """Search sessions from view and retrieve each by session ID.""" + + wait_time = between(1, 2) + + def on_start(self): + self.headers = { + "Content-Type": "application/json", + "Accept": "application/json", + } + + @task(1) + def view_search_and_get_by_id(self): + with self.client.get( + f"/api/views/namespaces/{view_namespace}?limit=100&skip=0", + headers=self.headers, + auth=(USER_ID, PASSWORD), + catch_response=True, + name="/api/views/namespaces/{namespace}", + ) as view_response: + if view_response.status_code != 200: + view_response.failure(f"View fetch failed: {view_response.status_code}") + return + + try: + view_payload = view_response.json() + except (json.JSONDecodeError, KeyError) as error: + view_response.failure(f"Failed to parse view response: {error}") + return + + search_payload = build_search_payload(view_payload) + if not search_payload: + view_response.failure("View response has no usable definition") + return + + view_response.success() + + with self.client.post( + "/api/sessions/search?limit=100", + json=search_payload, + headers=self.headers, + auth=(USER_ID, PASSWORD), + catch_response=True, + name="/api/sessions/search", + ) as search_response: + if search_response.status_code != 200: + search_response.failure(f"Session search failed: {search_response.status_code}") + return + + try: + session_ids = extract_session_ids(search_response.json()) + except (json.JSONDecodeError, KeyError) as error: + search_response.failure(f"Failed to parse search response: {error}") + return + + if not session_ids: + search_response.failure("No sessions found in nightly view") + return + + search_response.success() + + for session_id in session_ids: + with self.client.get( + f"/api/sessions/{session_id}", + headers=self.headers, + auth=(USER_ID, PASSWORD), + catch_response=True, + name="/api/sessions/{session_id}", + ) as session_response: + if session_response.status_code != 200: + session_response.failure(f"Session retrieval failed: {session_response.status_code}") + continue + + try: + payload = session_response.json() + response_id = payload.get("id") or payload.get("sessionId") + if response_id: + session_response.success() + else: + session_response.failure("Session payload has no id/sessionId") + except (json.JSONDecodeError, KeyError) as error: + session_response.failure(f"Failed to parse session payload: {error}") + + diff --git a/idv/locust-test/request-data/auth.json b/idv/locust-test/request-data/auth.json new file mode 100644 index 0000000..3ab2210 --- /dev/null +++ b/idv/locust-test/request-data/auth.json @@ -0,0 +1,6 @@ +{ + "user_id": "regula-idv", + "password": "t3stP@ss", + "api_key": "" +} + diff --git a/idv/locust-test/request-data/search.json b/idv/locust-test/request-data/search.json new file mode 100644 index 0000000..1529643 --- /dev/null +++ b/idv/locust-test/request-data/search.json @@ -0,0 +1,23 @@ +{ + "filter": { + "op": "$and", + "groups": [ + { + "op": "$and", + "conditions": [ + { + "field": "workflow.id", + "op": "$in", + "value": [ + "edda0192-b890-11ef-a348-17da751a6a22" + ] + } + ] + } + ] + }, + "columns": [], + "sort": [], + "groupIds": [] +} + From d8879dbbbd566bfd606feb9b64ba825bf039ca97 Mon Sep 17 00:00:00 2001 From: Andrei Pashkevich Date: Wed, 1 Apr 2026 15:06:42 +0300 Subject: [PATCH 2/2] Add IDVCreateSessionOnboarding scenario --- idv/locust-docker-compose.yml | 6 ++-- idv/locust-environment/.env | 6 ++-- idv/locust-test/locustfile.py | 59 +++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/idv/locust-docker-compose.yml b/idv/locust-docker-compose.yml index b921467..3fc1108 100644 --- a/idv/locust-docker-compose.yml +++ b/idv/locust-docker-compose.yml @@ -9,6 +9,8 @@ services: - idv-network volumes: - ./locust-test/:/mnt/locust + env_file: + - ./locust-environment/.env command: > -f /mnt/locust/locustfile.py ${SCENARIO} @@ -44,7 +46,3 @@ networks: idv-network: external: true name: idv_idv-network - - - - diff --git a/idv/locust-environment/.env b/idv/locust-environment/.env index f4162aa..25ea38d 100644 --- a/idv/locust-environment/.env +++ b/idv/locust-environment/.env @@ -3,8 +3,10 @@ IDV_API_URL=http://webserver:8000 NUMBER_LOCUST_USERS=1 SCENARIO=IDVViewGetByIdPerf +# Workflow configuration +WORKFLOW_ID=edda0192-b890-11ef-a348-17da751a6a22 +SESSION_LOCALE=en + # Test data paths AUTH_CREDENTIALS_PATH=/mnt/locust/request-data/auth.json VIEW_NAMESPACE=active-views-3 - - diff --git a/idv/locust-test/locustfile.py b/idv/locust-test/locustfile.py index f988b37..f596f72 100644 --- a/idv/locust-test/locustfile.py +++ b/idv/locust-test/locustfile.py @@ -168,3 +168,62 @@ def view_search_and_get_by_id(self): session_response.failure(f"Failed to parse session payload: {error}") +class IDVCreateSessionOnboarding(HttpUser): + """Create session by workflow and trigger ONBOARDING step.""" + + wait_time = between(1, 2) + + def on_start(self): + self.headers = { + "Content-Type": "application/json", + "Accept": "application/json", + } + self.workflow_id = os.getenv("WORKFLOW_ID", "8246cfb8-873a-11f0-9e3c-c37f9fc9dd9a") + self.locale = os.getenv("SESSION_LOCALE", "en") + + @task(1) + def create_session_and_post_onboarding(self): + with self.client.post( + f"/api/sessions?workflowId={self.workflow_id}", + json={"locale": self.locale}, + headers=self.headers, + auth=(USER_ID, PASSWORD), + catch_response=True, + name="/api/sessions [POST]", + ) as create_resp: + if create_resp.status_code != 200: + create_resp.failure(f"Session create failed: {create_resp.status_code}") + return + + try: + payload = create_resp.json() + session_id = payload.get("id") or payload.get("sessionId") + except (json.JSONDecodeError, KeyError) as error: + create_resp.failure(f"Failed to parse session create response: {error}") + return + + if not session_id: + create_resp.failure("Create response has no id/sessionId") + return + + create_resp.success() + + with self.client.post( + f"/api/sessions/{session_id}/step/ONBOARDING?type=data", + json={"continue": True}, + headers=self.headers, + auth=(USER_ID, PASSWORD), + catch_response=True, + name="/api/sessions/{session_id}/step/ONBOARDING [POST]", + ) as step_resp: + if step_resp.status_code != 200: + step_resp.failure(f"ONBOARDING step failed: {step_resp.status_code}") + return + + try: + _ = step_resp.json() + except json.JSONDecodeError as error: + step_resp.failure(f"Failed to parse onboarding response: {error}") + return + + step_resp.success()