Fix filtering of the supervisor app on the target state

Under some conditions, an aarch64 device may get a reference to a armv7hf
supervisor on the target state. One of the ways this can happen is if
an aarch64 device is added to an armv7hf fleet and the target supervisor
is set before the device fully provisions.

If that happens, the previous filtering for the supervisor app (which
relied on the architecture in device-type.json) would
fail and the user would end up with two supervisor containers, one
running correctly and the other crash looping.

This fixes the filtering and just checks if the supervisor uuid/service
name belongs to a group of known uuids.

Closes: #2006
Change-type: patch
This commit is contained in:
Felipe Lalanne 2022-09-12 16:15:10 -03:00
parent 39cf98243f
commit 5a57647450
2 changed files with 16 additions and 28 deletions

View File

@ -24,7 +24,7 @@ import { checkTruthy, checkString } from '../lib/validation';
import { ServiceComposeConfig, DeviceMetadata } from './types/service';
import { ImageInspectInfo } from 'dockerode';
import { pathExistsOnHost } from '../lib/fs-utils';
import { getSupervisorMetadata } from '../lib/supervisor-metadata';
import { isSupervisor } from '../lib/supervisor-metadata';
export interface AppConstructOpts {
appId: number;
@ -778,8 +778,6 @@ export class App {
...opts,
};
const supervisorMeta = await getSupervisorMetadata();
const isService = (svc: ServiceComposeConfig) =>
svc.labels?.['io.balena.image.class'] == null ||
svc.labels['io.balena.image.class'] === 'service';
@ -788,12 +786,6 @@ export class App {
svc.labels?.['io.balena.image.store'] == null ||
svc.labels['io.balena.image.store'] === 'data';
const isSupervisor = (svc: ServiceComposeConfig) =>
app.uuid === supervisorMeta.uuid &&
(svc.serviceName === supervisorMeta.serviceName ||
// keep compatibility with older supervisor releases
svc.serviceName === 'main');
// In the db, the services are an array, but here we switch them to an
// object so that they are consistent
const services: Service[] = await Promise.all(
@ -808,7 +800,10 @@ export class App {
)
// Ignore the supervisor service itself from the target state for now
// until the supervisor can update itself
.filter((svc: ServiceComposeConfig) => !isSupervisor(svc))
.filter(
(svc: ServiceComposeConfig) =>
!isSupervisor(app.uuid, svc.serviceName),
)
.map(async (svc: ServiceComposeConfig) => {
// Try to fill the image id if the image is downloaded
let imageInfo: ImageInspectInfo | undefined;

View File

@ -1,7 +1,3 @@
import * as memoizee from 'memoizee';
import * as config from '../config';
import { InternalInconsistencyError } from './errors';
export type SupervisorMetadata = {
uuid: string;
serviceName: string;
@ -41,24 +37,21 @@ const SUPERVISOR_APPS: { [arch: string]: SupervisorMetadata } = {
};
/**
* Get the metadata from the supervisor container
* Check if the supervisor in the target state belongs to the known
* supervisors
*
* This is needed for the supervisor to identify itself on the target
* state and on getStatus() in device-state.ts
*
* TODO: remove this once the supervisor knows how to update itself
*/
export const getSupervisorMetadata = memoizee(
async () => {
const deviceArch = await config.get('deviceArch');
const meta: SupervisorMetadata = SUPERVISOR_APPS[deviceArch];
if (meta == null) {
throw new InternalInconsistencyError(
`Unknown device architecture ${deviceArch}. Could not find matching supervisor metadata.`,
);
}
return meta;
},
{ promise: true },
);
export const isSupervisor = (appUuid: string, svcName: string) => {
return (
Object.values(SUPERVISOR_APPS).filter(
({ uuid, serviceName }) =>
// Compare with `main` as well for compatibility with older supervisors
appUuid === uuid && (svcName === serviceName || svcName === 'main'),
).length > 0
);
};