Don't attempt to cleanup any target state referenced volumes

The code before this change could potentially remove a volume which
should not be removed if a container was deleted before the call that
references said volume.

To avoid this, we additionally filter the list of volumes to cleanup by
any that are referenced in the target state. This means that cleanup
will never remove it, as long as it's still supposed to be there,
regardless of if a container references it or not.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
Cameron Diver 2019-07-11 16:35:29 +01:00
parent e4caf100b5
commit a796777967
3 changed files with 33 additions and 4 deletions

View File

@ -11,12 +11,15 @@ import ServiceManager from './compose/service-manager';
import DB from './db';
import { APIBinder } from './api-binder';
import { Service } from './compose/service';
import Config from './config';
import NetworkManager from './compose/network-manager';
import VolumeManager from './compose/volume-manager';
import Network from './compose/network';
import Service from './compose/service';
import Volume from './compose/volume';
declare interface Options {
force?: boolean;
running?: boolean;
@ -65,6 +68,14 @@ export class ApplicationManager extends EventEmitter {
// FIXME: Type this properly as it's some mutant state between
// the state endpoint and the ApplicationManager internals
public getStatus(): Promise<Dictionay<any>>;
// The return type is incompleted
public getTargetApps(): Promise<
Dictionary<{
services: Dictionary<Service>;
volumes: Dictionary<Volume>;
networks: Dictionary<Network>;
}>
>;
public serviceNameFromId(serviceId: number): Bluebird<string>;
}

View File

@ -129,7 +129,9 @@ export class VolumeManager {
return volume;
}
public async removeOrphanedVolumes(): Promise<void> {
public async removeOrphanedVolumes(
referencedVolumes: string[],
): Promise<void> {
// Iterate through every container, and track the
// references to a volume
// Note that we're not just interested in containers
@ -151,7 +153,13 @@ export class VolumeManager {
.value();
const volumeNames = _.map(dockerVolumes.Volumes, 'Name');
const volumesToRemove = _.difference(volumeNames, containerVolumes);
const volumesToRemove = _.difference(
volumeNames,
containerVolumes,
// Don't remove any volume which is still referenced
// in the target state
referencedVolumes,
);
await Promise.all(
volumesToRemove.map(v => this.docker.getVolume(v).remove()),
);

View File

@ -12,6 +12,7 @@ import {
} from '../lib/messages';
import { doPurge, doRestart, serviceAction } from './common';
import Volume from '../compose/volume';
import log from '../lib/supervisor-console';
import supervisorVersion = require('../lib/supervisor-version');
@ -488,7 +489,16 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
router.get('/v2/cleanup-volumes', async (_req, res) => {
try {
await applications.volumes.removeOrphanedVolumes();
const targetState = await applications.getTargetApps();
const referencedVolumes: string[] = [];
_.each(targetState, app => {
_.each(app.volumes, vol => {
referencedVolumes.push(
Volume.generateDockerName(vol.appId, vol.name),
);
});
});
await applications.volumes.removeOrphanedVolumes(referencedVolumes);
res.json({
status: 'success',
});