Агенты и runner-ы

Что такое агент

Агент в QA Hub — это identity runner-а:

  • уникальный id;
  • отображаемое имя;
  • набор capabilities;
  • список разрешенных окружений;
  • отдельный Bearer token.

Runner — процесс, который использует этот token, забирает совместимый job и возвращает результат. Один агент может последовательно обработать много jobs.

Pull-модель подключения

Дополнительный входящий порт на agent host не нужен:

loop:
  POST /agent/jobs/claim
  if job is null:
      sleep
      continue

  start heartbeat
  execute known scenario
  upload evidence
  POST events
  POST complete

Hub никогда не передает runner-у shell-команду. Поле agent_spec является декларативным контрактом, который runner обязан валидировать.

1. Спроектируйте capabilities

Capability — точная строка, описывающая доверенную возможность runner-а:

http
playwright.chromium
conference.participant
media.diagnostics
android.device
ios.safari

Не используйте capability как название конкретного теста. Хорошая capability переиспользуется разными кейсами и однозначно говорит, что runner умеет делать.

Кейс может требовать несколько capabilities:

{
  "required_capabilities": [
    "conference.participant",
    "media.diagnostics"
  ]
}

Job получит только агент, у которого есть все требуемые значения.

2. Зарегистрируйте агента

В UI откройте Управление → Агенты, укажите:

  • ID: http-stage1-01;
  • имя: HTTP stage1 runner;
  • capabilities: http;
  • environments: stage1.

Token показывается один раз. Сразу сохраните его в secret manager. Hub хранит только SHA-256 и восстановить token позже не сможет.

То же через API:

QA_TOKEN="$(
  curl -fsS https://qa.videograce.ru/api/v1/auth/token \
    -H 'Content-Type: application/json' \
    -d '{"username":"admin","password":"..."}' |
  python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])'
)"

curl -fsS https://qa.videograce.ru/api/v1/agents \
  -H "Authorization: Bearer $QA_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "id": "http-stage1-01",
    "name": "HTTP stage1 runner",
    "capabilities": ["http"],
    "environments": ["stage1"]
  }'

Ответ содержит token вида qaa_....

3. Запустите готовый HTTP runner

В репозитории VideoGrace находится безопасный declarative runner: Services/QaAgent.

Для первого production smoke:

  1. В разделе Наборы импортируйте Services/QaHub/seed/examples/http-health-production.json.
  2. В разделе Управление → Агенты зарегистрируйте агента: ID http-qa-prod-01, capability http, environment qa-hub-production.
  3. Сохраните показанный token.
  4. Создайте прогон QA Hub production HTTP smoke в режиме Только агенты.
  5. Запустите runner из корня репозитория:
VG_QA_HUB_URL=https://qa.videograce.ru \
VG_QA_AGENT_TOKEN='qaa_...' \
PYTHONPATH=Services/QaAgent \
python3 -m qaagent.runner

Runner последовательно заберет три jobs. В карточке прогона должны появиться назначенный агент и результаты passed; после этого прогон можно завершить и открыть итоговый отчет. Остановите runner через Ctrl+C.

Полезные параметры:

Переменная По умолчанию Назначение
VG_QA_POLL_SEC 3 Интервал claim при пустой очереди
VG_QA_HEARTBEAT_SEC 20 Интервал heartbeat
VG_QA_HUB_URL URL QA Hub
VG_QA_AGENT_TOKEN Token зарегистрированного агента

Флаг --once обрабатывает не более одного job и удобен для CI smoke.

Docker

cd Services/QaAgent
docker build -t videograce/qa-http-agent:0.1.0 .

docker run --rm \
  --read-only \
  --security-opt no-new-privileges \
  -e VG_QA_HUB_URL=https://qa.videograce.ru \
  -e VG_QA_AGENT_TOKEN='qaa_...' \
  videograce/qa-http-agent:0.1.0

4. Создайте свой runner

Минимальный runner реализует пять операций.

Claim

