mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-19 13:47:54 +00:00
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:
parent
471f0f0615
commit
aad5a9efc5
@ -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,
|
||||
|
@ -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())
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user