267 lines
12 KiB
Markdown
267 lines
12 KiB
Markdown
# Полный поток CREATE — рабочая схема (доказано curl 2026-07-24)
|
||
|
||
## Базовые константы
|
||
|
||
```bash
|
||
API="https://lk-api-gateway-test.ngcloud.ru/api/v1/svc"
|
||
TOKEN=$(tr -d '\n' < secrets/test.token)
|
||
UA="Mozilla/5.0"
|
||
CT="Content-Type: application/json"
|
||
```
|
||
|
||
## ⛔ Где работает curl, а где нет
|
||
|
||
| Источник | Работает? | Причина |
|
||
|----------|-----------|--------|
|
||
| Локальная машина (прокси) | ❌ | DDoS-Guard режет |
|
||
| ВМ 213 (5.172.178.213) | ✅ | Прямой доступ к API |
|
||
| Pod в кластере | ✅ | Внутренняя сеть |
|
||
|
||
**Решение:** все curl через `ssh naeel@5.172.178.213 "curl ..."`.
|
||
|
||
## Полный поток (5 шагов)
|
||
|
||
### Шаг 0: Узнать svcOperationId для create
|
||
|
||
```bash
|
||
curl -s --max-time 15 \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "User-Agent: $UA" \
|
||
"$API/services/1" | jq '.svc.operations[] | select(.operation=="create") | .svcOperationId'
|
||
# → 18 (для dummy)
|
||
```
|
||
|
||
### Шаг 0b: Получить список параметров
|
||
|
||
```bash
|
||
curl -s --max-time 15 \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "User-Agent: $UA" \
|
||
"$API/instanceOperations/default/18" | jq '.svcOperation.cfsParams[] | {svcOperationCfsParamId, svcOperationCfsParam, dataType, isRequired, defaultValue, valueList}'
|
||
```
|
||
|
||
### Шаг 1: Создать инстанс
|
||
|
||
```bash
|
||
RESP=$(curl -s --max-time 15 -i \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "User-Agent: $UA" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"serviceId":1,"displayName":"my-test-'$(date +%H%M%S)'","descr":""}' \
|
||
"$API/instances")
|
||
|
||
INSTANCE_UID=$(echo "$RESP" | grep -i '^location:' | tr -d '\r' | sed 's|.*/||')
|
||
echo "instanceUid=$INSTANCE_UID"
|
||
```
|
||
|
||
**Ответ:** `HTTP/2 201`, тело пустое. UUID в заголовке `Location`.
|
||
|
||
**Важно:**
|
||
- `displayName` должен быть уникальным → добавляем timestamp
|
||
- `descr` обязательно (пустая строка ок)
|
||
- UUID в Location — **uppercase** (например `6528854D-A4EA-428C-9FA4-68E85FA9B3AC`)
|
||
|
||
### Шаг 2: Создать операцию
|
||
|
||
```bash
|
||
RESP=$(curl -s --max-time 15 -i \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "User-Agent: $UA" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"instanceUid":"'$INSTANCE_UID'","operation":"create"}' \
|
||
"$API/instanceOperations")
|
||
|
||
OP_UID=$(echo "$RESP" | grep -i '^location:' | tr -d '\r' | sed 's|.*/||')
|
||
echo "opUid=$OP_UID"
|
||
```
|
||
|
||
**Ответ:** `HTTP/2 201`, тело: `{}`. UUID в `Location`.
|
||
|
||
### Шаг 3: Задать параметры (по одному на каждый)
|
||
|
||
```bash
|
||
# 5 обязательных параметров для dummy create:
|
||
|
||
# 242 — resourceRealm (string, required, default="dummy")
|
||
curl -s --max-time 10 -o /dev/null -w '%{http_code}' \
|
||
-H "Authorization: Bearer $TOKEN" -H "User-Agent: $UA" -H "Content-Type: application/json" \
|
||
-d '{"instanceOperationUid":"'$OP_UID'","svcOperationCfsParamId":242,"paramValue":"dummy"}' \
|
||
"$API/instanceOperationCfsParams"
|
||
# → 201
|
||
|
||
# 198 — durationMs (integer>=0, required, default="0")
|
||
curl -s --max-time 10 -o /dev/null -w '%{http_code}' \
|
||
-H "Authorization: Bearer $TOKEN" -H "User-Agent: $UA" -H "Content-Type: application/json" \
|
||
-d '{"instanceOperationUid":"'$OP_UID'","svcOperationCfsParamId":198,"paramValue":"0"}' \
|
||
"$API/instanceOperationCfsParams"
|
||
# → 201
|
||
|
||
# 199 — failAtStart (boolean, required, default="false")
|
||
curl -s --max-time 10 -o /dev/null -w '%{http_code}' \
|
||
-H "Authorization: Bearer $TOKEN" -H "User-Agent: $UA" -H "Content-Type: application/json" \
|
||
-d '{"instanceOperationUid":"'$OP_UID'","svcOperationCfsParamId":199,"paramValue":"false"}' \
|
||
"$API/instanceOperationCfsParams"
|
||
# → 201
|
||
|
||
# 200 — failInProgress (boolean, required, default="false")
|
||
curl -s --max-time 10 -o /dev/null -w '%{http_code}' \
|
||
-H "Authorization: Bearer $TOKEN" -H "User-Agent: $UA" -H "Content-Type: application/json" \
|
||
-d '{"instanceOperationUid":"'$OP_UID'","svcOperationCfsParamId":200,"paramValue":"false"}' \
|
||
"$API/instanceOperationCfsParams"
|
||
# → 201
|
||
|
||
# 863 — arr (array, required, valueList=["test","2","val1"])
|
||
# ⚠️ ВАЖНО: array параметры передаются как JSON-строка!
|
||
curl -s --max-time 10 -o /dev/null -w '%{http_code}' \
|
||
-H "Authorization: Bearer $TOKEN" -H "User-Agent: $UA" -H "Content-Type: application/json" \
|
||
-d '{"instanceOperationUid":"'$OP_UID'","svcOperationCfsParamId":863,"paramValue":"[\"test\"]"}' \
|
||
"$API/instanceOperationCfsParams"
|
||
# → 201
|
||
```
|
||
|
||
**Важно:**
|
||
- Порядок параметров **не важен**
|
||
- Все `paramValue` — **строки** (даже числа и булевы)
|
||
- Array: `"[\"test\"]"` — **JSON-строка**, не голый массив
|
||
- Map/map-fixed — **JSON-строка** вида `"{\"key\":\"value\"}"`
|
||
- Endpoint: `/instanceOperationCfsParams` (НЕ `/instanceOperations/{uid}/params` — такого нет!)
|
||
|
||
### Шаг 4: Запустить
|
||
|
||
```bash
|
||
curl -s --max-time 15 \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "User-Agent: $UA" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{}' \
|
||
"$API/instanceOperations/$OP_UID/run"
|
||
# → 201 (тело пустое)
|
||
```
|
||
|
||
**⚠️ БЕЗ /run НЕ РАБОТАЕТ!** HAR браузера показывает auto-start, но через API операция не стартует автоматически. `/run` обязателен.
|
||
|
||
### Шаг 5: Поллинг статуса
|
||
|
||
```bash
|
||
# Ждать пока operationIsInProgress == false и explainedStatus != "creating"
|
||
for i in $(seq 1 60); do
|
||
STATUS=$(curl -s --max-time 10 \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "User-Agent: $UA" \
|
||
"$API/instances?pageSize=200&page=1")
|
||
|
||
FOUND=$(echo "$STATUS" | jq -r --arg uid "$INSTANCE_UID" \
|
||
'.results[] | select(.instanceUid == $uid) | "\(.explainedStatus) inProgress=\(.operationIsInProgress)"')
|
||
|
||
if [ -n "$FOUND" ]; then
|
||
echo "[$i] $FOUND"
|
||
if echo "$FOUND" | grep -qv "inProgress=True" && echo "$FOUND" | grep -qv "creating"; then
|
||
echo "ГОТОВО!"
|
||
break
|
||
fi
|
||
fi
|
||
sleep 5
|
||
done
|
||
```
|
||
|
||
## Параметры dummy create (svcOperationId=18)
|
||
|
||
| ID | Имя | Тип | Обязательный | По умолчанию | valueList |
|
||
|----|-----|-----|-------------|-------------|-----------|
|
||
| 242 | resourceRealm | string | ✅ | dummy | ["dummy"] |
|
||
| 198 | durationMs | integer>=0 | ✅ | 0 | — |
|
||
| 199 | failAtStart | boolean | ✅ | false | ["false","true"] |
|
||
| 200 | failInProgress | boolean | ✅ | false | ["false","true"] |
|
||
| 863 | arr | array | ✅ | — | ["test","2","val1"] |
|
||
| 201 | whereFail | integer>0 | ❌ | 1 | ["1","2","3"] |
|
||
| 286 | bodymessage | string | ❌ | — | — |
|
||
| 321 | mapExample | map | ❌ | — | — |
|
||
| 322 | jsonExample | json | ❌ | — | — |
|
||
| 396 | nestedRefExample | — | ❌ | — | [] |
|
||
| 647 | mapFixed | map-fixed | ❌ | — | — |
|
||
| 654 | arrayMapFixedExample | array-map-fixed | ❌ | — | — |
|
||
|
||
## ⛔ ВСЕ ОШИБКИ (хронология)
|
||
|
||
### Ошибка 0: DDoS-Guard блокирует curl с локальной машины
|
||
- **Симптом:** `403 Forbidden` / пустой ответ
|
||
- **Причина:** DDoS-Guard требует `User-Agent: Mozilla/5.0` и всё равно может резать
|
||
- **Решение:** curl через ВМ 213 (`ssh naeel@5.172.178.213 "curl ..."`)
|
||
|
||
### Ошибка 1: `'HttpClient' object has no attribute 'post'` (v1.0.7)
|
||
- **Причина:** В http_client.py был только `get()`
|
||
- **Исправление:** Добавлен `post()` метод
|
||
|
||
### Ошибка 2: `r.json()` на пустом ответе (v1.0.8)
|
||
- **Причина:** POST /instances → 201 с пустым телом, `r.json()` падает
|
||
- **Исправление:** try/except, возвращаем `{}`
|
||
|
||
### Ошибка 3: instanceUid не извлекался (v1.0.9)
|
||
- **Причина:** UUID в заголовке `Location`, не в теле ответа
|
||
- **Исправление:** Парсинг Location → UUID
|
||
|
||
### Ошибка 4: cfsParams в POST /instances (v1.0.10)
|
||
- **Симптом:** `400 Bad Request`
|
||
- **Причина:** Параметры нельзя передавать при создании инстанса
|
||
- **Исправление:** Убраны cfsParams из payload
|
||
|
||
### Ошибка 5: `descr` обязателен (v1.0.11)
|
||
- **Симптом:** `400 Bad Request`
|
||
- **Причина:** Поле `descr` required в POST /instances
|
||
- **Исправление:** `"descr": ""`
|
||
|
||
### Ошибка 6: displayName не уникален (v1.0.13)
|
||
- **Симптом:** `400 "Instance Display Name not unique"`
|
||
- **Причина:** Имя `autotest-1` уже занято
|
||
- **Исправление:** Timestamp в имени
|
||
|
||
### Ошибка 7: cfsParams в POST /instanceOperations (v1.0.23)
|
||
- **Симптом:** `422 "required CFS parameter failInProgress (200) is missing"`
|
||
- **Причина:** cfsParams нельзя передавать в /instanceOperations — они идут отдельно
|
||
- **Исправление:** Параметры через `/instanceOperationCfsParams`
|
||
|
||
### Ошибка 8: Неправильный endpoint для параметров (v1.0.24)
|
||
- **Симптом:** 404 / параметры не применяются
|
||
- **Причина:** Использовался `/instanceOperations/{uid}/params` (не существует)
|
||
- **Исправление:** Правильный endpoint: `/instanceOperationCfsParams`
|
||
|
||
### Ошибка 9: Пустые параметры очищали defaults (v1.0.26)
|
||
- **Симптом:** defaults не подставлялись
|
||
- **Причина:** Пустые paramValue перезаписывали default
|
||
- **Исправление:** Отправлять все 12 параметров (HAR analysis показал что так правильно)
|
||
|
||
### Ошибка 10: Auto-start не работает через API (v1.0.34→1.0.35)
|
||
- **Симптом:** Инстанс в статусе `not created` после всех шагов
|
||
- **Причина:** HAR показывает auto-start через UI, но через API нужен явный `/run`
|
||
- **Исправление:** Добавлен шаг 4 — `POST /instanceOperations/{opUid}/run`
|
||
- **Доказательство:** curl без /run → `not created`, curl с /run → `running` ✅
|
||
|
||
### Ошибка 11: POST body = None → DDoS-Guard блокирует (v1.0.33)
|
||
- **Симптом:** Запрос отклонён
|
||
- **Причина:** `json=None` → тело пустое → DDoS-Guard режет
|
||
- **Исправление:** Всегда `json={}` минимум
|
||
|
||
### Ошибка 12: Array параметры как raw string (v1.0.31)
|
||
- **Симптом:** 400 / неверный формат
|
||
- **Причина:** `["test"]` — голый массив, а нужна JSON-строка
|
||
- **Исправление:** `"[\"test\"]"` — paramValue как экранированная JSON-строка
|
||
|
||
## Сводная таблица эндпоинтов CREATE
|
||
|
||
| Шаг | Метод | Путь | Payload | Ответ |
|
||
|-----|-------|------|---------|-------|
|
||
| 0 | GET | `/services/{svcId}` | — | `.svc.operations[]` |
|
||
| 0b | GET | `/instanceOperations/default/{svcOpId}` | — | `.svcOperation.cfsParams[]` |
|
||
| 1 | POST | `/instances` | `{serviceId, displayName, descr}` | 201, Location→instanceUid |
|
||
| 2 | POST | `/instanceOperations` | `{instanceUid, operation}` | 201, Location→opUid |
|
||
| 3 | POST | `/instanceOperationCfsParams` | `{instanceOperationUid, svcOperationCfsParamId, paramValue}` | 201 (×N) |
|
||
| 4 | POST | `/instanceOperations/{opUid}/run` | `{}` | 201 |
|
||
| 5 | GET | `/instances?pageSize=200` | — | Поллинг `explainedStatus` |
|
||
|
||
## Итог
|
||
|
||
- **Всего итераций:** 19 (v1.0.6 → v1.0.35)
|
||
- **Уникальных багов:** 12
|
||
- **Ключевой урок:** Всегда сверяться 1:1 с Go-клиентом, не гадать
|
||
- **Рабочий proof:** 2026-07-24 14:48, `curl-test-144834`, статус `running`
|