mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-04-19 08:36:14 +00:00
Allow application manager to match apps between environments
If an app with the same app uuid exists between environments, the supervisor will match the apps by uuid to prevent stopping the running app
This commit is contained in:
parent
5c5483dd3d
commit
e9af9d8e83
@ -27,6 +27,8 @@ import { getExecutors, CompositionStepT } from './composition-steps';
|
||||
import * as commitStore from './commit';
|
||||
|
||||
import Service from './service';
|
||||
import Network from './network';
|
||||
import Volume from './volume';
|
||||
|
||||
import { createV1Api } from '../device-api/v1';
|
||||
import { createV2Api } from '../device-api/v2';
|
||||
@ -351,35 +353,154 @@ export async function stopAll({ force = false, skipLock = false } = {}) {
|
||||
);
|
||||
}
|
||||
|
||||
// The following two function may look pretty odd, but after the move to uuids,
|
||||
// there's a chance that the current running apps don't have a uuid set. We
|
||||
// still need to be able to work on these and perform various state changes. To
|
||||
// do this we try to use the UUID to group the components, and if that isn't
|
||||
// available we revert to using the appIds instead
|
||||
export async function getCurrentApps(): Promise<InstancedAppState> {
|
||||
const volumes = _.groupBy(await volumeManager.getAll(), 'appId');
|
||||
const networks = _.groupBy(await networkManager.getAll(), 'appId');
|
||||
const services = _.groupBy(await serviceManager.getAll(), 'appId');
|
||||
|
||||
const allAppIds = _.union(
|
||||
Object.keys(volumes),
|
||||
Object.keys(networks),
|
||||
Object.keys(services),
|
||||
).map((i) => parseInt(i, 10));
|
||||
const componentGroups = groupComponents(
|
||||
await serviceManager.getAll(),
|
||||
await networkManager.getAll(),
|
||||
await volumeManager.getAll(),
|
||||
);
|
||||
|
||||
const apps: InstancedAppState = {};
|
||||
for (const appId of allAppIds) {
|
||||
for (const strAppId of Object.keys(componentGroups)) {
|
||||
const appId = parseInt(strAppId, 10);
|
||||
|
||||
// TODO: get commit and release version from container
|
||||
const commit = await commitStore.getCommitForApp(appId);
|
||||
apps[appId] = new App(
|
||||
{
|
||||
appId,
|
||||
services: services[appId] ?? [],
|
||||
networks: _.keyBy(networks[appId], 'name'),
|
||||
volumes: _.keyBy(volumes[appId], 'name'),
|
||||
commit,
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
const components = componentGroups[appId];
|
||||
|
||||
// fetch the correct uuid from any component within the appId
|
||||
const uuid = [
|
||||
components.services[0]?.appUuid,
|
||||
components.volumes[0]?.appUuid,
|
||||
components.networks[0]?.appUuid,
|
||||
]
|
||||
.filter((u) => !!u)
|
||||
.shift()!;
|
||||
|
||||
// If we don't have any components for this app, ignore it (this can
|
||||
// actually happen when moving between backends but maintaining UUIDs)
|
||||
if (
|
||||
!_.isEmpty(components.services) ||
|
||||
!_.isEmpty(components.volumes) ||
|
||||
!_.isEmpty(components.networks)
|
||||
) {
|
||||
apps[appId] = new App(
|
||||
{
|
||||
appId,
|
||||
appUuid: uuid,
|
||||
commit,
|
||||
services: componentGroups[appId].services,
|
||||
networks: _.keyBy(componentGroups[appId].networks, 'name'),
|
||||
volumes: _.keyBy(componentGroups[appId].volumes, 'name'),
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return apps;
|
||||
}
|
||||
|
||||
type AppGroup = {
|
||||
[appId: number]: {
|
||||
services: Service[];
|
||||
volumes: Volume[];
|
||||
networks: Network[];
|
||||
};
|
||||
};
|
||||
|
||||
function groupComponents(
|
||||
services: Service[],
|
||||
networks: Network[],
|
||||
volumes: Volume[],
|
||||
): AppGroup {
|
||||
const grouping: AppGroup = {};
|
||||
|
||||
const everyComponent: [{ appUuid?: string; appId: number }] = [
|
||||
...services,
|
||||
...networks,
|
||||
...volumes,
|
||||
] as any;
|
||||
|
||||
const allUuids: string[] = [];
|
||||
const allAppIds: number[] = [];
|
||||
everyComponent.forEach(({ appId, appUuid }) => {
|
||||
// Pre-populate the groupings
|
||||
grouping[appId] = {
|
||||
services: [],
|
||||
networks: [],
|
||||
volumes: [],
|
||||
};
|
||||
// Save all the uuids for later
|
||||
if (appUuid != null) {
|
||||
allUuids.push(appUuid);
|
||||
}
|
||||
allAppIds.push(appId);
|
||||
});
|
||||
|
||||
// First we try to group everything by it's uuid, but if any component does
|
||||
// not have a uuid, we fall back to the old appId style
|
||||
if (everyComponent.length === allUuids.length) {
|
||||
const uuidGroups: { [uuid: string]: AppGroup[0] } = {};
|
||||
new Set(allUuids).forEach((uuid) => {
|
||||
const uuidServices = services.filter(
|
||||
({ appUuid: sUuid }) => uuid === sUuid,
|
||||
);
|
||||
const uuidVolumes = volumes.filter(
|
||||
({ appUuid: vUuid }) => uuid === vUuid,
|
||||
);
|
||||
const uuidNetworks = networks.filter(
|
||||
({ appUuid: nUuid }) => uuid === nUuid,
|
||||
);
|
||||
|
||||
uuidGroups[uuid] = {
|
||||
services: uuidServices,
|
||||
networks: uuidNetworks,
|
||||
volumes: uuidVolumes,
|
||||
};
|
||||
});
|
||||
|
||||
for (const uuid of Object.keys(uuidGroups)) {
|
||||
// There's a chance that the uuid and the appId is different, and this
|
||||
// is fine. Unfortunately we have no way of knowing which is the "real"
|
||||
// appId (that is the app id which relates to the currently joined
|
||||
// backend) so we instead just choose the first and add everything to that
|
||||
const appId =
|
||||
uuidGroups[uuid].services[0]?.appId ||
|
||||
uuidGroups[uuid].networks[0]?.appId ||
|
||||
uuidGroups[uuid].volumes[0]?.appId;
|
||||
grouping[appId] = uuidGroups[uuid];
|
||||
}
|
||||
} else {
|
||||
// Otherwise group them by appId and let the state engine match them later.
|
||||
// This will only happen once, as every target state going forward will
|
||||
// contain UUIDs, we just need to handle the initial upgrade
|
||||
const appSvcs = _.groupBy(services, 'appId');
|
||||
const appVols = _.groupBy(volumes, 'appId');
|
||||
const appNets = _.groupBy(networks, 'appId');
|
||||
|
||||
_.uniq(allAppIds).forEach((appId) => {
|
||||
grouping[appId].services = grouping[appId].services.concat(
|
||||
appSvcs[appId] || [],
|
||||
);
|
||||
grouping[appId].networks = grouping[appId].networks.concat(
|
||||
appNets[appId] || [],
|
||||
);
|
||||
grouping[appId].volumes = grouping[appId].volumes.concat(
|
||||
appVols[appId] || [],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return grouping;
|
||||
}
|
||||
|
||||
function killServicesUsingApi(current: InstancedAppState): CompositionStep[] {
|
||||
const steps: CompositionStep[] = [];
|
||||
_.each(current, (app) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user