Move reboot breadcrumb check to device-state

This was on device-config before, but we'll need to set the reboot
breadcrumb from the application-manager as well when we introduce
`requires-reboot` as a label.

Change-type: patch
This commit is contained in:
Felipe Lalanne 2024-10-24 16:39:16 -03:00
parent 51f1fb0f30
commit 75127c6074
No known key found for this signature in database
GPG Key ID: 03E696BFD472B26A
3 changed files with 54 additions and 50 deletions

View File

@ -1,6 +1,5 @@
import _ from 'lodash';
import { inspect } from 'util';
import { promises as fs } from 'fs';
import * as config from '../config';
import * as db from '../db';
@ -10,8 +9,7 @@ import type { EnvVarObject } from '../types';
import { UnitNotLoadedError } from '../lib/errors';
import { checkInt, checkTruthy } from '../lib/validation';
import log from '../lib/supervisor-console';
import * as fsUtils from '../lib/fs-utils';
import { pathOnRoot } from '../lib/host-utils';
import { setRebootBreadcrumb } from '../lib/reboot';
import * as configUtils from '../config/utils';
import type { SchemaTypeKey } from '../config/schema-type';
@ -21,15 +19,6 @@ import { Odmdata } from '../config/backends/odmdata';
const vpnServiceName = 'openvpn';
// This indicates the file on the host /tmp directory that
// marks the need for a reboot. Since reboot is only triggered for now
// by some config changes, we leave this here for now. There is planned
// functionality to allow image installs to require reboots, at that moment
// this constant can be moved somewhere else
const REBOOT_BREADCRUMB = pathOnRoot(
'/tmp/balena-supervisor/reboot-after-apply',
);
interface ConfigOption {
envVarName: string;
varType: string;
@ -40,10 +29,7 @@ interface ConfigOption {
// FIXME: Bring this and the deviceState and
// applicationState steps together
export interface ConfigStep {
// TODO: This is a bit of a mess, the DeviceConfig class shouldn't
// know that the reboot action exists as it is implemented by
// DeviceState. Fix this weird circular dependency
action: keyof DeviceActionExecutors | 'reboot' | 'noop';
action: keyof DeviceActionExecutors | 'noop';
humanReadableTarget?: Dictionary<string>;
target?: string | Dictionary<string>;
}
@ -118,11 +104,7 @@ const actionExecutors: DeviceActionExecutors = {
await setBootConfig(backend, step.target as Dictionary<string>);
}
},
setRebootBreadcrumb: async () => {
// Just create the file. The last step in the target state calculation will check
// the file and create a reboot step
await fsUtils.touch(REBOOT_BREADCRUMB);
},
setRebootBreadcrumb,
};
const configBackends: ConfigBackend[] = [];
@ -551,18 +533,6 @@ async function getBackendSteps(
];
}
async function isRebootRequired() {
const hasBreadcrumb = await fsUtils.exists(REBOOT_BREADCRUMB);
if (hasBreadcrumb) {
const stats = await fs.stat(REBOOT_BREADCRUMB);
// If the breadcrumb exists and the last modified time is greater than the
// boot time, that means we need to reboot
return stats.mtime.getTime() > fsUtils.getBootTime().getTime();
}
return false;
}
export async function getRequiredSteps(
currentState: { local?: { config?: EnvVarObject } },
targetState: { local?: { config: EnvVarObject } },
@ -585,19 +555,6 @@ export async function getRequiredSteps(
: await getBackendSteps(current, target)),
];
// Check if there is either no steps, or they are all
// noops, and we need to reboot. We want to do this
// because in a preloaded setting with no internet
// connection, the device will try to start containers
// before any boot config has been applied, which can
// cause problems
const rebootRequired = await isRebootRequired();
if (_.every(steps, { action: 'noop' }) && rebootRequired) {
steps.push({
action: 'reboot',
});
}
return steps;
}
@ -643,7 +600,7 @@ export function executeStepAction(
step: ConfigStep,
opts: DeviceActionExecutorOpts,
) {
if (step.action !== 'reboot' && step.action !== 'noop') {
if (step.action !== 'noop') {
return actionExecutors[step.action](step, opts);
}
}

View File

@ -19,6 +19,7 @@ import * as updateLock from '../lib/update-lock';
import { getGlobalApiKey } from '../lib/api-keys';
import * as sysInfo from '../lib/system-info';
import { log } from '../lib/supervisor-console';
import { isRebootRequired } from '../lib/reboot';
import { loadTargetFromFile } from './preload';
import * as applicationManager from '../compose/application-manager';
import * as commitStore from '../compose/commit';
@ -518,7 +519,7 @@ export async function executeStepAction(
}
}
export async function applyStep(
async function applyStep(
step: DeviceStateStep<PossibleStepTargets>,
{
force,
@ -615,11 +616,12 @@ export const applyTarget = async ({
({ action }) => action === 'noop',
);
let backoff: boolean;
const rebootRequired = await isRebootRequired();
let backoff = false;
let steps: Array<DeviceStateStep<PossibleStepTargets>>;
if (!noConfigSteps) {
backoff = false;
steps = deviceConfigSteps;
} else {
const appSteps = await applicationManager.getRequiredSteps(
@ -646,6 +648,21 @@ export const applyTarget = async ({
}
}
// Check if there is either no steps, or they are all
// noops, and we need to reboot. We want to do this
// because in a preloaded setting with no internet
// connection, the device will try to start containers
// before any boot config has been applied, which can
// cause problems
// For application manager, the reboot breadcrumb should
// be set after all downloads are ready and target containers
// have been installed
if (_.every(steps, ({ action }) => action === 'noop') && rebootRequired) {
steps.push({
action: 'reboot',
});
}
if (_.isEmpty(steps)) {
emitAsync('apply-target-state-end', null);
if (!intermediate) {

30
src/lib/reboot.ts Normal file
View File

@ -0,0 +1,30 @@
import { pathOnRoot } from '../lib/host-utils';
import * as fsUtils from '../lib/fs-utils';
import { promises as fs } from 'fs';
// This indicates the file on the host /tmp directory that
// marks the need for a reboot. Since reboot is only triggered for now
// by some config changes, we leave this here for now. There is planned
// functionality to allow image installs to require reboots, at that moment
// this constant can be moved somewhere else
const REBOOT_BREADCRUMB = pathOnRoot(
'/tmp/balena-supervisor/reboot-after-apply',
);
export async function setRebootBreadcrumb() {
// Just create the file. The last step in the target state calculation will check
// the file and create a reboot step
await fsUtils.touch(REBOOT_BREADCRUMB);
}
export async function isRebootRequired() {
const hasBreadcrumb = await fsUtils.exists(REBOOT_BREADCRUMB);
if (hasBreadcrumb) {
const stats = await fs.stat(REBOOT_BREADCRUMB);
// If the breadcrumb exists and the last modified time is greater than the
// boot time, that means we need to reboot
return stats.mtime.getTime() > fsUtils.getBootTime().getTime();
}
return false;
}