mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-06-18 07:18:14 +00:00
Make network-manager module a singleton
Change-type: patch Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,3 +17,4 @@ test/data/led_file
|
|||||||
report.xml
|
report.xml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.tsbuildinfo
|
.tsbuildinfo
|
||||||
|
.prettierrc
|
||||||
|
2
src/application-manager.d.ts
vendored
2
src/application-manager.d.ts
vendored
@ -13,7 +13,6 @@ import DeviceState from './device-state';
|
|||||||
import { APIBinder } from './api-binder';
|
import { APIBinder } from './api-binder';
|
||||||
import * as config from './config';
|
import * as config from './config';
|
||||||
|
|
||||||
import NetworkManager from './compose/network-manager';
|
|
||||||
import VolumeManager from './compose/volume-manager';
|
import VolumeManager from './compose/volume-manager';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -50,7 +49,6 @@ class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
public services: ServiceManager;
|
public services: ServiceManager;
|
||||||
public volumes: VolumeManager;
|
public volumes: VolumeManager;
|
||||||
public networks: NetworkManager;
|
|
||||||
|
|
||||||
public proxyvisor: any;
|
public proxyvisor: any;
|
||||||
public timeSpentFetching: number;
|
public timeSpentFetching: number;
|
||||||
|
@ -14,17 +14,15 @@ import { docker } from './lib/docker-utils';
|
|||||||
import { LocalModeManager } from './local-mode';
|
import { LocalModeManager } from './local-mode';
|
||||||
import * as updateLock from './lib/update-lock';
|
import * as updateLock from './lib/update-lock';
|
||||||
import { checkTruthy, checkInt, checkString } from './lib/validation';
|
import { checkTruthy, checkInt, checkString } from './lib/validation';
|
||||||
import {
|
import { ContractViolationError, InternalInconsistencyError } from './lib/errors';
|
||||||
ContractViolationError,
|
|
||||||
InternalInconsistencyError,
|
|
||||||
} from './lib/errors';
|
|
||||||
|
|
||||||
import * as dbFormat from './device-state/db-format';
|
import * as dbFormat from './device-state/db-format';
|
||||||
|
|
||||||
import { Network } from './compose/network';
|
import { Network } from './compose/network';
|
||||||
import { ServiceManager } from './compose/service-manager';
|
import { ServiceManager } from './compose/service-manager';
|
||||||
import * as Images from './compose/images';
|
import * as Images from './compose/images';
|
||||||
import { NetworkManager } from './compose/network-manager';
|
import { Network } from './compose/network';
|
||||||
|
import * as networkManager from './compose/network-manager';
|
||||||
import { VolumeManager } from './compose/volume-manager';
|
import { VolumeManager } from './compose/volume-manager';
|
||||||
import * as compositionSteps from './compose/composition-steps';
|
import * as compositionSteps from './compose/composition-steps';
|
||||||
|
|
||||||
@ -37,7 +35,7 @@ import { serviceAction } from './device-api/common';
|
|||||||
import * as db from './db';
|
import * as db from './db';
|
||||||
|
|
||||||
// TODO: move this to an Image class?
|
// TODO: move this to an Image class?
|
||||||
const imageForService = (service) => ({
|
const imageForService = service => ({
|
||||||
name: service.imageName,
|
name: service.imageName,
|
||||||
appId: service.appId,
|
appId: service.appId,
|
||||||
serviceId: service.serviceId,
|
serviceId: service.serviceId,
|
||||||
@ -47,7 +45,7 @@ const imageForService = (service) => ({
|
|||||||
dependent: 0,
|
dependent: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchAction = (service) => ({
|
const fetchAction = service => ({
|
||||||
action: 'fetch',
|
action: 'fetch',
|
||||||
image: imageForService(service),
|
image: imageForService(service),
|
||||||
serviceId: service.serviceId,
|
serviceId: service.serviceId,
|
||||||
@ -78,12 +76,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
this.fetchAction = fetchAction;
|
this.fetchAction = fetchAction;
|
||||||
|
|
||||||
this._strategySteps = {
|
this._strategySteps = {
|
||||||
'download-then-kill'(
|
'download-then-kill'(current, target, needsDownload, dependenciesMetForKill) {
|
||||||
current,
|
|
||||||
target,
|
|
||||||
needsDownload,
|
|
||||||
dependenciesMetForKill,
|
|
||||||
) {
|
|
||||||
if (needsDownload) {
|
if (needsDownload) {
|
||||||
return fetchAction(target);
|
return fetchAction(target);
|
||||||
} else if (dependenciesMetForKill()) {
|
} else if (dependenciesMetForKill()) {
|
||||||
@ -135,12 +128,8 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
this._nextStepsForNetwork = this._nextStepsForNetwork.bind(this);
|
this._nextStepsForNetwork = this._nextStepsForNetwork.bind(this);
|
||||||
this._nextStepForService = this._nextStepForService.bind(this);
|
this._nextStepForService = this._nextStepForService.bind(this);
|
||||||
this._nextStepsForAppUpdate = this._nextStepsForAppUpdate.bind(this);
|
this._nextStepsForAppUpdate = this._nextStepsForAppUpdate.bind(this);
|
||||||
this.setTargetVolatileForService = this.setTargetVolatileForService.bind(
|
this.setTargetVolatileForService = this.setTargetVolatileForService.bind(this);
|
||||||
this,
|
this.clearTargetVolatileForServices = this.clearTargetVolatileForServices.bind(this);
|
||||||
);
|
|
||||||
this.clearTargetVolatileForServices = this.clearTargetVolatileForServices.bind(
|
|
||||||
this,
|
|
||||||
);
|
|
||||||
this.getTargetApps = this.getTargetApps.bind(this);
|
this.getTargetApps = this.getTargetApps.bind(this);
|
||||||
this.getDependentTargets = this.getDependentTargets.bind(this);
|
this.getDependentTargets = this.getDependentTargets.bind(this);
|
||||||
this._compareImages = this._compareImages.bind(this);
|
this._compareImages = this._compareImages.bind(this);
|
||||||
@ -148,9 +137,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
this.stopAll = this.stopAll.bind(this);
|
this.stopAll = this.stopAll.bind(this);
|
||||||
this._lockingIfNecessary = this._lockingIfNecessary.bind(this);
|
this._lockingIfNecessary = this._lockingIfNecessary.bind(this);
|
||||||
this.executeStepAction = this.executeStepAction.bind(this);
|
this.executeStepAction = this.executeStepAction.bind(this);
|
||||||
this.getExtraStateForComparison = this.getExtraStateForComparison.bind(
|
this.getExtraStateForComparison = this.getExtraStateForComparison.bind(this);
|
||||||
this,
|
|
||||||
);
|
|
||||||
this.getRequiredSteps = this.getRequiredSteps.bind(this);
|
this.getRequiredSteps = this.getRequiredSteps.bind(this);
|
||||||
this.serviceNameFromId = this.serviceNameFromId.bind(this);
|
this.serviceNameFromId = this.serviceNameFromId.bind(this);
|
||||||
this.removeAllVolumesForApp = this.removeAllVolumesForApp.bind(this);
|
this.removeAllVolumesForApp = this.removeAllVolumesForApp.bind(this);
|
||||||
@ -160,7 +147,6 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
this.apiBinder = apiBinder;
|
this.apiBinder = apiBinder;
|
||||||
|
|
||||||
this.services = new ServiceManager();
|
this.services = new ServiceManager();
|
||||||
this.networks = new NetworkManager();
|
|
||||||
this.volumes = new VolumeManager();
|
this.volumes = new VolumeManager();
|
||||||
this.proxyvisor = new Proxyvisor({
|
this.proxyvisor = new Proxyvisor({
|
||||||
applications: this,
|
applications: this,
|
||||||
@ -174,14 +160,13 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
this.actionExecutors = compositionSteps.getExecutors({
|
this.actionExecutors = compositionSteps.getExecutors({
|
||||||
lockFn: this._lockingIfNecessary,
|
lockFn: this._lockingIfNecessary,
|
||||||
services: this.services,
|
services: this.services,
|
||||||
networks: this.networks,
|
|
||||||
volumes: this.volumes,
|
volumes: this.volumes,
|
||||||
applications: this,
|
applications: this,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
containerStarted: (id) => {
|
containerStarted: id => {
|
||||||
this._containerStarted[id] = true;
|
this._containerStarted[id] = true;
|
||||||
},
|
},
|
||||||
containerKilled: (id) => {
|
containerKilled: id => {
|
||||||
delete this._containerStarted[id];
|
delete this._containerStarted[id];
|
||||||
},
|
},
|
||||||
fetchStart: () => {
|
fetchStart: () => {
|
||||||
@ -190,16 +175,14 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
fetchEnd: () => {
|
fetchEnd: () => {
|
||||||
this.fetchesInProgress -= 1;
|
this.fetchesInProgress -= 1;
|
||||||
},
|
},
|
||||||
fetchTime: (time) => {
|
fetchTime: time => {
|
||||||
this.timeSpentFetching += time;
|
this.timeSpentFetching += time;
|
||||||
},
|
},
|
||||||
stateReport: (state) => this.reportCurrentState(state),
|
stateReport: state => this.reportCurrentState(state),
|
||||||
bestDeltaSource: this.bestDeltaSource,
|
bestDeltaSource: this.bestDeltaSource,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.validActions = _.keys(this.actionExecutors).concat(
|
this.validActions = _.keys(this.actionExecutors).concat(this.proxyvisor.validActions);
|
||||||
this.proxyvisor.validActions,
|
|
||||||
);
|
|
||||||
this.router = createApplicationManagerRouter(this);
|
this.router = createApplicationManagerRouter(this);
|
||||||
Images.on('change', this.reportCurrentState);
|
Images.on('change', this.reportCurrentState);
|
||||||
this.services.on('change', this.reportCurrentState);
|
this.services.on('change', this.reportCurrentState);
|
||||||
@ -213,7 +196,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
await Images.initialized;
|
await Images.initialized;
|
||||||
await Images.cleanupDatabase();
|
await Images.cleanupDatabase();
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
return docker.listContainers({ all: true }).then((containers) => {
|
return docker.listContainers({ all: true }).then(containers => {
|
||||||
return logger.clearOutOfDateDBLogs(_.map(containers, 'Id'));
|
return logger.clearOutOfDateDBLogs(_.map(containers, 'Id'));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -265,10 +248,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (apps[appId].services[imageId] == null) {
|
if (apps[appId].services[imageId] == null) {
|
||||||
apps[appId].services[imageId] = _.pick(service, [
|
apps[appId].services[imageId] = _.pick(service, ['status', 'releaseId']);
|
||||||
'status',
|
|
||||||
'releaseId',
|
|
||||||
]);
|
|
||||||
creationTimesAndReleases[appId][imageId] = _.pick(service, [
|
creationTimesAndReleases[appId][imageId] = _.pick(service, [
|
||||||
'createdAt',
|
'createdAt',
|
||||||
'releaseId',
|
'releaseId',
|
||||||
@ -357,7 +337,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
// multi-app warning!
|
// multi-app warning!
|
||||||
// This is just wrong on every level
|
// This is just wrong on every level
|
||||||
_.each(apps, (app) => {
|
_.each(apps, app => {
|
||||||
app.commit = currentCommit;
|
app.commit = currentCommit;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -367,7 +347,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
getCurrentForComparison() {
|
getCurrentForComparison() {
|
||||||
return Promise.join(
|
return Promise.join(
|
||||||
this.services.getAll(),
|
this.services.getAll(),
|
||||||
this.networks.getAll(),
|
networkManager.getAll(),
|
||||||
this.volumes.getAll(),
|
this.volumes.getAll(),
|
||||||
config.get('currentCommit'),
|
config.get('currentCommit'),
|
||||||
this._buildApps,
|
this._buildApps,
|
||||||
@ -377,7 +357,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
getCurrentApp(appId) {
|
getCurrentApp(appId) {
|
||||||
return Promise.join(
|
return Promise.join(
|
||||||
this.services.getAllByAppId(appId),
|
this.services.getAllByAppId(appId),
|
||||||
this.networks.getAllByAppId(appId),
|
networkManager.getAllByAppId(appId),
|
||||||
this.volumes.getAllByAppId(appId),
|
this.volumes.getAllByAppId(appId),
|
||||||
config.get('currentCommit'),
|
config.get('currentCommit'),
|
||||||
this._buildApps,
|
this._buildApps,
|
||||||
@ -422,19 +402,13 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toBeMaybeUpdated = _.intersection(
|
const toBeMaybeUpdated = _.intersection(targetServiceIds, currentServiceIds);
|
||||||
targetServiceIds,
|
|
||||||
currentServiceIds,
|
|
||||||
);
|
|
||||||
const currentServicesPerId = {};
|
const currentServicesPerId = {};
|
||||||
const targetServicesPerId = _.keyBy(targetServices, 'serviceId');
|
const targetServicesPerId = _.keyBy(targetServices, 'serviceId');
|
||||||
for (const serviceId of toBeMaybeUpdated) {
|
for (const serviceId of toBeMaybeUpdated) {
|
||||||
const currentServiceContainers = _.filter(currentServices, { serviceId });
|
const currentServiceContainers = _.filter(currentServices, { serviceId });
|
||||||
if (currentServiceContainers.length > 1) {
|
if (currentServiceContainers.length > 1) {
|
||||||
currentServicesPerId[serviceId] = _.maxBy(
|
currentServicesPerId[serviceId] = _.maxBy(currentServiceContainers, 'createdAt');
|
||||||
currentServiceContainers,
|
|
||||||
'createdAt',
|
|
||||||
);
|
|
||||||
|
|
||||||
// All but the latest container for this service are spurious and should be removed
|
// All but the latest container for this service are spurious and should be removed
|
||||||
for (const service of _.without(
|
for (const service of _.without(
|
||||||
@ -454,7 +428,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
// Returns true if a service matches its target except it should be running and it is not, but we've
|
// Returns true if a service matches its target except it should be running and it is not, but we've
|
||||||
// already started it before. In this case it means it just exited so we don't want to start it again.
|
// already started it before. In this case it means it just exited so we don't want to start it again.
|
||||||
const alreadyStarted = (serviceId) => {
|
const alreadyStarted = serviceId => {
|
||||||
return (
|
return (
|
||||||
currentServicesPerId[serviceId].isEqualExceptForRunningState(
|
currentServicesPerId[serviceId].isEqualExceptForRunningState(
|
||||||
targetServicesPerId[serviceId],
|
targetServicesPerId[serviceId],
|
||||||
@ -467,7 +441,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
const needUpdate = _.filter(
|
const needUpdate = _.filter(
|
||||||
toBeMaybeUpdated,
|
toBeMaybeUpdated,
|
||||||
(serviceId) =>
|
serviceId =>
|
||||||
!currentServicesPerId[serviceId].isEqual(
|
!currentServicesPerId[serviceId].isEqual(
|
||||||
targetServicesPerId[serviceId],
|
targetServicesPerId[serviceId],
|
||||||
containerIds,
|
containerIds,
|
||||||
@ -502,7 +476,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
const toBeUpdated = _.filter(
|
const toBeUpdated = _.filter(
|
||||||
_.intersection(targetNames, currentNames),
|
_.intersection(targetNames, currentNames),
|
||||||
(name) => !current[name].isEqualConfig(target[name]),
|
name => !current[name].isEqualConfig(target[name]),
|
||||||
);
|
);
|
||||||
for (const name of toBeUpdated) {
|
for (const name of toBeUpdated) {
|
||||||
outputPairs.push({
|
outputPairs.push({
|
||||||
@ -515,7 +489,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compareNetworksForUpdate({ current, target }) {
|
compareNetworksForUpdate({ current, target }) {
|
||||||
return this._compareNetworksOrVolumesForUpdate(this.networks, {
|
return this._compareNetworksOrVolumesForUpdate(networkManager, {
|
||||||
current,
|
current,
|
||||||
target,
|
target,
|
||||||
});
|
});
|
||||||
@ -535,8 +509,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
const hasNetwork = _.some(
|
const hasNetwork = _.some(
|
||||||
networkPairs,
|
networkPairs,
|
||||||
(pair) =>
|
pair => `${service.appId}_${pair.current?.name}` === service.networkMode,
|
||||||
`${service.appId}_${pair.current?.name}` === service.networkMode,
|
|
||||||
);
|
);
|
||||||
if (hasNetwork) {
|
if (hasNetwork) {
|
||||||
return true;
|
return true;
|
||||||
@ -545,7 +518,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
const name = _.split(volume, ':')[0];
|
const name = _.split(volume, ':')[0];
|
||||||
return _.some(
|
return _.some(
|
||||||
volumePairs,
|
volumePairs,
|
||||||
(pair) => `${service.appId}_${pair.current?.name}` === name,
|
pair => `${service.appId}_${pair.current?.name}` === name,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return hasVolume;
|
return hasVolume;
|
||||||
@ -553,15 +526,10 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
// TODO: account for volumes-from, networks-from, links, etc
|
// TODO: account for volumes-from, networks-from, links, etc
|
||||||
// TODO: support networks instead of only networkMode
|
// TODO: support networks instead of only networkMode
|
||||||
_dependenciesMetForServiceStart(
|
_dependenciesMetForServiceStart(target, networkPairs, volumePairs, pendingPairs) {
|
||||||
target,
|
|
||||||
networkPairs,
|
|
||||||
volumePairs,
|
|
||||||
pendingPairs,
|
|
||||||
) {
|
|
||||||
// for dependsOn, check no install or update pairs have that service
|
// for dependsOn, check no install or update pairs have that service
|
||||||
const dependencyUnmet = _.some(target.dependsOn, (dependency) =>
|
const dependencyUnmet = _.some(target.dependsOn, dependency =>
|
||||||
_.some(pendingPairs, (pair) => pair.target?.serviceName === dependency),
|
_.some(pendingPairs, pair => pair.target?.serviceName === dependency),
|
||||||
);
|
);
|
||||||
if (dependencyUnmet) {
|
if (dependencyUnmet) {
|
||||||
return false;
|
return false;
|
||||||
@ -570,7 +538,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
if (
|
if (
|
||||||
_.some(
|
_.some(
|
||||||
networkPairs,
|
networkPairs,
|
||||||
(pair) => `${target.appId}_${pair.target?.name}` === target.networkMode,
|
pair => `${target.appId}_${pair.target?.name}` === target.networkMode,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
@ -583,7 +551,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
return _.some(
|
return _.some(
|
||||||
volumePairs,
|
volumePairs,
|
||||||
(pair) => `${target.appId}_${pair.target?.name}` === sourceName,
|
pair => `${target.appId}_${pair.target?.name}` === sourceName,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return !volumeUnmet;
|
return !volumeUnmet;
|
||||||
@ -592,12 +560,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
// Unless the update strategy requires an early kill (i.e. kill-then-download, delete-then-download), we only want
|
// Unless the update strategy requires an early kill (i.e. kill-then-download, delete-then-download), we only want
|
||||||
// to kill a service once the images for the services it depends on have been downloaded, so as to minimize
|
// to kill a service once the images for the services it depends on have been downloaded, so as to minimize
|
||||||
// downtime (but not block the killing too much, potentially causing a deadlock)
|
// downtime (but not block the killing too much, potentially causing a deadlock)
|
||||||
_dependenciesMetForServiceKill(
|
_dependenciesMetForServiceKill(target, targetApp, availableImages, localMode) {
|
||||||
target,
|
|
||||||
targetApp,
|
|
||||||
availableImages,
|
|
||||||
localMode,
|
|
||||||
) {
|
|
||||||
// Because we only check for an image being available, in local mode this will always
|
// Because we only check for an image being available, in local mode this will always
|
||||||
// be the case, so return true regardless. If this function ever checks for anything else,
|
// be the case, so return true regardless. If this function ever checks for anything else,
|
||||||
// we'll need to change the logic here
|
// we'll need to change the logic here
|
||||||
@ -612,7 +575,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
if (
|
if (
|
||||||
!_.some(
|
!_.some(
|
||||||
availableImages,
|
availableImages,
|
||||||
(image) =>
|
image =>
|
||||||
image.dockerImageId === dependencyService.image ||
|
image.dockerImageId === dependencyService.image ||
|
||||||
Images.isSameImage(image, { name: dependencyService.imageName }),
|
Images.isSameImage(image, { name: dependencyService.imageName }),
|
||||||
)
|
)
|
||||||
@ -633,7 +596,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
) {
|
) {
|
||||||
// Check none of the currentApp.services use this network or volume
|
// Check none of the currentApp.services use this network or volume
|
||||||
if (current != null) {
|
if (current != null) {
|
||||||
const dependencies = _.filter(currentApp.services, (service) =>
|
const dependencies = _.filter(currentApp.services, service =>
|
||||||
dependencyComparisonFn(service, current),
|
dependencyComparisonFn(service, current),
|
||||||
);
|
);
|
||||||
if (_.isEmpty(dependencies)) {
|
if (_.isEmpty(dependencies)) {
|
||||||
@ -679,9 +642,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
const dependencyComparisonFn = (service, curr) =>
|
const dependencyComparisonFn = (service, curr) =>
|
||||||
_.some(service.config.volumes, function (volumeDefinition) {
|
_.some(service.config.volumes, function (volumeDefinition) {
|
||||||
const [sourceName, destName] = volumeDefinition.split(':');
|
const [sourceName, destName] = volumeDefinition.split(':');
|
||||||
return (
|
return destName != null && sourceName === `${service.appId}_${curr?.name}`;
|
||||||
destName != null && sourceName === `${service.appId}_${curr?.name}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
return this._nextStepsForNetworkOrVolume(
|
return this._nextStepsForNetworkOrVolume(
|
||||||
{ current, target },
|
{ current, target },
|
||||||
@ -694,10 +655,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
// Infers steps that do not require creating a new container
|
// Infers steps that do not require creating a new container
|
||||||
_updateContainerStep(current, target) {
|
_updateContainerStep(current, target) {
|
||||||
if (
|
if (current.releaseId !== target.releaseId || current.imageId !== target.imageId) {
|
||||||
current.releaseId !== target.releaseId ||
|
|
||||||
current.imageId !== target.imageId
|
|
||||||
) {
|
|
||||||
return serviceAction('updateMetadata', target.serviceId, current, target);
|
return serviceAction('updateMetadata', target.serviceId, current, target);
|
||||||
} else if (target.config.running) {
|
} else if (target.config.running) {
|
||||||
return serviceAction('start', target.serviceId, current, target);
|
return serviceAction('start', target.serviceId, current, target);
|
||||||
@ -716,12 +674,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_nextStepForService(
|
_nextStepForService({ current, target }, updateContext, localMode, containerIds) {
|
||||||
{ current, target },
|
|
||||||
updateContext,
|
|
||||||
localMode,
|
|
||||||
containerIds,
|
|
||||||
) {
|
|
||||||
const {
|
const {
|
||||||
targetApp,
|
targetApp,
|
||||||
networkPairs,
|
networkPairs,
|
||||||
@ -746,7 +699,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
if (!localMode) {
|
if (!localMode) {
|
||||||
needsDownload = !_.some(
|
needsDownload = !_.some(
|
||||||
availableImages,
|
availableImages,
|
||||||
(image) =>
|
image =>
|
||||||
image.dockerImageId === target?.config.image ||
|
image.dockerImageId === target?.config.image ||
|
||||||
Images.isSameImage(image, { name: target.imageName }),
|
Images.isSameImage(image, { name: target.imageName }),
|
||||||
);
|
);
|
||||||
@ -768,12 +721,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
const dependenciesMetForKill = () => {
|
const dependenciesMetForKill = () => {
|
||||||
return (
|
return (
|
||||||
!needsDownload &&
|
!needsDownload &&
|
||||||
this._dependenciesMetForServiceKill(
|
this._dependenciesMetForServiceKill(target, targetApp, availableImages, localMode)
|
||||||
target,
|
|
||||||
targetApp,
|
|
||||||
availableImages,
|
|
||||||
localMode,
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -797,9 +745,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
dependenciesMetForStart,
|
dependenciesMetForStart,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let strategy = checkString(
|
let strategy = checkString(target.config.labels['io.balena.update.strategy']);
|
||||||
target.config.labels['io.balena.update.strategy'],
|
|
||||||
);
|
|
||||||
const validStrategies = [
|
const validStrategies = [
|
||||||
'download-then-kill',
|
'download-then-kill',
|
||||||
'kill-then-download',
|
'kill-then-download',
|
||||||
@ -809,9 +755,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
if (!_.includes(validStrategies, strategy)) {
|
if (!_.includes(validStrategies, strategy)) {
|
||||||
strategy = 'download-then-kill';
|
strategy = 'download-then-kill';
|
||||||
}
|
}
|
||||||
const timeout = checkInt(
|
const timeout = checkInt(target.config.labels['io.balena.update.handover-timeout']);
|
||||||
target.config.labels['io.balena.update.handover-timeout'],
|
|
||||||
);
|
|
||||||
return this._strategySteps[strategy](
|
return this._strategySteps[strategy](
|
||||||
current,
|
current,
|
||||||
target,
|
target,
|
||||||
@ -857,11 +801,8 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
if (
|
if (
|
||||||
currentApp.services?.length === 1 &&
|
currentApp.services?.length === 1 &&
|
||||||
targetApp.services?.length === 1 &&
|
targetApp.services?.length === 1 &&
|
||||||
targetApp.services[0].serviceName ===
|
targetApp.services[0].serviceName === currentApp.services[0].serviceName &&
|
||||||
currentApp.services[0].serviceName &&
|
checkTruthy(currentApp.services[0].config.labels['io.balena.legacy-container'])
|
||||||
checkTruthy(
|
|
||||||
currentApp.services[0].config.labels['io.balena.legacy-container'],
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
// This is a legacy preloaded app or container, so we didn't have things like serviceId.
|
// This is a legacy preloaded app or container, so we didn't have things like serviceId.
|
||||||
// We hack a few things to avoid an unnecessary restart of the preloaded app
|
// We hack a few things to avoid an unnecessary restart of the preloaded app
|
||||||
@ -881,11 +822,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
current: currentApp.volumes,
|
current: currentApp.volumes,
|
||||||
target: targetApp.volumes,
|
target: targetApp.volumes,
|
||||||
});
|
});
|
||||||
const {
|
const { removePairs, installPairs, updatePairs } = this.compareServicesForUpdate(
|
||||||
removePairs,
|
|
||||||
installPairs,
|
|
||||||
updatePairs,
|
|
||||||
} = this.compareServicesForUpdate(
|
|
||||||
currentApp.services,
|
currentApp.services,
|
||||||
targetApp.services,
|
targetApp.services,
|
||||||
containerIds,
|
containerIds,
|
||||||
@ -952,7 +889,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const appId = targetApp.appId ?? currentApp.appId;
|
const appId = targetApp.appId ?? currentApp.appId;
|
||||||
return _.map(steps, (step) => _.assign({}, step, { appId }));
|
return _.map(steps, step => _.assign({}, step, { appId }));
|
||||||
}
|
}
|
||||||
|
|
||||||
async setTarget(apps, dependent, source, maybeTrx) {
|
async setTarget(apps, dependent, source, maybeTrx) {
|
||||||
@ -990,10 +927,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
const filteredApps = _.cloneDeep(apps);
|
const filteredApps = _.cloneDeep(apps);
|
||||||
_.each(
|
_.each(
|
||||||
fulfilledContracts,
|
fulfilledContracts,
|
||||||
(
|
({ valid, unmetServices, fulfilledServices, unmetAndOptional }, appId) => {
|
||||||
{ valid, unmetServices, fulfilledServices, unmetAndOptional },
|
|
||||||
appId,
|
|
||||||
) => {
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
contractViolators[apps[appId].name] = unmetServices;
|
contractViolators[apps[appId].name] = unmetServices;
|
||||||
return delete filteredApps[appId];
|
return delete filteredApps[appId];
|
||||||
@ -1035,17 +969,15 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearTargetVolatileForServices(imageIds) {
|
clearTargetVolatileForServices(imageIds) {
|
||||||
return imageIds.map(
|
return imageIds.map(imageId => (this._targetVolatilePerImageId[imageId] = {}));
|
||||||
(imageId) => (this._targetVolatilePerImageId[imageId] = {}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTargetApps() {
|
async getTargetApps() {
|
||||||
const apps = await dbFormat.getApps();
|
const apps = await dbFormat.getApps();
|
||||||
|
|
||||||
_.each(apps, (app) => {
|
_.each(apps, app => {
|
||||||
if (!_.isEmpty(app.services)) {
|
if (!_.isEmpty(app.services)) {
|
||||||
app.services = _.mapValues(app.services, (svc) => {
|
app.services = _.mapValues(app.services, svc => {
|
||||||
if (this._targetVolatilePerImageId[svc.imageId] != null) {
|
if (this._targetVolatilePerImageId[svc.imageId] != null) {
|
||||||
return {
|
return {
|
||||||
...svc,
|
...svc,
|
||||||
@ -1092,8 +1024,8 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
// - are locally available (i.e. an image with the same digest exists)
|
// - are locally available (i.e. an image with the same digest exists)
|
||||||
// - are not saved to the DB with all their metadata (serviceId, serviceName, etc)
|
// - are not saved to the DB with all their metadata (serviceId, serviceName, etc)
|
||||||
_compareImages(current, target, available, localMode) {
|
_compareImages(current, target, available, localMode) {
|
||||||
const allImagesForTargetApp = (app) => _.map(app.services, imageForService);
|
const allImagesForTargetApp = app => _.map(app.services, imageForService);
|
||||||
const allImagesForCurrentApp = (app) =>
|
const allImagesForCurrentApp = app =>
|
||||||
_.map(app.services, function (service) {
|
_.map(app.services, function (service) {
|
||||||
const img =
|
const img =
|
||||||
_.find(available, {
|
_.find(available, {
|
||||||
@ -1102,13 +1034,13 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}) ?? _.find(available, { dockerImageId: service.config.image });
|
}) ?? _.find(available, { dockerImageId: service.config.image });
|
||||||
return _.omit(img, ['dockerImageId', 'id']);
|
return _.omit(img, ['dockerImageId', 'id']);
|
||||||
});
|
});
|
||||||
const allImageDockerIdsForTargetApp = (app) =>
|
const allImageDockerIdsForTargetApp = app =>
|
||||||
_(app.services)
|
_(app.services)
|
||||||
.map((svc) => [svc.imageName, svc.config.image])
|
.map(svc => [svc.imageName, svc.config.image])
|
||||||
.filter((img) => img[1] != null)
|
.filter(img => img[1] != null)
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
const availableWithoutIds = _.map(available, (image) =>
|
const availableWithoutIds = _.map(available, image =>
|
||||||
_.omit(image, ['dockerImageId', 'id']),
|
_.omit(image, ['dockerImageId', 'id']),
|
||||||
);
|
);
|
||||||
const currentImages = _.flatMap(current.local.apps, allImagesForCurrentApp);
|
const currentImages = _.flatMap(current.local.apps, allImagesForCurrentApp);
|
||||||
@ -1119,16 +1051,16 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
const availableAndUnused = _.filter(
|
const availableAndUnused = _.filter(
|
||||||
availableWithoutIds,
|
availableWithoutIds,
|
||||||
(image) =>
|
image =>
|
||||||
!_.some(currentImages.concat(targetImages), (imageInUse) =>
|
!_.some(currentImages.concat(targetImages), imageInUse =>
|
||||||
_.isEqual(image, imageInUse),
|
_.isEqual(image, imageInUse),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const imagesToDownload = _.filter(
|
const imagesToDownload = _.filter(
|
||||||
targetImages,
|
targetImages,
|
||||||
(targetImage) =>
|
targetImage =>
|
||||||
!_.some(available, (availableImage) =>
|
!_.some(available, availableImage =>
|
||||||
Images.isSameImage(availableImage, targetImage),
|
Images.isSameImage(availableImage, targetImage),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -1136,46 +1068,39 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
let imagesToSave = [];
|
let imagesToSave = [];
|
||||||
if (!localMode) {
|
if (!localMode) {
|
||||||
imagesToSave = _.filter(targetImages, function (targetImage) {
|
imagesToSave = _.filter(targetImages, function (targetImage) {
|
||||||
const isActuallyAvailable = _.some(available, function (
|
const isActuallyAvailable = _.some(available, function (availableImage) {
|
||||||
availableImage,
|
|
||||||
) {
|
|
||||||
if (Images.isSameImage(availableImage, targetImage)) {
|
if (Images.isSameImage(availableImage, targetImage)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (
|
if (availableImage.dockerImageId === targetImageDockerIds[targetImage.name]) {
|
||||||
availableImage.dockerImageId ===
|
|
||||||
targetImageDockerIds[targetImage.name]
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
const isNotSaved = !_.some(availableWithoutIds, (img) =>
|
const isNotSaved = !_.some(availableWithoutIds, img =>
|
||||||
_.isEqual(img, targetImage),
|
_.isEqual(img, targetImage),
|
||||||
);
|
);
|
||||||
return isActuallyAvailable && isNotSaved;
|
return isActuallyAvailable && isNotSaved;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const deltaSources = _.map(imagesToDownload, (image) => {
|
const deltaSources = _.map(imagesToDownload, image => {
|
||||||
return this.bestDeltaSource(image, available);
|
return this.bestDeltaSource(image, available);
|
||||||
});
|
});
|
||||||
const proxyvisorImages = this.proxyvisor.imagesInUse(current, target);
|
const proxyvisorImages = this.proxyvisor.imagesInUse(current, target);
|
||||||
|
|
||||||
const potentialDeleteThenDownload = _.filter(
|
const potentialDeleteThenDownload = _.filter(
|
||||||
current.local.apps.services,
|
current.local.apps.services,
|
||||||
(svc) =>
|
svc =>
|
||||||
svc.config.labels['io.balena.update.strategy'] ===
|
svc.config.labels['io.balena.update.strategy'] === 'delete-then-download' &&
|
||||||
'delete-then-download' && svc.status === 'Stopped',
|
svc.status === 'Stopped',
|
||||||
);
|
);
|
||||||
|
|
||||||
const imagesToRemove = _.filter(
|
const imagesToRemove = _.filter(
|
||||||
availableAndUnused.concat(potentialDeleteThenDownload),
|
availableAndUnused.concat(potentialDeleteThenDownload),
|
||||||
function (image) {
|
function (image) {
|
||||||
const notUsedForDelta = !_.includes(deltaSources, image.name);
|
const notUsedForDelta = !_.includes(deltaSources, image.name);
|
||||||
const notUsedByProxyvisor = !_.some(
|
const notUsedByProxyvisor = !_.some(proxyvisorImages, proxyvisorImage =>
|
||||||
proxyvisorImages,
|
|
||||||
(proxyvisorImage) =>
|
|
||||||
Images.isSameImage(image, { name: proxyvisorImage }),
|
Images.isSameImage(image, { name: proxyvisorImage }),
|
||||||
);
|
);
|
||||||
return notUsedForDelta && notUsedByProxyvisor;
|
return notUsedForDelta && notUsedByProxyvisor;
|
||||||
@ -1218,10 +1143,8 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
// multi-app warning: this will break
|
// multi-app warning: this will break
|
||||||
let appsForVolumeRemoval;
|
let appsForVolumeRemoval;
|
||||||
if (!localMode) {
|
if (!localMode) {
|
||||||
const currentAppIds = _.keys(current.local.apps).map((n) =>
|
const currentAppIds = _.keys(current.local.apps).map(n => checkInt(n));
|
||||||
checkInt(n),
|
const targetAppIds = _.keys(target.local.apps).map(n => checkInt(n));
|
||||||
);
|
|
||||||
const targetAppIds = _.keys(target.local.apps).map((n) => checkInt(n));
|
|
||||||
appsForVolumeRemoval = _.difference(currentAppIds, targetAppIds);
|
appsForVolumeRemoval = _.difference(currentAppIds, targetAppIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1234,15 +1157,11 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
const { services } = currentByAppId[appId];
|
const { services } = currentByAppId[appId];
|
||||||
for (const n in services) {
|
for (const n in services) {
|
||||||
if (
|
if (
|
||||||
checkTruthy(
|
checkTruthy(services[n].config.labels['io.balena.features.supervisor-api'])
|
||||||
services[n].config.labels['io.balena.features.supervisor-api'],
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
containersUsingSupervisorNetwork = true;
|
containersUsingSupervisorNetwork = true;
|
||||||
if (services[n].status !== 'Stopping') {
|
if (services[n].status !== 'Stopping') {
|
||||||
nextSteps.push(
|
nextSteps.push(serviceAction('kill', services[n].serviceId, services[n]));
|
||||||
serviceAction('kill', services[n].serviceId, services[n]),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
nextSteps.push({ action: 'noop' });
|
nextSteps.push({ action: 'noop' });
|
||||||
}
|
}
|
||||||
@ -1274,10 +1193,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
// If we have to remove any images, we do that before anything else
|
// If we have to remove any images, we do that before anything else
|
||||||
if (_.isEmpty(nextSteps)) {
|
if (_.isEmpty(nextSteps)) {
|
||||||
const allAppIds = _.union(
|
const allAppIds = _.union(_.keys(currentByAppId), _.keys(targetByAppId));
|
||||||
_.keys(currentByAppId),
|
|
||||||
_.keys(targetByAppId),
|
|
||||||
);
|
|
||||||
for (const appId of allAppIds) {
|
for (const appId of allAppIds) {
|
||||||
nextSteps = nextSteps.concat(
|
nextSteps = nextSteps.concat(
|
||||||
this._nextStepsForAppUpdate(
|
this._nextStepsForAppUpdate(
|
||||||
@ -1294,15 +1210,13 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
// the old app to be removed. If it has, we then
|
// the old app to be removed. If it has, we then
|
||||||
// remove all of the volumes
|
// remove all of the volumes
|
||||||
if (_.every(nextSteps, { action: 'noop' })) {
|
if (_.every(nextSteps, { action: 'noop' })) {
|
||||||
volumePromises.push(
|
volumePromises.push(this.removeAllVolumesForApp(checkInt(appId)));
|
||||||
this.removeAllVolumesForApp(checkInt(appId)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const newDownloads = nextSteps.filter((s) => s.action === 'fetch').length;
|
const newDownloads = nextSteps.filter(s => s.action === 'fetch').length;
|
||||||
|
|
||||||
if (!ignoreImages && delta && newDownloads > 0) {
|
if (!ignoreImages && delta && newDownloads > 0) {
|
||||||
// Check that this is not the first pull for an
|
// Check that this is not the first pull for an
|
||||||
@ -1334,7 +1248,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
nextSteps.push({ action: 'noop' });
|
nextSteps.push({ action: 'noop' });
|
||||||
}
|
}
|
||||||
return _.uniqWith(nextSteps, _.isEqual);
|
return _.uniqWith(nextSteps, _.isEqual);
|
||||||
}).then((nextSteps) =>
|
}).then(nextSteps =>
|
||||||
Promise.all(volumePromises).then(function (volSteps) {
|
Promise.all(volumePromises).then(function (volSteps) {
|
||||||
nextSteps = nextSteps.concat(_.flatten(volSteps));
|
nextSteps = nextSteps.concat(_.flatten(volSteps));
|
||||||
return nextSteps;
|
return nextSteps;
|
||||||
@ -1344,18 +1258,14 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
stopAll({ force = false, skipLock = false } = {}) {
|
stopAll({ force = false, skipLock = false } = {}) {
|
||||||
return Promise.resolve(this.services.getAll())
|
return Promise.resolve(this.services.getAll())
|
||||||
.map((service) => {
|
.map(service => {
|
||||||
return this._lockingIfNecessary(
|
return this._lockingIfNecessary(service.appId, { force, skipLock }, () => {
|
||||||
service.appId,
|
|
||||||
{ force, skipLock },
|
|
||||||
() => {
|
|
||||||
return this.services
|
return this.services
|
||||||
.kill(service, { removeContainer: false, wait: true })
|
.kill(service, { removeContainer: false, wait: true })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
delete this._containerStarted[service.containerId];
|
delete this._containerStarted[service.containerId];
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.return();
|
.return();
|
||||||
}
|
}
|
||||||
@ -1366,10 +1276,8 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
.get('lockOverride')
|
.get('lockOverride')
|
||||||
.then((lockOverride) => lockOverride || force)
|
.then(lockOverride => lockOverride || force)
|
||||||
.then((lockOverridden) =>
|
.then(lockOverridden => updateLock.lock(appId, { force: lockOverridden }, fn));
|
||||||
updateLock.lock(appId, { force: lockOverridden }, fn),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
executeStepAction(step, { force = false, skipLock = false } = {}) {
|
executeStepAction(step, { force = false, skipLock = false } = {}) {
|
||||||
@ -1379,9 +1287,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
if (!_.includes(this.validActions, step.action)) {
|
if (!_.includes(this.validActions, step.action)) {
|
||||||
return Promise.reject(new Error(`Invalid action ${step.action}`));
|
return Promise.reject(new Error(`Invalid action ${step.action}`));
|
||||||
}
|
}
|
||||||
return this.actionExecutors[step.action](
|
return this.actionExecutors[step.action](_.merge({}, step, { force, skipLock }));
|
||||||
_.merge({}, step, { force, skipLock }),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getExtraStateForComparison(currentState, targetState) {
|
getExtraStateForComparison(currentState, targetState) {
|
||||||
@ -1390,7 +1296,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
.keys()
|
.keys()
|
||||||
.concat(_.keys(targetState.local.apps))
|
.concat(_.keys(targetState.local.apps))
|
||||||
.uniq()
|
.uniq()
|
||||||
.each((id) => {
|
.each(id => {
|
||||||
const intId = checkInt(id);
|
const intId = checkInt(id);
|
||||||
if (intId == null) {
|
if (intId == null) {
|
||||||
throw new Error(`Invalid id: ${id}`);
|
throw new Error(`Invalid id: ${id}`);
|
||||||
@ -1398,12 +1304,12 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
containerIdsByAppId[intId] = this.services.getContainerIdMap(intId);
|
containerIdsByAppId[intId] = this.services.getContainerIdMap(intId);
|
||||||
});
|
});
|
||||||
|
|
||||||
return config.get('localMode').then((localMode) => {
|
return config.get('localMode').then(localMode => {
|
||||||
return Promise.props({
|
return Promise.props({
|
||||||
cleanupNeeded: Images.isCleanupNeeded(),
|
cleanupNeeded: Images.isCleanupNeeded(),
|
||||||
availableImages: Images.getAvailable(),
|
availableImages: Images.getAvailable(),
|
||||||
downloading: Images.getDownloadingImageIds(),
|
downloading: Images.getDownloadingImageIds(),
|
||||||
supervisorNetworkReady: this.networks.supervisorNetworkReady(),
|
supervisorNetworkReady: networkManager.supervisorNetworkReady(),
|
||||||
delta: config.get('delta'),
|
delta: config.get('delta'),
|
||||||
containerIds: Promise.props(containerIdsByAppId),
|
containerIds: Promise.props(containerIdsByAppId),
|
||||||
localMode,
|
localMode,
|
||||||
@ -1439,7 +1345,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
ignoreImages,
|
ignoreImages,
|
||||||
conf,
|
conf,
|
||||||
containerIds,
|
containerIds,
|
||||||
).then((nextSteps) => {
|
).then(nextSteps => {
|
||||||
if (ignoreImages && _.some(nextSteps, { action: 'fetch' })) {
|
if (ignoreImages && _.some(nextSteps, { action: 'fetch' })) {
|
||||||
throw new Error('Cannot fetch images while executing an API action');
|
throw new Error('Cannot fetch images while executing an API action');
|
||||||
}
|
}
|
||||||
@ -1451,7 +1357,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
targetState,
|
targetState,
|
||||||
nextSteps,
|
nextSteps,
|
||||||
)
|
)
|
||||||
.then((proxyvisorSteps) => nextSteps.concat(proxyvisorSteps));
|
.then(proxyvisorSteps => nextSteps.concat(proxyvisorSteps));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1462,10 +1368,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
// application
|
// application
|
||||||
for (const appId of Object.keys(apps)) {
|
for (const appId of Object.keys(apps)) {
|
||||||
const app = apps[appId];
|
const app = apps[appId];
|
||||||
const service = _.find(
|
const service = _.find(app.services, svc => svc.serviceId === serviceId);
|
||||||
app.services,
|
|
||||||
(svc) => svc.serviceId === serviceId,
|
|
||||||
);
|
|
||||||
if (service?.serviceName == null) {
|
if (service?.serviceName == null) {
|
||||||
throw new InternalInconsistencyError(
|
throw new InternalInconsistencyError(
|
||||||
`Could not find service name for id: ${serviceId}`,
|
`Could not find service name for id: ${serviceId}`,
|
||||||
@ -1480,8 +1383,8 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeAllVolumesForApp(appId) {
|
removeAllVolumesForApp(appId) {
|
||||||
return this.volumes.getAllByAppId(appId).then((volumes) =>
|
return this.volumes.getAllByAppId(appId).then(volumes =>
|
||||||
volumes.map((v) => ({
|
volumes.map(v => ({
|
||||||
action: 'removeVolume',
|
action: 'removeVolume',
|
||||||
current: v,
|
current: v,
|
||||||
})),
|
})),
|
||||||
@ -1500,11 +1403,6 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
'. ',
|
'. ',
|
||||||
)}`;
|
)}`;
|
||||||
log.info(message);
|
log.info(message);
|
||||||
return logger.logSystemMessage(
|
return logger.logSystemMessage(message, {}, 'optionalContainerViolation', true);
|
||||||
message,
|
|
||||||
{},
|
|
||||||
'optionalContainerViolation',
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import ServiceManager from './service-manager';
|
|||||||
import Volume from './volume';
|
import Volume from './volume';
|
||||||
|
|
||||||
import { checkTruthy } from '../lib/validation';
|
import { checkTruthy } from '../lib/validation';
|
||||||
import { NetworkManager } from './network-manager';
|
import * as networkManager from './network-manager';
|
||||||
import VolumeManager from './volume-manager';
|
import VolumeManager from './volume-manager';
|
||||||
|
|
||||||
interface BaseCompositionStepArgs {
|
interface BaseCompositionStepArgs {
|
||||||
@ -137,7 +137,6 @@ interface CompositionCallbacks {
|
|||||||
export function getExecutors(app: {
|
export function getExecutors(app: {
|
||||||
lockFn: LockingFn;
|
lockFn: LockingFn;
|
||||||
services: ServiceManager;
|
services: ServiceManager;
|
||||||
networks: NetworkManager;
|
|
||||||
volumes: VolumeManager;
|
volumes: VolumeManager;
|
||||||
applications: ApplicationManager;
|
applications: ApplicationManager;
|
||||||
callbacks: CompositionCallbacks;
|
callbacks: CompositionCallbacks;
|
||||||
@ -281,19 +280,19 @@ export function getExecutors(app: {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
createNetwork: async (step) => {
|
createNetwork: async (step) => {
|
||||||
await app.networks.create(step.target);
|
await networkManager.create(step.target);
|
||||||
},
|
},
|
||||||
createVolume: async (step) => {
|
createVolume: async (step) => {
|
||||||
await app.volumes.create(step.target);
|
await app.volumes.create(step.target);
|
||||||
},
|
},
|
||||||
removeNetwork: async (step) => {
|
removeNetwork: async (step) => {
|
||||||
await app.networks.remove(step.current);
|
await networkManager.remove(step.current);
|
||||||
},
|
},
|
||||||
removeVolume: async (step) => {
|
removeVolume: async (step) => {
|
||||||
await app.volumes.remove(step.current);
|
await app.volumes.remove(step.current);
|
||||||
},
|
},
|
||||||
ensureSupervisorNetwork: async () => {
|
ensureSupervisorNetwork: async () => {
|
||||||
app.networks.ensureSupervisorNetwork();
|
networkManager.ensureSupervisorNetwork();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,9 +12,8 @@ import { Network } from './network';
|
|||||||
import log from '../lib/supervisor-console';
|
import log from '../lib/supervisor-console';
|
||||||
import { ResourceRecreationAttemptError } from './errors';
|
import { ResourceRecreationAttemptError } from './errors';
|
||||||
|
|
||||||
export class NetworkManager {
|
export function getAll(): Bluebird<Network[]> {
|
||||||
public getAll(): Bluebird<Network[]> {
|
return getWithBothLabels().map((network: { Name: string }) => {
|
||||||
return this.getWithBothLabels().map((network: { Name: string }) => {
|
|
||||||
return docker
|
return docker
|
||||||
.getNetwork(network.Name)
|
.getNetwork(network.Name)
|
||||||
.inspect()
|
.inspect()
|
||||||
@ -22,22 +21,25 @@ export class NetworkManager {
|
|||||||
return Network.fromDockerNetwork(net);
|
return Network.fromDockerNetwork(net);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAllByAppId(appId: number): Bluebird<Network[]> {
|
export function getAllByAppId(appId: number): Bluebird<Network[]> {
|
||||||
return this.getAll().filter((network: Network) => network.appId === appId);
|
return getAll().filter((network: Network) => network.appId === appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async get(network: { name: string; appId: number }): Promise<Network> {
|
export async function get(network: {
|
||||||
|
name: string;
|
||||||
|
appId: number;
|
||||||
|
}): Promise<Network> {
|
||||||
const dockerNet = await docker
|
const dockerNet = await docker
|
||||||
.getNetwork(Network.generateDockerName(network.appId, network.name))
|
.getNetwork(Network.generateDockerName(network.appId, network.name))
|
||||||
.inspect();
|
.inspect();
|
||||||
return Network.fromDockerNetwork(dockerNet);
|
return Network.fromDockerNetwork(dockerNet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async create(network: Network) {
|
export async function create(network: Network) {
|
||||||
try {
|
try {
|
||||||
const existing = await this.get({
|
const existing = await get({
|
||||||
name: network.name,
|
name: network.name,
|
||||||
appId: network.appId,
|
appId: network.appId,
|
||||||
});
|
});
|
||||||
@ -59,22 +61,20 @@ export class NetworkManager {
|
|||||||
// If we got a not found error, create the network
|
// If we got a not found error, create the network
|
||||||
await network.create();
|
await network.create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async remove(network: Network) {
|
export async function remove(network: Network) {
|
||||||
// We simply forward this to the network object, but we
|
// We simply forward this to the network object, but we
|
||||||
// add this method to provide a consistent interface
|
// add this method to provide a consistent interface
|
||||||
await network.remove();
|
await network.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
public supervisorNetworkReady(): Bluebird<boolean> {
|
export function supervisorNetworkReady(): Bluebird<boolean> {
|
||||||
return Bluebird.resolve(
|
return Bluebird.resolve(
|
||||||
fs.stat(`/sys/class/net/${constants.supervisorNetworkInterface}`),
|
fs.stat(`/sys/class/net/${constants.supervisorNetworkInterface}`),
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return docker
|
return docker.getNetwork(constants.supervisorNetworkInterface).inspect();
|
||||||
.getNetwork(constants.supervisorNetworkInterface)
|
|
||||||
.inspect();
|
|
||||||
})
|
})
|
||||||
.then((network) => {
|
.then((network) => {
|
||||||
return (
|
return (
|
||||||
@ -86,16 +86,14 @@ export class NetworkManager {
|
|||||||
})
|
})
|
||||||
.catchReturn(NotFoundError, false)
|
.catchReturn(NotFoundError, false)
|
||||||
.catchReturn(ENOENT, false);
|
.catchReturn(ENOENT, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ensureSupervisorNetwork(): Bluebird<void> {
|
export function ensureSupervisorNetwork(): Bluebird<void> {
|
||||||
const removeIt = () => {
|
const removeIt = () => {
|
||||||
return Bluebird.resolve(
|
return Bluebird.resolve(
|
||||||
docker.getNetwork(constants.supervisorNetworkInterface).remove(),
|
docker.getNetwork(constants.supervisorNetworkInterface).remove(),
|
||||||
).then(() => {
|
).then(() => {
|
||||||
return docker
|
return docker.getNetwork(constants.supervisorNetworkInterface).inspect();
|
||||||
.getNetwork(constants.supervisorNetworkInterface)
|
|
||||||
.inspect();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -139,9 +137,9 @@ export class NetworkManager {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getWithBothLabels() {
|
function getWithBothLabels() {
|
||||||
return Bluebird.join(
|
return Bluebird.join(
|
||||||
docker.listNetworks({
|
docker.listNetworks({
|
||||||
filters: {
|
filters: {
|
||||||
@ -157,5 +155,4 @@ export class NetworkManager {
|
|||||||
return _.unionBy(currentNetworks, legacyNetworks, 'Id');
|
return _.unionBy(currentNetworks, legacyNetworks, 'Id');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { fs } from 'mz';
|
|||||||
import { stub } from 'sinon';
|
import { stub } from 'sinon';
|
||||||
|
|
||||||
import { ApplicationManager } from '../../src/application-manager';
|
import { ApplicationManager } from '../../src/application-manager';
|
||||||
import { NetworkManager } from '../../src/compose/network-manager';
|
import * as networkManager from '../../src/compose/network-manager';
|
||||||
import { ServiceManager } from '../../src/compose/service-manager';
|
import { ServiceManager } from '../../src/compose/service-manager';
|
||||||
import { VolumeManager } from '../../src/compose/volume-manager';
|
import { VolumeManager } from '../../src/compose/volume-manager';
|
||||||
import * as config from '../../src/config';
|
import * as config from '../../src/config';
|
||||||
@ -133,20 +133,23 @@ function buildRoutes(appManager: ApplicationManager): Router {
|
|||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const originalNetGetAll = networkManager.getAllByAppId;
|
||||||
function setupStubs() {
|
function setupStubs() {
|
||||||
stub(ServiceManager.prototype, 'getStatus').resolves(STUBBED_VALUES.services);
|
stub(ServiceManager.prototype, 'getStatus').resolves(STUBBED_VALUES.services);
|
||||||
stub(NetworkManager.prototype, 'getAllByAppId').resolves(
|
|
||||||
STUBBED_VALUES.networks,
|
|
||||||
);
|
|
||||||
stub(VolumeManager.prototype, 'getAllByAppId').resolves(
|
stub(VolumeManager.prototype, 'getAllByAppId').resolves(
|
||||||
STUBBED_VALUES.volumes,
|
STUBBED_VALUES.volumes,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// @ts-expect-error Assigning to a RO property
|
||||||
|
networkManager.getAllByAppId = () => Promise.resolve(STUBBED_VALUES.networks);
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreStubs() {
|
function restoreStubs() {
|
||||||
(ServiceManager.prototype as any).getStatus.restore();
|
(ServiceManager.prototype as any).getStatus.restore();
|
||||||
(NetworkManager.prototype as any).getAllByAppId.restore();
|
|
||||||
(VolumeManager.prototype as any).getAllByAppId.restore();
|
(VolumeManager.prototype as any).getAllByAppId.restore();
|
||||||
|
|
||||||
|
// @ts-expect-error Assigning to a RO property
|
||||||
|
networkManager.getAllByAppId = originalNetGetAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SupervisorAPIOpts {
|
interface SupervisorAPIOpts {
|
||||||
|
Reference in New Issue
Block a user