Merge pull request #1262 from balena-io/1049-no-dbus-native

Move from dbus-native to dbus
This commit is contained in:
CameronDiver 2020-04-13 11:21:01 +01:00 committed by GitHub
commit 96a54af21c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 835 additions and 350 deletions

View File

@ -5,7 +5,7 @@
"*.ts": [
"balena-lint --typescript --fix",
],
"*!(*webpack.config).js": [
"*.js": [
"balena-lint --typescript --fix",
],
"test/**/*.coffee": [

View File

@ -32,7 +32,8 @@ RUN apk add --no-cache \
libstdc++ \
libuv \
sqlite-libs \
sqlite-dev
sqlite-dev \
dbus-dev
COPY build-conf/node-sums.txt .

View File

@ -58,9 +58,14 @@ version: 2
jobs:
generic:
docker:
- image: library/node:10
- image: balenalib/amd64-alpine-node:10
steps:
- checkout
- run:
name: Install dependencies
command: |
apk add dbus-dev python make \
gcc libgcc libc-dev g++
- run:
name: Run tests
command: |

848
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@
},
"private": true,
"dependencies": {
"dbus": "^1.0.7",
"sqlite3": "^4.1.1"
},
"engines": {
@ -42,6 +43,7 @@
"@types/chai": "^4.2.11",
"@types/chai-as-promised": "^7.1.2",
"@types/common-tags": "^1.8.0",
"@types/dbus": "^1.0.0",
"@types/dockerode": "^2.5.25",
"@types/event-stream": "^3.3.34",
"@types/express": "^4.17.3",

View File

@ -8,8 +8,8 @@ import Logger from './logger';
import { ConfigOptions, DeviceConfigBackend } from './config/backend';
import * as configUtils from './config/utils';
import * as dbus from './lib/dbus';
import { UnitNotLoadedError } from './lib/errors';
import * as systemd from './lib/systemd';
import { EnvVarObject } from './lib/types';
import { checkInt, checkTruthy } from './lib/validation';
import { DeviceStatus } from './types/state';
@ -573,7 +573,7 @@ export class DeviceConfig {
private async getVPNEnabled(): Promise<boolean> {
try {
const activeState = await systemd.serviceActiveState(vpnServiceName);
const activeState = await dbus.serviceActiveState(vpnServiceName);
return !_.includes(['inactive', 'deactivating'], activeState);
} catch (e) {
if (UnitNotLoadedError(e)) {
@ -588,9 +588,9 @@ export class DeviceConfig {
const enable = v != null ? v : true;
if (enable) {
await systemd.startService(vpnServiceName);
await dbus.startService(vpnServiceName);
} else {
await systemd.stopService(vpnServiceName);
await dbus.stopService(vpnServiceName);
}
}

View File

@ -20,8 +20,8 @@ import { loadTargetFromFile } from './device-state/preload';
import * as globalEventBus from './event-bus';
import * as hostConfig from './host-config';
import constants = require('./lib/constants');
import * as dbus from './lib/dbus';
import { InternalInconsistencyError, UpdatesLockedError } from './lib/errors';
import * as systemd from './lib/systemd';
import * as updateLock from './lib/update-lock';
import * as validation from './lib/validation';
import * as network from './network';
@ -555,7 +555,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
private async reboot(force?: boolean, skipLock?: boolean) {
await this.applications.stopAll({ force, skipLock });
this.logger.logSystemMessage('Rebooting', {}, 'Reboot');
const reboot = await systemd.reboot();
const reboot = await dbus.reboot();
this.shuttingDown = true;
this.emitAsync('shutdown', undefined);
return reboot;
@ -564,7 +564,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
private async shutdown(force?: boolean, skipLock?: boolean) {
await this.applications.stopAll({ force, skipLock });
this.logger.logSystemMessage('Shutting down', {}, 'Shutdown');
const shutdown = await systemd.shutdown();
const shutdown = await dbus.shutdown();
this.shuttingDown = true;
this.emitAsync('shutdown', undefined);
return shutdown;

View File

@ -7,9 +7,9 @@ import * as path from 'path';
import Config from './config';
import * as constants from './lib/constants';
import * as dbus from './lib/dbus';
import { ENOENT } from './lib/errors';
import { writeFileAtomic } from './lib/fs-utils';
import * as systemd from './lib/systemd';
const mkdirp = Bluebird.promisify(mkdirCb) as (
path: string,
@ -140,8 +140,8 @@ async function setProxy(maybeConf: ProxyConfig | null): Promise<void> {
await writeFileAtomic(redsocksConfPath, redsocksConf);
}
await systemd.restartService('resin-proxy-config');
await systemd.restartService('redsocks');
await dbus.restartService('resin-proxy-config');
await dbus.restartService('redsocks');
}
const hostnamePath = path.join(constants.rootMountPoint, '/etc/hostname');
@ -152,7 +152,7 @@ async function readHostname() {
async function setHostname(val: string, configModel: Config) {
await configModel.set({ hostname: val });
await systemd.restartService('resin-hostname');
await dbus.restartService('resin-hostname');
}
// Don't use async/await here to maintain the bluebird

132
src/lib/dbus.ts Normal file
View File

@ -0,0 +1,132 @@
import * as Bluebird from 'bluebird';
import * as dbus from 'dbus';
import TypedError = require('typed-error');
import log from './supervisor-console';
export class DbusError extends TypedError {}
const bus = dbus.getBus('system');
const getInterfaceAsync = Bluebird.promisify(bus.getInterface, {
context: bus,
});
async function getSystemdInterface() {
try {
return await getInterfaceAsync(
'org.freedesktop.systemd1',
'/org/freedesktop/systemd1',
'org.freedesktop.systemd1.Manager',
);
} catch (e) {
throw new DbusError(e);
}
}
export async function getLoginManagerInterface() {
try {
return await getInterfaceAsync(
'org.freedesktop.login1',
'/org/freedesktop/login1',
'org.freedesktop.login1.Manager',
);
} catch (e) {
throw new DbusError(e);
}
}
export async function restartService(serviceName: string) {
const systemd = await getSystemdInterface();
try {
systemd.RestartUnit(`${serviceName}.service`, 'fail');
} catch (e) {
throw new DbusError(e);
}
}
export async function startService(serviceName: string) {
const systemd = await getSystemdInterface();
try {
systemd.StartUnit(`${serviceName}.service`, 'fail');
} catch (e) {
throw new DbusError(e);
}
}
export async function stopService(serviceName: string) {
const systemd = await getSystemdInterface();
try {
systemd.StopUnit(`${serviceName}.service`, 'fail');
} catch (e) {
throw new DbusError(e);
}
}
export async function enableService(serviceName: string) {
const systemd = await getSystemdInterface();
try {
systemd.EnableUnitFiles([`${serviceName}.service`], false, false);
} catch (e) {
throw new DbusError(e);
}
}
export async function disableService(serviceName: string) {
const systemd = await getSystemdInterface();
try {
systemd.DisableUnitFiles([`${serviceName}.service`], false);
} catch (e) {
throw new DbusError(e);
}
}
export const reboot = async () =>
setTimeout(async () => {
try {
const logind = await getLoginManagerInterface();
logind.Reboot(false);
} catch (e) {
log.error(`Unable to reboot: ${e}`);
}
}, 1000);
export const shutdown = async () =>
setTimeout(async () => {
try {
const logind = await getLoginManagerInterface();
logind.PowerOff(false);
} catch (e) {
log.error(`Unable to shutdown: ${e}`);
}
}, 1000);
async function getUnitProperty(unitName: string, property: string) {
const systemd = await getSystemdInterface();
return new Promise((resolve, reject) => {
systemd.GetUnit(unitName, async (err: Error, unitPath: string) => {
if (err) {
return reject(err);
}
const iface = await getInterfaceAsync(
'org.freedesktop.systemd1',
unitPath,
'org.freedesktop.DBus.Properties',
);
iface.Get(
'org.freedesktop.systemd1.Unit',
property,
(e: Error, value: unknown) => {
if (e) {
return reject(new DbusError(e));
}
resolve(value);
},
);
});
});
}
export function serviceActiveState(serviceName: string) {
return getUnitProperty(`${serviceName}.service`, 'ActiveState');
}

View File

@ -1,115 +0,0 @@
import * as Bluebird from 'bluebird';
import * as dbus from 'dbus-native';
const bus = dbus.systemBus();
const invokeAsync = Bluebird.promisify(bus.invoke).bind(bus);
const defaultPathInfo = {
path: '/org/freedesktop/systemd1',
destination: 'org.freedesktop.systemd1',
interface: 'org.freedesktop.systemd1.Manager',
};
const systemdManagerMethodCall = (
method: string,
signature?: string,
body?: any[],
pathInfo: {
path: string;
destination: string;
interface: string;
} = defaultPathInfo,
) => {
if (signature == null) {
signature = '';
}
if (body == null) {
body = [];
}
return invokeAsync({
...pathInfo,
member: method,
signature,
body,
});
};
export function restartService(serviceName: string) {
return systemdManagerMethodCall('RestartUnit', 'ss', [
`${serviceName}.service`,
'fail',
]);
}
export function startService(serviceName: string) {
return systemdManagerMethodCall('StartUnit', 'ss', [
`${serviceName}.service`,
'fail',
]);
}
export function stopService(serviceName: string) {
return systemdManagerMethodCall('StopUnit', 'ss', [
`${serviceName}.service`,
'fail',
]);
}
export function enableService(serviceName: string) {
return systemdManagerMethodCall('EnableUnitFiles', 'asbb', [
[`${serviceName}.service`],
false,
false,
]);
}
export function disableService(serviceName: string) {
return systemdManagerMethodCall('DisableUnitFiles', 'asb', [
[`${serviceName}.service`],
false,
]);
}
export const reboot = Bluebird.method(() =>
setTimeout(
() =>
systemdManagerMethodCall('Reboot', 'b', [false], {
path: '/org/freedesktop/login1',
destination: 'org.freedesktop.login1',
interface: 'org.freedesktop.login1.Manager',
}),
1000,
),
);
export const shutdown = Bluebird.method(() =>
setTimeout(
() =>
systemdManagerMethodCall('PowerOff', 'b', [false], {
path: '/org/freedesktop/login1',
destination: 'org.freedesktop.login1',
interface: 'org.freedesktop.login1.Manager',
}),
1000,
),
);
function getUnitProperty(unitName: string, property: string) {
return systemdManagerMethodCall('GetUnit', 's', [unitName])
.then((unitPath: string) =>
invokeAsync({
path: unitPath,
destination: 'org.freedesktop.systemd1',
interface: 'org.freedesktop.DBus.Properties',
member: 'Get',
signature: 'ss',
body: ['org.freedesktop.systemd1.Unit', property],
}),
)
.get(1)
.get(0);
}
export function serviceActiveState(serviceName: string) {
return getUnitProperty(`${serviceName}.service`, 'ActiveState');
}

View File

@ -8,11 +8,28 @@ process.env.LED_FILE = './test/data/led_file';
import { stub } from 'sinon';
import dbus = require('dbus-native');
import * as dbus from 'dbus';
stub(dbus, 'systemBus').returns(({
invoke(obj: unknown, cb: () => void) {
console.log(obj);
return cb();
// Stub the dbus objects to dynamically generate the methods
// on request
stub(dbus, 'getBus').returns({
getInterface: (
_obj: unknown,
cb: (err: Error | undefined, iface: dbus.DBusInterface) => void,
) => {
return cb(
undefined,
new Proxy(
{},
{
get(_target, name) {
console.log(`Dbus method ${String(name)} requested`);
return () => {
/* noop */
};
},
},
) as any,
);
},
} as unknown) as dbus.MessageBus);
} as any);

View File

@ -19,6 +19,7 @@ var externalModules = [
'oracledb',
'pg-query-stream',
'tedious',
'dbus',
/mssql\/.*/,
];
@ -35,19 +36,19 @@ lookForOptionalDeps = function(sourceDir) {
}
try {
packageJson = JSON.parse(
fs.readFileSync(path.join(sourceDir, dir, '/package.json'))
fs.readFileSync(path.join(sourceDir, dir, '/package.json')),
);
} catch (e) {
continue;
}
if (packageJson.optionalDependencies != null) {
maybeOptionalModules = maybeOptionalModules.concat(
_.keys(packageJson.optionalDependencies)
_.keys(packageJson.optionalDependencies),
);
}
if (packageJson.dependencies != null) {
requiredModules = requiredModules.concat(
_.keys(packageJson.dependencies)
_.keys(packageJson.dependencies),
);
}
}
@ -60,8 +61,8 @@ externalModules.push(
_.reject(maybeOptionalModules, requiredModules)
.map(_.escapeRegExp)
.join('|') +
')(/.*)?$'
)
')(/.*)?$',
),
);
console.log('Using the following dependencies as external:', externalModules);
@ -88,23 +89,23 @@ module.exports = function(env) {
new TerserWebpackPlugin({
terserOptions: {
mangle: false,
keep_classnames: true
}
})
]
keep_classnames: true,
},
}),
],
},
module: {
rules: [
{
test: new RegExp(
_.escapeRegExp(path.join('knex', 'lib', 'migrate', 'index.js')) +
'$'
'$',
),
use: require.resolve('./hardcode-migrations'),
},
{
test: new RegExp(
_.escapeRegExp(path.join('JSONStream', 'index.js')) + '$'
_.escapeRegExp(path.join('JSONStream', 'index.js')) + '$',
),
use: require.resolve('./fix-jsonstream'),
},
@ -152,7 +153,7 @@ module.exports = function(env) {
]),
new webpack.ContextReplacementPlugin(
/\.\/migrations/,
path.resolve(__dirname, 'build/migrations')
path.resolve(__dirname, 'build/migrations'),
),
],
};