test: expand onebox integration coverage
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { dirname, fromFileUrl, join, resolve } from '@std/path';
|
||||
|
||||
import { Onebox } from '../../../onebox/ts/classes/onebox.ts';
|
||||
import { disableManagedDcRouterForScenario } from '../onebox-test-helpers.ts';
|
||||
|
||||
const scenarioName = 'onebox-backup-restore';
|
||||
const smokeId = `onebox-backup-${Date.now().toString(36)}`;
|
||||
@@ -10,6 +11,8 @@ const serviceName = `app-${Date.now().toString(36)}`;
|
||||
const cloneServiceName = `${serviceName}-clone`;
|
||||
const dockerServiceName = `onebox-${serviceName}`;
|
||||
const cloneDockerServiceName = `onebox-${cloneServiceName}`;
|
||||
const volumeName = `onebox-${serviceName}-data`;
|
||||
const cloneVolumeName = `onebox-${cloneServiceName}-data`;
|
||||
|
||||
const delayFor = async (millisecondsArg: number) => {
|
||||
await new Promise((resolveArg) => setTimeout(resolveArg, millisecondsArg));
|
||||
@@ -85,9 +88,35 @@ const removeDockerService = async (serviceNameArg: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const removeDockerVolume = async (volumeNameArg: string) => {
|
||||
let lastErrorText = '';
|
||||
const startTime = Date.now();
|
||||
while (Date.now() - startTime < 30000) {
|
||||
const output = await outputCommand(
|
||||
'docker',
|
||||
['volume', 'rm', '-f', volumeNameArg],
|
||||
{
|
||||
stdout: 'piped',
|
||||
stderr: 'piped',
|
||||
},
|
||||
15000,
|
||||
);
|
||||
if (output.success) {
|
||||
return;
|
||||
}
|
||||
lastErrorText = new TextDecoder().decode(output.stderr).trim();
|
||||
await delayFor(1000);
|
||||
}
|
||||
console.log(
|
||||
`[${scenarioName}] Failed to remove Docker volume ${volumeNameArg}: ${lastErrorText}`,
|
||||
);
|
||||
};
|
||||
|
||||
const assertNoPreexistingOneboxIngress = async () => {
|
||||
if (await dockerServiceExists('onebox-smartproxy')) {
|
||||
throw new Error('onebox-smartproxy already exists; refusing to overwrite a running Onebox ingress service');
|
||||
throw new Error(
|
||||
'onebox-smartproxy already exists; refusing to overwrite a running Onebox ingress service',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -111,14 +140,50 @@ const waitForDockerServiceRunning = async (serviceNameArg: string) => {
|
||||
};
|
||||
|
||||
const waitForDockerServiceRemoved = async (serviceNameArg: string) => {
|
||||
await waitFor(async () => !(await dockerServiceExists(serviceNameArg)), `${serviceNameArg} removal`);
|
||||
await waitFor(
|
||||
async () => !(await dockerServiceExists(serviceNameArg)),
|
||||
`${serviceNameArg} removal`,
|
||||
);
|
||||
};
|
||||
|
||||
const inspectDockerServiceJson = async <T>(serviceNameArg: string, formatArg: string) => {
|
||||
const output = await outputCommand('docker', [
|
||||
'service',
|
||||
'inspect',
|
||||
serviceNameArg,
|
||||
'--format',
|
||||
formatArg,
|
||||
], {
|
||||
stdout: 'piped',
|
||||
stderr: 'piped',
|
||||
}, 15000);
|
||||
if (!output.success) {
|
||||
throw new Error(`docker service inspect ${serviceNameArg} exited with ${output.code}`);
|
||||
}
|
||||
return JSON.parse(new TextDecoder().decode(output.stdout).trim()) as T;
|
||||
};
|
||||
|
||||
const assertCloneVolumeMount = async () => {
|
||||
const mounts = await inspectDockerServiceJson<Array<Record<string, unknown>>>(
|
||||
cloneDockerServiceName,
|
||||
'{{json .Spec.TaskTemplate.ContainerSpec.Mounts}}',
|
||||
);
|
||||
if (
|
||||
!mounts.some((mountArg) => mountArg.Source === cloneVolumeName && mountArg.Target === '/data')
|
||||
) {
|
||||
throw new Error(
|
||||
`Cloned service volume mount missing from Docker service: ${JSON.stringify(mounts)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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.',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -136,6 +201,7 @@ const main = async () => {
|
||||
|
||||
console.log(`[${scenarioName}] Starting Onebox from ${buildDir}`);
|
||||
onebox = new Onebox();
|
||||
disableManagedDcRouterForScenario(onebox);
|
||||
await onebox.init();
|
||||
|
||||
await waitForDockerServiceRunning('onebox-smartproxy');
|
||||
@@ -149,6 +215,7 @@ const main = async () => {
|
||||
envVars: {
|
||||
ONEBOX_BACKUP_SCENARIO: smokeId,
|
||||
},
|
||||
volumes: [{ mountPath: '/data', backup: true }],
|
||||
});
|
||||
deployedService = true;
|
||||
|
||||
@@ -162,17 +229,25 @@ 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(`Backup did not produce a ContainerArchive snapshot: ${JSON.stringify(backupResult)}`);
|
||||
throw new Error(
|
||||
`Backup did not produce a ContainerArchive snapshot: ${JSON.stringify(backupResult)}`,
|
||||
);
|
||||
}
|
||||
if (!backupResult.backup.includesImage) {
|
||||
throw new Error(`Backup did not include Docker image: ${JSON.stringify(backupResult.backup)}`);
|
||||
throw new Error(
|
||||
`Backup did not include Docker image: ${JSON.stringify(backupResult.backup)}`,
|
||||
);
|
||||
}
|
||||
if ((backupResult.backup.storedSizeBytes ?? 0) <= 0 || backupResult.backup.sizeBytes <= 0) {
|
||||
throw new Error(`Backup size metadata is invalid: ${JSON.stringify(backupResult.backup)}`);
|
||||
}
|
||||
|
||||
const backups = onebox.backupManager.listBackups(serviceName);
|
||||
if (!backups.some((backupArg) => backupArg.id === backupId && backupArg.snapshotId === backupResult.snapshotId)) {
|
||||
if (
|
||||
!backups.some((backupArg) =>
|
||||
backupArg.id === backupId && backupArg.snapshotId === backupResult.snapshotId
|
||||
)
|
||||
) {
|
||||
throw new Error(`Backup not listed for service: ${JSON.stringify(backups)}`);
|
||||
}
|
||||
|
||||
@@ -183,20 +258,34 @@ const main = async () => {
|
||||
});
|
||||
clonedService = true;
|
||||
|
||||
if (restoreResult.service.name !== cloneServiceName || restoreResult.service.status !== 'running') {
|
||||
throw new Error(`Unexpected restored service state: ${JSON.stringify(restoreResult.service)}`);
|
||||
if (
|
||||
restoreResult.service.name !== cloneServiceName || restoreResult.service.status !== 'running'
|
||||
) {
|
||||
throw new Error(
|
||||
`Unexpected restored service state: ${JSON.stringify(restoreResult.service)}`,
|
||||
);
|
||||
}
|
||||
if (restoreResult.service.domain !== undefined) {
|
||||
throw new Error(`Clone unexpectedly retained a domain: ${JSON.stringify(restoreResult.service)}`);
|
||||
throw new Error(
|
||||
`Clone unexpectedly retained a domain: ${JSON.stringify(restoreResult.service)}`,
|
||||
);
|
||||
}
|
||||
if (restoreResult.service.envVars.ONEBOX_BACKUP_SCENARIO !== smokeId) {
|
||||
throw new Error(`Clone did not preserve env vars: ${JSON.stringify(restoreResult.service.envVars)}`);
|
||||
throw new Error(
|
||||
`Clone did not preserve env vars: ${JSON.stringify(restoreResult.service.envVars)}`,
|
||||
);
|
||||
}
|
||||
if (!restoreResult.service.volumes?.some((volumeArg) => volumeArg.mountPath === '/data')) {
|
||||
throw new Error(
|
||||
`Clone did not preserve volumes: ${JSON.stringify(restoreResult.service.volumes)}`,
|
||||
);
|
||||
}
|
||||
if (restoreResult.warnings.length > 0) {
|
||||
throw new Error(`Restore completed with warnings: ${restoreResult.warnings.join('; ')}`);
|
||||
}
|
||||
|
||||
await waitForDockerServiceRunning(cloneDockerServiceName);
|
||||
await assertCloneVolumeMount();
|
||||
|
||||
console.log(`[${scenarioName}] Removing restored workload ${cloneServiceName}`);
|
||||
await onebox.services.removeService(cloneServiceName);
|
||||
@@ -212,12 +301,16 @@ const main = async () => {
|
||||
} finally {
|
||||
if (onebox && clonedService) {
|
||||
await onebox.services.removeService(cloneServiceName).catch((error) => {
|
||||
console.log(`[${scenarioName}] Failed to remove restored Onebox service: ${(error as Error).message}`);
|
||||
console.log(
|
||||
`[${scenarioName}] Failed to remove restored Onebox service: ${(error as Error).message}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
if (onebox && deployedService) {
|
||||
await onebox.services.removeService(serviceName).catch((error) => {
|
||||
console.log(`[${scenarioName}] Failed to remove Onebox service: ${(error as Error).message}`);
|
||||
console.log(
|
||||
`[${scenarioName}] Failed to remove Onebox service: ${(error as Error).message}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
if (onebox) {
|
||||
@@ -228,6 +321,8 @@ const main = async () => {
|
||||
await removeDockerService(cloneDockerServiceName);
|
||||
await removeDockerService(dockerServiceName);
|
||||
await removeDockerService('onebox-smartproxy');
|
||||
await removeDockerVolume(cloneVolumeName);
|
||||
await removeDockerVolume(volumeName);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user