Improve tests surrounding Engine-host race patch

See: #2170
Change-type: patch
Signed-off-by: Christina Ying Wang <christina@balena.io>
This commit is contained in:
Christina Ying Wang 2023-06-14 19:50:26 -07:00
parent 9e249e6ae8
commit 7eba48f8b8
2 changed files with 406 additions and 236 deletions

View File

@ -1424,20 +1424,19 @@ describe('compose/application-manager', () => {
// In this case, the Supervisor needs to wait a grace period for the Engine to start the container, and if this does not occur, the Supervisor // In this case, the Supervisor needs to wait a grace period for the Engine to start the container, and if this does not occur, the Supervisor
// deduces the existence of this race condition and generates another start step after a delay (SECONDS_TO_WAIT_FOR_START). // deduces the existence of this race condition and generates another start step after a delay (SECONDS_TO_WAIT_FOR_START).
describe('handling Engine restart policy inaction when host resource required by container is delayed in creation', () => { describe('handling Engine restart policy inaction when host resource required by container is delayed in creation', () => {
// Time 61 seconds ago const SECONDS_TO_WAIT_FOR_START = 30;
const date61SecondsAgo = new Date(); const newer = SECONDS_TO_WAIT_FOR_START - 2;
date61SecondsAgo.setSeconds(date61SecondsAgo.getSeconds() - 61); const older = SECONDS_TO_WAIT_FOR_START + 2;
// Time 59 seconds ago const getTimeNSecondsAgo = (date: Date, seconds: number) => {
const date50SecondsAgo = new Date(); const newDate = new Date(date);
date50SecondsAgo.setSeconds(date50SecondsAgo.getSeconds() - 50); newDate.setSeconds(newDate.getSeconds() - seconds);
return newDate;
};
// TODO: We need to be able to start a service with restart policy "no" if that service did not start at all due to it('should not infer any steps for a service with a status of "exited" if restart policy is "no"', async () => {
// the host resource race condition described above. However, this is harder to parse as the containers do not include
// the proper metadata for this. The last resort would be parsing the error message that caused the container to exit.
it('should not infer any steps for a service with a status of "exited" if restart policy is "no" or "on-failure"', async () => {
// Conditions: // Conditions:
// - restart: "no" || "on-failure" // - restart: "no"
// - status: "exited" // - status: "exited"
const targetApps = createApps( const targetApps = createApps(
{ {
@ -1456,20 +1455,6 @@ describe('compose/application-manager', () => {
restart: 'no', restart: 'no',
}, },
}), }),
await createService({
image: 'image-3',
serviceName: 'three',
composition: {
restart: 'on-failure',
},
}),
await createService({
image: 'image-4',
serviceName: 'four',
composition: {
restart: 'on-failure',
},
}),
], ],
networks: [DEFAULT_NETWORK], networks: [DEFAULT_NETWORK],
}, },
@ -1490,8 +1475,7 @@ describe('compose/application-manager', () => {
{ {
state: { state: {
status: 'exited', status: 'exited',
// Should not generate noop if exited within SECONDS_TO_WAIT_FOR_START createdAt: getTimeNSecondsAgo(new Date(), newer),
createdAt: date50SecondsAgo,
}, },
}, },
), ),
@ -1506,40 +1490,7 @@ describe('compose/application-manager', () => {
{ {
state: { state: {
status: 'exited', status: 'exited',
// Should not generate start if exited more than SECONDS_TO_WAIT_FOR_START ago createdAt: getTimeNSecondsAgo(new Date(), older),
createdAt: date61SecondsAgo,
},
},
),
await createService(
{
image: 'image-3',
serviceName: 'three',
composition: {
restart: 'on-failure',
},
},
{
state: {
status: 'exited',
// Should not generate noop if exited within SECONDS_TO_WAIT_FOR_START
createdAt: date50SecondsAgo,
},
},
),
await createService(
{
image: 'image-4',
serviceName: 'four',
composition: {
restart: 'on-failure',
},
},
{
state: {
status: 'exited',
// Should not generate start if exited more than SECONDS_TO_WAIT_FOR_START ago
createdAt: date61SecondsAgo,
}, },
}, },
), ),
@ -1554,14 +1505,6 @@ describe('compose/application-manager', () => {
name: 'image-2', name: 'image-2',
serviceName: 'two', serviceName: 'two',
}), }),
createImage({
name: 'image-3',
serviceName: 'three',
}),
createImage({
name: 'image-4',
serviceName: 'four',
}),
], ],
}); });
@ -1578,36 +1521,41 @@ describe('compose/application-manager', () => {
expect(steps).to.have.lengthOf(0); expect(steps).to.have.lengthOf(0);
}); });
it('should infer a noop step for a service that was created <= SECONDS_TO_WAIT_FOR_START ago with status of "exited" if restart policy is "always" or "unless-stopped"', async () => { describe('restart policy "on-failure"', () => {
// Conditions: it('should not infer any steps for a service that exited with code 0', async () => {
// - restart: "always" || "unless-stopped" // Conditions:
// - status: "exited" // - restart: "on-failure"
// - createdAt: <= SECONDS_TO_WAIT_FOR_START ago // - status: "exited"
const targetApps = createApps( // - exitCode: 0
{ const targetApps = createApps(
services: [ {
await createService({ services: [
image: 'image-1', await createService({
serviceName: 'one', image: 'image-1',
composition: { serviceName: 'one',
restart: 'always', composition: {
}, restart: 'on-failure',
}), },
await createService({ }),
image: 'image-2', await createService({
serviceName: 'two', image: 'image-2',
composition: { serviceName: 'two',
restart: 'unless-stopped', composition: {
}, restart: 'on-failure',
}), },
], }),
networks: [DEFAULT_NETWORK], ],
}, networks: [DEFAULT_NETWORK],
true, },
); true,
);
const { currentApps, availableImages, downloading, containerIdsByAppId } = const {
createCurrentState({ currentApps,
availableImages,
downloading,
containerIdsByAppId,
} = createCurrentState({
services: [ services: [
await createService( await createService(
{ {
@ -1615,13 +1563,14 @@ describe('compose/application-manager', () => {
serviceName: 'one', serviceName: 'one',
running: false, running: false,
composition: { composition: {
restart: 'always', restart: 'on-failure',
}, },
}, },
{ {
state: { state: {
status: 'exited', status: 'exited',
createdAt: date50SecondsAgo, exitCode: 0,
createdAt: getTimeNSecondsAgo(new Date(), newer),
}, },
}, },
), ),
@ -1631,13 +1580,14 @@ describe('compose/application-manager', () => {
serviceName: 'two', serviceName: 'two',
running: false, running: false,
composition: { composition: {
restart: 'unless-stopped', restart: 'on-failure',
}, },
}, },
{ {
state: { state: {
status: 'exited', status: 'exited',
createdAt: date50SecondsAgo, exitCode: 0,
createdAt: getTimeNSecondsAgo(new Date(), older),
}, },
}, },
), ),
@ -1655,49 +1605,189 @@ describe('compose/application-manager', () => {
], ],
}); });
const [noopStep1, noopStep2, ...nextSteps] = const [...steps] = await applicationManager.inferNextSteps(
await applicationManager.inferNextSteps(currentApps, targetApps, { currentApps,
downloading, targetApps,
{
downloading,
availableImages,
containerIdsByAppId,
},
);
expect(steps).to.have.lengthOf(0);
});
it('should infer a noop step for a service that was created <= SECONDS_TO_WAIT_FOR_START ago and exited non-zero', async () => {
// Conditions:
// - restart: "on-failure"
// - status: "exited"
// - exitCode: non-zero
// - createdAt: <= SECONDS_TO_WAIT_FOR_START
const targetApps = createApps(
{
services: [
await createService({
image: 'image-1',
serviceName: 'one',
composition: {
restart: 'on-failure',
},
}),
],
networks: [DEFAULT_NETWORK],
},
true,
);
const {
currentApps,
availableImages, availableImages,
downloading,
containerIdsByAppId, containerIdsByAppId,
} = createCurrentState({
services: [
await createService(
{
image: 'image-1',
serviceName: 'one',
running: false,
composition: {
restart: 'on-failure',
},
},
{
state: {
status: 'exited',
exitCode: 1,
createdAt: getTimeNSecondsAgo(new Date(), newer),
},
},
),
],
networks: [DEFAULT_NETWORK],
images: [
createImage({
name: 'image-1',
serviceName: 'one',
}),
],
}); });
expect(noopStep1).to.have.property('action').that.equals('noop'); const [noopStep, ...nextSteps] =
expect(noopStep2).to.have.property('action').that.equals('noop'); await applicationManager.inferNextSteps(currentApps, targetApps, {
downloading,
availableImages,
containerIdsByAppId,
});
expect(nextSteps).to.have.lengthOf(0); expect(noopStep).to.have.property('action').that.equals('noop');
expect(nextSteps).to.have.lengthOf(0);
});
it('should infer a start step for a service that was created > SECONDS_TO_WAIT_FOR_START ago and exited non-zero', async () => {
// Conditions:
// - restart: "on-failure"
// - status: "exited"
// - exitCode: non-zero\
// - createdAt: > SECONDS_TO_WAIT_FOR_START
const targetApps = createApps(
{
services: [
await createService({
image: 'image-1',
serviceName: 'one',
composition: {
restart: 'on-failure',
},
}),
],
networks: [DEFAULT_NETWORK],
},
true,
);
const {
currentApps,
availableImages,
downloading,
containerIdsByAppId,
} = createCurrentState({
services: [
await createService(
{
image: 'image-1',
serviceName: 'one',
running: false,
composition: {
restart: 'on-failure',
},
},
{
state: {
status: 'exited',
exitCode: 1,
createdAt: getTimeNSecondsAgo(new Date(), older),
},
},
),
],
networks: [DEFAULT_NETWORK],
images: [
createImage({
name: 'image-1',
serviceName: 'one',
}),
],
});
const [startStep, ...nextSteps] =
await applicationManager.inferNextSteps(currentApps, targetApps, {
downloading,
availableImages,
containerIdsByAppId,
});
expect(startStep).to.have.property('action').that.equals('start');
expect(nextSteps).to.have.lengthOf(0);
});
}); });
it('should infer a start step for a service that was created > SECONDS_TO_WAIT_FOR_START ago with status of "exited" if restart policy is "always" or "unless-stopped"', async () => { describe('restart policy "always" or "unless-stopped"', () => {
// Conditions: it('should infer a noop step for a service that was created <= SECONDS_TO_WAIT_FOR_START ago with status of "exited"', async () => {
// - restart: "always" || "unless-stopped" // Conditions:
// - status: "exited" // - restart: "always" || "unless-stopped"
// - createdAt: > SECONDS_TO_WAIT_FOR_START ago // - status: "exited"
const targetApps = createApps( // - createdAt: <= SECONDS_TO_WAIT_FOR_START ago
{ const targetApps = createApps(
services: [ {
await createService({ services: [
image: 'image-1', await createService({
serviceName: 'one', image: 'image-1',
composition: { serviceName: 'one',
restart: 'always', composition: {
}, restart: 'always',
}), },
await createService({ }),
image: 'image-2', await createService({
serviceName: 'two', image: 'image-2',
composition: { serviceName: 'two',
restart: 'unless-stopped', composition: {
}, restart: 'unless-stopped',
}), },
], }),
networks: [DEFAULT_NETWORK], ],
}, networks: [DEFAULT_NETWORK],
true, },
); true,
);
const { currentApps, availableImages, downloading, containerIdsByAppId } = const {
createCurrentState({ currentApps,
availableImages,
downloading,
containerIdsByAppId,
} = createCurrentState({
services: [ services: [
await createService( await createService(
{ {
@ -1711,7 +1801,7 @@ describe('compose/application-manager', () => {
{ {
state: { state: {
status: 'exited', status: 'exited',
createdAt: date61SecondsAgo, createdAt: getTimeNSecondsAgo(new Date(), newer),
}, },
}, },
), ),
@ -1727,7 +1817,7 @@ describe('compose/application-manager', () => {
{ {
state: { state: {
status: 'exited', status: 'exited',
createdAt: date61SecondsAgo, createdAt: getTimeNSecondsAgo(new Date(), newer),
}, },
}, },
), ),
@ -1745,21 +1835,116 @@ describe('compose/application-manager', () => {
], ],
}); });
const [startStep1, startStep2, ...nextSteps] = const [noopStep1, noopStep2, ...nextSteps] =
await applicationManager.inferNextSteps(currentApps, targetApps, { await applicationManager.inferNextSteps(currentApps, targetApps, {
downloading, downloading,
availableImages,
containerIdsByAppId,
});
expect(noopStep1).to.have.property('action').that.equals('noop');
expect(noopStep2).to.have.property('action').that.equals('noop');
expect(nextSteps).to.have.lengthOf(0);
});
it('should infer a start step for a service that was created > SECONDS_TO_WAIT_FOR_START ago with status of "exited"', async () => {
// Conditions:
// - restart: "always" || "unless-stopped"
// - status: "exited"
// - createdAt: > SECONDS_TO_WAIT_FOR_START ago
const targetApps = createApps(
{
services: [
await createService({
image: 'image-1',
serviceName: 'one',
composition: {
restart: 'always',
},
}),
await createService({
image: 'image-2',
serviceName: 'two',
composition: {
restart: 'unless-stopped',
},
}),
],
networks: [DEFAULT_NETWORK],
},
true,
);
const {
currentApps,
availableImages, availableImages,
downloading,
containerIdsByAppId, containerIdsByAppId,
} = createCurrentState({
services: [
await createService(
{
image: 'image-1',
serviceName: 'one',
running: false,
composition: {
restart: 'always',
},
},
{
state: {
status: 'exited',
createdAt: getTimeNSecondsAgo(new Date(), older),
},
},
),
await createService(
{
image: 'image-2',
serviceName: 'two',
running: false,
composition: {
restart: 'unless-stopped',
},
},
{
state: {
status: 'exited',
createdAt: getTimeNSecondsAgo(new Date(), older),
},
},
),
],
networks: [DEFAULT_NETWORK],
images: [
createImage({
name: 'image-1',
serviceName: 'one',
}),
createImage({
name: 'image-2',
serviceName: 'two',
}),
],
}); });
[startStep1, startStep2].forEach((step) => { const [startStep1, startStep2, ...nextSteps] =
expect(step).to.have.property('action').that.equals('start'); await applicationManager.inferNextSteps(currentApps, targetApps, {
expect(step) downloading,
.to.have.property('target') availableImages,
.that.has.property('serviceName') containerIdsByAppId,
.that.is.oneOf(['one', 'two']); });
[startStep1, startStep2].forEach((step) => {
expect(step).to.have.property('action').that.equals('start');
expect(step)
.to.have.property('target')
.that.has.property('serviceName')
.that.is.oneOf(['one', 'two']);
});
expect(nextSteps).to.have.lengthOf(0);
}); });
expect(nextSteps).to.have.lengthOf(0);
}); });
}); });
}); });

