""" Общий цикл поллинга операции Nubes — ждём завершения или таймаута. Поллинг — это периодический опрос API: GET /instanceOperations/{opUid}?fields=dtFinish,isSuccessful,errorLog,duration,stages,svc Каждые 5 секунд проверяем не появился ли dtFinish (дата завершения). Как только dtFinish есть → операция завершена → возвращаем результат. Используется в двух режимах: - _finish_op (api_test.py) — асинхронно, в threading.Thread. Запускается после возврата HTTP-ответа пользователю. Пользователь видит "RUNNING" и поллит через /api/test/status/. - run_scenario (scenario.py) — синхронно, в основном потоке сценария. Сценарий ждёт завершения шага перед переходом к следующему. """ import time def poll_until_done(client, op_uid, timeout=1800): """Поллинг операции до завершения или таймаута. Алгоритм: 1. Запоминаем время старта (t0). 2. В цикле: GET /instanceOperations/{opUid} с нужными полями. 3. Если dtFinish есть и не пустой → операция завершена. - isSuccessful=True → status="OK" - isSuccessful=False → status="FAIL" 4. Если dtFinish нет → sleep(5) и повторяем. 5. Если вышли за deadline → status="TIMEOUT". Args: client: HttpClient op_uid: str — UUID операции (instanceOperationUid) timeout: int — максимальное время ожидания в секундах. По умолчанию 1800 (30 минут). Столько может длиться create сложного сервиса. Returns: dict с ВСЕГДА одинаковыми ключами: {status, is_successful, error_log, stages, duration, svc} status ∈ {"OK", "FAIL", "TIMEOUT"} OK — операция завершилась успешно FAIL — операция завершилась с ошибкой TIMEOUT — не дождались за timeout секунд is_successful: bool — True только для OK error_log: str — текст ошибки из API (или "TIMEOUT after ...") stages: list — этапы выполнения [{stage, dtStart, dtFinish, isSuccessful, duration}, ...] duration: float — реальное время поллинга в секундах (округлено до 0.1) svc: str — имя сервиса (из ответа API) """ # Фиксируем время начала поллинга t0 = time.time() deadline = t0 + timeout # крайний срок # Основной цикл поллинга while time.time() < deadline: try: # GET с fields — запрашиваем только нужные поля (экономия трафика) # dtFinish — ключевое: если есть → операция завершена # isSuccessful — true/false # errorLog — текст ошибки если FAIL # duration — сколько заняла операция по версии API # stages — этапы (Terraform: plan → apply → ...) # svc — имя сервиса (может отличаться от того что знаем) data = client.get( f"/instanceOperations/{op_uid}" "?fields=dtFinish,isSuccessful,errorLog,duration,stages,svc" ) except Exception: # Сетевая ошибка или API временно недоступен — не падаем. # Ждём 5 секунд и пробуем снова. # Это нормально: Nubes API иногда отдаёт 502 при высокой нагрузке. time.sleep(5) continue # Ответ API: {"instanceOperation": {...}} op = data.get("instanceOperation", {}) dt_finish = op.get("dtFinish") # dtFinish может быть None, пустой строкой, или датой. # Проверяем что dtFinish не пустой: # str(dt_finish).strip() — отсекаем None→"None" и пустые строки if dt_finish and str(dt_finish).strip(): is_ok = op.get("isSuccessful") return { "status": "OK" if is_ok else "FAIL", "is_successful": is_ok, "error_log": op.get("errorLog") or "", "stages": op.get("stages", []), "duration": round(time.time() - t0, 1), "svc": op.get("svc", ""), } # Операция ещё не завершена — ждём 5 секунд time.sleep(5) # Вышли из while по deadline — таймаут. # 30 минут истекли, операция всё ещё выполняется. # Это редкий случай (обычно create занимает 2-10 минут). return { "status": "TIMEOUT", "is_successful": False, "error_log": f"TIMEOUT after {timeout}s", "stages": [], "duration": round(time.time() - t0, 1), "svc": "", }