Fix VDC flow and FullPipe example
This commit is contained in:
@@ -4,8 +4,8 @@ api_token = "ВАШ_ТОКЕН_ИЗ_ЛК"
|
|||||||
organization = "kontora"
|
organization = "kontora"
|
||||||
|
|
||||||
vdc_resource_name = "fullpipe-vdc"
|
vdc_resource_name = "fullpipe-vdc"
|
||||||
# vdc_network_provider = "<взять из текущей страницы ЛК>"
|
vdc_network_provider = "snb1"
|
||||||
# vdc_provider_vdc = "<взять из текущей страницы ЛК>"
|
vdc_provider_vdc = "Intel Broadwell 2.4"
|
||||||
vdc_cpu_allocated = 8
|
vdc_cpu_allocated = 8
|
||||||
vdc_cpu_guaranteed = 0
|
vdc_cpu_guaranteed = 0
|
||||||
vdc_mem_allocated = 32
|
vdc_mem_allocated = 32
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ TOKEN_FILE="secrets/dev.token"
|
|||||||
|
|
||||||
# Release versions
|
# Release versions
|
||||||
# Version
|
# Version
|
||||||
VERSION="2.0.2"
|
VERSION="2.0.3"
|
||||||
|
|
||||||
NAMESPACE="nubes-dev"
|
NAMESPACE="nubes-dev"
|
||||||
PROVIDER_NAME="nubes"
|
PROVIDER_NAME="nubes"
|
||||||
|
|||||||
@@ -6,17 +6,12 @@
|
|||||||
12 s3 # S3 Object Storage
|
12 s3 # S3 Object Storage
|
||||||
13 s3bucket # S3 бакет
|
13 s3bucket # S3 бакет
|
||||||
19 vcOrg # Организация в Cloud Director
|
19 vcOrg # Организация в Cloud Director
|
||||||
# 20 vcOrgSaas # Организация [DEPRECATED] — нет в UI
|
|
||||||
21 vc_vdc # Виртуальный датацентр (vDC)
|
21 vc_vdc # Виртуальный датацентр (vDC)
|
||||||
22 vc_nsxt # Сетевой шлюз периметра (Edge)
|
22 vc_nsxt # Сетевой шлюз периметра (Edge)
|
||||||
# 23 vc_vm # VM в Cloud Director (старый формат) (vc_vm) — нет в UI
|
|
||||||
# 24 vcNat # DEPRECATED Правила маршрутизации для VM (vc_nat)
|
|
||||||
25 vcexternalip # Публичные IP адреса
|
25 vcexternalip # Публичные IP адреса
|
||||||
26 vapp # Виртуальный каталог ВМ (vApp)
|
26 vapp # Виртуальный каталог ВМ (vApp)
|
||||||
# 27 vc_vm_v2 # VM в Cloud Director (vc_vmV2) — нет в DEV UI
|
|
||||||
28 vc_vm_v3 # Виртуальная машина
|
28 vc_vm_v3 # Виртуальная машина
|
||||||
29 vcVdcGroup # Группа датацентров
|
29 vcVdcGroup # Группа датацентров
|
||||||
# 32 vmpostgre # vc_vm_postgresql_std
|
|
||||||
50 nextcloud # Nextcloud
|
50 nextcloud # Nextcloud
|
||||||
81 superset # Apache Superset
|
81 superset # Apache Superset
|
||||||
82 harbor # Container Registry
|
82 harbor # Container Registry
|
||||||
@@ -34,13 +29,8 @@
|
|||||||
97 nodered # NodeRed
|
97 nodered # NodeRed
|
||||||
98 http # Простой HTTP контейнер
|
98 http # Простой HTTP контейнер
|
||||||
99 gitea # Gitea
|
99 gitea # Gitea
|
||||||
# 100 openwhisk # Serverless Openwhisk — нет в UI
|
|
||||||
109 zonesV2 # Управление DNS
|
109 zonesV2 # Управление DNS
|
||||||
# 110 dnszone # DNS зона — нет в UI
|
|
||||||
111 dnsrecord # DNS запись
|
111 dnsrecord # DNS запись
|
||||||
# 112 tenant # Тенант в Grafana — нет в UI
|
|
||||||
# 113 vcComplex # Быстрый старт — нет в UI
|
|
||||||
# 114 GiteaComplex # Комплексная услуга по созданию gitea — нет в UI
|
|
||||||
115 mariadb # Mariadb
|
115 mariadb # Mariadb
|
||||||
116 kafka # ApacheKafka
|
116 kafka # ApacheKafka
|
||||||
# 117 nifi # Nifi
|
# 117 nifi # Nifi
|
||||||
@@ -50,6 +40,5 @@
|
|||||||
149 valoTenant # VALO Cloud
|
149 valoTenant # VALO Cloud
|
||||||
150 k8sSthutrvalCluster # Kubernetes кластер Штурвал
|
150 k8sSthutrvalCluster # Kubernetes кластер Штурвал
|
||||||
151 k8sOpenbao # Vault
|
151 k8sOpenbao # Vault
|
||||||
# 153 nifi # Nifi (DEV)
|
153 nifi # Nifi (DEV)
|
||||||
163 llmAi # LLM
|
163 llmAi # LLM
|
||||||
# 175 k8sGo # Go — нет в UI
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ PROVIDER_DIR="${PROVIDER_DIR:-${ROOT_DIR}/provider}"
|
|||||||
BUILD_DIR="${BUILD_DIR:-${ROOT_DIR}/artifacts/provider_build}"
|
BUILD_DIR="${BUILD_DIR:-${ROOT_DIR}/artifacts/provider_build}"
|
||||||
GPG_KEY_FILE="${GPG_KEY_FILE:-${ROOT_DIR}/secrets/private_key.asc}"
|
GPG_KEY_FILE="${GPG_KEY_FILE:-${ROOT_DIR}/secrets/private_key.asc}"
|
||||||
|
|
||||||
VERSION="${1:-2.0.2}"
|
VERSION="${1:-2.0.3}"
|
||||||
REGISTRY_HOSTNAME="${REGISTRY_HOSTNAME:-tf-registry.containerk8s.services.ngcloud.ru}"
|
REGISTRY_HOSTNAME="${REGISTRY_HOSTNAME:-tf-registry.containerk8s.services.ngcloud.ru}"
|
||||||
NAMESPACE="${NAMESPACE:-nubes}"
|
NAMESPACE="${NAMESPACE:-nubes}"
|
||||||
PROVIDER_NAME="${PROVIDER_NAME:-nubes}"
|
PROVIDER_NAME="${PROVIDER_NAME:-nubes}"
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
| Стенд | Namespace | Версия | Дата заливки |
|
| Стенд | Namespace | Версия | Дата заливки |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| PROD | `nubes` | `1.0.0` | 2026-09-03 | (новая нумерация) |
|
| PROD | `nubes` | `1.0.0` | 2026-09-03 | (новая нумерация) |
|
||||||
| DEV | `nubes-dev` | `2.0.2` | 2026-09-21 | (graceful fallback для cfsParams при 500) |
|
| DEV | `nubes-dev` | `2.0.3` | 2026-09-21 | (graceful fallback для cfsParams при 500) |
|
||||||
| TEST | `nubes-test` | `3.0.0` | 2026-09-03 | (новая нумерация) |
|
| TEST | `nubes-test` | `3.0.0` | 2026-09-03 | (новая нумерация) |
|
||||||
|
|
||||||
## Как проверить
|
## Как проверить
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
# Резюме сессии: VDC create-flow, диалог с Opus и правка fallback (2026-09-21)
|
||||||
|
|
||||||
|
## 1. Контекст задачи
|
||||||
|
- Цель: довести до рабочего состояния создание `nubes_vc_vdc` в `FullPipe`.
|
||||||
|
- Симптом: при создании VDC провайдер падал на `GET /instanceOperations/{opUid}?fields=cfsParams` с HTTP 500.
|
||||||
|
- В ходе разбора было подтверждено, что обычные сервисы через тот же провайдер работали, а VDC попадал в отдельную проблемную ветку backend-обработки.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Диагностика проблемы
|
||||||
|
|
||||||
|
### 2.1. Что ломалось
|
||||||
|
- В `provider/internal/core/client.go` create-flow делал `GET /instanceOperations/{opUid}?fields=cfsParams` сразу после создания операции.
|
||||||
|
- Для VDC этот запрос приводил к ошибке backend’а:
|
||||||
|
- `Invalid call of the function [getResourceRealmConfig]`
|
||||||
|
- `Cannot cast Object type [Struct] to a value of type [string]`
|
||||||
|
- источник ошибки: `/app/api/v1/resources/instance_operation_cfs_param.cfc`
|
||||||
|
- Причина по отчету: для `vc_vdc` вычислялся динамический `descr` у `storageConfig.name`, и backend падал на `resourceRealm`, который в DEV хранится как `Struct`, а не `string`.
|
||||||
|
|
||||||
|
### 2.2. Почему обычные сервисы не ломались
|
||||||
|
- На обычных сервисах этот `GET` либо не попадал в проблемный backend-код, либо не требовал вычисления `resourceRealm`.
|
||||||
|
- Для VDC в YAML есть специфическая зависимость:
|
||||||
|
- `generated/dev/resources_yaml/21_vc_vdc.yaml`
|
||||||
|
- `storageConfig.name` содержит вычисляемый `descr` с `getResourceRealmConfig(...resourceRealm...)`.
|
||||||
|
- Для обычных сервисов, например `vapp` и `postgres`, такого вычисляемого `resourceRealm`-контекста нет.
|
||||||
|
|
||||||
|
### 2.3. Почему `hasUnresolvedParams` мешал
|
||||||
|
- Эвристика проверяла **все** строковые параметры, а не только параметры с `ref_svc_id`.
|
||||||
|
- Для VDC это ломало fallback на обычных литералах вроде:
|
||||||
|
- `providerVdc = fast-2.8`
|
||||||
|
- `networkProvider = default`
|
||||||
|
- Эти значения не UUID и не JSON, но и резолвить их не нужно.
|
||||||
|
- В результате при падении GET провайдер вместо продолжения переходил в ошибку.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Диалог с Opus
|
||||||
|
|
||||||
|
### 3.1. Что просили у Opus
|
||||||
|
- Проверить только:
|
||||||
|
- `provider/internal/core/client.go`
|
||||||
|
- `docs/DEBUG_REPORT_VC_VDC_500.md`
|
||||||
|
- `generated/dev/resources_yaml/21_vc_vdc.yaml`
|
||||||
|
- `generated/dev/resources_yaml/26_vapp.yaml`
|
||||||
|
- `generated/dev/resources_yaml/90_postgres.yaml`
|
||||||
|
- Вопросы к Opus были узкими:
|
||||||
|
1. почему обычные сервисы работали, а VDC начал падать на `GET ?fields=cfsParams`
|
||||||
|
2. есть ли в VDC специфическая структура или зависимость, которой нет у обычных сервисов
|
||||||
|
3. является ли `hasUnresolvedParams` неверной эвристикой именно в этом месте
|
||||||
|
4. что именно надо исправить
|
||||||
|
|
||||||
|
### 3.2. Что ответил Opus по сути
|
||||||
|
- Root cause — не данные Terraform и не сами строки `fast-2.8` / `default`, а backend-ошибка на `GET /instanceOperations/{opUid}?fields=cfsParams` именно для VDC.
|
||||||
|
- VDC отличается от обычных сервисов тем, что в его YAML есть динамический `descr` для `storageConfig.name`, который тянет `resourceRealm`.
|
||||||
|
- `hasUnresolvedParams` была признана лишней и хрупкой эвристикой: она может ломать fallback на обычных строках.
|
||||||
|
- Итоговое решение Opus: при ошибке GET идти дальше по браузерному flow, без условий по всем строковым параметрам.
|
||||||
|
|
||||||
|
### 3.3. Дополнительные уточнения в диалоге
|
||||||
|
- Был отдельный спор по формулировке про Lucee / ColdFusion backend.
|
||||||
|
- В итоге было зафиксировано, что этот термин — не отдельная гипотеза, а просто обозначение backend-слоя, который уже фигурировал в отчетах и traceback’ах.
|
||||||
|
- Opus также подтвердил, что для VDC этот GET нужен только как вспомогательный шаг для `resolveRefSvcParamValues`, а не как обязательный бизнес-этап.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Что изменили в коде
|
||||||
|
|
||||||
|
### 4.1. `provider/internal/core/client.go`
|
||||||
|
- В `CreateGenericInstanceUniversalV6` удалён gate по `hasUnresolvedParams`.
|
||||||
|
- Теперь логика такая:
|
||||||
|
- если `GET /instanceOperations/{opUid}?fields=cfsParams` успешен — парсим и резолвим `ref_svc_id`
|
||||||
|
- если GET падает — просто продолжаем POST’ить параметры, а потом идём в `validate-cfs` и `run`
|
||||||
|
- Функция `hasUnresolvedParams` удалена полностью.
|
||||||
|
- После удаления была убрана осиротевшая документационная строка, оставшаяся над `isHexDigit`.
|
||||||
|
|
||||||
|
### 4.2. `provider/internal/core/client_test.go`
|
||||||
|
- Добавлен тест:
|
||||||
|
- `TestCreateGenericInstanceUniversalV6_ContinuesWhenOpDetailsGETFails`
|
||||||
|
- Тест моделирует:
|
||||||
|
- `POST /instances`
|
||||||
|
- `POST /instanceOperations`
|
||||||
|
- `GET /instanceOperations/{opUid}?fields=cfsParams` → 500
|
||||||
|
- `POST /instanceOperationCfsParams`
|
||||||
|
- `GET /instanceOperations/{opUid}/validate-cfs`
|
||||||
|
- `POST /instanceOperations/{opUid}/run`
|
||||||
|
- финальный `GET /instances/{uid}`
|
||||||
|
- Проверка теста:
|
||||||
|
- create-flow завершился успешно
|
||||||
|
- все 7 параметров были отправлены с ожидаемыми значениями
|
||||||
|
- polling по операции был ровно один раз
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Проверка после правки
|
||||||
|
- `cd /home/naeel/TF/tf_provider/provider && go test ./internal/core` — успешно.
|
||||||
|
- После ревью был пойман и исправлен только косметический хвост:
|
||||||
|
- старый комментарий над `isHexDigit`, оставшийся после удаления `hasUnresolvedParams`.
|
||||||
|
- После этого пакет `internal/core` снова прошёл тесты.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Вывод по итогам сессии
|
||||||
|
- Проблема была не в обычных сервисах как таковых, а в специфике VDC-данных и backend-пути, который срабатывал на `GET ?fields=cfsParams`.
|
||||||
|
- `hasUnresolvedParams` была неверной эвристикой именно в create-flow VDC и ломала рабочий fallback.
|
||||||
|
- Правильное поведение: если GET падает, не гадать по строковым параметрам, а продолжать browser-like flow через POST параметров, validate и run.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Что дальше
|
||||||
|
- Следующий этап — уже не правка логики, а публикация и стендовая проверка при необходимости.
|
||||||
|
- DEV-релиз `2.0.3` успешно собран и загружен в registry `nubes-dev` через `TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/dev`.
|
||||||
|
- Перед этим уже был подготовлен короткий запрос на ревью для Opus и получен ответ, который подтвердил направление правки.
|
||||||
@@ -272,15 +272,10 @@ func (c *UniversalClient) CreateGenericInstanceUniversalV6(ctx context.Context,
|
|||||||
opDetailsResp, _, err := c.doRequest(ctx, "GET", fmt.Sprintf("/instanceOperations/%s?fields=cfsParams", opUid), nil)
|
opDetailsResp, _, err := c.doRequest(ctx, "GET", fmt.Sprintf("/instanceOperations/%s?fields=cfsParams", opUid), nil)
|
||||||
var opDetails universalOpResponse
|
var opDetails universalOpResponse
|
||||||
hasOpDetails := false
|
hasOpDetails := false
|
||||||
if err != nil {
|
if err == nil {
|
||||||
if hasUnresolvedParams(params) {
|
if jsonErr := json.Unmarshal(opDetailsResp, &opDetails); jsonErr != nil {
|
||||||
return "", fmt.Errorf("не удалось получить детали операции: %w", err)
|
|
||||||
}
|
|
||||||
} else if jsonErr := json.Unmarshal(opDetailsResp, &opDetails); jsonErr != nil {
|
|
||||||
if hasUnresolvedParams(params) {
|
|
||||||
return "", fmt.Errorf("не удалось разобрать детали операции: %w", jsonErr)
|
return "", fmt.Errorf("не удалось разобрать детали операции: %w", jsonErr)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
hasOpDetails = true
|
hasOpDetails = true
|
||||||
params, err = c.resolveRefSvcParamValues(ctx, opDetails.InstanceOperation.CfsParams, params)
|
params, err = c.resolveRefSvcParamValues(ctx, opDetails.InstanceOperation.CfsParams, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1359,41 +1354,6 @@ func isUUIDLike(value string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasUnresolvedParams возвращает true, если хотя бы одно значение параметров похоже на
|
|
||||||
// неразрешённое имя вместо UUID. Значения map-fixed/json/array, числа и булевы пропускаются.
|
|
||||||
func hasUnresolvedParams(params map[int]string) bool {
|
|
||||||
for _, val := range params {
|
|
||||||
trimmed := strings.TrimSpace(val)
|
|
||||||
if trimmed == "" || trimmed == "{}" || trimmed == "[]" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(trimmed, "{") || strings.HasPrefix(trimmed, "[") {
|
|
||||||
// json/map-fixed/array не являются именами одиночных инстансов
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if trimmed == "true" || trimmed == "false" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
isNum := true
|
|
||||||
for _, r := range trimmed {
|
|
||||||
if r < '0' || r > '9' {
|
|
||||||
isNum = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isNum {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if isUUIDLike(trimmed) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Обнаружена не-числовая строка, не JSON и не UUID (потенциальное имя).
|
|
||||||
// Если GET ?fields=cfsParams упал, такое имя невозможно разрешить в UUID.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isHexDigit(r rune) bool {
|
func isHexDigit(r rune) bool {
|
||||||
return (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')
|
return (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package core
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -231,4 +232,80 @@ func TestWaitForOperationFinish_Timeout(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateGenericInstanceUniversalV6_ContinuesWhenOpDetailsGETFails(t *testing.T) {
|
||||||
|
var postedParams = make(map[int]string)
|
||||||
|
var pollCount atomic.Int32
|
||||||
|
|
||||||
|
c, cleanup := makeTestClient(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/instances":
|
||||||
|
w.Header().Set("Location", "./inst-1")
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
_, _ = w.Write([]byte(`{"instanceUid":"inst-1"}`))
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/instanceOperations":
|
||||||
|
w.Header().Set("Location", "./op-1")
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
_, _ = w.Write([]byte(`{"instanceOperationUid":"op-1"}`))
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/instanceOperations/op-1" && strings.Contains(r.URL.RawQuery, "fields=cfsParams"):
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
_, _ = w.Write([]byte(`{"message":"boom"}`))
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/instanceOperationCfsParams":
|
||||||
|
var req genericParamReq
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
t.Fatalf("failed to decode param request: %v", err)
|
||||||
|
}
|
||||||
|
postedParams[req.SvcOperationCfsParamId] = req.ParamValue
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte(`{}`))
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/instanceOperations/op-1/validate-cfs":
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"ok":true}`))
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/instanceOperations/op-1/run":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte(`{}`))
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/instanceOperations/op-1" && strings.Contains(r.URL.RawQuery, "fields=dtFinish"):
|
||||||
|
if pollCount.Add(1) != 1 {
|
||||||
|
t.Fatalf("unexpected extra polling request: %s", r.URL.String())
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"instanceOperation":{"dtFinish":"2026-01-01T00:00:03Z","isSuccessful":true,"errorLog":null,"isInProgress":false,"isPending":false,"duration":1.5,"stages":[{"instanceOperationStageUid":"1","stage":"1. Validation","isSuccessful":true,"dtFinish":"2026-01-01T00:00:01Z","duration":1.0}]}}`))
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/instances/inst-1":
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"instance":{"instanceUid":"inst-1","explainedStatus":"running","isDeleted":false,"operationIsPending":false,"operationIsInProgress":false}}`))
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
c.PollInterval = 10 * time.Millisecond
|
||||||
|
c.RetryBaseDelay = 10 * time.Millisecond
|
||||||
|
|
||||||
|
params := map[int]string{
|
||||||
|
30: "c3f81e4b-7a2b-4e1d-9e0a-123456789abc",
|
||||||
|
335: "fast-2.8",
|
||||||
|
361: `[{"name":"fast","size":200}]`,
|
||||||
|
397: "0",
|
||||||
|
557: "8",
|
||||||
|
558: "32",
|
||||||
|
746: "default",
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := c.CreateGenericInstanceUniversalV6(context.Background(), 21, "f12vdc-2", params); err != nil {
|
||||||
|
t.Fatalf("expected create to succeed, got error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(postedParams) != len(params) {
|
||||||
|
t.Fatalf("expected %d posted params, got %d", len(params), len(postedParams))
|
||||||
|
}
|
||||||
|
for id, value := range params {
|
||||||
|
if got := postedParams[id]; got != value {
|
||||||
|
t.Fatalf("param %d mismatch: got %q, want %q", id, got, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pollCount.Load() != 1 {
|
||||||
|
t.Fatalf("expected exactly 1 polling request, got %d", pollCount.Load())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func strPtr(s string) *string { return &s }
|
func strPtr(s string) *string { return &s }
|
||||||
|
|||||||
Reference in New Issue
Block a user