View File

@ -111,7 +111,7 @@ const defaultNetwork = Network.fromComposeObject('default', 1, 'appuuid', {});
describe('compose/app', () => { describe('compose/app', () => {
describe('volume state behavior', () => { describe('volume state behavior', () => {
it('should correctly infer a volume create step', async () => { it('should correctly infer a volume create step', () => {
// Setup current and target apps // Setup current and target apps
const current = createApp(); const current = createApp();
const target = createApp({ const target = createApp({
@ -120,7 +120,7 @@ describe('compose/app', () => {
}); });
// Calculate the steps // Calculate the steps
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
// Check that a createVolume step has been created // Check that a createVolume step has been created
const [createVolumeStep] = expectSteps('createVolume', steps); const [createVolumeStep] = expectSteps('createVolume', steps);
@ -129,7 +129,7 @@ describe('compose/app', () => {
.that.deep.includes({ name: 'test-volume' }); .that.deep.includes({ name: 'test-volume' });
}); });
it('should correctly infer more than one volume create step', async () => { it('should correctly infer more than one volume create step', () => {
const current = createApp(); const current = createApp();
const target = createApp({ const target = createApp({
volumes: [ volumes: [
@ -139,7 +139,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
// Check that 2 createVolume steps are found // Check that 2 createVolume steps are found
const createVolumeSteps = expectSteps('createVolume', steps, 2); const createVolumeSteps = expectSteps('createVolume', steps, 2);
@ -160,7 +160,7 @@ describe('compose/app', () => {
}); });
// We don't remove volumes until the end // We don't remove volumes until the end
it('should not infer a volume remove step when the app is still referenced', async () => { it('should not infer a volume remove step when the app is still referenced', () => {
const current = createApp({ const current = createApp({
volumes: [ volumes: [
Volume.fromComposeObject('test-volume', 1, 'deadbeef'), Volume.fromComposeObject('test-volume', 1, 'deadbeef'),
@ -172,11 +172,11 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
expectNoStep('removeVolume', steps); expectNoStep('removeVolume', steps);
}); });
it('should correctly infer volume recreation steps', async () => { it('should correctly infer volume recreation steps', () => {
const current = createApp({ const current = createApp({
volumes: [Volume.fromComposeObject('test-volume', 1, 'deadbeef')], volumes: [Volume.fromComposeObject('test-volume', 1, 'deadbeef')],
}); });
@ -190,7 +190,7 @@ describe('compose/app', () => {
}); });
// First step should create a volume removal step // First step should create a volume removal step
const stepsForRemoval = await current.nextStepsForAppUpdate( const stepsForRemoval = current.nextStepsForAppUpdate(
defaultContext, defaultContext,
target, target,
); );
@ -212,7 +212,7 @@ describe('compose/app', () => {
}); });
// This test is extra since we have already tested that the volume gets created // This test is extra since we have already tested that the volume gets created
const stepsForCreation = await intermediate.nextStepsForAppUpdate( const stepsForCreation = intermediate.nextStepsForAppUpdate(
defaultContext, defaultContext,
target, target,
); );
@ -254,7 +254,7 @@ describe('compose/app', () => {
}); });
// Calculate steps // Calculate steps
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [killStep] = expectSteps('kill', steps); const [killStep] = expectSteps('kill', steps);
expect(killStep) expect(killStep)
@ -294,7 +294,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
expectNoStep('kill', steps); expectNoStep('kill', steps);
}); });
@ -333,7 +333,7 @@ describe('compose/app', () => {
}); });
// Step 1: kill // Step 1: kill
const steps = await current.nextStepsForAppUpdate( const steps = current.nextStepsForAppUpdate(
contextWithImages, contextWithImages,
intermediateTarget, intermediateTarget,
); );
@ -341,7 +341,7 @@ describe('compose/app', () => {
// Step 2: noop (service is stopping) // Step 2: noop (service is stopping)
service.status = 'Stopping'; service.status = 'Stopping';
const secondStageSteps = await current.nextStepsForAppUpdate( const secondStageSteps = current.nextStepsForAppUpdate(
contextWithImages, contextWithImages,
intermediateTarget, intermediateTarget,
); );
@ -355,7 +355,7 @@ describe('compose/app', () => {
volumes: [volume], volumes: [volume],
}); });
expect( expect(
await currentWithServiceRemoved.nextStepsForAppUpdate( currentWithServiceRemoved.nextStepsForAppUpdate(
contextWithImages, contextWithImages,
intermediateTarget, intermediateTarget,
), ),
@ -377,7 +377,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const recreateVolumeSteps = const recreateVolumeSteps =
await currentWithVolumesRemoved.nextStepsForAppUpdate( currentWithVolumesRemoved.nextStepsForAppUpdate(
contextWithImages, contextWithImages,
target, target,
); );
@ -393,7 +393,7 @@ describe('compose/app', () => {
}); });
const createServiceSteps = const createServiceSteps =
await currentWithVolumeRecreated.nextStepsForAppUpdate( currentWithVolumeRecreated.nextStepsForAppUpdate(
contextWithImages, contextWithImages,
target, target,
); );
@ -402,14 +402,14 @@ describe('compose/app', () => {
}); });
describe('network state behavior', () => { describe('network state behavior', () => {
it('should correctly infer a network create step', async () => { it('should correctly infer a network create step', () => {
const current = createApp({ networks: [] }); const current = createApp({ networks: [] });
const target = createApp({ const target = createApp({
networks: [Network.fromComposeObject('default', 1, 'deadbeef', {})], networks: [Network.fromComposeObject('default', 1, 'deadbeef', {})],
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [createNetworkStep] = expectSteps('createNetwork', steps); const [createNetworkStep] = expectSteps('createNetwork', steps);
expect(createNetworkStep).to.have.property('target').that.deep.includes({ expect(createNetworkStep).to.have.property('target').that.deep.includes({
@ -417,7 +417,7 @@ describe('compose/app', () => {
}); });
}); });
it('should correctly infer a network remove step', async () => { it('should correctly infer a network remove step', () => {
const current = createApp({ const current = createApp({
networks: [ networks: [
Network.fromComposeObject('test-network', 1, 'deadbeef', {}), Network.fromComposeObject('test-network', 1, 'deadbeef', {}),
@ -425,7 +425,7 @@ describe('compose/app', () => {
}); });
const target = createApp({ networks: [], isTarget: true }); const target = createApp({ networks: [], isTarget: true });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [removeNetworkStep] = expectSteps('removeNetwork', steps); const [removeNetworkStep] = expectSteps('removeNetwork', steps);
@ -434,7 +434,7 @@ describe('compose/app', () => {
}); });
}); });
it('should correctly remove default duplicate networks', async () => { it('should correctly remove default duplicate networks', () => {
const current = createApp({ const current = createApp({
networks: [defaultNetwork, defaultNetwork], networks: [defaultNetwork, defaultNetwork],
}); });
@ -443,7 +443,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [removeNetworkStep] = expectSteps('removeNetwork', steps); const [removeNetworkStep] = expectSteps('removeNetwork', steps);
@ -452,7 +452,7 @@ describe('compose/app', () => {
}); });
}); });
it('should correctly remove duplicate networks', async () => { it('should correctly remove duplicate networks', () => {
const current = createApp({ const current = createApp({
networks: [ networks: [
Network.fromComposeObject('test-network', 1, 'deadbeef', {}), Network.fromComposeObject('test-network', 1, 'deadbeef', {}),
@ -468,7 +468,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [removeNetworkStep] = expectSteps('removeNetwork', steps); const [removeNetworkStep] = expectSteps('removeNetwork', steps);
@ -477,7 +477,7 @@ describe('compose/app', () => {
}); });
}); });
it('should ignore the duplicates if there are changes already', async () => { it('should ignore the duplicates if there are changes already', () => {
const current = createApp({ const current = createApp({
networks: [ networks: [
Network.fromComposeObject('test-network', 1, 'deadbeef', {}), Network.fromComposeObject('test-network', 1, 'deadbeef', {}),
@ -494,7 +494,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [removeNetworkStep] = expectSteps('removeNetwork', steps); const [removeNetworkStep] = expectSteps('removeNetwork', steps);
expect(removeNetworkStep).to.have.property('current').that.deep.includes({ expect(removeNetworkStep).to.have.property('current').that.deep.includes({
@ -534,7 +534,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [removeNetworkStep] = expectSteps('kill', steps); const [removeNetworkStep] = expectSteps('kill', steps);
@ -543,7 +543,7 @@ describe('compose/app', () => {
}); });
}); });
it('should correctly infer more than one network removal step', async () => { it('should correctly infer more than one network removal step', () => {
const current = createApp({ const current = createApp({
networks: [ networks: [
Network.fromComposeObject('test-network', 1, 'deadbeef', {}), Network.fromComposeObject('test-network', 1, 'deadbeef', {}),
@ -553,7 +553,7 @@ describe('compose/app', () => {
}); });
const target = createApp({ networks: [], isTarget: true }); const target = createApp({ networks: [], isTarget: true });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [first, second] = expectSteps('removeNetwork', steps, 2); const [first, second] = expectSteps('removeNetwork', steps, 2);
@ -565,7 +565,7 @@ describe('compose/app', () => {
}); });
}); });
it('should correctly infer a network recreation step', async () => { it('should correctly infer a network recreation step', () => {
const current = createApp({ const current = createApp({
networks: [ networks: [
Network.fromComposeObject('test-network', 1, 'deadbeef', {}), Network.fromComposeObject('test-network', 1, 'deadbeef', {}),
@ -580,7 +580,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const stepsForRemoval = await current.nextStepsForAppUpdate( const stepsForRemoval = current.nextStepsForAppUpdate(
defaultContext, defaultContext,
target, target,
); );
@ -595,7 +595,7 @@ describe('compose/app', () => {
networks: [], networks: [],
}); });
const stepsForCreation = await intermediate.nextStepsForAppUpdate( const stepsForCreation = intermediate.nextStepsForAppUpdate(
defaultContext, defaultContext,
target, target,
); );
@ -641,7 +641,7 @@ describe('compose/app', () => {
const availableImages = [createImage({ appUuid: 'deadbeef' })]; const availableImages = [createImage({ appUuid: 'deadbeef' })];
const steps = await current.nextStepsForAppUpdate( const steps = current.nextStepsForAppUpdate(
{ ...defaultContext, availableImages }, { ...defaultContext, availableImages },
target, target,
); );
@ -674,7 +674,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [killStep] = expectSteps('kill', steps); const [killStep] = expectSteps('kill', steps);
expect(killStep) expect(killStep)
@ -730,7 +730,7 @@ describe('compose/app', () => {
createImage({ appId: 1, serviceName: 'two', name: 'alpine' }), createImage({ appId: 1, serviceName: 'two', name: 'alpine' }),
]; ];
const steps = await current.nextStepsForAppUpdate( const steps = current.nextStepsForAppUpdate(
{ ...defaultContext, availableImages }, { ...defaultContext, availableImages },
target, target,
); );
@ -793,7 +793,7 @@ describe('compose/app', () => {
createImage({ appId: 1, serviceName: 'two', name: 'alpine' }), createImage({ appId: 1, serviceName: 'two', name: 'alpine' }),
]; ];
const steps = await current.nextStepsForAppUpdate( const steps = current.nextStepsForAppUpdate(
{ ...defaultContext, availableImages }, { ...defaultContext, availableImages },
target, target,
); );
@ -808,11 +808,11 @@ describe('compose/app', () => {
expectNoStep('removeNetwork', steps); expectNoStep('removeNetwork', steps);
}); });
it('should create the default network if it does not exist', async () => { it('should create the default network if it does not exist', () => {
const current = createApp({ networks: [] }); const current = createApp({ networks: [] });
const target = createApp({ networks: [], isTarget: true }); const target = createApp({ networks: [], isTarget: true });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
// A default network should always be created // A default network should always be created
const [createNetworkStep] = expectSteps('createNetwork', steps); const [createNetworkStep] = expectSteps('createNetwork', steps);
@ -821,13 +821,13 @@ describe('compose/app', () => {
.that.deep.includes({ name: 'default' }); .that.deep.includes({ name: 'default' });
}); });
it('should not create the default network if it already exists', async () => { it('should not create the default network if it already exists', () => {
const current = createApp({ const current = createApp({
networks: [Network.fromComposeObject('default', 1, 'deadbeef', {})], networks: [Network.fromComposeObject('default', 1, 'deadbeef', {})],
}); });
const target = createApp({ networks: [], isTarget: true }); const target = createApp({ networks: [], isTarget: true });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
// The network should not be created again // The network should not be created again
expectNoStep('createNetwork', steps); expectNoStep('createNetwork', steps);
@ -854,7 +854,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [createNetworkStep] = expectSteps('createNetwork', steps); const [createNetworkStep] = expectSteps('createNetwork', steps);
expect(createNetworkStep) expect(createNetworkStep)
@ -883,7 +883,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [createNetworkStep] = expectSteps('createNetwork', steps); const [createNetworkStep] = expectSteps('createNetwork', steps);
expect(createNetworkStep) expect(createNetworkStep)
@ -892,11 +892,11 @@ describe('compose/app', () => {
.that.deep.includes({ configOnly: false }); .that.deep.includes({ configOnly: false });
}); });
it('should create a config-only network if there are no services in the app', async () => { it('should create a config-only network if there are no services in the app', () => {
const current = createApp({}); const current = createApp({});
const target = createApp({ isTarget: true }); const target = createApp({ isTarget: true });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [createNetworkStep] = expectSteps('createNetwork', steps); const [createNetworkStep] = expectSteps('createNetwork', steps);
expect(createNetworkStep) expect(createNetworkStep)
@ -921,7 +921,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [killStep] = expectSteps('kill', steps); const [killStep] = expectSteps('kill', steps);
expect(killStep) expect(killStep)
.to.have.property('current') .to.have.property('current')
@ -939,7 +939,7 @@ describe('compose/app', () => {
}); });
const target = createApp({ services: [], isTarget: true }); const target = createApp({ services: [], isTarget: true });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
expectSteps('noop', steps); expectSteps('noop', steps);
// Kill was already emitted for this service // Kill was already emitted for this service
@ -959,7 +959,7 @@ describe('compose/app', () => {
services: [await createService({ serviceName: 'main', running: true })], services: [await createService({ serviceName: 'main', running: true })],
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
expectSteps('noop', steps); expectSteps('noop', steps);
}); });
@ -977,7 +977,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [removeStep] = expectSteps('remove', steps); const [removeStep] = expectSteps('remove', steps);
expect(removeStep) expect(removeStep)
@ -996,7 +996,7 @@ describe('compose/app', () => {
}); });
const target = createApp({ services: [], isTarget: true }); const target = createApp({ services: [], isTarget: true });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [removeStep] = expectSteps('remove', steps); const [removeStep] = expectSteps('remove', steps);
expect(removeStep) expect(removeStep)
@ -1013,7 +1013,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate( const steps = current.nextStepsForAppUpdate(
{ ...defaultContext, ...{ downloading: ['main-image'] } }, { ...defaultContext, ...{ downloading: ['main-image'] } },
target, target,
); );
@ -1034,7 +1034,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [updateMetadataStep] = expectSteps('updateMetadata', steps); const [updateMetadataStep] = expectSteps('updateMetadata', steps);
expect(updateMetadataStep) expect(updateMetadataStep)
@ -1057,7 +1057,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [stopStep] = expectSteps('stop', steps); const [stopStep] = expectSteps('stop', steps);
expect(stopStep) expect(stopStep)
.to.have.property('current') .to.have.property('current')
@ -1086,7 +1086,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
expectNoStep('start', steps); expectNoStep('start', steps);
}); });
@ -1118,7 +1118,7 @@ describe('compose/app', () => {
}); });
// should see a 'stop' // should see a 'stop'
const stepsToIntermediate = await current.nextStepsForAppUpdate( const stepsToIntermediate = current.nextStepsForAppUpdate(
contextWithImages, contextWithImages,
target, target,
); );
@ -1135,7 +1135,7 @@ describe('compose/app', () => {
}); });
// now should see a 'start' // now should see a 'start'
const stepsToTarget = await intermediate.nextStepsForAppUpdate( const stepsToTarget = intermediate.nextStepsForAppUpdate(
contextWithImages, contextWithImages,
target, target,
); );
@ -1193,7 +1193,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const stepsToIntermediate = await current.nextStepsForAppUpdate( const stepsToIntermediate = current.nextStepsForAppUpdate(
contextWithImages, contextWithImages,
target, target,
); );
@ -1216,7 +1216,7 @@ describe('compose/app', () => {
}); });
// we should now see a start for the 'main' service... // we should now see a start for the 'main' service...
const stepsToTarget = await intermediate.nextStepsForAppUpdate( const stepsToTarget = intermediate.nextStepsForAppUpdate(
{ ...contextWithImages, ...{ containerIds: { dep: 'dep-id' } } }, { ...contextWithImages, ...{ containerIds: { dep: 'dep-id' } } },
target, target,
); );
@ -1248,10 +1248,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate( const steps = current.nextStepsForAppUpdate(contextWithImages, target);
contextWithImages,
target,
);
// There should be no steps since the engine manages restart policy for stopped containers // There should be no steps since the engine manages restart policy for stopped containers
expect(steps.length).to.equal(0); expect(steps.length).to.equal(0);
@ -1295,7 +1292,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const stepsToIntermediate = await current.nextStepsForAppUpdate( const stepsToIntermediate = current.nextStepsForAppUpdate(
contextWithImages, contextWithImages,
target, target,
); );
@ -1311,7 +1308,7 @@ describe('compose/app', () => {
networks: [defaultNetwork], networks: [defaultNetwork],
}); });
const stepsToTarget = await intermediate.nextStepsForAppUpdate( const stepsToTarget = intermediate.nextStepsForAppUpdate(
contextWithImages, contextWithImages,
target, target,
); );
@ -1382,10 +1379,7 @@ describe('compose/app', () => {
}); });
// No kill steps should be generated // No kill steps should be generated
const steps = await current.nextStepsForAppUpdate( const steps = current.nextStepsForAppUpdate(contextWithImages, target);
contextWithImages,
target,
);
expectNoStep('kill', steps); expectNoStep('kill', steps);
}); });
@ -1427,7 +1421,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const stepsFirstTry = await current.nextStepsForAppUpdate( const stepsFirstTry = current.nextStepsForAppUpdate(
contextWithImages, contextWithImages,
target, target,
); );
@ -1438,7 +1432,7 @@ describe('compose/app', () => {
.that.deep.includes({ serviceName: 'main' }); .that.deep.includes({ serviceName: 'main' });
// if at first you don't succeed // if at first you don't succeed
const stepsSecondTry = await current.nextStepsForAppUpdate( const stepsSecondTry = current.nextStepsForAppUpdate(
contextWithImages, contextWithImages,
target, target,
); );
@ -1470,7 +1464,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [killStep] = expectSteps('kill', steps); const [killStep] = expectSteps('kill', steps);
expect(killStep) expect(killStep)
.to.have.property('current') .to.have.property('current')
@ -1493,7 +1487,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [createNetworkStep] = expectSteps('createNetwork', steps); const [createNetworkStep] = expectSteps('createNetwork', steps);
expect(createNetworkStep) expect(createNetworkStep)
.to.have.property('target') .to.have.property('target')
@ -1534,7 +1528,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
expectSteps('kill', steps, 2); expectSteps('kill', steps, 2);
}); });
@ -1596,10 +1590,7 @@ describe('compose/app', () => {
}); });
// No kill steps should be generated // No kill steps should be generated
const steps = await current.nextStepsForAppUpdate( const steps = current.nextStepsForAppUpdate(contextWithImages, target);
contextWithImages,
target,
);
expectNoStep('kill', steps); expectNoStep('kill', steps);
}); });
@ -1638,10 +1629,7 @@ describe('compose/app', () => {
}); });
// No kill steps should be generated // No kill steps should be generated
const steps = await current.nextStepsForAppUpdate( const steps = current.nextStepsForAppUpdate(contextWithImages, target);
contextWithImages,
target,
);
expectNoStep('start', steps); expectNoStep('start', steps);
}); });
@ -1685,10 +1673,7 @@ describe('compose/app', () => {
}); });
// No kill steps should be generated // No kill steps should be generated
const steps = await current.nextStepsForAppUpdate( const steps = current.nextStepsForAppUpdate(contextWithImages, target);
contextWithImages,
target,
);
expectSteps('start', steps, 2); expectSteps('start', steps, 2);
}); });
}); });
@ -1701,7 +1686,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [fetchStep] = expectSteps('fetch', steps); const [fetchStep] = expectSteps('fetch', steps);
expect(fetchStep) expect(fetchStep)
.to.have.property('image') .to.have.property('image')
@ -1723,7 +1708,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate( const steps = current.nextStepsForAppUpdate(
contextWithDownloading, contextWithDownloading,
target, target,
); );
@ -1739,7 +1724,7 @@ describe('compose/app', () => {
isTarget: true, isTarget: true,
}); });
const steps = await current.nextStepsForAppUpdate(defaultContext, target); const steps = current.nextStepsForAppUpdate(defaultContext, target);
const [fetchStep] = expectSteps('fetch', steps); const [fetchStep] = expectSteps('fetch', steps);
expect(fetchStep) expect(fetchStep)