Use locks before shutdown/reboot instead of stopping containers

Closes: #1940
Change-type: patch
Signed-off-by: 20k-ultra <3946250+20k-ultra@users.noreply.github.com>
This commit is contained in:
20k-ultra 2022-05-26 17:23:08 -04:00
parent 471f0f0615
commit aad5a9efc5
3 changed files with 78 additions and 19 deletions

View File

@ -684,24 +684,36 @@ export function reportCurrentState(newState: DeviceReport = {}) {
emitAsync('change', undefined);
}
export async function reboot(force?: boolean, skipLock?: boolean) {
await updateLock.abortIfHUPInProgress({ force });
await applicationManager.stopAll({ force, skipLock });
logger.logSystemMessage('Rebooting', {}, 'Reboot');
const $reboot = await dbus.reboot();
shuttingDown = true;
emitAsync('shutdown', undefined);
return await $reboot;
export interface ShutdownOpts {
force?: boolean;
reboot?: boolean;
}
export async function shutdown(force?: boolean, skipLock?: boolean) {
export async function shutdown({
force = false,
reboot = false,
}: ShutdownOpts = {}) {
await updateLock.abortIfHUPInProgress({ force });
await applicationManager.stopAll({ force, skipLock });
logger.logSystemMessage('Shutting down', {}, 'Shutdown');
const $shutdown = await dbus.shutdown();
shuttingDown = true;
emitAsync('shutdown', undefined);
return $shutdown;
// Get current apps to create locks for
const apps = await applicationManager.getCurrentApps();
const appIds = Object.keys(apps).map((strId) => parseInt(strId, 10));
// Try to create a lock for all the services before shutting down
return updateLock.lock(appIds, { force }, async () => {
let dbusAction;
switch (reboot) {
case true:
logger.logSystemMessage('Rebooting', {}, 'Reboot');
dbusAction = await dbus.reboot();
break;
case false:
logger.logSystemMessage('Shutting down', {}, 'Shutdown');
dbusAction = await dbus.shutdown();
break;
}
shuttingDown = true;
emitAsync('shutdown', undefined);
return dbusAction;
});
}
export async function executeStepAction<T extends PossibleStepTargets>(
@ -728,13 +740,13 @@ export async function executeStepAction<T extends PossibleStepTargets>(
// and if they do, we wouldn't know about it until after
// the response has been sent back to the API. Just return
// "OK" for this and the below action
await reboot(force, skipLock);
await shutdown({ force, reboot: true });
return {
Data: 'OK',
Error: null,
};
case 'shutdown':
await shutdown(force, skipLock);
await shutdown({ force, reboot: false });
return {
Data: 'OK',
Error: null,

View File

@ -548,7 +548,7 @@ describe('device-state', () => {
.stub(updateLock, 'abortIfHUPInProgress')
.throws(new UpdatesLockedError(testErrMsg));
await expect(deviceState.reboot())
await expect(deviceState.shutdown({ reboot: true }))
.to.eventually.be.rejectedWith(testErrMsg)
.and.be.an.instanceOf(UpdatesLockedError);
await expect(deviceState.shutdown())

View File

@ -538,7 +538,54 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
shutdownMock.resetHistory();
});
it('should return 423 and reject the reboot if no locks are set', async () => {
it('should lock all applications before trying to shutdown', async () => {
// Setup 2 applications running
const twoContainers = [
mockedAPI.mockService({
containerId: 'abc123',
appId: 1000,
releaseId: 55555,
}),
mockedAPI.mockService({
containerId: 'def456',
appId: 2000,
releaseId: 77777,
}),
];
const twoImages = [
mockedAPI.mockImage({
appId: 1000,
}),
mockedAPI.mockImage({
appId: 2000,
}),
];
appMock.mockManagers(twoContainers, [], []);
appMock.mockImages([], false, twoImages);
const lockSpy = spy(updateLock, 'lock');
await mockedDockerode.testWithData(
{ containers: twoContainers, images: twoImages },
async () => {
const response = await request
.post('/v1/shutdown')
.set('Accept', 'application/json')
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
.expect(202);
expect(lockSpy.callCount).to.equal(1);
// Check that lock was passed both application Ids
expect(lockSpy.lastCall.args[0]).to.deep.equal([1000, 2000]);
expect(response.body).to.have.property('Data').that.is.not.empty;
expect(shutdownMock).to.have.been.calledOnce;
},
);
shutdownMock.resetHistory();
lockSpy.restore();
});
it('should return 423 and reject the reboot if locks are set', async () => {
stub(updateLock, 'lock').callsFake((__, opts, fn) => {
if (opts.force) {
return Bluebird.resolve(fn());