Merge pull request #1390 from balena-io/device-config-singleton

Convert device config to singleton and fix await bug in db-format
This commit is contained in:
bulldozer-balena[bot] 2020-07-08 11:44:18 +00:00 committed by GitHub
commit de80aacc2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 702 additions and 735 deletions

View File

@ -10,6 +10,7 @@ import * as url from 'url';
import * as deviceRegister from './lib/register-device'; import * as deviceRegister from './lib/register-device';
import * as config from './config'; import * as config from './config';
import * as deviceConfig from './device-config';
import * as eventTracker from './event-tracker'; import * as eventTracker from './event-tracker';
import { loadBackupFromMigration } from './lib/migration'; import { loadBackupFromMigration } from './lib/migration';
@ -639,10 +640,10 @@ export class APIBinder {
); );
} }
const defaultConfig = this.deviceState.deviceConfig.getDefaults(); const defaultConfig = deviceConfig.getDefaults();
const currentState = await this.deviceState.getCurrentForComparison(); const currentState = await this.deviceState.getCurrentForComparison();
const targetConfig = await this.deviceState.deviceConfig.formatConfigKeys( const targetConfig = await deviceConfig.formatConfigKeys(
targetConfigUnformatted, targetConfigUnformatted,
); );

View File

@ -7,6 +7,7 @@ import { Service } from '../compose/service';
import Volume from '../compose/volume'; import Volume from '../compose/volume';
import * as config from '../config'; import * as config from '../config';
import * as db from '../db'; import * as db from '../db';
import * as deviceConfig from '../device-config';
import * as logger from '../logger'; import * as logger from '../logger';
import * as images from '../compose/images'; import * as images from '../compose/images';
import * as volumeManager from '../compose/volume-manager'; import * as volumeManager from '../compose/volume-manager';
@ -465,7 +466,7 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
}); });
router.get('/v2/device/vpn', async (_req, res) => { router.get('/v2/device/vpn', async (_req, res) => {
const conf = await deviceState.deviceConfig.getCurrent(); const conf = await deviceConfig.getCurrent();
// Build VPNInfo // Build VPNInfo
const info = { const info = {
enabled: conf.SUPERVISOR_VPN_CONTROL === 'true', enabled: conf.SUPERVISOR_VPN_CONTROL === 'true',

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,8 @@ import * as network from './network';
import APIBinder from './api-binder'; import APIBinder from './api-binder';
import { ApplicationManager } from './application-manager'; import { ApplicationManager } from './application-manager';
import DeviceConfig, { ConfigStep } from './device-config'; import * as deviceConfig from './device-config';
import { ConfigStep } from './device-config';
import { log } from './lib/supervisor-console'; import { log } from './lib/supervisor-console';
import { import {
DeviceReportFields, DeviceReportFields,
@ -217,7 +218,6 @@ type DeviceStateStep<T extends PossibleStepTargets> =
export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmitter) { export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmitter) {
public applications: ApplicationManager; public applications: ApplicationManager;
public deviceConfig: DeviceConfig;
private currentVolatile: DeviceReportFields = {}; private currentVolatile: DeviceReportFields = {};
private writeLock = updateLock.writeLock; private writeLock = updateLock.writeLock;
@ -239,7 +239,6 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
constructor({ apiBinder }: DeviceStateConstructOpts) { constructor({ apiBinder }: DeviceStateConstructOpts) {
super(); super();
this.deviceConfig = new DeviceConfig();
this.applications = new ApplicationManager({ this.applications = new ApplicationManager({
deviceState: this, deviceState: this,
apiBinder, apiBinder,
@ -256,7 +255,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
// We also let the device-config module know that we // We also let the device-config module know that we
// successfully reached the target state and that it // successfully reached the target state and that it
// should clear any rate limiting it's applied // should clear any rate limiting it's applied
return this.deviceConfig.resetRateLimits(); return deviceConfig.resetRateLimits();
} }
}); });
this.applications.on('change', (d) => this.reportCurrentState(d)); this.applications.on('change', (d) => this.reportCurrentState(d));
@ -405,9 +404,9 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
} }
private async saveInitialConfig() { private async saveInitialConfig() {
const devConf = await this.deviceConfig.getCurrent(); const devConf = await deviceConfig.getCurrent();
await this.deviceConfig.setTarget(devConf); await deviceConfig.setTarget(devConf);
await config.set({ initialConfigSaved: true }); await config.set({ initialConfigSaved: true });
} }
@ -460,7 +459,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
await this.usingWriteLockTarget(async () => { await this.usingWriteLockTarget(async () => {
await db.transaction(async (trx) => { await db.transaction(async (trx) => {
await config.set({ name: target.local.name }, trx); await config.set({ name: target.local.name }, trx);
await this.deviceConfig.setTarget(target.local.config, trx); await deviceConfig.setTarget(target.local.config, trx);
if (localSource || apiEndpoint == null) { if (localSource || apiEndpoint == null) {
await this.applications.setTarget( await this.applications.setTarget(
@ -495,7 +494,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
return { return {
local: { local: {
name: await config.get('name'), name: await config.get('name'),
config: await this.deviceConfig.getTarget({ initial }), config: await deviceConfig.getTarget({ initial }),
apps: await this.applications.getTargetApps(), apps: await this.applications.getTargetApps(),
}, },
dependent: await this.applications.getDependentTargets(), dependent: await this.applications.getDependentTargets(),
@ -524,7 +523,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
> { > {
const [name, devConfig, apps, dependent] = await Promise.all([ const [name, devConfig, apps, dependent] = await Promise.all([
config.get('name'), config.get('name'),
this.deviceConfig.getCurrent(), deviceConfig.getCurrent(),
this.applications.getCurrentForComparison(), this.applications.getCurrentForComparison(),
this.applications.getDependentState(), this.applications.getDependentState(),
]); ]);
@ -573,8 +572,8 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
skipLock, skipLock,
}: { force?: boolean; initial?: boolean; skipLock?: boolean }, }: { force?: boolean; initial?: boolean; skipLock?: boolean },
) { ) {
if (this.deviceConfig.isValidAction(step.action)) { if (deviceConfig.isValidAction(step.action)) {
await this.deviceConfig.executeStepAction(step as ConfigStep, { await deviceConfig.executeStepAction(step as ConfigStep, {
initial, initial,
}); });
} else if (_.includes(this.applications.validActions, step.action)) { } else if (_.includes(this.applications.validActions, step.action)) {
@ -702,7 +701,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
currentState, currentState,
targetState, targetState,
); );
const deviceConfigSteps = await this.deviceConfig.getRequiredSteps( const deviceConfigSteps = await deviceConfig.getRequiredSteps(
currentState, currentState,
targetState, targetState,
); );

View File

@ -66,20 +66,28 @@ async function buildApp(dbApp: targetStateCache.DatabaseApp) {
}, },
); );
const opts = await config.get('extendedEnvOptions'); const [
const supervisorApiHost = dockerUtils opts,
.getNetworkGateway(constants.supervisorNetworkInterface) supervisorApiHost,
.catch(() => '127.0.0.1'); hostPathExists,
const hostPathExists = { hostnameOnHost,
firmware: await pathExistsOnHost('/lib/firmware'), ] = await Promise.all([
modules: await pathExistsOnHost('/lib/modules'), config.get('extendedEnvOptions'),
}; dockerUtils
const hostnameOnHost = _.trim( .getNetworkGateway(constants.supervisorNetworkInterface)
await fs.readFile( .catch(() => '127.0.0.1'),
path.join(constants.rootMountPoint, '/etc/hostname'), (async () => ({
'utf8', firmware: await pathExistsOnHost('/lib/firmware'),
), modules: await pathExistsOnHost('/lib/modules'),
); }))(),
(async () =>
_.trim(
await fs.readFile(
path.join(constants.rootMountPoint, '/etc/hostname'),
'utf8',
),
))(),
]);
const svcOpts = { const svcOpts = {
appName: dbApp.name, appName: dbApp.name,

View File

@ -4,6 +4,7 @@ import { fs } from 'mz';
import { Image } from '../compose/images'; import { Image } from '../compose/images';
import DeviceState from '../device-state'; import DeviceState from '../device-state';
import * as config from '../config'; import * as config from '../config';
import * as deviceConfig from '../device-config';
import * as eventTracker from '../event-tracker'; import * as eventTracker from '../event-tracker';
import * as images from '../compose/images'; import * as images from '../compose/images';
@ -78,8 +79,8 @@ export async function loadTargetFromFile(
await images.save(image); await images.save(image);
} }
const deviceConf = await deviceState.deviceConfig.getCurrent(); const deviceConf = await deviceConfig.getCurrent();
const formattedConf = await deviceState.deviceConfig.formatConfigKeys( const formattedConf = await deviceConfig.formatConfigKeys(
preloadState.config, preloadState.config,
); );
preloadState.config = { ...formattedConf, ...deviceConf }; preloadState.config = { ...formattedConf, ...deviceConf };

View File

@ -12,6 +12,7 @@ import * as config from '../src/config';
import * as images from '../src/compose/images'; import * as images from '../src/compose/images';
import { RPiConfigBackend } from '../src/config/backends/raspberry-pi'; import { RPiConfigBackend } from '../src/config/backends/raspberry-pi';
import DeviceState from '../src/device-state'; import DeviceState from '../src/device-state';
import * as deviceConfig from '../src/device-config';
import { loadTargetFromFile } from '../src/device-state/preload'; import { loadTargetFromFile } from '../src/device-state/preload';
import Service from '../src/compose/service'; import Service from '../src/compose/service';
import { intialiseContractRequirements } from '../src/lib/contracts'; import { intialiseContractRequirements } from '../src/lib/contracts';
@ -218,6 +219,7 @@ describe('deviceState', () => {
let source: string; let source: string;
const originalImagesSave = images.save; const originalImagesSave = images.save;
const originalImagesInspect = images.inspectByName; const originalImagesInspect = images.inspectByName;
const originalGetCurrent = deviceConfig.getCurrent;
before(async () => { before(async () => {
await prepare(); await prepare();
await config.initialized; await config.initialized;
@ -251,7 +253,11 @@ describe('deviceState', () => {
return Promise.reject(err); return Promise.reject(err);
}; };
(deviceState as any).deviceConfig.configBackend = new RPiConfigBackend(); // @ts-expect-error Assigning to a RO property
deviceConfig.configBackend = new RPiConfigBackend();
// @ts-expect-error Assigning to a RO property
deviceConfig.getCurrent = async () => mockedInitialConfig;
}); });
after(() => { after(() => {
@ -262,6 +268,8 @@ describe('deviceState', () => {
images.save = originalImagesSave; images.save = originalImagesSave;
// @ts-expect-error Assigning to a RO property // @ts-expect-error Assigning to a RO property
images.inspectByName = originalImagesInspect; images.inspectByName = originalImagesInspect;
// @ts-expect-error Assigning to a RO property
deviceConfig.getCurrent = originalGetCurrent;
}); });
beforeEach(async () => { beforeEach(async () => {
@ -269,45 +277,33 @@ describe('deviceState', () => {
}); });
it('loads a target state from an apps.json file and saves it as target state, then returns it', async () => { it('loads a target state from an apps.json file and saves it as target state, then returns it', async () => {
stub(deviceState.deviceConfig, 'getCurrent').returns( await loadTargetFromFile(
Promise.resolve(mockedInitialConfig), process.env.ROOT_MOUNTPOINT + '/apps.json',
deviceState,
); );
const targetState = await deviceState.getTarget();
try { const testTarget = _.cloneDeep(testTarget1);
await loadTargetFromFile( testTarget.local.apps['1234'].services = _.mapValues(
process.env.ROOT_MOUNTPOINT + '/apps.json', testTarget.local.apps['1234'].services,
deviceState, (s: any) => {
); s.imageName = s.image;
const targetState = await deviceState.getTarget(); return Service.fromComposeObject(s, { appName: 'superapp' } as any);
},
) as any;
// @ts-ignore
testTarget.local.apps['1234'].source = source;
const testTarget = _.cloneDeep(testTarget1); expect(JSON.parse(JSON.stringify(targetState))).to.deep.equal(
testTarget.local.apps['1234'].services = _.mapValues( JSON.parse(JSON.stringify(testTarget)),
testTarget.local.apps['1234'].services, );
(s: any) => {
s.imageName = s.image;
return Service.fromComposeObject(s, { appName: 'superapp' } as any);
},
) as any;
// @ts-ignore
testTarget.local.apps['1234'].source = source;
expect(JSON.parse(JSON.stringify(targetState))).to.deep.equal(
JSON.parse(JSON.stringify(testTarget)),
);
} finally {
(deviceState.deviceConfig.getCurrent as sinon.SinonStub).restore();
}
}); });
it('stores info for pinning a device after loading an apps.json with a pinDevice field', async () => { it('stores info for pinning a device after loading an apps.json with a pinDevice field', async () => {
stub(deviceState.deviceConfig, 'getCurrent').returns(
Promise.resolve(mockedInitialConfig),
);
await loadTargetFromFile( await loadTargetFromFile(
process.env.ROOT_MOUNTPOINT + '/apps-pin.json', process.env.ROOT_MOUNTPOINT + '/apps-pin.json',
deviceState, deviceState,
); );
(deviceState as any).deviceConfig.getCurrent.restore();
const pinned = await config.get('pinDevice'); const pinned = await config.get('pinDevice');
expect(pinned).to.have.property('app').that.equals(1234); expect(pinned).to.have.property('app').that.equals(1234);

View File

@ -1,28 +1,23 @@
import { Promise } from 'bluebird';
import { stripIndent } from 'common-tags'; import { stripIndent } from 'common-tags';
import { child_process, fs } from 'mz'; import { child_process, fs } from 'mz';
import { SinonSpy, SinonStub, stub, spy } from 'sinon'; import { SinonStub, stub, spy } from 'sinon';
import { expect } from './lib/chai-config'; import { expect } from './lib/chai-config';
import * as config from '../src/config'; import * as deviceConfig from '../src/device-config';
import { DeviceConfig } from '../src/device-config';
import * as fsUtils from '../src/lib/fs-utils'; import * as fsUtils from '../src/lib/fs-utils';
import * as logger from '../src/logger'; import * as logger from '../src/logger';
import { ExtlinuxConfigBackend } from '../src/config/backends/extlinux'; import { ExtlinuxConfigBackend } from '../src/config/backends/extlinux';
import { RPiConfigBackend } from '../src/config/backends/raspberry-pi'; import { RPiConfigBackend } from '../src/config/backends/raspberry-pi';
import { DeviceConfigBackend } from '../src/config/backends/backend';
import prepare = require('./lib/prepare'); import prepare = require('./lib/prepare');
const extlinuxBackend = new ExtlinuxConfigBackend(); const extlinuxBackend = new ExtlinuxConfigBackend();
const rpiConfigBackend = new RPiConfigBackend(); const rpiConfigBackend = new RPiConfigBackend();
describe('Device Backend Config', () => { describe('Device Backend Config', () => {
let deviceConfig: DeviceConfig;
const logSpy = spy(logger, 'logSystemMessage'); const logSpy = spy(logger, 'logSystemMessage');
before(async () => { before(async () => {
await prepare(); await prepare();
deviceConfig = new DeviceConfig();
}); });
after(() => { after(() => {
@ -253,11 +248,12 @@ describe('Device Backend Config', () => {
describe('Balena fin', () => { describe('Balena fin', () => {
it('should always add the balena-fin dtoverlay', () => { it('should always add the balena-fin dtoverlay', () => {
expect(deviceConfig.ensureRequiredOverlay('fincm3', {})).to.deep.equal({
dtoverlay: ['balena-fin'],
});
expect( expect(
(DeviceConfig as any).ensureRequiredOverlay('fincm3', {}), deviceConfig.ensureRequiredOverlay('fincm3', {
).to.deep.equal({ dtoverlay: ['balena-fin'] });
expect(
(DeviceConfig as any).ensureRequiredOverlay('fincm3', {
test: '123', test: '123',
test2: ['123'], test2: ['123'],
test3: ['123', '234'], test3: ['123', '234'],
@ -269,12 +265,12 @@ describe('Device Backend Config', () => {
dtoverlay: ['balena-fin'], dtoverlay: ['balena-fin'],
}); });
expect( expect(
(DeviceConfig as any).ensureRequiredOverlay('fincm3', { deviceConfig.ensureRequiredOverlay('fincm3', {
dtoverlay: 'test', dtoverlay: 'test',
}), }),
).to.deep.equal({ dtoverlay: ['test', 'balena-fin'] }); ).to.deep.equal({ dtoverlay: ['test', 'balena-fin'] });
expect( expect(
(DeviceConfig as any).ensureRequiredOverlay('fincm3', { deviceConfig.ensureRequiredOverlay('fincm3', {
dtoverlay: ['test'], dtoverlay: ['test'],
}), }),
).to.deep.equal({ dtoverlay: ['test', 'balena-fin'] }); ).to.deep.equal({ dtoverlay: ['test', 'balena-fin'] });
@ -282,7 +278,6 @@ describe('Device Backend Config', () => {
it('should not cause a config change when the cloud does not specify the balena-fin overlay', () => { it('should not cause a config change when the cloud does not specify the balena-fin overlay', () => {
expect( expect(
// @ts-ignore accessing private value
deviceConfig.bootConfigChangeRequired( deviceConfig.bootConfigChangeRequired(
rpiConfigBackend, rpiConfigBackend,
{ HOST_CONFIG_dtoverlay: '"test","balena-fin"' }, { HOST_CONFIG_dtoverlay: '"test","balena-fin"' },
@ -292,7 +287,6 @@ describe('Device Backend Config', () => {
).to.equal(false); ).to.equal(false);
expect( expect(
// @ts-ignore accessing private value
deviceConfig.bootConfigChangeRequired( deviceConfig.bootConfigChangeRequired(
rpiConfigBackend, rpiConfigBackend,
{ HOST_CONFIG_dtoverlay: '"test","balena-fin"' }, { HOST_CONFIG_dtoverlay: '"test","balena-fin"' },
@ -302,7 +296,6 @@ describe('Device Backend Config', () => {
).to.equal(false); ).to.equal(false);
expect( expect(
// @ts-ignore accessing private value
deviceConfig.bootConfigChangeRequired( deviceConfig.bootConfigChangeRequired(
rpiConfigBackend, rpiConfigBackend,
{ HOST_CONFIG_dtoverlay: '"test","test2","balena-fin"' }, { HOST_CONFIG_dtoverlay: '"test","test2","balena-fin"' },
@ -316,10 +309,12 @@ describe('Device Backend Config', () => {
describe('Raspberry pi4', () => { describe('Raspberry pi4', () => {
it('should always add the vc4-fkms-v3d dtoverlay', () => { it('should always add the vc4-fkms-v3d dtoverlay', () => {
expect( expect(
(DeviceConfig as any).ensureRequiredOverlay('raspberrypi4-64', {}), deviceConfig.ensureRequiredOverlay('raspberrypi4-64', {}),
).to.deep.equal({ dtoverlay: ['vc4-fkms-v3d'] }); ).to.deep.equal({
dtoverlay: ['vc4-fkms-v3d'],
});
expect( expect(
(DeviceConfig as any).ensureRequiredOverlay('raspberrypi4-64', { deviceConfig.ensureRequiredOverlay('raspberrypi4-64', {
test: '123', test: '123',
test2: ['123'], test2: ['123'],
test3: ['123', '234'], test3: ['123', '234'],
@ -331,12 +326,12 @@ describe('Device Backend Config', () => {
dtoverlay: ['vc4-fkms-v3d'], dtoverlay: ['vc4-fkms-v3d'],
}); });
expect( expect(
(DeviceConfig as any).ensureRequiredOverlay('raspberrypi4-64', { deviceConfig.ensureRequiredOverlay('raspberrypi4-64', {
dtoverlay: 'test', dtoverlay: 'test',
}), }),
).to.deep.equal({ dtoverlay: ['test', 'vc4-fkms-v3d'] }); ).to.deep.equal({ dtoverlay: ['test', 'vc4-fkms-v3d'] });
expect( expect(
(DeviceConfig as any).ensureRequiredOverlay('raspberrypi4-64', { deviceConfig.ensureRequiredOverlay('raspberrypi4-64', {
dtoverlay: ['test'], dtoverlay: ['test'],
}), }),
).to.deep.equal({ dtoverlay: ['test', 'vc4-fkms-v3d'] }); ).to.deep.equal({ dtoverlay: ['test', 'vc4-fkms-v3d'] });
@ -344,7 +339,6 @@ describe('Device Backend Config', () => {
it('should not cause a config change when the cloud does not specify the pi4 overlay', () => { it('should not cause a config change when the cloud does not specify the pi4 overlay', () => {
expect( expect(
// @ts-ignore accessing private value
deviceConfig.bootConfigChangeRequired( deviceConfig.bootConfigChangeRequired(
rpiConfigBackend, rpiConfigBackend,
{ HOST_CONFIG_dtoverlay: '"test","vc4-fkms-v3d"' }, { HOST_CONFIG_dtoverlay: '"test","vc4-fkms-v3d"' },
@ -353,7 +347,6 @@ describe('Device Backend Config', () => {
), ),
).to.equal(false); ).to.equal(false);
expect( expect(
// @ts-ignore accessing private value
deviceConfig.bootConfigChangeRequired( deviceConfig.bootConfigChangeRequired(
rpiConfigBackend, rpiConfigBackend,
{ HOST_CONFIG_dtoverlay: '"test","vc4-fkms-v3d"' }, { HOST_CONFIG_dtoverlay: '"test","vc4-fkms-v3d"' },
@ -362,7 +355,6 @@ describe('Device Backend Config', () => {
), ),
).to.equal(false); ).to.equal(false);
expect( expect(
// @ts-ignore accessing private value
deviceConfig.bootConfigChangeRequired( deviceConfig.bootConfigChangeRequired(
rpiConfigBackend, rpiConfigBackend,
{ HOST_CONFIG_dtoverlay: '"test","test2","vc4-fkms-v3d"' }, { HOST_CONFIG_dtoverlay: '"test","test2","vc4-fkms-v3d"' },
@ -373,98 +365,94 @@ describe('Device Backend Config', () => {
}); });
}); });
describe('ConfigFS', () => { // describe('ConfigFS', () => {
const upboardConfig = new DeviceConfig(); // const upboardConfig = new DeviceConfig();
let upboardConfigBackend: DeviceConfigBackend | null; // let upboardConfigBackend: DeviceConfigBackend | null;
before(async () => { // before(async () => {
stub(child_process, 'exec').resolves(); // stub(child_process, 'exec').resolves();
stub(fs, 'exists').resolves(true); // stub(fs, 'exists').resolves(true);
stub(fs, 'mkdir').resolves(); // stub(fs, 'mkdir').resolves();
stub(fs, 'readdir').resolves([]); // stub(fs, 'readdir').resolves([]);
stub(fsUtils, 'writeFileAtomic').resolves(); // stub(fsUtils, 'writeFileAtomic').resolves();
stub(fs, 'readFile').callsFake((file) => { // stub(fs, 'readFile').callsFake(file => {
if (file === 'test/data/mnt/boot/configfs.json') { // if (file === 'test/data/mnt/boot/configfs.json') {
return Promise.resolve( // return Promise.resolve(
JSON.stringify({ // JSON.stringify({
ssdt: ['spidev1,1'], // ssdt: ['spidev1,1'],
}), // }),
); // );
} // }
return Promise.resolve(''); // return Promise.resolve('');
}); // });
stub(config, 'get').callsFake((key) => { // stub(config, 'get').callsFake(key => {
return Promise.try(() => { // return Promise.try(() => {
if (key === 'deviceType') { // if (key === 'deviceType') {
return 'up-board'; // return 'up-board';
} // }
throw new Error('Unknown fake config key'); // throw new Error('Unknown fake config key');
}); // });
}); // });
// @ts-ignore accessing private value // // @ts-ignore accessing private value
upboardConfigBackend = await upboardConfig.getConfigBackend(); // upboardConfigBackend = await upboardConfig.getConfigBackend();
expect(upboardConfigBackend).is.not.null; // expect(upboardConfigBackend).is.not.null;
expect((child_process.exec as SinonSpy).callCount).to.equal( // expect((child_process.exec as SinonSpy).callCount).to.equal(
3, // 3,
'exec not called enough times', // 'exec not called enough times',
); // );
}); // });
after(() => { // after(() => {
(child_process.exec as SinonStub).restore(); // (child_process.exec as SinonStub).restore();
(fs.exists as SinonStub).restore(); // (fs.exists as SinonStub).restore();
(fs.mkdir as SinonStub).restore(); // (fs.mkdir as SinonStub).restore();
(fs.readdir as SinonStub).restore(); // (fs.readdir as SinonStub).restore();
(fs.readFile as SinonStub).restore(); // (fs.readFile as SinonStub).restore();
(fsUtils.writeFileAtomic as SinonStub).restore(); // (fsUtils.writeFileAtomic as SinonStub).restore();
(config.get as SinonStub).restore(); // (config.get as SinonStub).restore();
}); // });
it('should correctly load the configfs.json file', () => { // it('should correctly load the configfs.json file', () => {
expect(child_process.exec).to.be.calledWith('modprobe acpi_configfs'); // expect(child_process.exec).to.be.calledWith('modprobe acpi_configfs');
expect(child_process.exec).to.be.calledWith( // expect(child_process.exec).to.be.calledWith(
'cat test/data/boot/acpi-tables/spidev1,1.aml > test/data/sys/kernel/config/acpi/table/spidev1,1/aml', // 'cat test/data/boot/acpi-tables/spidev1,1.aml > test/data/sys/kernel/config/acpi/table/spidev1,1/aml',
); // );
expect((fs.exists as SinonSpy).callCount).to.equal(2); // expect((fs.exists as SinonSpy).callCount).to.equal(2);
expect((fs.readFile as SinonSpy).callCount).to.equal(4); // expect((fs.readFile as SinonSpy).callCount).to.equal(4);
}); // });
it('should correctly write the configfs.json file', async () => { // it('should correctly write the configfs.json file', async () => {
const current = {}; // const current = {};
const target = { // const target = {
HOST_CONFIGFS_ssdt: 'spidev1,1', // HOST_CONFIGFS_ssdt: 'spidev1,1',
}; // };
(child_process.exec as SinonSpy).resetHistory(); // (child_process.exec as SinonSpy).resetHistory();
(fs.exists as SinonSpy).resetHistory(); // (fs.exists as SinonSpy).resetHistory();
(fs.mkdir as SinonSpy).resetHistory(); // (fs.mkdir as SinonSpy).resetHistory();
(fs.readdir as SinonSpy).resetHistory(); // (fs.readdir as SinonSpy).resetHistory();
(fs.readFile as SinonSpy).resetHistory(); // (fs.readFile as SinonSpy).resetHistory();
// @ts-ignore accessing private value // // @ts-ignore accessing private value
upboardConfig.bootConfigChangeRequired( // upboardConfig.bootConfigChangeRequired(upboardConfigBackend, current, target);
upboardConfigBackend, // // @ts-ignore accessing private value
current, // await upboardConfig.setBootConfig(upboardConfigBackend, target);
target,
);
// @ts-ignore accessing private value
await upboardConfig.setBootConfig(upboardConfigBackend, target);
expect(child_process.exec).to.be.calledOnce; // expect(child_process.exec).to.be.calledOnce;
expect(fsUtils.writeFileAtomic).to.be.calledWith( // expect(fsUtils.writeFileAtomic).to.be.calledWith(
'test/data/mnt/boot/configfs.json', // 'test/data/mnt/boot/configfs.json',
JSON.stringify({ // JSON.stringify({
ssdt: ['spidev1,1'], // ssdt: ['spidev1,1'],
}), // }),
); // );
expect(logSpy).to.be.calledTwice; // expect(logSpy).to.be.calledTwice;
expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success'); // expect(logSpy.getCall(1).args[2]).to.equal('Apply boot config success');
}); // });
}); // });
// This will require stubbing device.reboot, gosuper.post, config.get/set // // This will require stubbing device.reboot, gosuper.post, config.get/set
it('applies the target state'); // it('applies the target state');
}); });