mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-02-21 10:01:55 +00:00
Fixed evaluating if updates are needed to reach target state
Closes: #1476 Change-type: patch Signed-off-by: Miguel Casqueira <miguel@balena.io>
This commit is contained in:
parent
30c352f4f6
commit
77333f1e11
@ -307,46 +307,78 @@ export class App {
|
||||
}
|
||||
}
|
||||
|
||||
const alreadyStarted = (serviceId: number) => {
|
||||
const equalExceptForRunning = currentByServiceId[
|
||||
serviceId
|
||||
].isEqualExceptForRunningState(
|
||||
targetByServiceId[serviceId],
|
||||
containerIds,
|
||||
);
|
||||
if (!equalExceptForRunning) {
|
||||
// We need to recreate the container, as the configuration has changed
|
||||
/**
|
||||
* Checks that the config for the current and target services matches, ignoring their run state.
|
||||
* @param serviceCurrent
|
||||
* @param serviceTarget
|
||||
*/
|
||||
const isEqualExceptForRunningState = (
|
||||
serviceCurrent: Service,
|
||||
serviceTarget: Service,
|
||||
) =>
|
||||
serviceCurrent.isEqualExceptForRunningState(serviceTarget, containerIds);
|
||||
|
||||
/**
|
||||
* Checks if a service is running, if we tracked it as being started, if the config matches the desired config, and if we actually want it to ever be started.
|
||||
* @param serviceCurrent
|
||||
* @param serviceTarget
|
||||
*/
|
||||
const shouldBeStarted = (
|
||||
serviceCurrent: Service,
|
||||
serviceTarget: Service,
|
||||
) => {
|
||||
// If the target run state is stopped, or we are actually running, then we don't care about anything else
|
||||
if (
|
||||
serviceTarget.config.running === false ||
|
||||
serviceCurrent.config.running === true
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetByServiceId[serviceId].config.running) {
|
||||
// If the container is already running, and we don't need to change it
|
||||
// due to the config, we know it's already been started
|
||||
return true;
|
||||
// Check if we previously remember starting it
|
||||
if (
|
||||
applicationManager.containerStarted[serviceCurrent.containerId!] != null
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We recently ran a start step for this container, it just hasn't
|
||||
// started running yet
|
||||
// If the config otherwise matches, then we should be running
|
||||
return isEqualExceptForRunningState(serviceCurrent, serviceTarget);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a service should be stopped and if we have tracked it as being stopped.
|
||||
*
|
||||
* @param serviceCurrent
|
||||
* @param serviceTarget
|
||||
*/
|
||||
const shouldBeStopped = (
|
||||
serviceCurrent: Service,
|
||||
serviceTarget: Service,
|
||||
) => {
|
||||
// check that we want to stop it, and that it isn't stopped
|
||||
return (
|
||||
applicationManager.containerStarted[
|
||||
currentByServiceId[serviceId].containerId!
|
||||
] != null
|
||||
serviceTarget.config.running === false &&
|
||||
// When we issue a stop step, we remove the containerId from this structure.
|
||||
// We check if the container has been removed first, so that we can ensure we're not providing multiple stop steps.
|
||||
applicationManager.containerStarted[serviceCurrent.containerId!] == null
|
||||
);
|
||||
};
|
||||
|
||||
const needUpdate = maybeUpdate.filter(
|
||||
(serviceId) =>
|
||||
!(
|
||||
currentByServiceId[serviceId].isEqual(
|
||||
targetByServiceId[serviceId],
|
||||
containerIds,
|
||||
) && alreadyStarted(serviceId)
|
||||
),
|
||||
);
|
||||
const toBeUpdated = needUpdate.map((serviceId) => ({
|
||||
current: currentByServiceId[serviceId],
|
||||
target: targetByServiceId[serviceId],
|
||||
}));
|
||||
/**
|
||||
* Filter all the services which should be updated due to run state change, or config mismatch.
|
||||
*/
|
||||
const toBeUpdated = maybeUpdate
|
||||
.map((serviceId) => ({
|
||||
current: currentByServiceId[serviceId],
|
||||
target: targetByServiceId[serviceId],
|
||||
}))
|
||||
.filter(
|
||||
({ current: c, target: t }) =>
|
||||
!isEqualExceptForRunningState(c, t) ||
|
||||
shouldBeStarted(c, t) ||
|
||||
shouldBeStopped(c, t),
|
||||
);
|
||||
|
||||
return {
|
||||
installPairs: toBeInstalled,
|
||||
|
@ -583,6 +583,49 @@ describe('compose/app', () => {
|
||||
expectStep('stop', steps);
|
||||
});
|
||||
|
||||
it('should not try to start a container which has exitted and has restart policy of no', async () => {
|
||||
// Container is a "run once" type of service so it has exitted.
|
||||
const current = createApp(
|
||||
[
|
||||
await createService(
|
||||
{ restart: 'no', running: false },
|
||||
1,
|
||||
'main',
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
{ containerId: 'run_once' },
|
||||
),
|
||||
],
|
||||
[],
|
||||
[],
|
||||
false,
|
||||
);
|
||||
|
||||
// Mark this container as previously being started
|
||||
applicationManager.containerStarted['run_once'] = true;
|
||||
|
||||
// Now test that another start step is not added on this service
|
||||
const target = createApp(
|
||||
[
|
||||
await createService(
|
||||
{ restart: 'no', running: true },
|
||||
1,
|
||||
'main',
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
{ containerId: 'run_once' },
|
||||
),
|
||||
],
|
||||
[],
|
||||
[],
|
||||
true,
|
||||
);
|
||||
const steps = current.nextStepsForAppUpdate(defaultContext, target);
|
||||
withSteps(steps).rejectStep('start');
|
||||
});
|
||||
|
||||
it('should recreate a container if the target configuration changes', async () => {
|
||||
const contextWithImages = {
|
||||
...defaultContext,
|
||||
|
Loading…
x
Reference in New Issue
Block a user