Add up flag `--renew-anon-volumes` (shorthand -V)

to avoid reusing the previous container's data

Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
Joffrey F 2018-01-22 15:59:52 -08:00
parent fd1e8024f7
commit b07091ac5f
4 changed files with 101 additions and 12 deletions

View File

@ -936,7 +936,7 @@ class TopLevelCommand(object):
--always-recreate-deps Recreate dependent containers.
Incompatible with --no-recreate.
--no-recreate If containers already exist, don't recreate
them. Incompatible with --force-recreate.
them. Incompatible with --force-recreate and -V.
--no-build Don't build an image, even if it's missing.
--no-start Don't start the services after creating them.
--build Build images before starting containers.
@ -945,8 +945,10 @@ class TopLevelCommand(object):
-t, --timeout TIMEOUT Use this timeout in seconds for container
shutdown when attached or when containers are
already running. (default: 10)
--remove-orphans Remove containers for services not
defined in the Compose file
-V, --renew-anon-volumes Recreate anonymous volumes instead of retrieving
data from the previous containers.
--remove-orphans Remove containers for services not defined
in the Compose file.
--exit-code-from SERVICE Return the exit code of the selected service
container. Implies --abort-on-container-exit.
--scale SERVICE=NUM Scale SERVICE to NUM instances. Overrides the
@ -992,6 +994,7 @@ class TopLevelCommand(object):
start=not no_start,
always_recreate_deps=always_recreate_deps,
reset_container_image=rebuild,
renew_anonymous_volumes=options.get('--renew-anon-volumes')
)
try:
@ -1083,10 +1086,14 @@ def compute_exit_code(exit_value_from, attached_containers, cascade_starter, all
def convergence_strategy_from_opts(options):
no_recreate = options['--no-recreate']
force_recreate = options['--force-recreate']
renew_anonymous_volumes = options.get('--renew-anon-volumes')
if force_recreate and no_recreate:
raise UserError("--force-recreate and --no-recreate cannot be combined.")
if force_recreate:
if no_recreate and renew_anonymous_volumes:
raise UserError('--no-recreate and --renew-anon-volumes cannot be combined.')
if force_recreate or renew_anonymous_volumes:
return ConvergenceStrategy.always
if no_recreate:

View File

@ -445,7 +445,8 @@ class Project(object):
rescale=True,
start=True,
always_recreate_deps=False,
reset_container_image=False):
reset_container_image=False,
renew_anonymous_volumes=False):
self.initialize()
if not ignore_orphans:
@ -474,7 +475,8 @@ class Project(object):
rescale=rescale,
start=start,
project_services=scaled_services,
reset_container_image=reset_container_image
reset_container_image=reset_container_image,
renew_anonymous_volumes=renew_anonymous_volumes,
)
def get_deps(service):

View File

