diff --git a/llm_project/tests/redeploy.js b/llm_project/tests/redeploy.js new file mode 100644 index 0000000..ae6cea6 --- /dev/null +++ b/llm_project/tests/redeploy.js @@ -0,0 +1,136 @@ +'use strict'; + +/** + * redeploy.js — запуск операции redeploy для инстанса tf-docs через Nubes API. + * + * Авторизация: Bearer-токен из tf_provider/secrets/test.token. + * Токен НЕ печатается. + * + * Запуск: node llm_project/tests/redeploy.js [--uid ] [--profile test] + */ + +const fs = require('fs'); + +const argv = process.argv.slice(2); +function arg(name, def) { + const i = argv.indexOf('--' + name); + return i !== -1 && argv[i + 1] ? argv[i + 1] : def; +} + +const UID = arg('uid', '7a48a863-9b40-4cef-a352-033ee9dde8de'); +const TOKEN_FILE = arg('token', '/home/naeel/TF/tf_provider/secrets/test.token'); +const BASE = arg('base', 'https://lk-api-gateway-test.ngcloud.ru/api/v1/svc'); +const ACTION = arg('action', 'redeploy'); + +const HEADERS = () => ({ + Authorization: 'Bearer ' + fs.readFileSync(TOKEN_FILE, 'utf8').trim(), + 'Content-Type': 'application/json', + 'User-Agent': 'Mozilla/5.0', +}); + +async function api(method, path, body) { + const opts = { method, headers: HEADERS() }; + if (body !== undefined) opts.body = JSON.stringify(body); + const resp = await fetch(BASE + path, opts); + const text = await resp.text(); + let json = null; + try { json = JSON.parse(text); } catch { /* не JSON */ } + return { status: resp.status, text, json, location: resp.headers.get('location') }; +} + +/** Извлекает UID из заголовка Location (последний сегмент пути). */ +function uidFromLocation(loc) { + if (!loc) return ''; + const clean = loc.split('?')[0].replace(/\/+$/, ''); + const parts = clean.split('/'); + return parts[parts.length - 1] || ''; +} + +function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); } + +async function main() { + console.log('Инстанс:', UID, '| операция:', ACTION); + +// 1) Состояние инстанса: доступные операции + текущие params. + const st = await api('GET', `/instances/${UID}`); + if (st.status !== 200) { + console.error('Ошибка GET /instances:', st.status, st.text.slice(0, 300)); + process.exit(1); + } + const inst = (st.json && st.json.instance) || {}; + console.log('Статус:', inst.explainedStatus, '| inProgress:', inst.operationIsInProgress, '| pending:', inst.operationIsPending); + const instanceParams = (inst.state && inst.state.params) || {}; + + const ops = inst.availableOperations || []; + const op = ops.find((o) => String(o.operation).toLowerCase() === ACTION.toLowerCase()); + if (!op) { + console.error('Операция', ACTION, 'недоступна. Доступные:', ops.map((o) => o.operation).join(', ')); + process.exit(1); + } + console.log('svcOperationId:', op.svcOperationId); + + // 2) Создать операцию. + const created = await api('POST', '/instanceOperations', { + instanceUid: UID, + svcOperationId: op.svcOperationId, + operation: ACTION, + }); + let opUid = ''; + if (created.json && (created.json.uid || created.json.instanceOperationUid || created.json.opUid)) { + opUid = created.json.uid || created.json.instanceOperationUid || created.json.opUid; + } + if (!opUid) opUid = uidFromLocation(created.location); + if (!opUid) opUid = created.text.replace(/^"|"$/g, '').trim(); +console.log('Создана операция:', created.status, '| opUid:', opUid); + if (!opUid || opUid.length < 8) { + console.error('Не удалось получить opUid. Ответ:', created.text.slice(0, 300)); + process.exit(1); + } + + // 2б) Установить требуемые CFS-параметры (например startupConfiguration) из live-состояния. + const cfs = await api('GET', `/instanceOperations/${opUid}?fields=cfsParams`); + const cfsParams = (cfs.json && cfs.json.instanceOperation && cfs.json.instanceOperation.cfsParams) || []; + for (const p of cfsParams) { + const val = instanceParams[p.svcOperationCfsParam]; + if (val === undefined) { + console.log(' параметр', p.svcOperationCfsParam, '(id=' + p.svcOperationCfsParamId + ') — в состоянии нет, пропуск'); + continue; + } + const paramValue = typeof val === 'string' ? val : JSON.stringify(val); + const pr = await api('POST', '/instanceOperationCfsParams', { + instanceOperationUid: opUid, + svcOperationCfsParamId: p.svcOperationCfsParamId, + paramValue: paramValue, + }); + console.log(' параметр', p.svcOperationCfsParam, '=', paramValue, '->', pr.status); + } + + // 3) Запустить операцию. + const run = await api('POST', `/instanceOperations/${opUid}/run`, {}); + console.log('Запуск:', run.status, run.text.slice(0, 120)); + + // 4) Ждать завершения. + const deadline = Date.now() + 6 * 60 * 1000; + let last = ''; + while (Date.now() < deadline) { + await sleep(5000); + const o = await api('GET', `/instanceOperations/${opUid}?fields=dtFinish,isSuccessful,errorLog,isInProgress,isPending,duration,stages`); + if (o.status !== 200) { console.log(' poll:', o.status); continue; } + const d = (o.json && (o.json.instanceOperation || o.json)) || {}; + const line = ` inProgress=${d.isInProgress} pending=${d.isPending} finish=${d.dtFinish || '—'} ok=${d.isSuccessful}`; + if (line !== last) { console.log(line); last = line; } + if (d.dtFinish && !d.isInProgress && !d.isPending) { + if (d.isSuccessful) { + console.log('РЕДЕПЛОЙ ЗАВЕРШЁН УСПЕШНО'); + } else { + console.error('РЕДЕПЛОЙ ЗАВЕРШЁН С ОШИБКОЙ:', d.errorLog || '(нет errorLog)'); + process.exit(2); + } + return; + } + } + console.error('Таймаут ожидания операции'); + process.exit(3); +} + +main().catch((e) => { console.error('FATAL', e.message); process.exit(1); });