mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-02-21 10:01:55 +00:00
Perform config.json sequentially to other config changes
As config.json changes may restart the engine (and hence the supervisor) in newer OS versions, this ensures that the supervisor does not get interrupted while writing to backends.
This commit is contained in:
parent
63cb985c53
commit
2917f03452
@ -541,10 +541,19 @@ export async function getRequiredSteps(
|
||||
{},
|
||||
);
|
||||
|
||||
const configSteps = getConfigSteps(current, target);
|
||||
const steps = ([] as ConfigStep[]).concat(
|
||||
getConfigSteps(current, target),
|
||||
configSteps,
|
||||
await getVPNSteps(current, target),
|
||||
await getBackendSteps(current, target),
|
||||
|
||||
// Only apply backend steps if no more config changes are left since
|
||||
// changing config.json may restart the supervisor
|
||||
configSteps.length > 0 &&
|
||||
// if any config step is a not 'noop' step, skip the backend steps
|
||||
configSteps.filter((s) => s.action !== 'noop').length > 0
|
||||
? // Set a 'noop' action so the apply function knows to retry
|
||||
[{ action: 'noop' }]
|
||||
: await getBackendSteps(current, target),
|
||||
);
|
||||
|
||||
// Check if there is either no steps, or they are all
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { stripIndent } from 'common-tags';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { SinonStub, stub, spy, SinonSpy } from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
|
||||
@ -15,6 +16,7 @@ import * as constants from '../src/lib/constants';
|
||||
import * as config from '../src/config';
|
||||
|
||||
import prepare = require('./lib/prepare');
|
||||
import mock = require('mock-fs');
|
||||
|
||||
const extlinuxBackend = new Extlinux();
|
||||
const configTxtBackend = new ConfigTxt();
|
||||
@ -22,6 +24,9 @@ const odmdataBackend = new Odmdata();
|
||||
const configFsBackend = new ConfigFs();
|
||||
const splashImageBackend = new SplashImage();
|
||||
|
||||
// TODO: Since the getBootConfig method is simple enough
|
||||
// these tests could probably be removed if each backend has its own
|
||||
// test and the src/config/utils module is properly tested.
|
||||
describe('Device Backend Config', () => {
|
||||
let logSpy: SinonSpy;
|
||||
|
||||
@ -566,7 +571,6 @@ describe('Device Backend Config', () => {
|
||||
'fincm3',
|
||||
),
|
||||
).to.equal(true);
|
||||
|
||||
await deviceConfig.setBootConfig(splashImageBackend, target);
|
||||
|
||||
expect(fsUtils.exec).to.be.calledOnce;
|
||||
@ -675,3 +679,108 @@ describe('Device Backend Config', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRequiredSteps', () => {
|
||||
const bootMountPoint = path.join(
|
||||
constants.rootMountPoint,
|
||||
constants.bootMountPoint,
|
||||
);
|
||||
const configJson = 'test/data/config.json';
|
||||
const configTxt = path.join(bootMountPoint, 'config.txt');
|
||||
const deviceTypeJson = path.join(bootMountPoint, 'device-type.json');
|
||||
const osRelease = path.join(constants.rootMountPoint, '/etc/os-release');
|
||||
const splash = path.join(bootMountPoint, 'splash/balena-logo.png');
|
||||
|
||||
// TODO: something like this could be done as a fixture instead of
|
||||
// doing the file initialisation on 00-init.ts
|
||||
const mockFs = () => {
|
||||
mock({
|
||||
// This is only needed so config.get doesn't fail
|
||||
[configJson]: JSON.stringify({}),
|
||||
[configTxt]: stripIndent`
|
||||
enable_uart=true
|
||||
`,
|
||||
[osRelease]: stripIndent`
|
||||
PRETTY_NAME="balenaOS 2.88.5+rev1"
|
||||
META_BALENA_VERSION="2.88.5"
|
||||
VARIANT_ID="dev"
|
||||
`,
|
||||
[deviceTypeJson]: JSON.stringify({
|
||||
slug: 'raspberrypi4-64',
|
||||
arch: 'aarch64',
|
||||
}),
|
||||
[splash]: Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
|
||||
'base64',
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const unmockFs = () => {
|
||||
mock.restore();
|
||||
};
|
||||
|
||||
before(() => {
|
||||
mockFs();
|
||||
|
||||
// TODO: remove this once the remount on backend.ts is no longer
|
||||
// necessary
|
||||
stub(fsUtils, 'exec');
|
||||
});
|
||||
|
||||
after(() => {
|
||||
unmockFs();
|
||||
(fsUtils.exec as SinonStub).restore();
|
||||
});
|
||||
|
||||
it('returns required steps to config.json first if any', async () => {
|
||||
const steps = await deviceConfig.getRequiredSteps(
|
||||
{
|
||||
local: {
|
||||
config: {
|
||||
SUPERVISOR_POLL_INTERVAL: 900000,
|
||||
SUPERVISOR_PERSISTENT_LOGGING: true,
|
||||
HOST_CONFIG_enable_uart: true,
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
{
|
||||
local: {
|
||||
config: {
|
||||
SUPERVISOR_POLL_INTERVAL: 600000,
|
||||
SUPERVISOR_PERSISTENT_LOGGING: false,
|
||||
HOST_CONFIG_enable_uart: false,
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
);
|
||||
expect(steps.map((s) => s.action)).to.have.members([
|
||||
'changeConfig',
|
||||
'noop', // The noop has to be here since there are also changes from config backends
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns required steps for backends if no steps are required for config.json', async () => {
|
||||
const steps = await deviceConfig.getRequiredSteps(
|
||||
{
|
||||
local: {
|
||||
config: {
|
||||
SUPERVISOR_POLL_INTERVAL: 900000,
|
||||
SUPERVISOR_PERSISTENT_LOGGING: true,
|
||||
HOST_CONFIG_enable_uart: true,
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
{
|
||||
local: {
|
||||
config: {
|
||||
SUPERVISOR_POLL_INTERVAL: 900000,
|
||||
SUPERVISOR_PERSISTENT_LOGGING: true,
|
||||
HOST_CONFIG_enable_uart: false,
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
);
|
||||
expect(steps.map((s) => s.action)).to.have.members(['setBootConfig']);
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user