@ -409,7 +409,8 @@ class Service(object):
return containers
def _execute_convergence_recreate(self, containers, scale, timeout, detached, start):
def _execute_convergence_recreate(self, containers, scale, timeout, detached, start,
renew_anonymous_volumes):
if scale is not None and len(containers) > scale:
self._downscale(containers[scale:], timeout)
containers = containers[:scale]
@ -417,7 +418,7 @@ class Service(object):
def recreate(container):
return self.recreate_container(
container, timeout=timeout, attach_logs=not detached,
start_new_container=start
start_new_container=start, renew_anonymous_volumes=renew_anonymous_volumes
)
containers, errors = parallel_execute(
containers,
@ -470,7 +471,7 @@ class Service(object):
def execute_convergence_plan(self, plan, timeout=None, detached=False,
start=True, scale_override=None,
rescale=True, project_services=None,
reset_container_image=False):
reset_container_image=False, renew_anonymous_volumes=False):
(action, containers) = plan
scale = scale_override if scale_override is not None else self.scale_num
containers = sorted(containers, key=attrgetter('number'))
@ -495,7 +496,8 @@ class Service(object):
for c in containers:
c.reset_image(img_id)
return self._execute_convergence_recreate(
containers, scale, timeout, detached, start
containers, scale, timeout, detached, start,
renew_anonymous_volumes,
)
if action == 'start':
@ -515,7 +517,8 @@ class Service(object):
raise Exception("Invalid action: {}".format(action))
def recreate_container(self, container, timeout=None, attach_logs=False, start_new_container=True):
def recreate_container(self, container, timeout=None, attach_logs=False, start_new_container=True,
renew_anonymous_volumes=False):
"""Recreate a container.
The original container is renamed to a temporary name so that data
@ -526,7 +529,7 @@ class Service(object):
container.stop(timeout=self.stop_timeout(timeout))
container.rename_to_tmp_name()
new_container = self.create_container(
previous_container=container,
previous_container=container if not renew_anonymous_volumes else None,
number=container.labels.get(LABEL_CONTAINER_NUMBER),
quiet=True,
)

View File

@ -589,6 +589,25 @@ class ServiceTest(DockerClientTestCase):
assert [mount['Destination'] for mount in new_container.get('Mounts')] == ['/data']
assert new_container.get_mount('/data')['Source'] == volume_path
def test_execute_convergence_plan_with_image_declared_volume_renew(self):
service = Service(
project='composetest',
name='db',
client=self.client,
build={'context': 'tests/fixtures/dockerfile-with-volume'},
)
old_container = create_and_start_container(service)
assert [mount['Destination'] for mount in old_container.get('Mounts')] == ['/data']
volume_path = old_container.get_mount('/data')['Source']
new_container, = service.execute_convergence_plan(
ConvergencePlan('recreate', [old_container]), renew_anonymous_volumes=True
)
assert [mount['Destination'] for mount in new_container.get('Mounts')] == ['/data']
assert new_container.get_mount('/data')['Source'] != volume_path
def test_execute_convergence_plan_when_image_volume_masks_config(self):
service = self.create_service(
'db',
@ -637,6 +656,64 @@ class ServiceTest(DockerClientTestCase):
)
assert new_container.get_mount('/data')['Source'] != host_path
def test_execute_convergence_plan_anonymous_volume_renew(self):
service = self.create_service(
'db',
image='busybox',
volumes=[VolumeSpec(None, '/data', 'rw')])
old_container = create_and_start_container(service)
assert (
[mount['Destination'] for mount in old_container.get('Mounts')] ==
['/data']
)
volume_path = old_container.get_mount('/data')['Source']
new_container, = service.execute_convergence_plan(
ConvergencePlan('recreate', [old_container]),
renew_anonymous_volumes=True
)
assert (
[mount['Destination'] for mount in new_container.get('Mounts')] ==
['/data']
)
assert new_container.get_mount('/data')['Source'] != volume_path
def test_execute_convergence_plan_anonymous_volume_recreate_then_renew(self):
service = self.create_service(
'db',
image='busybox',
volumes=[VolumeSpec(None, '/data', 'rw')])
old_container = create_and_start_container(service)
assert (
[mount['Destination'] for mount in old_container.get('Mounts')] ==
['/data']
)
volume_path = old_container.get_mount('/data')['Source']
mid_container, = service.execute_convergence_plan(
ConvergencePlan('recreate', [old_container]),
)
assert (
[mount['Destination'] for mount in mid_container.get('Mounts')] ==
['/data']
)
assert mid_container.get_mount('/data')['Source'] == volume_path
new_container, = service.execute_convergence_plan(
ConvergencePlan('recreate', [mid_container]),
renew_anonymous_volumes=True
)
assert (
[mount['Destination'] for mount in new_container.get('Mounts')] ==
['/data']
)
assert new_container.get_mount('/data')['Source'] != volume_path
def test_execute_convergence_plan_without_start(self):
service = self.create_service(
'db',