Merge pull request #2210 from balena-os/native-promises

Convert multiple bluebird uses to native promises
This commit is contained in:
flowzone-app[bot] 2023-10-16 13:49:11 +00:00 committed by GitHub
commit 3c84c1e3aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 326 additions and 334 deletions

View File

@ -28,6 +28,7 @@ import * as logger from '../logger';
import * as apiHelper from '../lib/api-helper';
import { startReporting, stateReportErrors } from './report';
import { setTimeout } from 'timers/promises';
interface DevicePinInfo {
app: number;
@ -415,7 +416,7 @@ async function reportInitialConfig(
await reportInitialEnv(apiEndpoint, deviceId, initialName);
} catch (err) {
log.error('Error reporting initial configuration, will retry', err);
await Bluebird.delay(retryDelay);
await setTimeout(retryDelay);
await reportInitialConfig(apiEndpoint, deviceId, retryDelay, initialName);
}
}
@ -454,7 +455,7 @@ async function provisionOrRetry(retryDelay: number): Promise<void> {
error: e,
delay: retryDelay,
});
await Bluebird.delay(retryDelay);
await setTimeout(retryDelay);
return provisionOrRetry(retryDelay);
}
}

View File

@ -1,6 +1,5 @@
import * as url from 'url';
import * as _ from 'lodash';
import { delay } from 'bluebird';
import { CoreOptions } from 'request';
import { performance } from 'perf_hooks';
@ -16,6 +15,7 @@ import * as eventTracker from '../event-tracker';
import * as deviceState from '../device-state';
import { shallowDiff, prune, empty } from '../lib/json';
import { setTimeout } from 'timers/promises';
let lastReport: DeviceState = {};
let lastReportTime: number = -Infinity;
@ -215,7 +215,7 @@ export async function startReporting() {
}
} finally {
// Wait until we want to report again
await delay(delayBy);
await setTimeout(delayBy);
// Try to report again
await recursivelyReport(delayBy);
}

View File

