Allow users to override HUP lock if device is stuck in invalid state

This functionality is needed when breadcrumbs aren't deleted after a HUP
rollback for whatever reason. Also rename HUP lock function.

Change-type: patch
Connects-to: #1459
Signed-off-by: Christina Wang <christina@balena.io>
This commit is contained in:
Christina Wang 2021-07-02 10:52:44 +09:00
parent 3caf608158
commit 17e740a4ba
No known key found for this signature in database
GPG Key ID: 7C3ED0230F440835
4 changed files with 23 additions and 10 deletions

View File

@ -638,7 +638,7 @@ export function reportCurrentState(
}
export async function reboot(force?: boolean, skipLock?: boolean) {
await updateLock.ensureNoHUPBreadcrumbsOnHost();
await updateLock.abortIfHUPInProgress({ force });
await applicationManager.stopAll({ force, skipLock });
logger.logSystemMessage('Rebooting', {}, 'Reboot');
const $reboot = await dbus.reboot();
@ -648,7 +648,7 @@ export async function reboot(force?: boolean, skipLock?: boolean) {
}
export async function shutdown(force?: boolean, skipLock?: boolean) {
await updateLock.ensureNoHUPBreadcrumbsOnHost();
await updateLock.abortIfHUPInProgress({ force });
await applicationManager.stopAll({ force, skipLock });
logger.logSystemMessage('Shutting down', {}, 'Shutdown');
const $shutdown = await dbus.shutdown();

View File

@ -42,7 +42,11 @@ function lockFilesOnHost(appId: number, serviceName: string): string[] {
* prevent reboot. If the Supervisor reboots while those services are still running,
* the device may become stuck in an invalid state during HUP.
*/
export function ensureNoHUPBreadcrumbsOnHost(): Promise<boolean | never> {
export function abortIfHUPInProgress({
force = false,
}: {
force: boolean | undefined;
}): Promise<boolean | never> {
return Promise.all(
[
'rollback-health-breadcrumb',
@ -52,7 +56,7 @@ export function ensureNoHUPBreadcrumbsOnHost(): Promise<boolean | never> {
),
).then((existsArray) => {
const anyExists = existsArray.some((exists) => exists);
if (anyExists) {
if (anyExists && !force) {
throw new UpdatesLockedError('Waiting for Host OS update to finish');
}
return anyExists;

View File

@ -359,7 +359,7 @@ describe('deviceState', () => {
it('prevents reboot or shutdown when HUP rollback breadcrumbs are present', async () => {
const testErrMsg = 'Waiting for Host OS updates to finish';
stub(updateLock, 'ensureNoHUPBreadcrumbsOnHost').throws(
stub(updateLock, 'abortIfHUPInProgress').throws(
new UpdatesLockedError(testErrMsg),
);
@ -370,6 +370,6 @@ describe('deviceState', () => {
.to.eventually.be.rejectedWith(testErrMsg)
.and.be.an.instanceOf(UpdatesLockedError);
(updateLock.ensureNoHUPBreadcrumbsOnHost as SinonStub).restore();
(updateLock.abortIfHUPInProgress as SinonStub).restore();
});
});

View File

@ -79,24 +79,33 @@ describe('lib/update-lock', () => {
});
});
describe('ensureNoHUPBreadcrumbsOnHost', () => {
describe('abortIfHUPInProgress', () => {
afterEach(() => mockFs.restore());
it('should throw if any breadcrumbs exist on host', async () => {
for (const bc of breadcrumbFiles) {
mockBreadcrumbs(bc);
await expect(updateLock.ensureNoHUPBreadcrumbsOnHost())
await expect(updateLock.abortIfHUPInProgress({ force: false }))
.to.eventually.be.rejectedWith('Waiting for Host OS update to finish')
.and.be.an.instanceOf(UpdatesLockedError);
}
});
it('should resolve to true if no breadcrumbs on host', async () => {
it('should resolve to false if no breadcrumbs on host', async () => {
mockBreadcrumbs();
await expect(
updateLock.ensureNoHUPBreadcrumbsOnHost(),
updateLock.abortIfHUPInProgress({ force: false }),
).to.eventually.equal(false);
});
it('should resolve to true if breadcrumbs are on host but force is passed', async () => {
for (const bc of breadcrumbFiles) {
mockBreadcrumbs(bc);
await expect(
updateLock.abortIfHUPInProgress({ force: true }),
).to.eventually.equal(true);
}
});
});
describe('Lock/dispose functionality', () => {