test: expand onebox integration coverage
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
import { dirname, fromFileUrl, join, resolve } from '@std/path';
|
||||
|
||||
import { Onebox } from '../../../onebox/ts/classes/onebox.ts';
|
||||
import type { IAppVersionConfig } from '../../../onebox/ts/classes/appstore-types.ts';
|
||||
import {
|
||||
disableManagedDcRouterForScenario,
|
||||
useLocalAppStoreForScenario,
|
||||
} from '../onebox-test-helpers.ts';
|
||||
|
||||
const scenarioName = 'onebox-cloudly-appstore-worker';
|
||||
const smokeId = `cloudly-appstore-${Date.now().toString(36)}`;
|
||||
const testingDir = resolve(dirname(fromFileUrl(import.meta.url)), '../..');
|
||||
const repoRoot = resolve(testingDir, '..');
|
||||
const appCatalogDir = join(repoRoot, 'appstore-apptemplates');
|
||||
const buildDir = join(testingDir, '.nogit', scenarioName, smokeId);
|
||||
const serviceName = `cloudly-${Date.now().toString(36)}`;
|
||||
const dockerServiceName = `onebox-${serviceName}`;
|
||||
@@ -80,6 +85,22 @@ const dockerContainerRunning = async (containerNameArg: string) => {
|
||||
return new TextDecoder().decode(output.stdout).trim() === 'true';
|
||||
};
|
||||
|
||||
const dockerContainerExists = async (containerNameArg: string) => {
|
||||
const command = new Deno.Command('docker', {
|
||||
args: ['container', 'inspect', containerNameArg],
|
||||
stdout: 'null',
|
||||
stderr: 'null',
|
||||
});
|
||||
return (await command.output()).success;
|
||||
};
|
||||
|
||||
const removeDockerContainer = async (containerNameArg: string) => {
|
||||
if (await dockerContainerExists(containerNameArg)) {
|
||||
await run('docker', ['container', 'rm', '-f', containerNameArg]).catch(() => null);
|
||||
await delayFor(1000);
|
||||
}
|
||||
};
|
||||
|
||||
const assertNoPreexistingScenarioServices = async () => {
|
||||
for (const serviceNameArg of [dockerServiceName, 'onebox-smartproxy']) {
|
||||
if (await dockerServiceExists(serviceNameArg)) {
|
||||
@@ -87,8 +108,11 @@ const assertNoPreexistingScenarioServices = async () => {
|
||||
}
|
||||
}
|
||||
for (const containerNameArg of ['onebox-mongodb', 'onebox-minio']) {
|
||||
if (await dockerContainerRunning(containerNameArg)) {
|
||||
throw new Error(`${containerNameArg} is already running; refusing to overwrite a platform container`);
|
||||
if (await dockerContainerExists(containerNameArg)) {
|
||||
const state = await dockerContainerRunning(containerNameArg) ? 'running' : 'present';
|
||||
throw new Error(
|
||||
`${containerNameArg} is already ${state}; refusing to overwrite a platform container`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -109,49 +133,30 @@ const waitForDockerServiceRunning = async (serviceNameArg: string) => {
|
||||
}, `${serviceNameArg} running task`);
|
||||
} catch (error) {
|
||||
await run('docker', ['service', 'ps', '--no-trunc', serviceNameArg]).catch(() => null);
|
||||
await run('docker', ['service', 'logs', '--raw', '--tail', '120', serviceNameArg]).catch(() => null);
|
||||
await run('docker', ['service', 'logs', '--raw', '--tail', '120', serviceNameArg]).catch(() =>
|
||||
null
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const waitForDockerContainerRunning = async (containerNameArg: string) => {
|
||||
await waitFor(async () => dockerContainerRunning(containerNameArg), `${containerNameArg} running container`);
|
||||
await waitFor(
|
||||
async () => dockerContainerRunning(containerNameArg),
|
||||
`${containerNameArg} running container`,
|
||||
);
|
||||
};
|
||||
|
||||
const ensureDockerReady = async () => {
|
||||
await run('docker', ['version']);
|
||||
const { stdout } = await run('docker', ['info', '--format', '{{.Swarm.LocalNodeState}}']);
|
||||
if (stdout.trim() !== 'active') {
|
||||
throw new Error('Docker Swarm must be active. In Vagrant this is handled by scripts/provision-vm.sh.');
|
||||
throw new Error(
|
||||
'Docker Swarm must be active. In Vagrant this is handled by scripts/provision-vm.sh.',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getTemplateEnvVars = (
|
||||
configArg: IAppVersionConfig,
|
||||
overridesArg: Record<string, string>,
|
||||
) => {
|
||||
const envVars: Record<string, string> = {};
|
||||
const missingRequiredEnvVars: string[] = [];
|
||||
|
||||
for (const envVar of configArg.envVars || []) {
|
||||
const value = overridesArg[envVar.key] ?? envVar.value ?? '';
|
||||
if (envVar.required && !value) {
|
||||
missingRequiredEnvVars.push(envVar.key);
|
||||
}
|
||||
envVars[envVar.key] = value;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(overridesArg)) {
|
||||
envVars[key] = value;
|
||||
}
|
||||
|
||||
if (missingRequiredEnvVars.length > 0) {
|
||||
throw new Error(`Missing required template env vars: ${missingRequiredEnvVars.join(', ')}`);
|
||||
}
|
||||
|
||||
return envVars;
|
||||
};
|
||||
|
||||
const requestCloudlyBootstrapScript = async () => {
|
||||
const curlArgs = [
|
||||
'-sS',
|
||||
@@ -209,9 +214,13 @@ const waitForCloudlyWorkerBootstrapRoute = async () => {
|
||||
console.log(`[${scenarioName}] Last bootstrap error: ${lastError.message}`);
|
||||
}
|
||||
await run('docker', ['service', 'ps', 'onebox-smartproxy']).catch(() => null);
|
||||
await run('docker', ['service', 'logs', '--raw', '--tail', '120', 'onebox-smartproxy']).catch(() => null);
|
||||
await run('docker', ['service', 'logs', '--raw', '--tail', '120', 'onebox-smartproxy']).catch(
|
||||
() => null,
|
||||
);
|
||||
await run('docker', ['service', 'ps', dockerServiceName]).catch(() => null);
|
||||
await run('docker', ['service', 'logs', '--raw', '--tail', '120', dockerServiceName]).catch(() => null);
|
||||
await run('docker', ['service', 'logs', '--raw', '--tail', '120', dockerServiceName]).catch(
|
||||
() => null,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -229,6 +238,8 @@ const main = async () => {
|
||||
|
||||
console.log(`[${scenarioName}] Starting Onebox from ${buildDir}`);
|
||||
onebox = new Onebox();
|
||||
disableManagedDcRouterForScenario(onebox);
|
||||
useLocalAppStoreForScenario(onebox, appCatalogDir);
|
||||
await onebox.init();
|
||||
await waitForDockerServiceRunning('onebox-smartproxy');
|
||||
|
||||
@@ -236,28 +247,23 @@ const main = async () => {
|
||||
const version = appMeta.latestVersion;
|
||||
const config = await onebox.appStore.getAppVersionConfig('cloudly', version);
|
||||
if (!config.platformRequirements?.mongodb || !config.platformRequirements?.s3) {
|
||||
throw new Error(`Cloudly template must require MongoDB and S3: ${JSON.stringify(config.platformRequirements)}`);
|
||||
throw new Error(
|
||||
`Cloudly template must require MongoDB and S3: ${
|
||||
JSON.stringify(config.platformRequirements)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`[${scenarioName}] Installing Cloudly template ${version} as ${serviceName}`);
|
||||
const envVars = getTemplateEnvVars(config, {
|
||||
SERVEZONE_ADMINACCOUNT: 'testadmin:testpassword',
|
||||
});
|
||||
|
||||
const service = await onebox.services.deployService({
|
||||
name: serviceName,
|
||||
image: config.image,
|
||||
port: config.port,
|
||||
const service = await onebox.appStore.installApp({
|
||||
appId: 'cloudly',
|
||||
version,
|
||||
serviceName,
|
||||
domain: routeDomain,
|
||||
autoDNS: false,
|
||||
envVars,
|
||||
enableMongoDB: Boolean(config.platformRequirements.mongodb),
|
||||
enableS3: Boolean(config.platformRequirements.s3),
|
||||
enableClickHouse: Boolean(config.platformRequirements.clickhouse),
|
||||
enableRedis: Boolean(config.platformRequirements.redis),
|
||||
enableMariaDB: Boolean(config.platformRequirements.mariadb),
|
||||
appTemplateId: 'cloudly',
|
||||
appTemplateVersion: version,
|
||||
envVars: {
|
||||
SERVEZONE_ADMINACCOUNT: 'testadmin:testpassword',
|
||||
},
|
||||
});
|
||||
deployedService = true;
|
||||
|
||||
@@ -272,13 +278,38 @@ const main = async () => {
|
||||
|
||||
const deployedServiceRecord = onebox.services.getService(serviceName);
|
||||
if (!deployedServiceRecord?.id) {
|
||||
throw new Error(`Cloudly service missing after deploy: ${JSON.stringify(deployedServiceRecord)}`);
|
||||
throw new Error(
|
||||
`Cloudly service missing after deploy: ${JSON.stringify(deployedServiceRecord)}`,
|
||||
);
|
||||
}
|
||||
if (deployedServiceRecord.envVars.SERVEZONE_URL !== routeDomain) {
|
||||
throw new Error(
|
||||
`Cloudly SERVICE_DOMAIN template was not resolved: ${
|
||||
JSON.stringify(deployedServiceRecord.envVars)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
!deployedServiceRecord.envVars.MONGODB_URL ||
|
||||
deployedServiceRecord.envVars.MONGODB_URL.includes('${')
|
||||
) {
|
||||
throw new Error(
|
||||
`Cloudly MongoDB template was not resolved: ${
|
||||
JSON.stringify(deployedServiceRecord.envVars)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
const platformResources = await onebox.platformServices.getResourcesForService(deployedServiceRecord.id);
|
||||
const platformResourceTypes = platformResources.map((resourceArg) => resourceArg.platformService.type).sort();
|
||||
const platformResources = await onebox.platformServices.getResourcesForService(
|
||||
deployedServiceRecord.id,
|
||||
);
|
||||
const platformResourceTypes = platformResources.map((resourceArg) =>
|
||||
resourceArg.platformService.type
|
||||
).sort();
|
||||
if (platformResourceTypes.join(',') !== 'minio,mongodb') {
|
||||
throw new Error(`Unexpected Cloudly platform resources: ${JSON.stringify(platformResourceTypes)}`);
|
||||
throw new Error(
|
||||
`Unexpected Cloudly platform resources: ${JSON.stringify(platformResourceTypes)}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`[${scenarioName}] Creating Cloudly platform-resource backup for ${serviceName}`);
|
||||
@@ -286,13 +317,23 @@ const main = async () => {
|
||||
const backupResult = await onebox.backupManager.createBackup(serviceName);
|
||||
const backupId = backupResult.backup.id;
|
||||
if (!backupId || !backupResult.snapshotId || !backupResult.backup.snapshotId) {
|
||||
throw new Error(`Cloudly backup did not produce a ContainerArchive snapshot: ${JSON.stringify(backupResult)}`);
|
||||
throw new Error(
|
||||
`Cloudly backup did not produce a ContainerArchive snapshot: ${
|
||||
JSON.stringify(backupResult)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
if (backupResult.backup.includesImage) {
|
||||
throw new Error(`Cloudly backup unexpectedly included the large app image: ${JSON.stringify(backupResult.backup)}`);
|
||||
throw new Error(
|
||||
`Cloudly backup unexpectedly included the large app image: ${
|
||||
JSON.stringify(backupResult.backup)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
if (backupResult.backup.platformResources.sort().join(',') !== 'minio,mongodb') {
|
||||
throw new Error(`Cloudly backup missing platform resources: ${JSON.stringify(backupResult.backup)}`);
|
||||
throw new Error(
|
||||
`Cloudly backup missing platform resources: ${JSON.stringify(backupResult.backup)}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`[${scenarioName}] Restoring Cloudly platform-resource backup for ${serviceName}`);
|
||||
@@ -301,10 +342,14 @@ const main = async () => {
|
||||
overwriteExisting: true,
|
||||
});
|
||||
if (restoreResult.service.name !== serviceName) {
|
||||
throw new Error(`Cloudly restore returned unexpected service: ${JSON.stringify(restoreResult.service)}`);
|
||||
throw new Error(
|
||||
`Cloudly restore returned unexpected service: ${JSON.stringify(restoreResult.service)}`,
|
||||
);
|
||||
}
|
||||
if (restoreResult.platformResourcesRestored !== 2 || restoreResult.warnings.length > 0) {
|
||||
throw new Error(`Cloudly restore failed platform resource validation: ${JSON.stringify(restoreResult)}`);
|
||||
throw new Error(
|
||||
`Cloudly restore failed platform resource validation: ${JSON.stringify(restoreResult)}`,
|
||||
);
|
||||
}
|
||||
|
||||
await waitForCloudlyWorkerBootstrapRoute();
|
||||
@@ -323,8 +368,8 @@ const main = async () => {
|
||||
}
|
||||
|
||||
await removeDockerService(dockerServiceName);
|
||||
await removeDockerService('onebox-mongodb');
|
||||
await removeDockerService('onebox-minio');
|
||||
await removeDockerContainer('onebox-mongodb');
|
||||
await removeDockerContainer('onebox-minio');
|
||||
await removeDockerService('onebox-smartproxy');
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user