@ -1,4 +1,3 @@
import * as Bluebird from 'bluebird';
import * as Docker from 'dockerode';
import { EventEmitter } from 'events';
import * as _ from 'lodash';
@ -318,10 +317,14 @@ async function withImagesFromDockerAndDB<T>(
cb: (dockerImages: Docker.ImageInfo[], composeImages: Image[]) => T,
) {
const [normalisedImages, dbImages] = await Promise.all([
Bluebird.map(docker.listImages({ digests: true }), (image) => ({
...image,
RepoTag: getNormalisedTags(image),
})),
docker.listImages({ digests: true }).then(async (images) => {
return await Promise.all(
images.map((image) => ({
...image,
RepoTag: getNormalisedTags(image),
})),
);
}),
db.models('image').select(),
]);
return cb(normalisedImages, dbImages);

View File

@ -1,4 +1,3 @@
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import * as constants from '../lib/constants';
@ -12,14 +11,13 @@ import { Network } from './network';
import { ResourceRecreationAttemptError } from './errors';
export async function getAll(): Promise<Network[]> {
return getWithBothLabels().map(async (network: { Id: string }) => {
return docker
.getNetwork(network.Id)
.inspect()
.then((net) => {
return Network.fromDockerNetwork(net);
});
});
const networks = await getWithBothLabels();
return await Promise.all(
networks.map(async (network: { Id: string }) => {
const net = await docker.getNetwork(network.Id).inspect();
return Network.fromDockerNetwork(net);
}),
);
}
async function get(network: {
@ -126,8 +124,8 @@ export async function ensureSupervisorNetwork(): Promise<void> {
}
}
function getWithBothLabels() {
return Bluebird.join(
async function getWithBothLabels() {
const [legacyNetworks, currentNetworks] = await Promise.all([
docker.listNetworks({
filters: {
label: ['io.resin.supervised'],
@ -138,8 +136,6 @@ function getWithBothLabels() {
label: ['io.balena.supervised'],
},
}),
(legacyNetworks, currentNetworks) => {
return _.unionBy(currentNetworks, legacyNetworks, 'Id');
},
);
]);
return _.unionBy(currentNetworks, legacyNetworks, 'Id');
}

View File

@ -1,4 +1,3 @@
import * as Bluebird from 'bluebird';
import * as Dockerode from 'dockerode';
import { EventEmitter } from 'events';
import { isLeft } from 'fp-ts/lib/Either';
@ -26,6 +25,7 @@ import { serviceNetworksToDockerNetworks } from './utils';
import log from '../lib/supervisor-console';
import logMonitor from '../logging/monitor';
import { setTimeout } from 'timers/promises';
interface ServiceManagerEvents {
change: void;
@ -61,24 +61,28 @@ export const getAll = async (
const filterLabels = ['supervised'].concat(extraLabelFilters);
const containers = await listWithBothLabels(filterLabels);
const services = await Bluebird.map(containers, async (container) => {
try {
const serviceInspect = await docker.getContainer(container.Id).inspect();
const service = Service.fromDockerContainer(serviceInspect);
// We know that the containerId is set below, because `fromDockerContainer`
// always sets it
const vState = volatileState[service.containerId!];
if (vState != null && vState.status != null) {
service.status = vState.status;
const services = await Promise.all(
containers.map(async (container) => {
try {
const serviceInspect = await docker
.getContainer(container.Id)
.inspect();
const service = Service.fromDockerContainer(serviceInspect);
// We know that the containerId is set below, because `fromDockerContainer`
// always sets it
const vState = volatileState[service.containerId!];
if (vState != null && vState.status != null) {
service.status = vState.status;
}
return service;
} catch (e: unknown) {
if (isNotFoundError(e)) {
return null;
}
throw e;
}
return service;
} catch (e: unknown) {
if (isNotFoundError(e)) {
return null;
}
throw e;
}
});
}),
);
return services.filter((s) => s != null) as Service[];
};
@ -432,14 +436,17 @@ export function listenToEvents() {
});
};
Bluebird.resolve(listen())
.catch((e) => {
log.error('Error listening to events:', e, e.stack);
})
.finally(() => {
(async () => {
try {
await listen();
} catch (e) {
log.error('Error listening to events:', e);
} finally {
listening = false;
setTimeout(listenToEvents, 1000);
});
await setTimeout(1000);
listenToEvents();
}
})();
return;
}
@ -517,77 +524,73 @@ function reportNewStatus(
);
}
function killContainer(
async function killContainer(
containerId: string,
service: Partial<Service> = {},
{ removeContainer = true, wait = false }: KillOpts = {},
): Bluebird<void> {
): Promise<void> {
// To maintain compatibility of the `wait` flag, this function is not
// async, but it feels like whether or not the promise should be waited on
// should performed by the caller
// TODO: Remove the need for the wait flag
return Bluebird.try(() => {
logger.logSystemEvent(LogTypes.stopService, { service });
if (service.imageId != null) {
reportNewStatus(containerId, service, 'Stopping');
}
logger.logSystemEvent(LogTypes.stopService, { service });
if (service.imageId != null) {
reportNewStatus(containerId, service, 'Stopping');
}
const containerObj = docker.getContainer(containerId);
const killPromise = Bluebird.resolve(containerObj.stop())
.then(() => {
const containerObj = docker.getContainer(containerId);
const killPromise = containerObj
.stop()
.then(() => {
if (removeContainer) {
return containerObj.remove({ v: true });
}
})
.catch((e) => {
// Get the statusCode from the original cause and make sure it's
// definitely an int for comparison reasons
const maybeStatusCode = PermissiveNumber.decode(e.statusCode);
if (isLeft(maybeStatusCode)) {
throw new Error(`Could not parse status code from docker error: ${e}`);
}
const statusCode = maybeStatusCode.right;
// 304 means the container was already stopped, so we can just remove it
if (statusCode === 304) {
logger.logSystemEvent(LogTypes.stopServiceNoop, { service });
// Why do we attempt to remove the container again?
if (removeContainer) {
return containerObj.remove({ v: true });
}
})
.catch((e) => {
// Get the statusCode from the original cause and make sure it's
// definitely an int for comparison reasons
const maybeStatusCode = PermissiveNumber.decode(e.statusCode);
if (isLeft(maybeStatusCode)) {
throw new Error(
`Could not parse status code from docker error: ${e}`,
);
}
const statusCode = maybeStatusCode.right;
// 304 means the container was already stopped, so we can just remove it
if (statusCode === 304) {
logger.logSystemEvent(LogTypes.stopServiceNoop, { service });
// Why do we attempt to remove the container again?
if (removeContainer) {
return containerObj.remove({ v: true });
}
} else if (statusCode === 404) {
// 404 means the container doesn't exist, precisely what we want!
logger.logSystemEvent(LogTypes.stopRemoveServiceNoop, {
service,
});
} else {
throw e;
}
})
.tap(() => {
delete containerHasDied[containerId];
logger.logSystemEvent(LogTypes.stopServiceSuccess, { service });
})
.catch((e) => {
logger.logSystemEvent(LogTypes.stopServiceError, {
} else if (statusCode === 404) {
// 404 means the container doesn't exist, precisely what we want!
logger.logSystemEvent(LogTypes.stopRemoveServiceNoop, {
service,
error: e,
});
})
.finally(() => {
if (service.imageId != null) {
reportChange(containerId);
}
} else {
throw e;
}
})
.then(() => {
delete containerHasDied[containerId];
logger.logSystemEvent(LogTypes.stopServiceSuccess, { service });
})
.catch((e) => {
logger.logSystemEvent(LogTypes.stopServiceError, {
service,
error: e,
});
})
.finally(() => {
if (service.imageId != null) {
reportChange(containerId);
}
});
if (wait) {
return killPromise;
}
return;
});
if (wait) {
return killPromise;
}
}
async function listWithBothLabels(
@ -630,21 +633,29 @@ function waitToKill(service: Service, timeout: number | string) {
const handoverCompletePaths = service.handoverCompleteFullPathsOnHost();
const wait = (): Bluebird<void> =>
Bluebird.any(
handoverCompletePaths.map((file) =>
fs.stat(file).then(() => fs.unlink(file).catch(_.noop)),
),
).catch(async () => {
const wait = async (): Promise<void> => {
try {
await Promise.any(
handoverCompletePaths.map(async (file) => {
try {
await fs.stat(file);
await fs.unlink(file);
} catch {
// noop
}
}),
);
} catch {
if (Date.now() < deadline) {
await Bluebird.delay(pollInterval);
await setTimeout(pollInterval);
return wait();
} else {
log.info(
`Handover timeout has passed, assuming handover was completed for service ${service.serviceName}`,
);
}
});
}
};
log.info(
`Waiting for handover to be completed for service: ${service.serviceName}`,

View File

@ -1,4 +1,3 @@
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import { promises as fs } from 'fs';
import * as path from 'path';
@ -50,12 +49,10 @@ export class SplashImage extends ConfigBackend {
const [
// If no logo is found, assume the file is `balena-logo.png`
splashFile = path.join(SplashImage.BASEPATH, 'balena-logo.png'),
] = (
await Bluebird.resolve(fs.readdir(SplashImage.BASEPATH))
// Read the splash dir (will throw if the path does not exist)
// And filter valid filenames
.filter((filename) => SplashImage.FILENAMES.includes(filename))
)
] = (await fs.readdir(SplashImage.BASEPATH))
// Read the splash dir (will throw if the path does not exist)
// And filter valid filenames
.filter((filename) => SplashImage.FILENAMES.includes(filename))
// Sort by name, so in case both files are defined, balena-logo will
// be chosen
.sort()

View File

@ -1,4 +1,3 @@
import * as Bluebird from 'bluebird';
import * as express from 'express';
import type { Response, NextFunction } from 'express';
import * as _ from 'lodash';
@ -156,93 +155,89 @@ router.post(
router.get(
'/v2/applications/state',
async (req: AuthorizedRequest, res: Response, next: NextFunction) => {
// It's very hacky to access the services and db via the application manager
// refactor this code to use applicationManager.getState() instead.
Bluebird.join(
serviceManager.getState(),
images.getState(),
db.models('app').select(['appId', 'commit', 'name']),
(
services,
imgs,
apps: Array<{ appId: string; commit: string; name: string }>,
) => {
// Create an object which is keyed my application name
const response: {
[appName: string]: {
appId: number;
commit: string;
services: {
[serviceName: string]: {
status?: string;
releaseId: number;
downloadProgress: number | null;
};
try {
// It's very hacky to access the services and db via the application manager
// refactor this code to use applicationManager.getState() instead.
const [services, imgs, apps] = await Promise.all([
serviceManager.getState(),
images.getState(),
db.models('app').select(['appId', 'commit', 'name']) as Promise<
Array<{ appId: string; commit: string; name: string }>
>,
]);
// Create an object which is keyed my application name
const response: {
[appName: string]: {
appId: number;
commit: string;
services: {
[serviceName: string]: {
status?: string;
releaseId: number;
downloadProgress: number | null;
};
};
} = {};
};
} = {};
const appNameById: { [id: number]: string } = {};
const commits: string[] = [];
const appNameById: { [id: number]: string } = {};
const commits: string[] = [];
// only access scoped apps
apps
.filter((app) =>
req.auth.isScoped({ apps: [parseInt(app.appId, 10)] }),
)
.forEach((app) => {
const appId = parseInt(app.appId, 10);
response[app.name] = {
appId,
commit: app.commit,
services: {},
};
// only access scoped apps
apps
.filter((app) => req.auth.isScoped({ apps: [parseInt(app.appId, 10)] }))
.forEach((app) => {
const appId = parseInt(app.appId, 10);
response[app.name] = {
appId,
commit: app.commit,
services: {},
};
appNameById[appId] = app.name;
commits.push(app.commit);
appNameById[appId] = app.name;
commits.push(app.commit);
});
// only access scoped images
imgs
.filter(
(img) =>
req.auth.isScoped({ apps: [img.appId] }) &&
// Ensure we are using the apps for the target release
commits.includes(img.commit),
)
.forEach((img) => {
const appName = appNameById[img.appId];
if (appName == null) {
log.warn(
`Image found for unknown application!\nImage: ${JSON.stringify(
img,
)}`,
);
return;
}
const svc = _.find(services, (s: Service) => {
return s.serviceName === img.serviceName && s.commit === img.commit;
});
// only access scoped images
imgs
.filter(
(img) =>
req.auth.isScoped({ apps: [img.appId] }) &&
// Ensure we are using the apps for the target release
commits.includes(img.commit),
)
.forEach((img) => {
const appName = appNameById[img.appId];
if (appName == null) {
log.warn(
`Image found for unknown application!\nImage: ${JSON.stringify(
img,
)}`,
);
return;
}
let status: string | undefined;
if (svc == null) {
status = img.status;
} else {
status = svc.status || img.status;
}
response[appName].services[img.serviceName] = {
status,
releaseId: img.releaseId,
downloadProgress: img.downloadProgress || null,
};
});
const svc = _.find(services, (s: Service) => {
return (
s.serviceName === img.serviceName && s.commit === img.commit
);
});
let status: string | undefined;
if (svc == null) {
status = img.status;
} else {
status = svc.status || img.status;
}
response[appName].services[img.serviceName] = {
status,
releaseId: img.releaseId,
downloadProgress: img.downloadProgress || null,
};
});
res.status(200).json(response);
},
).catch(next);
res.status(200).json(response);
} catch (err) {
next(err);
}
},
);

View File

@ -45,6 +45,7 @@ import type {
} from './compose/composition-steps';
import * as fsUtils from './lib/fs-utils';
import { pathOnRoot } from './lib/host-utils';
import { setTimeout } from 'timers/promises';
const TARGET_STATE_CONFIG_DUMP = pathOnRoot(
'/tmp/balena-supervisor/target-state-config',
@ -769,7 +770,7 @@ export const applyTarget = async ({
steps.map((s) => applyStep(s, { force, initial, skipLock })),
);
await Bluebird.delay(nextDelay);
await setTimeout(nextDelay);
await applyTarget({
force,
initial,
@ -850,8 +851,8 @@ export function triggerApplyTarget({
}
applyCancelled = false;
applyInProgress = true;
new Bluebird((resolve, reject) => {
setTimeout(resolve, delay);
new Promise((resolve, reject) => {
setTimeout(delay).then(resolve);
cancelDelay = reject;
})
.catch(() => {

View File

@ -1,6 +1,6 @@
import { EventEmitter } from 'events';
import * as url from 'url';
import { delay } from 'bluebird';
import { setTimeout } from 'timers/promises';
import * as _ from 'lodash';
import * as Bluebird from 'bluebird';
import type StrictEventEmitter from 'strict-event-emitter-types';
@ -171,7 +171,7 @@ const poll = async (
// Convenience function used for delaying poll loops
const delayedLoop = async (delayBy: number) => {
// Wait until we want to poll again
await delay(delayBy);
await setTimeout(delayBy);
// Poll again
await poll(false, fetchErrors);
};
@ -232,7 +232,7 @@ export const startPoll = async (): Promise<void> => {
appUpdatePollInterval = interval;
} catch {
// Delay 10 seconds and retry loading config
await delay(10000);
await setTimeout(10000);
// Attempt to start poll again
return startPoll();
}

View File

@ -1,4 +1,3 @@
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import { promises as fs } from 'fs';
import * as path from 'path';
@ -11,6 +10,7 @@ import { BackupError, isNotFoundError } from './errors';
import { exec, exists, mkdirp, unlinkAll } from './fs-utils';
import { log } from './supervisor-console';
import { pathOnData } from './host-utils';
import { setTimeout } from 'timers/promises';
export async function loadBackupFromMigration(
targetState: TargetState,
@ -85,7 +85,7 @@ export async function loadBackupFromMigration(
} catch (err) {
log.error(`Error restoring migration backup, retrying: ${err}`);
await Bluebird.delay(retryDelay);
await setTimeout(retryDelay);
return loadBackupFromMigration(targetState, retryDelay);
}
}

View File

@ -3,10 +3,6 @@ import * as _ from 'lodash';
import * as memoizee from 'memoizee';
import { promises as fs } from 'fs';
// TODO: remove this once we can update Node to v16 and typescript
// to an es2021 compatible version for native Promise.any
import { Promise } from 'bluebird';
import { exec } from './fs-utils';
export async function getCpuUsage(): Promise<number> {

View File

@ -1,4 +1,3 @@
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import * as config from './config';
@ -76,7 +75,7 @@ export class LocalModeManager {
) {}
// Indicates that switch from or to the local mode is not complete.
private switchInProgress: Bluebird<void> | null = null;
private switchInProgress: Promise<void> | null = null;
public async init() {
// Setup a listener to catch state changes relating to local mode
@ -111,11 +110,11 @@ export class LocalModeManager {
}
public startLocalModeChangeHandling(local: boolean) {
this.switchInProgress = Bluebird.resolve(
this.handleLocalModeStateChange(local),
).finally(() => {
this.switchInProgress = null;
});
this.switchInProgress = this.handleLocalModeStateChange(local).finally(
() => {
this.switchInProgress = null;
},
);
}
// Query the engine to get currently running containers and installed images.
@ -131,7 +130,7 @@ export class LocalModeManager {
.listNetworks()
.then((resp) => _.map(resp, 'Id'));
const [containers, images, volumes, networks] = await Bluebird.all([
const [containers, images, volumes, networks] = await Promise.all([
containersPromise,
imagesPromise,
volumesPromise,
@ -243,30 +242,38 @@ export class LocalModeManager {
log.debug(`Going to delete the following objects: ${objects}`);
// Delete engine objects. We catch every deletion error, so that we can attempt other objects deletions.
await Bluebird.map(objects.containers, (cId) => {
return docker
.getContainer(cId)
.remove({ force: true })
.catch((e) => log.error(`Unable to delete container ${cId}`, e));
});
await Bluebird.map(objects.images, (iId) => {
return docker
.getImage(iId)
.remove({ force: true })
.catch((e) => log.error(`Unable to delete image ${iId}`, e));
});
await Bluebird.map(objects.networks, (nId) => {
return docker
.getNetwork(nId)
.remove()
.catch((e) => log.error(`Unable to delete network ${nId}`, e));
});
await Bluebird.map(objects.volumes, (vId) => {
return docker
.getVolume(vId)
.remove()
.catch((e) => log.error(`Unable to delete volume ${vId}`, e));
});
await Promise.all(
objects.containers.map((cId) => {
return docker
.getContainer(cId)
.remove({ force: true })
.catch((e) => log.error(`Unable to delete container ${cId}`, e));
}),
);
await Promise.all(
objects.images.map((iId) => {
return docker
.getImage(iId)
.remove({ force: true })
.catch((e) => log.error(`Unable to delete image ${iId}`, e));
}),
);
await Promise.all(
objects.networks.map((nId) => {
return docker
.getNetwork(nId)
.remove()
.catch((e) => log.error(`Unable to delete network ${nId}`, e));
}),
);
await Promise.all(
objects.volumes.map((vId) => {
return docker
.getVolume(vId)
.remove()
.catch((e) => log.error(`Unable to delete volume ${vId}`, e));
}),
);
// Remove any local mode state added to the database.
await db

View File

@ -1,4 +1,3 @@
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import { Readable } from 'stream';
@ -10,18 +9,16 @@ import log from '../lib/supervisor-console';
export class LocalLogBackend extends LogBackend {
private globalListeners: Readable[] = [];
private serviceNameResolver: (
serviceId: number,
) => Promise<string> | Bluebird<string>;
private serviceNameResolver: (serviceId: number) => Promise<string>;
public log(message: LogMessage): void {
public async log(message: LogMessage): Promise<void> {
if (this.publishEnabled) {
Bluebird.try(() => {
try {
if (!message.isSystem) {
if (this.serviceNameResolver == null) {
// This means there is no listener assigned, drop the logs
// TODO: Store these, and resolve them when a listener is attached
return null;
return;
}
const svcId = checkInt(message.serviceId);
if (svcId == null) {
@ -29,29 +26,20 @@ export class LocalLogBackend extends LogBackend {
'Non-integer service id found in local logs:\n',
JSON.stringify(message),
);
return null;
return;
}
// TODO: Can we cache this value? The service ids are reused, so
// we would need a way of invalidating the cache
return (this.serviceNameResolver(svcId) as Promise<string>).then(
(serviceName: string) => {
return _.assign({}, { serviceName }, message);
},
);
} else {
return message;
const serviceName = await this.serviceNameResolver(svcId);
_.assign({}, { serviceName }, message);
}
})
.then((msg: LogMessage | null) => {
if (msg != null) {
_.each(this.globalListeners, (listener) => {
listener.push(`${JSON.stringify(msg)}\n`);
});
}
})
.catch((e) => {
log.error('Error streaming local log output:', e);
_.each(this.globalListeners, (listener) => {
listener.push(`${JSON.stringify(message)}\n`);
});
} catch (e) {
log.error('Error streaming local log output:', e);
}
}
}

View File

@ -1,9 +1,9 @@
import * as JSONstream from 'JSONStream';
import { delay } from 'bluebird';
import * as db from '../db';
import { spawnJournalctl, toJournalDate } from '../lib/journald';
import log from '../lib/supervisor-console';
import { setTimeout } from 'timers/promises';
export type MonitorHook = (message: {
message: string;
@ -91,7 +91,7 @@ class LogMonitor {
wait / 1000
}s`,
);
await delay(wait);
await setTimeout(wait);
return this.start();
},
);

View File

@ -1,4 +1,3 @@
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import { promises as fs, watch } from 'fs';
import * as networkCheck from 'network-checker';
@ -69,13 +68,16 @@ export const startConnectivityCheck = _.once(
return;
}
await Bluebird.resolve(fs.mkdir(constants.vpnStatusPath))
.catch(EEXIST, () => {
try {
await fs.mkdir(constants.vpnStatusPath);
} catch (err: any) {
if (EEXIST(err)) {
log.debug('VPN status path exists.');
})
.then(() => {
watch(constants.vpnStatusPath, vpnStatusInotifyCallback);
});
} else {
throw err;
}
}
watch(constants.vpnStatusPath, vpnStatusInotifyCallback);
if (enable) {
vpnStatusInotifyCallback();
@ -112,9 +114,7 @@ export function enableConnectivityCheck(enable: boolean) {
log.debug(`Connectivity check enabled: ${enable}`);
}
export const connectivityCheckEnabled = Bluebird.method(
() => isConnectivityCheckEnabled,
);
export const connectivityCheckEnabled = async () => isConnectivityCheckEnabled;
const IP_REGEX =
/^(?:(?:balena|docker|rce|tun)[0-9]+|tun[0-9]+|resin-vpn|lo|resin-dns|supervisor0|balena-redsocks|resin-redsocks|br-[0-9a-f]{12})$/;

View File

@ -1,8 +1,8 @@
import * as Bluebird from 'bluebird';
import * as Docker from 'dockerode';
import { Dockerfile } from 'livepush';
import * as device from './device';
import { setTimeout } from 'timers/promises';
interface Opts {
address: string;
@ -54,7 +54,7 @@ export async function initDevice(opts: Opts) {
true,
);
} catch {
await Bluebird.delay(500);
await setTimeout(500);
}
}
return { containerId: supervisorContainer.Id, stageImages };

View File

@ -1,9 +1,10 @@
import * as https from 'https';
import * as stream from 'stream';
import * as zlib from 'zlib';
import * as Promise from 'bluebird';
import * as Bluebird from 'bluebird';
import { expect } from 'chai';
import * as sinon from 'sinon';
import { setTimeout } from 'timers/promises';
import * as config from '~/src/config';
@ -25,7 +26,7 @@ describe('Logger', function () {
configStub = sinon.stub(config, 'getMany').returns(
// @ts-expect-error this should actually work but the type system doesnt like it
Promise.resolve({
Bluebird.resolve({
apiEndpoint: 'https://example.com',
uuid: 'deadbeef',
deviceApiKey: 'secretkey',
@ -49,66 +50,63 @@ describe('Logger', function () {
delete require.cache[require.resolve('~/src/logger')];
});
it('waits the grace period before sending any logs', function () {
it('waits the grace period before sending any logs', async function () {
const clock = sinon.useFakeTimers();
logger.log({ message: 'foobar', serviceId: 15 });
clock.tick(4999);
clock.restore();
return Promise.delay(100).then(() => {
expect(this._req.body).to.equal('');
});
await setTimeout(100);
expect(this._req.body).to.equal('');
});
it('tears down the connection after inactivity', function () {
it('tears down the connection after inactivity', async function () {
const clock = sinon.useFakeTimers();
logger.log({ message: 'foobar', serviceId: 15 });
clock.tick(61000);
clock.restore();
return Promise.delay(100).then(() => {
expect(this._req.end.calledOnce).to.be.true;
});
await setTimeout(100);
expect(this._req.end.calledOnce).to.be.true;
});
it('sends logs as gzipped ndjson', function () {
it('sends logs as gzipped ndjson', async function () {
const timestamp = Date.now();
logger.log({ message: 'foobar', serviceId: 15 });
logger.log({ timestamp: 1337, message: 'foobar', serviceId: 15 });
logger.log({ message: 'foobar' }); // shold be ignored
return Promise.delay(5500).then(() => {
expect(this.requestStub.calledOnce).to.be.true;
const opts = this.requestStub.firstCall.args[0];
await setTimeout(5500);
expect(this.requestStub.calledOnce).to.be.true;
const opts = this.requestStub.firstCall.args[0];
expect(opts.href).to.equal(
'https://example.com/device/v2/deadbeef/log-stream',
);
expect(opts.method).to.equal('POST');
expect(opts.headers).to.deep.equal({
Authorization: 'Bearer secretkey',
'Content-Type': 'application/x-ndjson',
'Content-Encoding': 'gzip',
});
expect(opts.href).to.equal(
'https://example.com/device/v2/deadbeef/log-stream',
);
expect(opts.method).to.equal('POST');
expect(opts.headers).to.deep.equal({
Authorization: 'Bearer secretkey',
'Content-Type': 'application/x-ndjson',
'Content-Encoding': 'gzip',
});
const lines = this._req.body.split('\n');
expect(lines.length).to.equal(3);
expect(lines[2]).to.equal('');
const lines = this._req.body.split('\n');
expect(lines.length).to.equal(3);
expect(lines[2]).to.equal('');
let msg = JSON.parse(lines[0]);
expect(msg).to.have.property('message').that.equals('foobar');
expect(msg).to.have.property('serviceId').that.equals(15);
expect(msg).to.have.property('timestamp').that.is.at.least(timestamp);
msg = JSON.parse(lines[1]);
expect(msg).to.deep.equal({
timestamp: 1337,
message: 'foobar',
serviceId: 15,
});
let msg = JSON.parse(lines[0]);
expect(msg).to.have.property('message').that.equals('foobar');
expect(msg).to.have.property('serviceId').that.equals(15);
expect(msg).to.have.property('timestamp').that.is.at.least(timestamp);
msg = JSON.parse(lines[1]);
expect(msg).to.deep.equal({
timestamp: 1337,
message: 'foobar',
serviceId: 15,
});
});
it('allows logging system messages which are also reported to the eventTracker', function () {
it('allows logging system messages which are also reported to the eventTracker', async function () {
const timestamp = Date.now();
logger.logSystemMessage(
'Hello there!',
@ -116,15 +114,14 @@ describe('Logger', function () {
'Some event name',
);
return Promise.delay(5500).then(() => {
const lines = this._req.body.split('\n');
expect(lines.length).to.equal(2);
expect(lines[1]).to.equal('');
await setTimeout(5500);
const lines = this._req.body.split('\n');
expect(lines.length).to.equal(2);
expect(lines[1]).to.equal('');
const msg = JSON.parse(lines[0]);
expect(msg).to.have.property('message').that.equals('Hello there!');
expect(msg).to.have.property('isSystem').that.equals(true);
expect(msg).to.have.property('timestamp').that.is.at.least(timestamp);
});
const msg = JSON.parse(lines[0]);
expect(msg).to.have.property('message').that.equals('Hello there!');
expect(msg).to.have.property('isSystem').that.equals(true);
expect(msg).to.have.property('timestamp').that.is.at.least(timestamp);
});
});

View File

@ -1,5 +1,5 @@
import { SinonStub, stub, spy, SinonSpy } from 'sinon';
import { Promise } from 'bluebird';
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import rewire = require('rewire');
import { expect } from 'chai';
@ -30,7 +30,7 @@ const stateEndpointBody = {
const req = {
getAsync: () =>
Promise.resolve([
Bluebird.resolve([
{
statusCode: 200,
headers: { etag: 'abc' },
@ -66,7 +66,7 @@ describe('Target state', () => {
// new request returns 304
const newReq = {
getAsync: () =>
Promise.resolve([
Bluebird.resolve([
{
statusCode: 304,
headers: {},
@ -106,7 +106,7 @@ describe('Target state', () => {
// new request returns 304
const newReq = {
getAsync: () =>
Promise.resolve([
Bluebird.resolve([
{
statusCode: 304,
headers: {},
@ -148,7 +148,7 @@ describe('Target state', () => {
// new request returns 304
const newReq = {
getAsync: () =>
Promise.resolve([
Bluebird.resolve([
{
statusCode: 304,
headers: {},
@ -246,7 +246,7 @@ describe('Target state', () => {
(request.getRequestInstance as SinonStub).restore();
const newReq = {
getAsync: () =>
Promise.resolve([
Bluebird.resolve([
{
statusCode: 304,
headers: {},