Christina Ying Wang 828bd22ba0 Add PowerFanConfig config backend
This config backend uses ConfigJsonConfigBackend to update
os.power and os.fan subfields under the "os" key, in order
to set power and fan configs. The expected format for os.power
and os.fan settings is:
```
{
  os: {
    power: {
      mode: string
    },
    fan: {
      profile: string
    }
  }
}
```

There may be other keys in os which are not managed by the Supervisor,
so PowerFanConfig backend doesn't read or write to them. Extra keys in os.power
and os.fan are ignored when getting boot config and removed when setting
boot config.

After this backend writes to config.json, host services os-power-mode
and os-fan-profile pick up the changes, on reboot in the former's case
and at runtime in the latter's case. The changes are applied by the host
services, which the Supervisor does not manage aside from streaming
their service logs to the dashboard.

Change-type: minor
Signed-off-by: Christina Ying Wang <christina@balena.io>
2024-12-09 18:43:51 -08:00

140 lines
3.9 KiB
TypeScript

import { expect } from 'chai';
import { testfs } from 'mocha-pod';
import * as configUtils from '~/src/config/utils';
import { ExtraUEnv } from '~/src/config/backends/extra-uEnv';
import { Extlinux } from '~/src/config/backends/extlinux';
import { ConfigTxt } from '~/src/config/backends/config-txt';
import { ConfigFs } from '~/src/config/backends/config-fs';
import { SplashImage } from '~/src/config/backends/splash-image';
import { PowerFanConfig } from '~/src/config/backends/power-fan';
import { configJsonBackend } from '~/src/config';
import type { ConfigBackend } from '~/src/config/backends/backend';
import * as hostUtils from '~/lib/host-utils';
const keys = <T extends object>(obj: T) => Object.keys(obj) as Array<keyof T>;
describe('config/utils', () => {
it('gets list of supported backends', async () => {
const tFs = await testfs({
// This is only needed so config.get doesn't fail
[hostUtils.pathOnBoot('device-type.json')]: JSON.stringify({
slug: 'raspberrypi4-64',
arch: 'aarch64',
}),
[hostUtils.pathOnBoot('splash')]: {
'balena-logo.png': Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=',
'base64',
),
},
}).enable();
// Get list of backends
const devices = await configUtils.getSupportedBackends();
expect(devices.length).to.equal(2);
expect(devices[0].constructor.name).to.equal('ConfigTxt');
expect(devices[1].constructor.name).to.equal('SplashImage');
await tFs.restore();
// TO-DO: When we have a device that will match for multiple backends
// add a test that we get more then 1 backend for that device
});
it('transforms environment variables to boot configs', () => {
keys(CONFIGS).forEach((key) => {
expect(
configUtils.envToBootConfig(BACKENDS[key], CONFIGS[key].envVars),
).to.deep.equal(CONFIGS[key].bootConfig);
});
});
it('transforms boot configs to environment variables', () => {
keys(CONFIGS).forEach((key) => {
expect(
configUtils.bootConfigToEnv(BACKENDS[key], CONFIGS[key].bootConfig),
).to.deep.equal(CONFIGS[key].envVars);
});
});
});
const BACKENDS: Record<string, ConfigBackend> = {
extraUEnv: new ExtraUEnv(),
extlinux: new Extlinux(),
configtxt: new ConfigTxt(),
configfs: new ConfigFs(),
splashImage: new SplashImage(),
powerFan: new PowerFanConfig(configJsonBackend),
};
const CONFIGS = {
extraUEnv: {
envVars: {
HOST_EXTLINUX_fdt: '/boot/mycustomdtb.dtb',
HOST_EXTLINUX_isolcpus: '1,2,3',
HOST_EXTLINUX_rootwait: '',
},
bootConfig: {
fdt: '/boot/mycustomdtb.dtb',
isolcpus: '1,2,3',
rootwait: '',
},
},
extlinux: {
envVars: {
HOST_EXTLINUX_fdt: '/boot/mycustomdtb.dtb',
HOST_EXTLINUX_isolcpus: '1,2,3',
HOST_EXTLINUX_rootwait: '',
},
bootConfig: {
fdt: '/boot/mycustomdtb.dtb',
isolcpus: '1,2,3',
rootwait: '',
},
},
configtxt: {
envVars: {
HOST_CONFIG_initramfs: 'initramf.gz 0x00800000',
HOST_CONFIG_dtparam: '"i2c=on","audio=on"',
HOST_CONFIG_dtoverlay:
'"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"',
HOST_CONFIG_foobar: 'baz',
},
bootConfig: {
initramfs: 'initramf.gz 0x00800000',
dtparam: ['i2c=on', 'audio=on'],
dtoverlay: ['ads7846', 'lirc-rpi,gpio_out_pin=17,gpio_in_pin=13'],
foobar: 'baz',
},
},
splashImage: {
envVars: {
HOST_SPLASH_image: 'data:image/png;base64,aaa',
},
bootConfig: {
image: 'data:image/png;base64,aaa',
},
},
// TO-DO: Config-FS is commented out because it behaves differently and doesn't
// add value to the Config Utilities if we make it work but would like to add it
// configfs: {
// envVars: {
// ssdt: 'spidev1,1'
// },
// bootConfig: {
// ssdt: ['spidev1,1']
// },
// },
powerFan: {
envVars: {
HOST_CONFIG_power_mode: 'low',
HOST_CONFIG_fan_profile: 'quiet',
},
bootConfig: {
power_mode: 'low',
fan_profile: 'quiet',
},
},
};