mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-02-20 17:52:51 +00:00
Create tests with recovery from #1576
Devices affected by the bug described in 1576, are also stuck with some services in the `Downloaded` state, because the state engine does not detect that the running services should be killed on a network change even if they belong to a new release. This is a bug, which can be replicated by the tests in this commit Change-type: patch
This commit is contained in:
parent
7aecaae8b0
commit
7b8b187c74
@ -134,6 +134,118 @@ describe('state engine', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
// This test recovery from issue #1576, where a device running a service from the target release
|
||||
// would not stop the service even if there were still network and container changes to be applied
|
||||
it('always stops running services depending on a network being changed', async () => {
|
||||
// Install part of the target release
|
||||
await setTargetState({
|
||||
config: {},
|
||||
apps: {
|
||||
'123': {
|
||||
name: 'test-app',
|
||||
commit: 'deadca1f',
|
||||
releaseId: 2,
|
||||
services: {
|
||||
'1': {
|
||||
image: 'alpine:latest',
|
||||
imageId: 21,
|
||||
serviceName: 'one',
|
||||
restart: 'unless-stopped',
|
||||
running: true,
|
||||
command: 'sleep infinity',
|
||||
stop_signal: 'SIGKILL',
|
||||
labels: {},
|
||||
environment: {},
|
||||
},
|
||||
},
|
||||
networks: {},
|
||||
volumes: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const state = await getCurrentState();
|
||||
expect(
|
||||
state.apps['123'].services.map((s: any) => s.serviceName),
|
||||
).to.deep.equal(['one']);
|
||||
|
||||
const containers = await docker.listContainers();
|
||||
expect(
|
||||
containers.map(({ Names, State }) => ({ Name: Names[0], State })),
|
||||
).to.have.deep.members([{ Name: '/one_21_2_deadca1f', State: 'running' }]);
|
||||
const containerIds = containers.map(({ Id }) => Id);
|
||||
|
||||
await setTargetState({
|
||||
config: {},
|
||||
apps: {
|
||||
'123': {
|
||||
name: 'test-app',
|
||||
commit: 'deadca1f',
|
||||
releaseId: 2,
|
||||
services: {
|
||||
'1': {
|
||||
image: 'alpine:latest',
|
||||
imageId: 21,
|
||||
serviceName: 'one',
|
||||
restart: 'unless-stopped',
|
||||
running: true,
|
||||
command: 'sleep infinity',
|
||||
stop_signal: 'SIGKILL',
|
||||
networks: ['default'],
|
||||
labels: {},
|
||||
environment: {},
|
||||
},
|
||||
'2': {
|
||||
image: 'alpine:latest',
|
||||
imageId: 22,
|
||||
serviceName: 'two',
|
||||
restart: 'unless-stopped',
|
||||
running: true,
|
||||
command: 'sh -c "echo two && sleep infinity"',
|
||||
stop_signal: 'SIGKILL',
|
||||
networks: ['default'],
|
||||
labels: {},
|
||||
environment: {},
|
||||
},
|
||||
},
|
||||
networks: {
|
||||
default: {
|
||||
driver: 'bridge',
|
||||
ipam: {
|
||||
config: [
|
||||
{ gateway: '192.168.91.1', subnet: '192.168.91.0/24' },
|
||||
],
|
||||
driver: 'default',
|
||||
},
|
||||
},
|
||||
},
|
||||
volumes: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const updatedContainers = await docker.listContainers();
|
||||
expect(
|
||||
updatedContainers.map(({ Names, State }) => ({ Name: Names[0], State })),
|
||||
).to.have.deep.members([
|
||||
{ Name: '/one_21_2_deadca1f', State: 'running' },
|
||||
{ Name: '/two_22_2_deadca1f', State: 'running' },
|
||||
]);
|
||||
|
||||
// Container ids must have changed
|
||||
expect(updatedContainers.map(({ Id }) => Id)).to.not.have.members(
|
||||
containerIds,
|
||||
);
|
||||
|
||||
expect(await docker.getNetwork('123_default').inspect())
|
||||
.to.have.property('IPAM')
|
||||
.to.deep.equal({
|
||||
Config: [{ Gateway: '192.168.91.1', Subnet: '192.168.91.0/24' }],
|
||||
Driver: 'default',
|
||||
Options: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('updates an app with two services with a network change', async () => {
|
||||
await setTargetState({
|
||||
config: {},
|
||||
|
@ -639,8 +639,12 @@ describe('compose/app', () => {
|
||||
isTarget: true,
|
||||
});
|
||||
|
||||
const steps = current.nextStepsForAppUpdate(defaultContext, target);
|
||||
const availableImages = [createImage({ appUuid: 'deadbeef' })];
|
||||
|
||||
const steps = current.nextStepsForAppUpdate(
|
||||
{ ...defaultContext, availableImages },
|
||||
target,
|
||||
);
|
||||
const [killStep] = expectSteps('kill', steps);
|
||||
expect(killStep)
|
||||
.to.have.property('current')
|
||||
@ -681,6 +685,65 @@ describe('compose/app', () => {
|
||||
expectNoStep('removeNetwork', steps);
|
||||
});
|
||||
|
||||
it('should always kill dependencies of networks before removing', async () => {
|
||||
const current = createApp({
|
||||
services: [
|
||||
// The device for some reason is already running some services
|
||||
// of the new release, but we need to kill it anyways
|
||||
await createService({
|
||||
image: 'alpine',
|
||||
serviceName: 'one',
|
||||
commit: 'deadca1f',
|
||||
composition: { command: 'sleep infinity', networks: ['default'] },
|
||||
}),
|
||||
],
|
||||
networks: [Network.fromComposeObject('default', 1, 'appuuid', {})],
|
||||
});
|
||||
const target = createApp({
|
||||
services: [
|
||||
await createService({
|
||||
image: 'alpine',
|
||||
serviceName: 'one',
|
||||
commit: 'deadca1f',
|
||||
composition: { command: 'sleep infinity', networks: ['default'] },
|
||||
}),
|
||||
await createService({
|
||||
image: 'alpine',
|
||||
serviceName: 'two',
|
||||
commit: 'deadca1f',
|
||||
composition: {
|
||||
command: 'sh -c "echo two && sleep infinity"',
|
||||
networks: ['default'],
|
||||
},
|
||||
}),
|
||||
],
|
||||
networks: [
|
||||
Network.fromComposeObject('default', 1, 'appuuid', {
|
||||
labels: { test: 'test' },
|
||||
}),
|
||||
],
|
||||
isTarget: true,
|
||||
});
|
||||
|
||||
const availableImages = [
|
||||
createImage({ appId: 1, serviceName: 'one', name: 'alpine' }),
|
||||
createImage({ appId: 1, serviceName: 'two', name: 'alpine' }),
|
||||
];
|
||||
|
||||
const steps = current.nextStepsForAppUpdate(
|
||||
{ ...defaultContext, availableImages },
|
||||
target,
|
||||
);
|
||||
const [killStep] = expectSteps('kill', steps);
|
||||
|
||||
expect(killStep)
|
||||
.to.have.property('current')
|
||||
.that.deep.includes({ serviceName: 'one' });
|
||||
|
||||
// We shouldn't try to remove the network until we have gotten rid of the dependencies
|
||||
expectNoStep('removeNetwork', steps);
|
||||
});
|
||||
|
||||
it('should kill dependencies of networks before updating between releases', async () => {
|
||||
const current = createApp({
|
||||
services: [
|
||||
|
Loading…
x
Reference in New Issue
Block a user