mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-06-22 09:08:51 +00:00
Simplify lock interface to prep for adding takeLock to state funnel
This commit changes a few things: * Pass `force` to `takeLock` step directly. This allows us to remove the `lockFn` used by app manager's action executors, setting takeLock as the main interface to interact with the update lock module. Note that this commit by itself will not pass tests, as no update locking occurs where it once did. This will be amended in the next commit. * Remove locking functions from doRestart & doPurge, as this is the only area where skipLock is required. * Remove `skipLock` interface, as it's redundant with the functionality of `force`. The only time `skipLock` is true is in doRestart/doPurge, as those API methods are already run within a lock function. We removed the lock function which removes the need for skipLock, and in the next commit we'll add locking as a composition step to replace the functionality removed here. * Remove some methods not in use, such as app manager's `stopAll`. Signed-off-by: Christina Ying Wang <christina@balena.io>
This commit is contained in:
@ -45,6 +45,7 @@ export interface UpdateState {
|
||||
containerIds: Dictionary<string>;
|
||||
downloading: string[];
|
||||
locksTaken: LocksTakenMap;
|
||||
force: boolean;
|
||||
}
|
||||
|
||||
interface ChangingPair<T> {
|
||||
@ -111,8 +112,15 @@ export class App {
|
||||
// Check to see if we need to polyfill in some "new" data for legacy services
|
||||
this.migrateLegacy(target);
|
||||
|
||||
// Check for changes in the volumes. We don't remove any volumes until we remove an
|
||||
// entire app
|
||||
let steps: CompositionStep[] = [];
|
||||
|
||||
// Any services which have died get a remove step
|
||||
for (const service of this.services) {
|
||||
if (service.status === 'Dead') {
|
||||
steps.push(generateStep('remove', { current: service }));
|
||||
}
|
||||
}
|
||||
|
||||
const volumeChanges = this.compareComponents(
|
||||
this.volumes,
|
||||
target.volumes,
|
||||
@ -124,15 +132,6 @@ export class App {
|
||||
true,
|
||||
);
|
||||
|
||||
let steps: CompositionStep[] = [];
|
||||
|
||||
// Any services which have died get a remove step
|
||||
for (const service of this.services) {
|
||||
if (service.status === 'Dead') {
|
||||
steps.push(generateStep('remove', { current: service }));
|
||||
}
|
||||
}
|
||||
|
||||
const { removePairs, installPairs, updatePairs } = this.compareServices(
|
||||
this.services,
|
||||
target.services,
|
||||
@ -571,7 +570,12 @@ export class App {
|
||||
current.isEqualConfig(target, context.containerIds)
|
||||
) {
|
||||
// Update service metadata or start/stop a service
|
||||
return this.generateContainerStep(current, target, context.locksTaken);
|
||||
return this.generateContainerStep(
|
||||
current,
|
||||
target,
|
||||
context.locksTaken,
|
||||
context.force,
|
||||
);
|
||||
}
|
||||
|
||||
let strategy: string;
|
||||
@ -664,6 +668,7 @@ export class App {
|
||||
current: Service,
|
||||
target: Service,
|
||||
locksTaken: LocksTakenMap,
|
||||
force: boolean,
|
||||
) {
|
||||
// Update container metadata if service release has changed
|
||||
if (current.commit !== target.commit) {
|
||||
@ -679,6 +684,7 @@ export class App {
|
||||
return generateStep('takeLock', {
|
||||
appId: target.appId,
|
||||
services: [target.serviceName],
|
||||
force,
|
||||
});
|
||||
} else if (target.config.running !== current.config.running) {
|
||||
if (target.config.running) {
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
ContractViolationError,
|
||||
InternalInconsistencyError,
|
||||
} from '../lib/errors';
|
||||
import { getServicesLockedByAppId, lock } from '../lib/update-lock';
|
||||
import { getServicesLockedByAppId } from '../lib/update-lock';
|
||||
import { checkTruthy } from '../lib/validation';
|
||||
|
||||
import App from './app';
|
||||
@ -64,7 +64,6 @@ export function resetTimeSpentFetching(value: number = 0) {
|
||||
}
|
||||
|
||||
const actionExecutors = getExecutors({
|
||||
lockFn: lock,
|
||||
callbacks: {
|
||||
fetchStart: () => {
|
||||
fetchesInProgress += 1;
|
||||
@ -120,6 +119,7 @@ export async function getRequiredSteps(
|
||||
targetApps: InstancedAppState,
|
||||
keepImages?: boolean,
|
||||
keepVolumes?: boolean,
|
||||
force: boolean = false,
|
||||
): Promise<CompositionStep[]> {
|
||||
// get some required data
|
||||
const [downloading, availableImages, { localMode, delta }] =
|
||||
@ -146,6 +146,7 @@ export async function getRequiredSteps(
|
||||
// Volumes are not removed when stopping an app when going to local mode
|
||||
keepVolumes,
|
||||
delta,
|
||||
force,
|
||||
downloading,
|
||||
availableImages,
|
||||
containerIdsByAppId,
|
||||
@ -161,6 +162,7 @@ export async function inferNextSteps(
|
||||
keepImages = false,
|
||||
keepVolumes = false,
|
||||
delta = true,
|
||||
force = false,
|
||||
downloading = [] as UpdateState['downloading'],
|
||||
availableImages = [] as UpdateState['availableImages'],
|
||||
containerIdsByAppId = {} as {
|
||||
@ -219,6 +221,7 @@ export async function inferNextSteps(
|
||||
containerIds: containerIdsByAppId[id],
|
||||
downloading,
|
||||
locksTaken,
|
||||
force,
|
||||
},
|
||||
targetApps[id],
|
||||
),
|
||||
@ -233,6 +236,7 @@ export async function inferNextSteps(
|
||||
downloading,
|
||||
containerIds: containerIdsByAppId[id],
|
||||
locksTaken,
|
||||
force,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -257,6 +261,7 @@ export async function inferNextSteps(
|
||||
containerIds: containerIdsByAppId[id] ?? {},
|
||||
downloading,
|
||||
locksTaken,
|
||||
force,
|
||||
},
|
||||
targetApps[id],
|
||||
),
|
||||
@ -301,17 +306,6 @@ export async function inferNextSteps(
|
||||
return steps;
|
||||
}
|
||||
|
||||
export async function stopAll({ force = false, skipLock = false } = {}) {
|
||||
const services = await serviceManager.getAll();
|
||||
await Promise.all(
|
||||
services.map(async (s) => {
|
||||
return lock(s.appId, { force, skipLock }, async () => {
|
||||
await serviceManager.kill(s, { removeContainer: false, wait: true });
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -501,7 +495,7 @@ function killServicesUsingApi(current: InstancedAppState): CompositionStep[] {
|
||||
// intermediate targets to perform changes
|
||||
export async function executeStep(
|
||||
step: CompositionStep,
|
||||
{ force = false, skipLock = false } = {},
|
||||
{ force = false } = {},
|
||||
): Promise<void> {
|
||||
if (!validActions.includes(step.action)) {
|
||||
return Promise.reject(
|
||||
@ -515,7 +509,6 @@ export async function executeStep(
|
||||
await actionExecutors[step.action]({
|
||||
...step,
|
||||
force,
|
||||
skipLock,
|
||||
} as any);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import * as config from '../config';
|
||||
import type { Image } from './images';
|
||||
import * as images from './images';
|
||||
@ -13,33 +11,22 @@ import * as commitStore from './commit';
|
||||
import * as updateLock from '../lib/update-lock';
|
||||
import type { DeviceLegacyReport } from '../types/state';
|
||||
|
||||
interface BaseCompositionStepArgs {
|
||||
force?: boolean;
|
||||
skipLock?: boolean;
|
||||
}
|
||||
|
||||
// FIXME: Most of the steps take the
|
||||
// BaseCompositionStepArgs, but some also take an options
|
||||
// structure which includes some of the same fields. It
|
||||
// would be nice to remove the need for this
|
||||
interface CompositionStepArgs {
|
||||
stop: {
|
||||
current: Service;
|
||||
options?: {
|
||||
skipLock?: boolean;
|
||||
wait?: boolean;
|
||||
};
|
||||
} & BaseCompositionStepArgs;
|
||||
};
|
||||
kill: {
|
||||
current: Service;
|
||||
options?: {
|
||||
skipLock?: boolean;
|
||||
wait?: boolean;
|
||||
};
|
||||
} & BaseCompositionStepArgs;
|
||||
};
|
||||
remove: {
|
||||
current: Service;
|
||||
} & BaseCompositionStepArgs;
|
||||
};
|
||||
updateMetadata: {
|
||||
current: Service;
|
||||
target: Service;
|
||||
@ -47,13 +34,10 @@ interface CompositionStepArgs {
|
||||
restart: {
|
||||
current: Service;
|
||||
target: Service;
|
||||
options?: {
|
||||
skipLock?: boolean;
|
||||
};
|
||||
} & BaseCompositionStepArgs;
|
||||
};
|
||||
start: {
|
||||
target: Service;
|
||||
} & BaseCompositionStepArgs;
|
||||
};
|
||||
updateCommit: {
|
||||
target: string;
|
||||
appId: number;
|
||||
@ -62,10 +46,9 @@ interface CompositionStepArgs {
|
||||
current: Service;
|
||||
target: Service;
|
||||
options?: {
|
||||
skipLock?: boolean;
|
||||
timeout?: number;
|
||||
};
|
||||
} & BaseCompositionStepArgs;
|
||||
};
|
||||
fetch: {
|
||||
image: Image;
|
||||
serviceName: string;
|
||||
@ -94,6 +77,7 @@ interface CompositionStepArgs {
|
||||
takeLock: {
|
||||
appId: number;
|
||||
services: string[];
|
||||
force: boolean;
|
||||
};
|
||||
releaseLock: {
|
||||
appId: number;
|
||||
@ -119,13 +103,6 @@ export function generateStep<T extends CompositionStepAction>(
|
||||
type Executors<T extends CompositionStepAction> = {
|
||||
[key in T]: (step: CompositionStepT<key>) => Promise<unknown>;
|
||||
};
|
||||
type LockingFn = (
|
||||
// TODO: Once the entire codebase is typescript, change
|
||||
// this to number
|
||||
app: number | number[] | null,
|
||||
args: BaseCompositionStepArgs,
|
||||
fn: () => Promise<unknown>,
|
||||
) => Promise<unknown>;
|
||||
|
||||
interface CompositionCallbacks {
|
||||
// TODO: Once the entire codebase is typescript, change
|
||||
@ -137,38 +114,20 @@ interface CompositionCallbacks {
|
||||
bestDeltaSource: (image: Image, available: Image[]) => string | null;
|
||||
}
|
||||
|
||||
export function getExecutors(app: {
|
||||
lockFn: LockingFn;
|
||||
callbacks: CompositionCallbacks;
|
||||
}) {
|
||||
export function getExecutors(app: { callbacks: CompositionCallbacks }) {
|
||||
const executors: Executors<CompositionStepAction> = {
|
||||
stop: (step) => {
|
||||
return app.lockFn(
|
||||
step.current.appId,
|
||||
{
|
||||
force: step.force,
|
||||
skipLock: step.skipLock || _.get(step, ['options', 'skipLock']),
|
||||
},
|
||||
async () => {
|
||||
const wait = _.get(step, ['options', 'wait'], false);
|
||||
await serviceManager.kill(step.current, {
|
||||
removeContainer: false,
|
||||
wait,
|
||||
});
|
||||
},
|
||||
);
|
||||
stop: async (step) => {
|
||||
// Should always be preceded by a takeLock step,
|
||||
// so the call is executed assuming that the lock is taken.
|
||||
await serviceManager.kill(step.current, {
|
||||
removeContainer: false,
|
||||
wait: step.options?.wait || false,
|
||||
});
|
||||
},
|
||||
kill: (step) => {
|
||||
return app.lockFn(
|
||||
step.current.appId,
|
||||
{
|
||||
force: step.force,
|
||||
skipLock: step.skipLock || _.get(step, ['options', 'skipLock']),
|
||||
},
|
||||
async () => {
|
||||
await serviceManager.kill(step.current);
|
||||
},
|
||||
);
|
||||
kill: async (step) => {
|
||||
// Should always be preceded by a takeLock step,
|
||||
// so the call is executed assuming that the lock is taken.
|
||||
await serviceManager.kill(step.current);
|
||||
},
|
||||
remove: async (step) => {
|
||||
// Only called for dead containers, so no need to
|
||||
@ -180,18 +139,11 @@ export function getExecutors(app: {
|
||||
// so the call is executed assuming that the lock is taken.
|
||||
await serviceManager.updateMetadata(step.current, step.target);
|
||||
},
|
||||
restart: (step) => {
|
||||
return app.lockFn(
|
||||
step.current.appId,
|
||||
{
|
||||
force: step.force,
|
||||
skipLock: step.skipLock || _.get(step, ['options', 'skipLock']),
|
||||
},
|
||||
async () => {
|
||||
await serviceManager.kill(step.current, { wait: true });
|
||||
await serviceManager.start(step.target);
|
||||
},
|
||||
);
|
||||
restart: async (step) => {
|
||||
// Should always be preceded by a takeLock step,
|
||||
// so the call is executed assuming that the lock is taken.
|
||||
await serviceManager.kill(step.current, { wait: true });
|
||||
await serviceManager.start(step.target);
|
||||
},
|
||||
start: async (step) => {
|
||||
await serviceManager.start(step.target);
|
||||
@ -199,17 +151,10 @@ export function getExecutors(app: {
|
||||
updateCommit: async (step) => {
|
||||
await commitStore.upsertCommitForApp(step.appId, step.target);
|
||||
},
|
||||
handover: (step) => {
|
||||
return app.lockFn(
|
||||
step.current.appId,
|
||||
{
|
||||
force: step.force,
|
||||
skipLock: step.skipLock || _.get(step, ['options', 'skipLock']),
|
||||
},
|
||||
async () => {
|
||||
await serviceManager.handover(step.current, step.target);
|
||||
},
|
||||
);
|
||||
handover: async (step) => {
|
||||
// Should always be preceded by a takeLock step,
|
||||
// so the call is executed assuming that the lock is taken.
|
||||
await serviceManager.handover(step.current, step.target);
|
||||
},
|
||||
fetch: async (step) => {
|
||||
const startTime = process.hrtime();
|
||||
@ -271,7 +216,7 @@ export function getExecutors(app: {
|
||||
/* async noop */
|
||||
},
|
||||
takeLock: async (step) => {
|
||||
await updateLock.takeLock(step.appId, step.services);
|
||||
await updateLock.takeLock(step.appId, step.services, step.force);
|
||||
},
|
||||
releaseLock: async (step) => {
|
||||
await updateLock.releaseLock(step.appId);
|
||||
|
Reference in New Issue
Block a user