import { APIRequestContext } from '@playwright/test';
import YAML from 'yaml';
import {
FUYAO_BASE_URL,
NODE_PATH,
COLOCATION_CONFIG_CONFIGMAP,
VOLCANO_CONFIG_CONFIGMAP,
} from '../constants';
export interface ColocationConfig {
nodesConfig?: Array<{ name: string }>;
[key: string]: unknown;
}
export interface VolcanoSchedulerOptions {
usagePlugin?: {
enable?: boolean;
usageThreshold?: {
enable?: boolean;
cpu?: number;
memory?: number;
};
};
[key: string]: unknown;
}
export interface RubikOptions {
eviction?: {
enable?: boolean;
cpuevict?: {
threshold?: number;
};
memoryevict?: {
threshold?: number;
};
};
[key: string]: unknown;
}
const sleep = async (ms: number): Promise<void> => {
await new Promise((resolve) => setTimeout(resolve, ms));
};
const readJsonResponse = async (
response: Awaited<ReturnType<APIRequestContext['get']>>,
label: string,
): Promise<Record<string, unknown>> => {
const body = await response.text();
if (!response.ok()) {
throw new Error(
`${label} failed: status=${response.status()} ${response.statusText()}; body=${body}`,
);
}
try {
return JSON.parse(body) as Record<string, unknown>;
} catch (err) {
throw new Error(
`${label} returned non-JSON: status=${response.status()}; body=${body}; ` +
`${err instanceof Error ? err.message : String(err)}`,
);
}
};
const getConfigMapDataWithRetry = async (
apiContext: APIRequestContext,
url: string,
configName: string,
requiredKeys: string[],
timeoutMs = 30000,
intervalMs = 3000,
): Promise<Record<string, string>> => {
const deadline = Date.now() + timeoutMs;
let lastStatus = -1;
let lastError = '';
let lastBody = '';
while (Date.now() < deadline) {
const response = await apiContext.get(url);
lastStatus = response.status();
const rawBody = await response.text();
lastBody = rawBody;
let parsed: Record<string, unknown> | null = null;
try {
parsed = JSON.parse(rawBody) as Record<string, unknown>;
} catch (err) {
lastError = `parse response json failed: ${err instanceof Error ? err.message : String(err)}`;
await sleep(intervalMs);
continue;
}
const data = parsed.data;
if (!data || typeof data !== 'object') {
lastError = `response has no configmap data field (status=${response.status()} ${response.statusText()})`;
await sleep(intervalMs);
continue;
}
const configData = data as Record<string, string>;
const missingKeys = requiredKeys.filter((key) => typeof configData[key] !== 'string');
if (missingKeys.length > 0) {
lastError = `missing keys: ${missingKeys.join(', ')}`;
await sleep(intervalMs);
continue;
}
return configData;
}
const compactBody = lastBody.length > 1200 ? `${lastBody.slice(0, 1200)}...` : lastBody;
throw new Error(
`Failed to fetch ${configName} configmap data after ${timeoutMs}ms (status=${lastStatus}); last error: ${lastError}; last body: ${compactBody}`,
);
};
export const fetchAllNode = async (apiContext: APIRequestContext): Promise<Record<string, unknown>> => {
const response = await apiContext.get(FUYAO_BASE_URL + NODE_PATH);
return readJsonResponse(response, 'GET nodes list');
};
export const fetchNode = async (apiContext: APIRequestContext, nodeName: string): Promise<Record<string, unknown>> => {
const cacheBuster = `?_t=${Date.now()}`;
const response = await apiContext.get(`${FUYAO_BASE_URL}${NODE_PATH}${nodeName}${cacheBuster}`);
return readJsonResponse(response, `GET node ${nodeName}`);
};
export const fetchColocationConfig = async (
apiContext: APIRequestContext
): Promise<[ColocationConfig, VolcanoSchedulerOptions, RubikOptions]> => {
const data = await getConfigMapDataWithRetry(
apiContext,
FUYAO_BASE_URL + COLOCATION_CONFIG_CONFIGMAP,
'colocation',
['colocation-options', 'volcano-scheduler-options', 'rubik-options'],
);
const colocationOptions = JSON.parse(data['colocation-options']) as ColocationConfig;
const volcanoSchedulerOptions = JSON.parse(data['volcano-scheduler-options']) as VolcanoSchedulerOptions;
const rubikOptions = JSON.parse(data['rubik-options']) as RubikOptions;
return [colocationOptions, volcanoSchedulerOptions, rubikOptions];
};
export const fetchVolcanoConfig = async (apiContext: APIRequestContext): Promise<Record<string, unknown>> => {
const data = await getConfigMapDataWithRetry(
apiContext,
FUYAO_BASE_URL + VOLCANO_CONFIG_CONFIGMAP,
'volcano',
['volcano-scheduler.conf'],
);
const rawConfig = data['volcano-scheduler.conf'];
try {
return JSON.parse(rawConfig) as Record<string, unknown>;
} catch {
const parsed = YAML.parse(rawConfig) as Record<string, unknown> | null;
return parsed ?? {};
}
};