Don't attempt to setup a log stream to the cloud before provision

Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
Cameron Diver 2019-06-21 13:00:21 +01:00
parent 20a83e8e0a
commit 37945b4aa5
6 changed files with 85 additions and 26 deletions

View File

@ -528,7 +528,9 @@ export class APIBinder {
// the watchdog to kill the supervisor - and killing the supervisor will
// not help in this situation
log.error(
'Non-200 response from the API! Status code: ${e.statusCode} - message:',
`Non-200 response from the API! Status code: ${
e.statusCode
} - message:`,
e,
);
} else {

View File

@ -26,8 +26,10 @@ interface ConfigOpts {
configPath?: string;
}
type ConfigMap<T extends SchemaTypeKey> = { [key in T]: SchemaReturn<key> };
type ConfigChangeMap<T extends SchemaTypeKey> = {
export type ConfigMap<T extends SchemaTypeKey> = {
[key in T]: SchemaReturn<key>
};
export type ConfigChangeMap<T extends SchemaTypeKey> = {
[key in T]?: SchemaReturn<key>
};

View File

@ -1,7 +1,7 @@
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import { ConfigType } from './config';
import Config, { ConfigChangeMap, ConfigKey, ConfigType } from './config';
import DB from './db';
import { EventTracker } from './event-tracker';
import Docker from './lib/docker-utils';
@ -26,6 +26,7 @@ interface LoggerSetupOptions {
localMode: ConfigType<'localMode'>;
enableLogs: boolean;
config: Config;
}
type LogEventObject = Dictionary<any> | null;
@ -59,23 +60,53 @@ export class Logger {
unmanaged,
enableLogs,
localMode,
config,
}: LoggerSetupOptions) {
this.balenaBackend = new BalenaLogBackend(
apiEndpoint,
// This is definitely not correct, but this is indeed
// what has been happening before the conversion of
// `supervisor.coffee` to typescript. We need to fix
// the wider problem of the backend attempting to
// communicate with the api without being provisioned
uuid || '',
deviceApiKey,
);
this.balenaBackend = new BalenaLogBackend(apiEndpoint, uuid, deviceApiKey);
this.localBackend = new LocalLogBackend();
this.backend = localMode ? this.localBackend : this.balenaBackend;
this.backend.unmanaged = unmanaged;
this.backend.publishEnabled = enableLogs;
// Only setup a config listener if we have to
if (!this.balenaBackend.isIntialised()) {
const handler = async (values: ConfigChangeMap<ConfigKey>) => {
if (
'uuid' in values ||
'apiEndpoint' in values ||
'deviceApiKey' in values
) {
// If any of the values we're interested in have
// changed, retrieve all of the values, check that
// they're all set, and provide them to the
// balenaBackend
const conf = await config.getMany([
'uuid',
'apiEndpoint',
'deviceApiKey',
]);
// We use Boolean here, as deviceApiKey when unset
// is '' for legacy reasons. Once we're totally
// typescript, we can make it have a default value
// of undefined.
if (_.every(conf, Boolean)) {
// Everything is set, provide the values to the
// balenaBackend, and remove our listener
this.balenaBackend!.assignFields(
conf.apiEndpoint,
conf.uuid!,
conf.deviceApiKey,
);
config.removeListener('change', handler);
}
}
};
config.on('change', handler);
}
}
public switchBackend(localMode: boolean) {

View File

@ -31,17 +31,18 @@ export class BalenaLogBackend extends LogBackend {
private stream: stream.PassThrough;
private timeout: NodeJS.Timer;
public constructor(apiEndpoint: string, uuid: string, deviceApiKey: string) {
public initialised = false;
public constructor(
apiEndpoint: string,
uuid: Nullable<string>,
deviceApiKey: string,
) {
super();
this.opts = url.parse(`${apiEndpoint}/device/v2/${uuid}/log-stream`) as any;
this.opts.method = 'POST';
this.opts.headers = {
Authorization: `Bearer ${deviceApiKey}`,
'Content-Type': 'application/x-ndjson',
'Content-Encoding': 'gzip',
};
if (uuid != null && deviceApiKey !== '') {
this.assignFields(apiEndpoint, uuid, deviceApiKey);
}
// This stream serves serves as a message buffer during reconnections
// while we unpipe the old, malfunctioning connection and then repipe a
// new one.
@ -71,8 +72,15 @@ export class BalenaLogBackend extends LogBackend {
});
}
public isIntialised(): boolean {
return this.initialised;
}
public log(message: LogMessage) {
if (this.unmanaged || !this.publishEnabled) {
// TODO: Perhaps don't just drop logs when we haven't
// yet initialised (this happens when a device has not yet
// been provisioned)
if (this.unmanaged || !this.publishEnabled || !this.initialised) {
return;
}
@ -100,6 +108,18 @@ export class BalenaLogBackend extends LogBackend {
this.write(message);
}
public assignFields(apiEndpoint: string, uuid: string, deviceApiKey: string) {
this.opts = url.parse(`${apiEndpoint}/device/v2/${uuid}/log-stream`) as any;
this.opts.method = 'POST';
this.opts.headers = {
Authorization: `Bearer ${deviceApiKey}`,
'Content-Type': 'application/x-ndjson',
'Content-Encoding': 'gzip',
};
this.initialised = true;
}
private setup = _.throttle(() => {
this.req = https.request(this.opts);

View File

@ -89,7 +89,11 @@ export class Supervisor {
await this.apiBinder.initClient();
log.debug('Starting logging infrastructure');
this.logger.init({ enableLogs: conf.loggingEnabled, ...conf });
this.logger.init({
enableLogs: conf.loggingEnabled,
config: this.config,
...conf,
});
this.logger.logSystemMessage('Supervisor starting', {}, 'Supervisor start');
if (conf.legacyAppsPresent) {

View File

@ -1,6 +1,6 @@
{ expect } = require './lib/chai-config'
Supervisor = require '../src/supervisor'
{ Supervisor } = require '../src/supervisor'
describe 'Startup', ->
it 'should startup correctly', ->