mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-19 05:37:53 +00:00
Add additional update lock tests for lockOverride & force
Change-type: patch Signed-off-by: Christina Ying Wang <christina@balena.io>
This commit is contained in:
parent
0ca7896691
commit
57207c3539
@ -93,13 +93,17 @@ describe('manages application lifecycle', () => {
|
|||||||
|
|
||||||
const setSupervisorTarget = async (
|
const setSupervisorTarget = async (
|
||||||
target: Awaited<ReturnType<typeof generateTarget>>,
|
target: Awaited<ReturnType<typeof generateTarget>>,
|
||||||
) =>
|
) => {
|
||||||
await request(BALENA_SUPERVISOR_ADDRESS)
|
await request(BALENA_SUPERVISOR_ADDRESS)
|
||||||
.post('/v2/local/target-state')
|
.post('/v2/local/target-state')
|
||||||
.set('Content-Type', 'application/json')
|
.set('Content-Type', 'application/json')
|
||||||
.send(JSON.stringify(target))
|
.send(JSON.stringify(target))
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
|
// Give some time for the Supervisor to settle
|
||||||
|
await setTimeout(1000);
|
||||||
|
};
|
||||||
|
|
||||||
const generateTargetApps = ({
|
const generateTargetApps = ({
|
||||||
serviceCount,
|
serviceCount,
|
||||||
appId,
|
appId,
|
||||||
@ -147,10 +151,12 @@ describe('manages application lifecycle', () => {
|
|||||||
serviceCount,
|
serviceCount,
|
||||||
appId = APP_ID,
|
appId = APP_ID,
|
||||||
serviceNames = ['server', 'client'],
|
serviceNames = ['server', 'client'],
|
||||||
|
configOverride = {},
|
||||||
}: {
|
}: {
|
||||||
serviceCount: number;
|
serviceCount: number;
|
||||||
appId?: number;
|
appId?: number;
|
||||||
serviceNames?: string[];
|
serviceNames?: string[];
|
||||||
|
configOverride?: { [key: string]: string | boolean };
|
||||||
}) => {
|
}) => {
|
||||||
const { name, config: svConfig } = await getSupervisorTarget();
|
const { name, config: svConfig } = await getSupervisorTarget();
|
||||||
return {
|
return {
|
||||||
@ -158,7 +164,7 @@ describe('manages application lifecycle', () => {
|
|||||||
// We don't want to change name or config as this may result in
|
// We don't want to change name or config as this may result in
|
||||||
// unintended reboots. We just want to test state changes in containers.
|
// unintended reboots. We just want to test state changes in containers.
|
||||||
name,
|
name,
|
||||||
config: svConfig,
|
config: { ...svConfig, ...configOverride },
|
||||||
apps:
|
apps:
|
||||||
serviceCount === 0
|
serviceCount === 0
|
||||||
? {}
|
? {}
|
||||||
@ -256,6 +262,9 @@ describe('manages application lifecycle', () => {
|
|||||||
targetState = await generateTarget({
|
targetState = await generateTarget({
|
||||||
serviceCount,
|
serviceCount,
|
||||||
serviceNames,
|
serviceNames,
|
||||||
|
configOverride: {
|
||||||
|
SUPERVISOR_OVERRIDE_LOCK: 'false',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -370,6 +379,51 @@ describe('manages application lifecycle', () => {
|
|||||||
expect(await updateLock.getLocksTaken()).to.deep.equal([]);
|
expect(await updateLock.getLocksTaken()).to.deep.equal([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should restart an application when user locks are present if lockOverride is specified', async () => {
|
||||||
|
const targetStateWithLockOverride = await generateTarget({
|
||||||
|
serviceCount,
|
||||||
|
serviceNames,
|
||||||
|
configOverride: {
|
||||||
|
SUPERVISOR_OVERRIDE_LOCK: 'true',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await setSupervisorTarget(targetStateWithLockOverride);
|
||||||
|
containers = await waitForSetup(targetStateWithLockOverride);
|
||||||
|
const isRestartSuccessful = startTimesChanged(
|
||||||
|
containers.map((ctn) => ctn.State.StartedAt),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a lock
|
||||||
|
await lockfile.lock(
|
||||||
|
`${lockdir}/${APP_ID}/${serviceNames[0]}/updates.lock`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await request(BALENA_SUPERVISOR_ADDRESS)
|
||||||
|
.post(`/v1/restart`)
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.send(JSON.stringify({ appId: APP_ID }));
|
||||||
|
|
||||||
|
const restartedContainers = await waitForSetup(
|
||||||
|
targetStateWithLockOverride,
|
||||||
|
isRestartSuccessful,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Technically the wait function above should already verify that the two
|
||||||
|
// containers have been restarted, but verify explcitly with an assertion
|
||||||
|
expect(isRestartSuccessful(restartedContainers)).to.be.true;
|
||||||
|
|
||||||
|
// Containers should have different Ids since they're recreated
|
||||||
|
expect(restartedContainers.map(({ Id }) => Id)).to.not.have.members(
|
||||||
|
containers.map((ctn) => ctn.Id),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait briefly for state to settle which includes releasing locks
|
||||||
|
await setTimeout(1000);
|
||||||
|
|
||||||
|
// User lock should be overridden
|
||||||
|
expect(await updateLock.getLocksTaken()).to.deep.equal([]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should restart service by removing and recreating corresponding container', async () => {
|
it('should restart service by removing and recreating corresponding container', async () => {
|
||||||
containers = await waitForSetup(targetState);
|
containers = await waitForSetup(targetState);
|
||||||
const isRestartSuccessful = startTimesChanged(
|
const isRestartSuccessful = startTimesChanged(
|
||||||
@ -466,6 +520,53 @@ describe('manages application lifecycle', () => {
|
|||||||
expect(await updateLock.getLocksTaken()).to.deep.equal([]);
|
expect(await updateLock.getLocksTaken()).to.deep.equal([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should restart service when user locks are present if lockOverride is specified', async () => {
|
||||||
|
const targetStateWithLockOverride = await generateTarget({
|
||||||
|
serviceCount,
|
||||||
|
serviceNames,
|
||||||
|
configOverride: {
|
||||||
|
SUPERVISOR_OVERRIDE_LOCK: 'true',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await setSupervisorTarget(targetStateWithLockOverride);
|
||||||
|
containers = await waitForSetup(targetStateWithLockOverride);
|
||||||
|
const isRestartSuccessful = startTimesChanged(
|
||||||
|
containers
|
||||||
|
.filter((ctn) => ctn.Name.includes(serviceNames[0]))
|
||||||
|
.map((ctn) => ctn.State.StartedAt),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a lock
|
||||||
|
await lockfile.lock(
|
||||||
|
`${lockdir}/${APP_ID}/${serviceNames[0]}/updates.lock`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await request(BALENA_SUPERVISOR_ADDRESS)
|
||||||
|
.post('/v2/applications/1/restart-service')
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.send(JSON.stringify({ serviceName: serviceNames[0] }));
|
||||||
|
|
||||||
|
const restartedContainers = await waitForSetup(
|
||||||
|
targetStateWithLockOverride,
|
||||||
|
isRestartSuccessful,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Technically the wait function above should already verify that the two
|
||||||
|
// containers have been restarted, but verify explcitly with an assertion
|
||||||
|
expect(isRestartSuccessful(restartedContainers)).to.be.true;
|
||||||
|
|
||||||
|
// Containers should have different Ids since they're recreated
|
||||||
|
expect(restartedContainers.map(({ Id }) => Id)).to.not.have.members(
|
||||||
|
containers.map((ctn) => ctn.Id),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait briefly for state to settle which includes releasing locks
|
||||||
|
await setTimeout(1000);
|
||||||
|
|
||||||
|
// User lock should be overridden
|
||||||
|
expect(await updateLock.getLocksTaken()).to.deep.equal([]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should stop a running service', async () => {
|
it('should stop a running service', async () => {
|
||||||
containers = await waitForSetup(targetState);
|
containers = await waitForSetup(targetState);
|
||||||
|
|
||||||
@ -773,6 +874,51 @@ describe('manages application lifecycle', () => {
|
|||||||
expect(await updateLock.getLocksTaken()).to.deep.equal([]);
|
expect(await updateLock.getLocksTaken()).to.deep.equal([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should restart an application when user locks are present if lockOverride is specified', async () => {
|
||||||
|
const targetStateWithLockOverride = await generateTarget({
|
||||||
|
serviceCount,
|
||||||
|
serviceNames,
|
||||||
|
configOverride: {
|
||||||
|
SUPERVISOR_OVERRIDE_LOCK: 'true',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await setSupervisorTarget(targetStateWithLockOverride);
|
||||||
|
containers = await waitForSetup(targetStateWithLockOverride);
|
||||||
|
const isRestartSuccessful = startTimesChanged(
|
||||||
|
containers.map((ctn) => ctn.State.StartedAt),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a lock
|
||||||
|
await lockfile.lock(
|
||||||
|
`${lockdir}/${APP_ID}/${serviceNames[0]}/updates.lock`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await request(BALENA_SUPERVISOR_ADDRESS)
|
||||||
|
.post(`/v1/restart`)
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.send(JSON.stringify({ appId: APP_ID }));
|
||||||
|
|
||||||
|
const restartedContainers = await waitForSetup(
|
||||||
|
targetStateWithLockOverride,
|
||||||
|
isRestartSuccessful,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Technically the wait function above should already verify that the two
|
||||||
|
// containers have been restarted, but verify explcitly with an assertion
|
||||||
|
expect(isRestartSuccessful(restartedContainers)).to.be.true;
|
||||||
|
|
||||||
|
// Containers should have different Ids since they're recreated
|
||||||
|
expect(restartedContainers.map(({ Id }) => Id)).to.not.have.members(
|
||||||
|
containers.map((ctn) => ctn.Id),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait briefly for state to settle which includes releasing locks
|
||||||
|
await setTimeout(500);
|
||||||
|
|
||||||
|
// User lock should be overridden
|
||||||
|
expect(await updateLock.getLocksTaken()).to.deep.equal([]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should restart service by removing and recreating corresponding container', async () => {
|
it('should restart service by removing and recreating corresponding container', async () => {
|
||||||
containers = await waitForSetup(targetState);
|
containers = await waitForSetup(targetState);
|
||||||
const serviceName = serviceNames[0];
|
const serviceName = serviceNames[0];
|
||||||
@ -881,6 +1027,53 @@ describe('manages application lifecycle', () => {
|
|||||||
expect(await updateLock.getLocksTaken()).to.deep.equal([]);
|
expect(await updateLock.getLocksTaken()).to.deep.equal([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should restart service when user locks are present if lockOverride is specified', async () => {
|
||||||
|
const targetStateWithLockOverride = await generateTarget({
|
||||||
|
serviceCount,
|
||||||
|
serviceNames,
|
||||||
|
configOverride: {
|
||||||
|
SUPERVISOR_OVERRIDE_LOCK: 'true',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await setSupervisorTarget(targetStateWithLockOverride);
|
||||||
|
containers = await waitForSetup(targetStateWithLockOverride);
|
||||||
|
const isRestartSuccessful = startTimesChanged(
|
||||||
|
containers
|
||||||
|
.filter((ctn) => ctn.Name.includes(serviceNames[0]))
|
||||||
|
.map((ctn) => ctn.State.StartedAt),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a lock
|
||||||
|
await lockfile.lock(
|
||||||
|
`${lockdir}/${APP_ID}/${serviceNames[0]}/updates.lock`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await request(BALENA_SUPERVISOR_ADDRESS)
|
||||||
|
.post('/v2/applications/1/restart-service')
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.send(JSON.stringify({ serviceName: serviceNames[0] }));
|
||||||
|
|
||||||
|
const restartedContainers = await waitForSetup(
|
||||||
|
targetStateWithLockOverride,
|
||||||
|
isRestartSuccessful,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Technically the wait function above should already verify that the two
|
||||||
|
// containers have been restarted, but verify explcitly with an assertion
|
||||||
|
expect(isRestartSuccessful(restartedContainers)).to.be.true;
|
||||||
|
|
||||||
|
// Containers should have different Ids since they're recreated
|
||||||
|
expect(restartedContainers.map(({ Id }) => Id)).to.not.have.members(
|
||||||
|
containers.map((ctn) => ctn.Id),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait briefly for state to settle which includes releasing locks
|
||||||
|
await setTimeout(500);
|
||||||
|
|
||||||
|
// User lock should be overridden
|
||||||
|
expect(await updateLock.getLocksTaken()).to.deep.equal([]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should stop a running service', async () => {
|
it('should stop a running service', async () => {
|
||||||
containers = await waitForSetup(targetState);
|
containers = await waitForSetup(targetState);
|
||||||
|
|
||||||
|
@ -593,6 +593,24 @@ describe('lib/update-lock', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should take locks if service has non-Supervisor-taken lock and force is true', async () => {
|
||||||
|
// Simulate a user service taking the lock for services with appId 1
|
||||||
|
for (const lockPath of serviceLockPaths[1]) {
|
||||||
|
await fs.writeFile(lockPath, '');
|
||||||
|
}
|
||||||
|
// Take locks using takeLock & force, should not error
|
||||||
|
await updateLock.takeLock(1, ['server', 'client'], true);
|
||||||
|
// Check that locks are taken
|
||||||
|
expect(await updateLock.getLocksTaken()).to.deep.include(
|
||||||
|
serviceLockPaths[1][0],
|
||||||
|
serviceLockPaths[1][1],
|
||||||
|
);
|
||||||
|
// Clean up lockfiles
|
||||||
|
for (const lockPath of serviceLockPaths[1]) {
|
||||||
|
await lockfile.unlock(lockPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('waits to take locks until resource write lock is taken', async () => {
|
it('waits to take locks until resource write lock is taken', async () => {
|
||||||
// Take the write lock for appId 1
|
// Take the write lock for appId 1
|
||||||
const release = await takeGlobalLockRW(1);
|
const release = await takeGlobalLockRW(1);
|
||||||
|
Loading…
Reference in New Issue
Block a user