mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-25 08:21:07 +00:00
Merge pull request #1356 from balena-io/singleton-eventtracker
Make the event-tracker module a singleton
This commit is contained in:
commit
f8e48573c7
@ -10,7 +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 { EventTracker } from './event-tracker';
|
import * as eventTracker from './event-tracker';
|
||||||
import { loadBackupFromMigration } from './lib/migration';
|
import { loadBackupFromMigration } from './lib/migration';
|
||||||
|
|
||||||
import constants = require('./lib/constants');
|
import constants = require('./lib/constants');
|
||||||
@ -41,7 +41,6 @@ const INTERNAL_STATE_KEYS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export interface APIBinderConstructOpts {
|
export interface APIBinderConstructOpts {
|
||||||
eventTracker: EventTracker;
|
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +67,6 @@ export class APIBinder {
|
|||||||
public router: express.Router;
|
public router: express.Router;
|
||||||
|
|
||||||
private deviceState: DeviceState;
|
private deviceState: DeviceState;
|
||||||
private eventTracker: EventTracker;
|
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
public balenaApi: PinejsClientRequest | null = null;
|
public balenaApi: PinejsClientRequest | null = null;
|
||||||
@ -84,8 +82,7 @@ export class APIBinder {
|
|||||||
private stateReportErrors = 0;
|
private stateReportErrors = 0;
|
||||||
private readyForUpdates = false;
|
private readyForUpdates = false;
|
||||||
|
|
||||||
public constructor({ eventTracker, logger }: APIBinderConstructOpts) {
|
public constructor({ logger }: APIBinderConstructOpts) {
|
||||||
this.eventTracker = eventTracker;
|
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
|
||||||
this.router = this.createAPIBinderRouter(this);
|
this.router = this.createAPIBinderRouter(this);
|
||||||
@ -549,7 +546,7 @@ export class APIBinder {
|
|||||||
await this.report();
|
await this.report();
|
||||||
this.reportCurrentState();
|
this.reportCurrentState();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.eventTracker.track('Device state report failure', { error: e });
|
eventTracker.track('Device state report failure', { error: e });
|
||||||
// We use the poll interval as the upper limit of
|
// We use the poll interval as the upper limit of
|
||||||
// the exponential backoff
|
// the exponential backoff
|
||||||
const maxDelay = await config.get('appUpdatePollInterval');
|
const maxDelay = await config.get('appUpdatePollInterval');
|
||||||
@ -828,7 +825,7 @@ export class APIBinder {
|
|||||||
apiKey: null,
|
apiKey: null,
|
||||||
};
|
};
|
||||||
await config.set(configToUpdate);
|
await config.set(configToUpdate);
|
||||||
this.eventTracker.track('Device bootstrap success');
|
eventTracker.track('Device bootstrap success');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now check if we need to pin the device
|
// Now check if we need to pin the device
|
||||||
@ -847,11 +844,11 @@ export class APIBinder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async provisionOrRetry(retryDelay: number): Promise<void> {
|
private async provisionOrRetry(retryDelay: number): Promise<void> {
|
||||||
this.eventTracker.track('Device bootstrap');
|
eventTracker.track('Device bootstrap');
|
||||||
try {
|
try {
|
||||||
await this.provision();
|
await this.provision();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.eventTracker.track(`Device bootstrap failed, retrying`, {
|
eventTracker.track(`Device bootstrap failed, retrying`, {
|
||||||
error: e,
|
error: e,
|
||||||
delay: retryDelay,
|
delay: retryDelay,
|
||||||
});
|
});
|
||||||
@ -889,7 +886,7 @@ export class APIBinder {
|
|||||||
router.use(bodyParser.json({ limit: '10mb' }));
|
router.use(bodyParser.json({ limit: '10mb' }));
|
||||||
|
|
||||||
router.post('/v1/update', (req, res, next) => {
|
router.post('/v1/update', (req, res, next) => {
|
||||||
apiBinder.eventTracker.track('Update notification');
|
eventTracker.track('Update notification');
|
||||||
if (apiBinder.readyForUpdates) {
|
if (apiBinder.readyForUpdates) {
|
||||||
config
|
config
|
||||||
.get('instantUpdates')
|
.get('instantUpdates')
|
||||||
|
3
src/application-manager.d.ts
vendored
3
src/application-manager.d.ts
vendored
@ -4,7 +4,6 @@ import { Router } from 'express';
|
|||||||
import Knex = require('knex');
|
import Knex = require('knex');
|
||||||
|
|
||||||
import { ServiceAction } from './device-api/common';
|
import { ServiceAction } from './device-api/common';
|
||||||
import { EventTracker } from './event-tracker';
|
|
||||||
import { Logger } from './logger';
|
import { Logger } from './logger';
|
||||||
import { DeviceStatus, InstancedAppState } from './types/state';
|
import { DeviceStatus, InstancedAppState } from './types/state';
|
||||||
|
|
||||||
@ -49,7 +48,6 @@ class ApplicationManager extends EventEmitter {
|
|||||||
public _lockingIfNecessary: any;
|
public _lockingIfNecessary: any;
|
||||||
public logger: Logger;
|
public logger: Logger;
|
||||||
public deviceState: DeviceState;
|
public deviceState: DeviceState;
|
||||||
public eventTracker: EventTracker;
|
|
||||||
public apiBinder: APIBinder;
|
public apiBinder: APIBinder;
|
||||||
|
|
||||||
public services: ServiceManager;
|
public services: ServiceManager;
|
||||||
@ -67,7 +65,6 @@ class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
public constructor({
|
public constructor({
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
eventTracker: EventTracker,
|
|
||||||
deviceState: DeviceState,
|
deviceState: DeviceState,
|
||||||
apiBinder: APIBinder,
|
apiBinder: APIBinder,
|
||||||
});
|
});
|
||||||
|
@ -79,7 +79,7 @@ const createApplicationManagerRouter = function (applications) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class ApplicationManager extends EventEmitter {
|
export class ApplicationManager extends EventEmitter {
|
||||||
constructor({ logger, eventTracker, deviceState, apiBinder }) {
|
constructor({ logger, deviceState, apiBinder }) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.serviceAction = serviceAction;
|
this.serviceAction = serviceAction;
|
||||||
@ -170,7 +170,6 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
this.localModeSwitchCompletion = this.localModeSwitchCompletion.bind(this);
|
this.localModeSwitchCompletion = this.localModeSwitchCompletion.bind(this);
|
||||||
this.reportOptionalContainers = this.reportOptionalContainers.bind(this);
|
this.reportOptionalContainers = this.reportOptionalContainers.bind(this);
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.eventTracker = eventTracker;
|
|
||||||
this.deviceState = deviceState;
|
this.deviceState = deviceState;
|
||||||
this.apiBinder = apiBinder;
|
this.apiBinder = apiBinder;
|
||||||
this.images = new Images({
|
this.images = new Images({
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import * as Promise from 'bluebird';
|
import * as Promise from 'bluebird';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import * as eventTracker from '../event-tracker';
|
||||||
import * as constants from '../lib/constants';
|
import * as constants from '../lib/constants';
|
||||||
import { checkInt, checkTruthy } from '../lib/validation';
|
import { checkInt, checkTruthy } from '../lib/validation';
|
||||||
import { doRestart, doPurge, serviceAction } from './common';
|
import { doRestart, doPurge, serviceAction } from './common';
|
||||||
|
|
||||||
export const createV1Api = function (router, applications) {
|
export const createV1Api = function (router, applications) {
|
||||||
const { eventTracker } = applications;
|
|
||||||
|
|
||||||
router.post('/v1/restart', function (req, res, next) {
|
router.post('/v1/restart', function (req, res, next) {
|
||||||
const appId = checkInt(req.body.appId);
|
const appId = checkInt(req.body.appId);
|
||||||
const force = checkTruthy(req.body.force) ?? false;
|
const force = checkTruthy(req.body.force) ?? false;
|
||||||
|
@ -10,7 +10,6 @@ import prettyMs = require('pretty-ms');
|
|||||||
|
|
||||||
import * as config from './config';
|
import * as config from './config';
|
||||||
import * as db from './db';
|
import * as db from './db';
|
||||||
import EventTracker from './event-tracker';
|
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -177,7 +176,6 @@ function createDeviceStateRouter(deviceState: DeviceState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface DeviceStateConstructOpts {
|
interface DeviceStateConstructOpts {
|
||||||
eventTracker: EventTracker;
|
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
apiBinder: APIBinder;
|
apiBinder: APIBinder;
|
||||||
}
|
}
|
||||||
@ -218,7 +216,6 @@ type DeviceStateStep<T extends PossibleStepTargets> =
|
|||||||
| ConfigStep;
|
| ConfigStep;
|
||||||
|
|
||||||
export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmitter) {
|
export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmitter) {
|
||||||
public eventTracker: EventTracker;
|
|
||||||
public logger: Logger;
|
public logger: Logger;
|
||||||
|
|
||||||
public applications: ApplicationManager;
|
public applications: ApplicationManager;
|
||||||
@ -242,16 +239,14 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
|
|||||||
public connected: boolean;
|
public connected: boolean;
|
||||||
public router: express.Router;
|
public router: express.Router;
|
||||||
|
|
||||||
constructor({ eventTracker, logger, apiBinder }: DeviceStateConstructOpts) {
|
constructor({ logger, apiBinder }: DeviceStateConstructOpts) {
|
||||||
super();
|
super();
|
||||||
this.eventTracker = eventTracker;
|
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.deviceConfig = new DeviceConfig({
|
this.deviceConfig = new DeviceConfig({
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
});
|
});
|
||||||
this.applications = new ApplicationManager({
|
this.applications = new ApplicationManager({
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
eventTracker: this.eventTracker,
|
|
||||||
deviceState: this,
|
deviceState: this,
|
||||||
apiBinder,
|
apiBinder,
|
||||||
});
|
});
|
||||||
|
@ -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 eventTracker from '../event-tracker';
|
||||||
|
|
||||||
import constants = require('../lib/constants');
|
import constants = require('../lib/constants');
|
||||||
import { AppsJsonParseError, EISDIR, ENOENT } from '../lib/errors';
|
import { AppsJsonParseError, EISDIR, ENOENT } from '../lib/errors';
|
||||||
@ -111,7 +112,7 @@ export async function loadTargetFromFile(
|
|||||||
if (ENOENT(e) || EISDIR(e)) {
|
if (ENOENT(e) || EISDIR(e)) {
|
||||||
log.debug('No apps.json file present, skipping preload');
|
log.debug('No apps.json file present, skipping preload');
|
||||||
} else {
|
} else {
|
||||||
deviceState.eventTracker.track('Loading preloaded apps failed', {
|
eventTracker.track('Loading preloaded apps failed', {
|
||||||
error: e,
|
error: e,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,14 @@
|
|||||||
import * as Bluebird from 'bluebird';
|
|
||||||
import mask = require('json-mask');
|
import mask = require('json-mask');
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import * as memoizee from 'memoizee';
|
import * as memoizee from 'memoizee';
|
||||||
import * as mixpanel from 'mixpanel';
|
import * as mixpanel from 'mixpanel';
|
||||||
|
|
||||||
import { ConfigType } from './config';
|
import * as config from './config';
|
||||||
import log from './lib/supervisor-console';
|
import log from './lib/supervisor-console';
|
||||||
import supervisorVersion = require('./lib/supervisor-version');
|
import supervisorVersion = require('./lib/supervisor-version');
|
||||||
|
|
||||||
export type EventTrackProperties = Dictionary<any>;
|
export type EventTrackProperties = Dictionary<any>;
|
||||||
|
|
||||||
interface InitArgs {
|
|
||||||
uuid: ConfigType<'uuid'>;
|
|
||||||
unmanaged: ConfigType<'unmanaged'>;
|
|
||||||
mixpanelHost: ConfigType<'mixpanelHost'>;
|
|
||||||
mixpanelToken: ConfigType<'mixpanelToken'>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The minimum amount of time to wait between sending
|
// The minimum amount of time to wait between sending
|
||||||
// events of the same type
|
// events of the same type
|
||||||
const eventDebounceTime = 60000;
|
const eventDebounceTime = 60000;
|
||||||
@ -32,38 +24,47 @@ const mixpanelMask = [
|
|||||||
'stateDiff/local(os_version,superisor_version,ip_address,apps/*/services)',
|
'stateDiff/local(os_version,superisor_version,ip_address,apps/*/services)',
|
||||||
].join(',');
|
].join(',');
|
||||||
|
|
||||||
export class EventTracker {
|
let defaultProperties: EventTrackProperties;
|
||||||
private defaultProperties: EventTrackProperties | null;
|
// We must export this for the tests, but we make no references
|
||||||
private client: mixpanel.Mixpanel | null;
|
// to it within the rest of the supervisor codebase
|
||||||
|
export let client: mixpanel.Mixpanel | null = null;
|
||||||
|
|
||||||
public constructor() {
|
export const initialized = (async () => {
|
||||||
this.client = null;
|
await config.initialized;
|
||||||
this.defaultProperties = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public init({
|
const {
|
||||||
unmanaged,
|
unmanaged,
|
||||||
mixpanelHost,
|
mixpanelHost,
|
||||||
mixpanelToken,
|
mixpanelToken,
|
||||||
uuid,
|
uuid,
|
||||||
}: InitArgs): Bluebird<void> {
|
} = await config.getMany([
|
||||||
return Bluebird.try(() => {
|
'unmanaged',
|
||||||
this.defaultProperties = {
|
'mixpanelHost',
|
||||||
|
'mixpanelToken',
|
||||||
|
'uuid',
|
||||||
|
]);
|
||||||
|
|
||||||
|
defaultProperties = {
|
||||||
distinct_id: uuid,
|
distinct_id: uuid,
|
||||||
uuid,
|
uuid,
|
||||||
supervisorVersion,
|
supervisorVersion,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (unmanaged || mixpanelHost == null || mixpanelToken == null) {
|
if (unmanaged || mixpanelHost == null || mixpanelToken == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.client = mixpanel.init(mixpanelToken, {
|
client = mixpanel.init(mixpanelToken, {
|
||||||
host: mixpanelHost.host,
|
host: mixpanelHost.host,
|
||||||
path: mixpanelHost.path,
|
path: mixpanelHost.path,
|
||||||
});
|
});
|
||||||
});
|
})();
|
||||||
}
|
|
||||||
|
export async function track(
|
||||||
|
event: string,
|
||||||
|
properties: EventTrackProperties | Error = {},
|
||||||
|
) {
|
||||||
|
await initialized;
|
||||||
|
|
||||||
public track(event: string, properties: EventTrackProperties | Error = {}) {
|
|
||||||
if (properties instanceof Error) {
|
if (properties instanceof Error) {
|
||||||
properties = { error: properties };
|
properties = { error: properties };
|
||||||
}
|
}
|
||||||
@ -79,21 +80,21 @@ export class EventTracker {
|
|||||||
|
|
||||||
// Don't send potentially sensitive information, by using a whitelist
|
// Don't send potentially sensitive information, by using a whitelist
|
||||||
properties = mask(properties, mixpanelMask);
|
properties = mask(properties, mixpanelMask);
|
||||||
this.logEvent('Event:', event, JSON.stringify(properties));
|
log.event('Event:', event, JSON.stringify(properties));
|
||||||
if (this.client == null) {
|
if (client == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
properties = this.assignDefaultProperties(properties);
|
properties = assignDefaultProperties(properties);
|
||||||
this.throttleddLogger(event)(properties);
|
throttleddLogger(event)(properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
private throttleddLogger = memoizee(
|
const throttleddLogger = memoizee(
|
||||||
(event: string) => {
|
(event: string) => {
|
||||||
// Call this function at maximum once every minute
|
// Call this function at maximum once every minute
|
||||||
return _.throttle(
|
return _.throttle(
|
||||||
(properties: EventTrackProperties | Error) => {
|
(properties: EventTrackProperties | Error) => {
|
||||||
this.client?.track(event, properties);
|
client?.track(event, properties);
|
||||||
},
|
},
|
||||||
eventDebounceTime,
|
eventDebounceTime,
|
||||||
{ leading: true },
|
{ leading: true },
|
||||||
@ -102,15 +103,8 @@ export class EventTracker {
|
|||||||
{ primitive: true },
|
{ primitive: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
private logEvent(...args: string[]) {
|
function assignDefaultProperties(
|
||||||
log.event(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private assignDefaultProperties(
|
|
||||||
properties: EventTrackProperties,
|
properties: EventTrackProperties,
|
||||||
): EventTrackProperties {
|
): EventTrackProperties {
|
||||||
return _.merge({}, properties, this.defaultProperties);
|
return _.merge({}, properties, defaultProperties);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export default EventTracker;
|
|
||||||
|
@ -3,7 +3,7 @@ import * as _ from 'lodash';
|
|||||||
|
|
||||||
import * as config from './config';
|
import * as config from './config';
|
||||||
import * as db from './db';
|
import * as db from './db';
|
||||||
import { EventTracker } from './event-tracker';
|
import * as eventTracker from './event-tracker';
|
||||||
import { LogType } from './lib/log-types';
|
import { LogType } from './lib/log-types';
|
||||||
import { writeLock } from './lib/update-lock';
|
import { writeLock } from './lib/update-lock';
|
||||||
import {
|
import {
|
||||||
@ -30,22 +30,16 @@ interface LoggerSetupOptions {
|
|||||||
|
|
||||||
type LogEventObject = Dictionary<any> | null;
|
type LogEventObject = Dictionary<any> | null;
|
||||||
|
|
||||||
interface LoggerConstructOptions {
|
|
||||||
eventTracker: EventTracker;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Logger {
|
export class Logger {
|
||||||
private backend: LogBackend | null = null;
|
private backend: LogBackend | null = null;
|
||||||
private balenaBackend: BalenaLogBackend | null = null;
|
private balenaBackend: BalenaLogBackend | null = null;
|
||||||
private localBackend: LocalLogBackend | null = null;
|
private localBackend: LocalLogBackend | null = null;
|
||||||
|
|
||||||
private eventTracker: EventTracker;
|
|
||||||
private containerLogs: { [containerId: string]: ContainerLogs } = {};
|
private containerLogs: { [containerId: string]: ContainerLogs } = {};
|
||||||
private logMonitor: LogMonitor;
|
private logMonitor: LogMonitor;
|
||||||
|
|
||||||
public constructor({ eventTracker }: LoggerConstructOptions) {
|
public constructor() {
|
||||||
this.backend = null;
|
this.backend = null;
|
||||||
this.eventTracker = eventTracker;
|
|
||||||
this.logMonitor = new LogMonitor();
|
this.logMonitor = new LogMonitor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +138,7 @@ export class Logger {
|
|||||||
}
|
}
|
||||||
this.log(msgObj);
|
this.log(msgObj);
|
||||||
if (track) {
|
if (track) {
|
||||||
this.eventTracker.track(
|
eventTracker.track(
|
||||||
eventName != null ? eventName : message,
|
eventName != null ? eventName : message,
|
||||||
eventObj != null ? eventObj : {},
|
eventObj != null ? eventObj : {},
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,7 @@ import * as _ from 'lodash';
|
|||||||
import * as morgan from 'morgan';
|
import * as morgan from 'morgan';
|
||||||
|
|
||||||
import * as config from './config';
|
import * as config from './config';
|
||||||
import { EventTracker } from './event-tracker';
|
import * as eventTracker from './event-tracker';
|
||||||
import blink = require('./lib/blink');
|
import blink = require('./lib/blink');
|
||||||
import * as iptables from './lib/iptables';
|
import * as iptables from './lib/iptables';
|
||||||
import { checkTruthy } from './lib/validation';
|
import { checkTruthy } from './lib/validation';
|
||||||
@ -76,7 +76,6 @@ const expressLogger = morgan(
|
|||||||
);
|
);
|
||||||
|
|
||||||
interface SupervisorAPIConstructOpts {
|
interface SupervisorAPIConstructOpts {
|
||||||
eventTracker: EventTracker;
|
|
||||||
routers: express.Router[];
|
routers: express.Router[];
|
||||||
healthchecks: Array<() => Promise<boolean>>;
|
healthchecks: Array<() => Promise<boolean>>;
|
||||||
}
|
}
|
||||||
@ -86,7 +85,6 @@ interface SupervisorAPIStopOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SupervisorAPI {
|
export class SupervisorAPI {
|
||||||
private eventTracker: EventTracker;
|
|
||||||
private routers: express.Router[];
|
private routers: express.Router[];
|
||||||
private healthchecks: Array<() => Promise<boolean>>;
|
private healthchecks: Array<() => Promise<boolean>>;
|
||||||
|
|
||||||
@ -101,12 +99,7 @@ export class SupervisorAPI {
|
|||||||
}
|
}
|
||||||
: this.applyListeningRules.bind(this);
|
: this.applyListeningRules.bind(this);
|
||||||
|
|
||||||
public constructor({
|
public constructor({ routers, healthchecks }: SupervisorAPIConstructOpts) {
|
||||||
eventTracker,
|
|
||||||
routers,
|
|
||||||
healthchecks,
|
|
||||||
}: SupervisorAPIConstructOpts) {
|
|
||||||
this.eventTracker = eventTracker;
|
|
||||||
this.routers = routers;
|
this.routers = routers;
|
||||||
this.healthchecks = healthchecks;
|
this.healthchecks = healthchecks;
|
||||||
|
|
||||||
@ -132,7 +125,7 @@ export class SupervisorAPI {
|
|||||||
this.api.use(authenticate());
|
this.api.use(authenticate());
|
||||||
|
|
||||||
this.api.post('/v1/blink', (_req, res) => {
|
this.api.post('/v1/blink', (_req, res) => {
|
||||||
this.eventTracker.track('Device blink');
|
eventTracker.track('Device blink');
|
||||||
blink.pattern.start();
|
blink.pattern.start();
|
||||||
setTimeout(blink.pattern.stop, 15000);
|
setTimeout(blink.pattern.stop, 15000);
|
||||||
return res.sendStatus(200);
|
return res.sendStatus(200);
|
||||||
|
@ -2,7 +2,7 @@ import APIBinder from './api-binder';
|
|||||||
import * as db from './db';
|
import * as db from './db';
|
||||||
import * as config from './config';
|
import * as config from './config';
|
||||||
import DeviceState from './device-state';
|
import DeviceState from './device-state';
|
||||||
import EventTracker from './event-tracker';
|
import * as eventTracker from './event-tracker';
|
||||||
import { intialiseContractRequirements } from './lib/contracts';
|
import { intialiseContractRequirements } from './lib/contracts';
|
||||||
import { normaliseLegacyDatabase } from './lib/migration';
|
import { normaliseLegacyDatabase } from './lib/migration';
|
||||||
import * as osRelease from './lib/os-release';
|
import * as osRelease from './lib/os-release';
|
||||||
@ -29,21 +29,17 @@ const startupConfigFields: config.ConfigKey[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export class Supervisor {
|
export class Supervisor {
|
||||||
private eventTracker: EventTracker;
|
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
private deviceState: DeviceState;
|
private deviceState: DeviceState;
|
||||||
private apiBinder: APIBinder;
|
private apiBinder: APIBinder;
|
||||||
private api: SupervisorAPI;
|
private api: SupervisorAPI;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this.eventTracker = new EventTracker();
|
this.logger = new Logger();
|
||||||
this.logger = new Logger({ eventTracker: this.eventTracker });
|
|
||||||
this.apiBinder = new APIBinder({
|
this.apiBinder = new APIBinder({
|
||||||
eventTracker: this.eventTracker,
|
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
});
|
});
|
||||||
this.deviceState = new DeviceState({
|
this.deviceState = new DeviceState({
|
||||||
eventTracker: this.eventTracker,
|
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
apiBinder: this.apiBinder,
|
apiBinder: this.apiBinder,
|
||||||
});
|
});
|
||||||
@ -55,7 +51,6 @@ export class Supervisor {
|
|||||||
this.deviceState.applications.proxyvisor.bindToAPI(this.apiBinder);
|
this.deviceState.applications.proxyvisor.bindToAPI(this.apiBinder);
|
||||||
|
|
||||||
this.api = new SupervisorAPI({
|
this.api = new SupervisorAPI({
|
||||||
eventTracker: this.eventTracker,
|
|
||||||
routers: [this.apiBinder.router, this.deviceState.router],
|
routers: [this.apiBinder.router, this.deviceState.router],
|
||||||
healthchecks: [
|
healthchecks: [
|
||||||
this.apiBinder.healthcheck.bind(this.apiBinder),
|
this.apiBinder.healthcheck.bind(this.apiBinder),
|
||||||
@ -69,16 +64,10 @@ export class Supervisor {
|
|||||||
|
|
||||||
await db.initialized;
|
await db.initialized;
|
||||||
await config.initialized;
|
await config.initialized;
|
||||||
|
await eventTracker.initialized;
|
||||||
|
|
||||||
const conf = await config.getMany(startupConfigFields);
|
const conf = await config.getMany(startupConfigFields);
|
||||||
|
|
||||||
// We can't print to the dashboard until the logger
|
|
||||||
// has started up, so we leave a trail of breadcrumbs
|
|
||||||
// in the logs in case runtime fails to get to the
|
|
||||||
// first dashboard logs
|
|
||||||
log.debug('Starting event tracker');
|
|
||||||
await this.eventTracker.init(conf);
|
|
||||||
|
|
||||||
log.debug('Starting logging infrastructure');
|
log.debug('Starting logging infrastructure');
|
||||||
this.logger.init({
|
this.logger.init({
|
||||||
enableLogs: conf.loggingEnabled,
|
enableLogs: conf.loggingEnabled,
|
||||||
|
@ -216,9 +216,6 @@ describe('deviceState', () => {
|
|||||||
let deviceState: DeviceState;
|
let deviceState: DeviceState;
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await prepare();
|
await prepare();
|
||||||
const eventTracker = {
|
|
||||||
track: console.log,
|
|
||||||
};
|
|
||||||
|
|
||||||
stub(Service as any, 'extendEnvVars').callsFake((env) => {
|
stub(Service as any, 'extendEnvVars').callsFake((env) => {
|
||||||
env['ADDITIONAL_ENV_VAR'] = 'foo';
|
env['ADDITIONAL_ENV_VAR'] = 'foo';
|
||||||
@ -231,7 +228,6 @@ describe('deviceState', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
deviceState = new DeviceState({
|
deviceState = new DeviceState({
|
||||||
eventTracker: eventTracker as any,
|
|
||||||
logger: logger as any,
|
logger: logger as any,
|
||||||
apiBinder: null as any,
|
apiBinder: null as any,
|
||||||
});
|
});
|
||||||
|
@ -1,67 +1,96 @@
|
|||||||
import { Mixpanel } from 'mixpanel';
|
|
||||||
import * as mixpanel from 'mixpanel';
|
import * as mixpanel from 'mixpanel';
|
||||||
import { SinonStub, stub } from 'sinon';
|
import { SinonStub, stub, spy, SinonSpy } from 'sinon';
|
||||||
|
|
||||||
import { expect } from './lib/chai-config';
|
import { expect } from './lib/chai-config';
|
||||||
|
|
||||||
import EventTracker from '../src/event-tracker';
|
import log from '../src/lib/supervisor-console';
|
||||||
import supervisorVersion = require('../src/lib/supervisor-version');
|
import supervisorVersion = require('../src/lib/supervisor-version');
|
||||||
|
import * as config from '../src/config';
|
||||||
|
|
||||||
describe('EventTracker', () => {
|
describe('EventTracker', () => {
|
||||||
let eventTrackerOffline: EventTracker;
|
let logEventStub: SinonStub;
|
||||||
let eventTracker: EventTracker;
|
|
||||||
let initStub: SinonStub;
|
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
initStub = stub(mixpanel, 'init').callsFake(
|
logEventStub = stub(log, 'event');
|
||||||
(token) =>
|
|
||||||
(({
|
|
||||||
token,
|
|
||||||
track: stub().returns(undefined),
|
|
||||||
} as unknown) as Mixpanel),
|
|
||||||
);
|
|
||||||
|
|
||||||
eventTrackerOffline = new EventTracker();
|
delete require.cache[require.resolve('../src/event-tracker')];
|
||||||
eventTracker = new EventTracker();
|
});
|
||||||
return stub(EventTracker.prototype as any, 'logEvent');
|
|
||||||
|
afterEach(() => {
|
||||||
|
logEventStub.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
after(() => {
|
after(() => {
|
||||||
(EventTracker.prototype as any).logEvent.restore();
|
logEventStub.restore();
|
||||||
return initStub.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('initializes in unmanaged mode', () => {
|
describe('Unmanaged', () => {
|
||||||
const promise = eventTrackerOffline.init({
|
let configStub: SinonStub;
|
||||||
|
let eventTracker: typeof import('../src/event-tracker');
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
configStub = stub(config, 'getMany').returns(
|
||||||
|
Promise.resolve({
|
||||||
unmanaged: true,
|
unmanaged: true,
|
||||||
uuid: 'foobar',
|
uuid: 'foobar',
|
||||||
mixpanelHost: { host: '', path: '' },
|
mixpanelHost: { host: '', path: '' },
|
||||||
mixpanelToken: '',
|
mixpanelToken: '',
|
||||||
|
}) as any,
|
||||||
|
);
|
||||||
|
|
||||||
|
eventTracker = await import('../src/event-tracker');
|
||||||
});
|
});
|
||||||
expect(promise).to.be.fulfilled.then(() => {
|
|
||||||
// @ts-ignore
|
after(() => {
|
||||||
expect(eventTrackerOffline.client).to.be.null;
|
configStub.restore();
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('../src/event-tracker')];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializes in unmanaged mode', () => {
|
||||||
|
expect(eventTracker.initialized).to.be.fulfilled.then(() => {
|
||||||
|
expect(eventTracker.client).to.be.null;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs events in unmanaged mode, with the correct properties', () => {
|
it('logs events in unmanaged mode, with the correct properties', async () => {
|
||||||
eventTrackerOffline.track('Test event', { appId: 'someValue' });
|
await eventTracker.track('Test event', { appId: 'someValue' });
|
||||||
// @ts-ignore
|
expect(logEventStub).to.be.calledWith(
|
||||||
expect(eventTrackerOffline.logEvent).to.be.calledWith(
|
|
||||||
'Event:',
|
'Event:',
|
||||||
'Test event',
|
'Test event',
|
||||||
JSON.stringify({ appId: 'someValue' }),
|
JSON.stringify({ appId: 'someValue' }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('initializes a mixpanel client when not in unmanaged mode', () => {
|
describe('Init', () => {
|
||||||
const promise = eventTracker.init({
|
let eventTracker: typeof import('../src/event-tracker');
|
||||||
|
let configStub: SinonStub;
|
||||||
|
let mixpanelSpy: SinonSpy;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
configStub = stub(config, 'getMany').returns(
|
||||||
|
Promise.resolve({
|
||||||
mixpanelToken: 'someToken',
|
mixpanelToken: 'someToken',
|
||||||
uuid: 'barbaz',
|
uuid: 'barbaz',
|
||||||
mixpanelHost: { host: '', path: '' },
|
mixpanelHost: { host: '', path: '' },
|
||||||
unmanaged: false,
|
unmanaged: false,
|
||||||
|
}) as any,
|
||||||
|
);
|
||||||
|
|
||||||
|
mixpanelSpy = spy(mixpanel, 'init');
|
||||||
|
|
||||||
|
eventTracker = await import('../src/event-tracker');
|
||||||
});
|
});
|
||||||
expect(promise).to.be.fulfilled.then(() => {
|
|
||||||
|
after(() => {
|
||||||
|
configStub.restore();
|
||||||
|
mixpanelSpy.restore();
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('../src/event-tracker')];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializes a mixpanel client when not in unmanaged mode', () => {
|
||||||
|
expect(eventTracker.initialized).to.be.fulfilled.then(() => {
|
||||||
expect(mixpanel.init).to.have.been.calledWith('someToken');
|
expect(mixpanel.init).to.have.been.calledWith('someToken');
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
expect(eventTracker.client.token).to.equal('someToken');
|
expect(eventTracker.client.token).to.equal('someToken');
|
||||||
@ -69,11 +98,43 @@ describe('EventTracker', () => {
|
|||||||
expect(eventTracker.client.track).to.be.a('function');
|
expect(eventTracker.client.track).to.be.a('function');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('calls the mixpanel client track function with the event, properties and uuid as distinct_id', () => {
|
describe('Managed', () => {
|
||||||
eventTracker.track('Test event 2', { appId: 'someOtherValue' });
|
let eventTracker: typeof import('../src/event-tracker');
|
||||||
// @ts-ignore
|
let configStub: SinonStub;
|
||||||
expect(eventTracker.logEvent).to.be.calledWith(
|
let mixpanelStub: SinonStub;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
configStub = stub(config, 'getMany').returns(
|
||||||
|
Promise.resolve({
|
||||||
|
mixpanelToken: 'someToken',
|
||||||
|
uuid: 'barbaz',
|
||||||
|
mixpanelHost: { host: '', path: '' },
|
||||||
|
unmanaged: false,
|
||||||
|
}) as any,
|
||||||
|
);
|
||||||
|
|
||||||
|
mixpanelStub = stub(mixpanel, 'init').returns({
|
||||||
|
token: 'someToken',
|
||||||
|
track: stub(),
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
eventTracker = await import('../src/event-tracker');
|
||||||
|
await eventTracker.initialized;
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
configStub.restore();
|
||||||
|
mixpanelStub.restore();
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('../src/event-tracker')];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls the mixpanel client track function with the event, properties and uuid as distinct_id', async () => {
|
||||||
|
await eventTracker.track('Test event 2', { appId: 'someOtherValue' });
|
||||||
|
|
||||||
|
expect(logEventStub).to.be.calledWith(
|
||||||
'Event:',
|
'Event:',
|
||||||
'Test event 2',
|
'Test event 2',
|
||||||
JSON.stringify({ appId: 'someOtherValue' }),
|
JSON.stringify({ appId: 'someOtherValue' }),
|
||||||
@ -87,9 +148,9 @@ describe('EventTracker', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can be passed an Error and it is added to the event properties', () => {
|
it('can be passed an Error and it is added to the event properties', async () => {
|
||||||
const theError = new Error('something went wrong');
|
const theError = new Error('something went wrong');
|
||||||
eventTracker.track('Error event', theError);
|
await eventTracker.track('Error event', theError);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
expect(eventTracker.client.track).to.be.calledWith('Error event', {
|
expect(eventTracker.client.track).to.be.calledWith('Error event', {
|
||||||
error: {
|
error: {
|
||||||
@ -102,7 +163,7 @@ describe('EventTracker', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hides service environment variables, to avoid logging keys or secrets', () => {
|
it('hides service environment variables, to avoid logging keys or secrets', async () => {
|
||||||
const props = {
|
const props = {
|
||||||
service: {
|
service: {
|
||||||
appId: '1',
|
appId: '1',
|
||||||
@ -113,7 +174,7 @@ describe('EventTracker', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
eventTracker.track('Some app event', props);
|
await eventTracker.track('Some app event', props);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
expect(eventTracker.client.track).to.be.calledWith('Some app event', {
|
expect(eventTracker.client.track).to.be.calledWith('Some app event', {
|
||||||
service: { appId: '1' },
|
service: { appId: '1' },
|
||||||
@ -124,50 +185,65 @@ describe('EventTracker', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle being passed no properties object', () => {
|
it('should handle being passed no properties object', () => {
|
||||||
expect(eventTracker.track('no-options')).to.not.throw;
|
expect(eventTracker.track('no-options')).to.be.fulfilled;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return describe('Rate limiting', () => {
|
describe('Rate limiting', () => {
|
||||||
it('should rate limit events of the same type', () => {
|
let eventTracker: typeof import('../src/event-tracker');
|
||||||
// @ts-ignore
|
let mixpanelStub: SinonStub;
|
||||||
eventTracker.client.track.reset();
|
|
||||||
|
|
||||||
eventTracker.track('test', {});
|
before(async () => {
|
||||||
eventTracker.track('test', {});
|
mixpanelStub = stub(mixpanel, 'init').returns({
|
||||||
eventTracker.track('test', {});
|
track: stub(),
|
||||||
eventTracker.track('test', {});
|
} as any);
|
||||||
eventTracker.track('test', {});
|
eventTracker = await import('../src/event-tracker');
|
||||||
|
await eventTracker.initialized;
|
||||||
// @ts-ignore
|
|
||||||
expect(eventTracker.client.track).to.have.callCount(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should rate limit events of the same type with different arguments', () => {
|
after(() => {
|
||||||
// @ts-ignore
|
mixpanelStub.restore();
|
||||||
eventTracker.client.track.reset();
|
|
||||||
|
|
||||||
eventTracker.track('test2', { a: 1 });
|
delete require.cache[require.resolve('../src/event-tracker')];
|
||||||
eventTracker.track('test2', { b: 2 });
|
|
||||||
eventTracker.track('test2', { c: 3 });
|
|
||||||
eventTracker.track('test2', { d: 4 });
|
|
||||||
eventTracker.track('test2', { e: 5 });
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
expect(eventTracker.client.track).to.have.callCount(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not rate limit events of different types', () => {
|
it('should rate limit events of the same type', async () => {
|
||||||
// @ts-ignore
|
// @ts-expect-error resetting a non-stub typed function
|
||||||
eventTracker.client.track.reset();
|
eventTracker.client?.track.reset();
|
||||||
|
|
||||||
eventTracker.track('test3', { a: 1 });
|
await eventTracker.track('test', {});
|
||||||
eventTracker.track('test4', { b: 2 });
|
await eventTracker.track('test', {});
|
||||||
eventTracker.track('test5', { c: 3 });
|
await eventTracker.track('test', {});
|
||||||
eventTracker.track('test6', { d: 4 });
|
await eventTracker.track('test', {});
|
||||||
eventTracker.track('test7', { e: 5 });
|
await eventTracker.track('test', {});
|
||||||
|
|
||||||
// @ts-ignore
|
expect(eventTracker.client?.track).to.have.callCount(1);
|
||||||
expect(eventTracker.client.track).to.have.callCount(5);
|
});
|
||||||
|
|
||||||
|
it('should rate limit events of the same type with different arguments', async () => {
|
||||||
|
// @ts-expect-error resetting a non-stub typed function
|
||||||
|
eventTracker.client?.track.reset();
|
||||||
|
|
||||||
|
await eventTracker.track('test2', { a: 1 });
|
||||||
|
await eventTracker.track('test2', { b: 2 });
|
||||||
|
await eventTracker.track('test2', { c: 3 });
|
||||||
|
await eventTracker.track('test2', { d: 4 });
|
||||||
|
await eventTracker.track('test2', { e: 5 });
|
||||||
|
|
||||||
|
expect(eventTracker.client?.track).to.have.callCount(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not rate limit events of different types', async () => {
|
||||||
|
// @ts-expect-error resetting a non-stub typed function
|
||||||
|
eventTracker.client?.track.reset();
|
||||||
|
|
||||||
|
await eventTracker.track('test3', { a: 1 });
|
||||||
|
await eventTracker.track('test4', { b: 2 });
|
||||||
|
await eventTracker.track('test5', { c: 3 });
|
||||||
|
await eventTracker.track('test6', { d: 4 });
|
||||||
|
await eventTracker.track('test7', { e: 5 });
|
||||||
|
|
||||||
|
expect(eventTracker.client?.track).to.have.callCount(5);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -7,6 +7,7 @@ import ApiBinder from '../src/api-binder';
|
|||||||
import prepare = require('./lib/prepare');
|
import prepare = require('./lib/prepare');
|
||||||
import * as config from '../src/config';
|
import * as config from '../src/config';
|
||||||
import DeviceState from '../src/device-state';
|
import DeviceState from '../src/device-state';
|
||||||
|
import * as eventTracker from '../src/event-tracker';
|
||||||
import Log from '../src/lib/supervisor-console';
|
import Log from '../src/lib/supervisor-console';
|
||||||
import chai = require('./lib/chai-config');
|
import chai = require('./lib/chai-config');
|
||||||
import balenaAPI = require('./lib/mocked-balena-api');
|
import balenaAPI = require('./lib/mocked-balena-api');
|
||||||
@ -28,10 +29,6 @@ const initModels = async (obj: Dictionary<any>, filename: string) => {
|
|||||||
(config.configJsonBackend as any).cache = await (config.configJsonBackend as any).read();
|
(config.configJsonBackend as any).cache = await (config.configJsonBackend as any).read();
|
||||||
await config.generateRequiredFields();
|
await config.generateRequiredFields();
|
||||||
|
|
||||||
obj.eventTracker = {
|
|
||||||
track: stub().callsFake((ev, props) => console.log(ev, props)),
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
obj.logger = {
|
obj.logger = {
|
||||||
clearOutOfDateDBLogs: () => {
|
clearOutOfDateDBLogs: () => {
|
||||||
/* noop */
|
/* noop */
|
||||||
@ -40,11 +37,9 @@ const initModels = async (obj: Dictionary<any>, filename: string) => {
|
|||||||
|
|
||||||
obj.apiBinder = new ApiBinder({
|
obj.apiBinder = new ApiBinder({
|
||||||
logger: obj.logger,
|
logger: obj.logger,
|
||||||
eventTracker: obj.eventTracker,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
obj.deviceState = new DeviceState({
|
obj.deviceState = new DeviceState({
|
||||||
eventTracker: obj.eventTracker,
|
|
||||||
logger: obj.logger,
|
logger: obj.logger,
|
||||||
apiBinder: obj.apiBinder,
|
apiBinder: obj.apiBinder,
|
||||||
});
|
});
|
||||||
@ -65,6 +60,14 @@ const mockProvisioningOpts = {
|
|||||||
describe('ApiBinder', () => {
|
describe('ApiBinder', () => {
|
||||||
let server: Server;
|
let server: Server;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
stub(eventTracker, 'track');
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
// @ts-expect-error Restoring a non-stub type function
|
||||||
|
eventTracker.track.restore();
|
||||||
|
});
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
spy(balenaAPI.balenaBackend!, 'registerHandler');
|
spy(balenaAPI.balenaBackend!, 'registerHandler');
|
||||||
server = balenaAPI.listen(3000);
|
server = balenaAPI.listen(3000);
|
||||||
@ -102,9 +105,7 @@ describe('ApiBinder', () => {
|
|||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
balenaAPI.balenaBackend!.registerHandler.resetHistory();
|
balenaAPI.balenaBackend!.registerHandler.resetHistory();
|
||||||
expect(components.eventTracker.track).to.be.calledWith(
|
expect(eventTracker.track).to.be.calledWith('Device bootstrap success');
|
||||||
'Device bootstrap success',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,6 +7,9 @@ import * as sinon from 'sinon';
|
|||||||
|
|
||||||
import { Logger } from '../src/logger';
|
import { Logger } from '../src/logger';
|
||||||
import { ContainerLogs } from '../src/logging/container';
|
import { ContainerLogs } from '../src/logging/container';
|
||||||
|
import * as eventTracker from '../src/event-tracker';
|
||||||
|
import { stub } from 'sinon';
|
||||||
|
|
||||||
describe('Logger', function () {
|
describe('Logger', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this._req = new stream.PassThrough();
|
this._req = new stream.PassThrough();
|
||||||
@ -20,12 +23,9 @@ describe('Logger', function () {
|
|||||||
|
|
||||||
this.requestStub = sinon.stub(https, 'request').returns(this._req);
|
this.requestStub = sinon.stub(https, 'request').returns(this._req);
|
||||||
|
|
||||||
this.fakeEventTracker = {
|
this.eventTrackerStub = stub(eventTracker, 'track');
|
||||||
track: sinon.spy(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-ignore missing db property
|
this.logger = new Logger();
|
||||||
this.logger = new Logger({ eventTracker: this.fakeEventTracker });
|
|
||||||
return this.logger.init({
|
return this.logger.init({
|
||||||
apiEndpoint: 'https://example.com',
|
apiEndpoint: 'https://example.com',
|
||||||
uuid: 'deadbeef',
|
uuid: 'deadbeef',
|
||||||
@ -38,6 +38,7 @@ describe('Logger', function () {
|
|||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
this.requestStub.restore();
|
this.requestStub.restore();
|
||||||
|
this.eventTrackerStub.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('waits the grace period before sending any logs', function () {
|
it('waits the grace period before sending any logs', function () {
|
||||||
@ -108,7 +109,7 @@ describe('Logger', function () {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return Promise.delay(5500).then(() => {
|
return Promise.delay(5500).then(() => {
|
||||||
expect(this.fakeEventTracker.track).to.be.calledWith('Some event name', {
|
expect(this.eventTrackerStub).to.be.calledWith('Some event name', {
|
||||||
someProp: 'someVal',
|
someProp: 'someVal',
|
||||||
});
|
});
|
||||||
const lines = this._req.body.split('\n');
|
const lines = this._req.body.split('\n');
|
||||||
|
@ -7,7 +7,6 @@ import Network from '../src/compose/network';
|
|||||||
import Service from '../src/compose/service';
|
import Service from '../src/compose/service';
|
||||||
import Volume from '../src/compose/volume';
|
import Volume from '../src/compose/volume';
|
||||||
import DeviceState from '../src/device-state';
|
import DeviceState from '../src/device-state';
|
||||||
import EventTracker from '../src/event-tracker';
|
|
||||||
import * as dockerUtils from '../src/lib/docker-utils';
|
import * as dockerUtils from '../src/lib/docker-utils';
|
||||||
|
|
||||||
import chai = require('./lib/chai-config');
|
import chai = require('./lib/chai-config');
|
||||||
@ -126,14 +125,12 @@ const dependentDBFormat = {
|
|||||||
describe('ApplicationManager', function () {
|
describe('ApplicationManager', function () {
|
||||||
before(async function () {
|
before(async function () {
|
||||||
await prepare();
|
await prepare();
|
||||||
const eventTracker = new EventTracker();
|
|
||||||
this.logger = {
|
this.logger = {
|
||||||
clearOutOfDateDBLogs: () => {
|
clearOutOfDateDBLogs: () => {
|
||||||
/* noop */
|
/* noop */
|
||||||
},
|
},
|
||||||
} as any;
|
} as any;
|
||||||
this.deviceState = new DeviceState({
|
this.deviceState = new DeviceState({
|
||||||
eventTracker,
|
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
apiBinder: null as any,
|
apiBinder: null as any,
|
||||||
});
|
});
|
||||||
|
@ -13,7 +13,6 @@ import { createV1Api } from '../../src/device-api/v1';
|
|||||||
import { createV2Api } from '../../src/device-api/v2';
|
import { createV2Api } from '../../src/device-api/v2';
|
||||||
import APIBinder from '../../src/api-binder';
|
import APIBinder from '../../src/api-binder';
|
||||||
import DeviceState from '../../src/device-state';
|
import DeviceState from '../../src/device-state';
|
||||||
import EventTracker from '../../src/event-tracker';
|
|
||||||
import SupervisorAPI from '../../src/supervisor-api';
|
import SupervisorAPI from '../../src/supervisor-api';
|
||||||
|
|
||||||
const DB_PATH = './test/data/supervisor-api.sqlite';
|
const DB_PATH = './test/data/supervisor-api.sqlite';
|
||||||
@ -68,19 +67,17 @@ const STUBBED_VALUES = {
|
|||||||
|
|
||||||
async function create(): Promise<SupervisorAPI> {
|
async function create(): Promise<SupervisorAPI> {
|
||||||
// Get SupervisorAPI construct options
|
// Get SupervisorAPI construct options
|
||||||
const { eventTracker, deviceState, apiBinder } = await createAPIOpts();
|
const { deviceState, apiBinder } = await createAPIOpts();
|
||||||
// Stub functions
|
// Stub functions
|
||||||
setupStubs();
|
setupStubs();
|
||||||
// Create ApplicationManager
|
// Create ApplicationManager
|
||||||
const appManager = new ApplicationManager({
|
const appManager = new ApplicationManager({
|
||||||
eventTracker,
|
|
||||||
logger: null,
|
logger: null,
|
||||||
deviceState,
|
deviceState,
|
||||||
apiBinder: null,
|
apiBinder: null,
|
||||||
});
|
});
|
||||||
// Create SupervisorAPI
|
// Create SupervisorAPI
|
||||||
const api = new SupervisorAPI({
|
const api = new SupervisorAPI({
|
||||||
eventTracker,
|
|
||||||
routers: [buildRoutes(appManager)],
|
routers: [buildRoutes(appManager)],
|
||||||
healthchecks: [deviceState.healthcheck, apiBinder.healthcheck],
|
healthchecks: [deviceState.healthcheck, apiBinder.healthcheck],
|
||||||
});
|
});
|
||||||
@ -103,20 +100,15 @@ async function createAPIOpts(): Promise<SupervisorAPIOpts> {
|
|||||||
await db.initialized;
|
await db.initialized;
|
||||||
// Initialize and set values for mocked Config
|
// Initialize and set values for mocked Config
|
||||||
await initConfig();
|
await initConfig();
|
||||||
// Create EventTracker
|
|
||||||
const tracker = new EventTracker();
|
|
||||||
// Create deviceState
|
// Create deviceState
|
||||||
const deviceState = new DeviceState({
|
const deviceState = new DeviceState({
|
||||||
eventTracker: tracker,
|
|
||||||
logger: null as any,
|
logger: null as any,
|
||||||
apiBinder: null as any,
|
apiBinder: null as any,
|
||||||
});
|
});
|
||||||
const apiBinder = new APIBinder({
|
const apiBinder = new APIBinder({
|
||||||
eventTracker: tracker,
|
|
||||||
logger: null as any,
|
logger: null as any,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
eventTracker: tracker,
|
|
||||||
deviceState,
|
deviceState,
|
||||||
apiBinder,
|
apiBinder,
|
||||||
};
|
};
|
||||||
@ -165,7 +157,6 @@ function restoreStubs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface SupervisorAPIOpts {
|
interface SupervisorAPIOpts {
|
||||||
eventTracker: EventTracker;
|
|
||||||
deviceState: DeviceState;
|
deviceState: DeviceState;
|
||||||
apiBinder: APIBinder;
|
apiBinder: APIBinder;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user