Merge pull request #1498 from balena-io/1476-patch-update-needed

Fixed evaluating if updates are needed to reach target state
This commit is contained in:
bulldozer-balena[bot] 2020-10-26 19:43:16 +00:00 committed by GitHub
commit ae1d7185c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 106 additions and 31 deletions

View File

@ -307,46 +307,78 @@ export class App {
} }
} }
const alreadyStarted = (serviceId: number) => { /**
const equalExceptForRunning = currentByServiceId[ * Checks that the config for the current and target services matches, ignoring their run state.
serviceId * @param serviceCurrent
].isEqualExceptForRunningState( * @param serviceTarget
targetByServiceId[serviceId], */
containerIds, const isEqualExceptForRunningState = (
); serviceCurrent: Service,
if (!equalExceptForRunning) { serviceTarget: Service,
// We need to recreate the container, as the configuration has changed ) =>
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; return false;
} }
if (targetByServiceId[serviceId].config.running) { // Check if we previously remember starting it
// If the container is already running, and we don't need to change it if (
// due to the config, we know it's already been started applicationManager.containerStarted[serviceCurrent.containerId!] != null
return true; ) {
return false;
} }
// We recently ran a start step for this container, it just hasn't // If the config otherwise matches, then we should be running
// started running yet 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 ( return (
applicationManager.containerStarted[ serviceTarget.config.running === false &&
currentByServiceId[serviceId].containerId! // When we issue a stop step, we remove the containerId from this structure.
] != null // 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) => * Filter all the services which should be updated due to run state change, or config mismatch.
!( */
currentByServiceId[serviceId].isEqual( const toBeUpdated = maybeUpdate
targetByServiceId[serviceId], .map((serviceId) => ({
containerIds, current: currentByServiceId[serviceId],
) && alreadyStarted(serviceId) target: targetByServiceId[serviceId],
), }))
); .filter(
const toBeUpdated = needUpdate.map((serviceId) => ({ ({ current: c, target: t }) =>
current: currentByServiceId[serviceId], !isEqualExceptForRunningState(c, t) ||
target: targetByServiceId[serviceId], shouldBeStarted(c, t) ||
})); shouldBeStopped(c, t),
);
return { return {
installPairs: toBeInstalled, installPairs: toBeInstalled,

View File

@ -583,6 +583,49 @@ describe('compose/app', () => {
expectStep('stop', steps); 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 () => { it('should recreate a container if the target configuration changes', async () => {
const contextWithImages = { const contextWithImages = {
...defaultContext, ...defaultContext,