POST /api/v1/agent/jobs/claim
Authorization: Bearer qaa_...
Content-Type: application/json

{}

Если подходящих jobs нет:

{"job": null}

Если job найден:

{
  "job": {
    "id": "3c14...",
    "claimed_at": 1784790000,
    "lease_expires_at": 1784790060,
    "payload": {
      "run_id": "d92f...",
      "case_run_id": "2e8a...",
      "environment": {
        "name": "stage1",
        "base_url": "https://stage1.videograce.ru",
        "secret_ref": "env://VG_QA_STAGE1_SECRET"
      },
      "case": {
        "external_id": "API-001",
        "agent_spec": {}
      }
    }
  }
}

Считайте payload недоверенным вводом. Проверяйте driver, actions, timeout, paths и assertions по allowlist.

Heartbeat

POST /api/v1/agent/jobs/<job_id>/heartbeat
Authorization: Bearer qaa_...
Content-Type: application/json

{}

Отправляйте heartbeat не реже трети lease. При lease 60 секунд практический интервал — 20 секунд.

События

POST /api/v1/agent/jobs/<job_id>/events
Authorization: Bearer qaa_...
Content-Type: application/json

{
  "level": "info",
  "event": "media_connected",
  "data": {"elapsed_ms": 820}
}

Уровни: debug, info, warning, error. Не отправляйте heartbeat как событие: для продления lease есть отдельный endpoint.

Артефакты

curl -fsS \
  "https://qa.videograce.ru/api/v1/agent/runs/$RUN_ID/artifacts" \
  -H "Authorization: Bearer $QA_AGENT_TOKEN" \
  -H "X-QA-Job-Id: $JOB_ID" \
  -H "X-QA-Case-Run: $CASE_RUN_ID" \
  -H 'X-QA-Filename: diagnostics.json' \
  -H 'Content-Type: application/json' \
  --data-binary @diagnostics.json

Передается raw body, без base64. По умолчанию сервер принимает до 50 MiB.

Complete

POST /api/v1/agent/jobs/<job_id>/complete
Authorization: Bearer qaa_...
Content-Type: application/json

{
  "status": "passed",
  "result": {
    "schema": "my.runner.result.v1",
    "assertions": 12
  }
}

Допустимые итоговые статусы агента: passed, failed.

Lease, restart и идемпотентность

Если heartbeat прекратился:

  1. Lease истекает.
  2. При следующем обращении Hub возвращает job в queued.
  3. Другой агент может получить тот же job.
  4. Старый агент больше не сможет отправить complete или artifact.

Поэтому сценарий runner-а должен быть безопасен при повторном запуске:

  • создавать уникальные тестовые данные;
  • удалять временных пользователей и комнаты;
  • не считать наличие старого объекта успехом без проверки;
  • использовать run_id или job_id в external ID;
  • не выполнять необратимые production-действия.

Секреты окружения

Hub хранит только secret_ref. Готовый HTTP runner поддерживает ссылки env://VARIABLE:

export VG_QA_STAGE1_SECRET='{
  "headers": {
    "Authorization": "Bearer replace-me"
  }
}'

В production переменная должна приходить из Docker/Kubernetes secret или внешнего secret manager. Не записывайте секрет в suite JSON, agent result, event или artifact.

Отзыв агента

В UI нажмите Отозвать либо:

curl -fsS \
  https://qa.videograce.ru/api/v1/agents/http-stage1-01/revoke \
  -H "Authorization: Bearer $QA_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{}'

После отзыва token перестает проходить аутентификацию. Для замены token зарегистрируйте новый agent ID и затем отзовите старый.

Диагностика

Симптом Проверка
claim всегда возвращает null Совпадают ли capabilities и environment
Job постоянно возвращается в очередь Heartbeat реже lease или runner падает
job_lease_expired Старый runner продолжил работу после потери lease
job_not_found Job принадлежит другому агенту или уже перевыдан
401 invalid_agent_token Token неверен или агент отозван
Agent job висит после ручного прогона Запустить runner или завершить без агентов