mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-03-10 22:44:29 +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);
|
emitAsync('change', undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function reboot(force?: boolean, skipLock?: boolean) {
|
export interface ShutdownOpts {
|
||||||
await updateLock.abortIfHUPInProgress({ force });
|
force?: boolean;
|
||||||
await applicationManager.stopAll({ force, skipLock });
|
reboot?: boolean;
|
||||||
logger.logSystemMessage('Rebooting', {}, 'Reboot');
|
|
||||||
const $reboot = await dbus.reboot();
|
|
||||||
shuttingDown = true;
|
|
||||||
emitAsync('shutdown', undefined);
|
|
||||||
return await $reboot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function shutdown(force?: boolean, skipLock?: boolean) {
|
export async function shutdown({
|
||||||
|
force = false,
|
||||||
|
reboot = false,
|
||||||
|
}: ShutdownOpts = {}) {
|
||||||
await updateLock.abortIfHUPInProgress({ force });
|
await updateLock.abortIfHUPInProgress({ force });
|
||||||
await applicationManager.stopAll({ force, skipLock });
|
// Get current apps to create locks for
|
||||||
logger.logSystemMessage('Shutting down', {}, 'Shutdown');
|
const apps = await applicationManager.getCurrentApps();
|
||||||
const $shutdown = await dbus.shutdown();
|
const appIds = Object.keys(apps).map((strId) => parseInt(strId, 10));
|
||||||
shuttingDown = true;
|
// Try to create a lock for all the services before shutting down
|
||||||
emitAsync('shutdown', undefined);
|
return updateLock.lock(appIds, { force }, async () => {
|
||||||
return $shutdown;
|
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>(
|
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
|
// and if they do, we wouldn't know about it until after
|
||||||
// the response has been sent back to the API. Just return
|
// the response has been sent back to the API. Just return
|
||||||
// "OK" for this and the below action
|
// "OK" for this and the below action
|
||||||
await reboot(force, skipLock);
|
await shutdown({ force, reboot: true });
|
||||||
return {
|
return {
|
||||||
Data: 'OK',
|
Data: 'OK',
|
||||||
Error: null,
|
Error: null,
|
||||||
};
|
};
|
||||||
case 'shutdown':
|
case 'shutdown':
|
||||||
await shutdown(force, skipLock);
|
await shutdown({ force, reboot: false });
|
||||||
return {
|
return {
|
||||||
Data: 'OK',
|
Data: 'OK',
|
||||||
Error: null,
|
Error: null,
|
||||||
|
@ -548,7 +548,7 @@ describe('device-state', () => {
|
|||||||
.stub(updateLock, 'abortIfHUPInProgress')
|
.stub(updateLock, 'abortIfHUPInProgress')
|
||||||
.throws(new UpdatesLockedError(testErrMsg));
|
.throws(new UpdatesLockedError(testErrMsg));
|
||||||
|
|
||||||
await expect(deviceState.reboot())
|
await expect(deviceState.shutdown({ reboot: true }))
|
||||||
.to.eventually.be.rejectedWith(testErrMsg)
|
.to.eventually.be.rejectedWith(testErrMsg)
|
||||||
.and.be.an.instanceOf(UpdatesLockedError);
|
.and.be.an.instanceOf(UpdatesLockedError);
|
||||||
await expect(deviceState.shutdown())
|
await expect(deviceState.shutdown())
|
||||||
|
@ -538,7 +538,54 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
|||||||
shutdownMock.resetHistory();
|
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) => {
|
stub(updateLock, 'lock').callsFake((__, opts, fn) => {
|
||||||
if (opts.force) {
|
if (opts.force) {
|
||||||
return Bluebird.resolve(fn());
|
return Bluebird.resolve(fn());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user