Skip to content

Commit

Permalink
Add up flag --renew-anon-volumes (shorthand -V)
Browse files Browse the repository at this point in the history
to avoid reusing the previous container's data

Signed-off-by: Joffrey F <[email protected]>
  • Loading branch information
shin- committed Jan 23, 2018
1 parent fd1e802 commit b07091a
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 12 deletions.
15 changes: 11 additions & 4 deletions compose/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -936,7 +936,7 @@ def up(self, options):
--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.
Expand All @@ -945,8 +945,10 @@ def up(self, options):
-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
Expand Down Expand Up @@ -992,6 +994,7 @@ def up(rebuild):
start=not no_start,
always_recreate_deps=always_recreate_deps,
reset_container_image=rebuild,
renew_anonymous_volumes=options.get('--renew-anon-volumes')
)

try:
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 4 additions & 2 deletions compose/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,8 @@ def up(self,
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:
Expand Down Expand Up @@ -474,7 +475,8 @@ def do(service):
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):
Expand Down
15 changes: 9 additions & 6 deletions compose/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,15 +409,16 @@ def create_and_start(service, n):

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]

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,
Expand Down Expand Up @@ -470,7 +471,7 @@ def stop_and_remove(container):
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'))
Expand All @@ -495,7 +496,8 @@ def execute_convergence_plan(self, plan, timeout=None, detached=False,
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':
Expand All @@ -515,7 +517,8 @@ def execute_convergence_plan(self, plan, timeout=None, detached=False,

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
Expand All @@ -526,7 +529,7 @@ def recreate_container(self, container, timeout=None, attach_logs=False, start_n
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,
)
Expand Down
77 changes: 77 additions & 0 deletions tests/integration/service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,25 @@ def test_execute_convergence_plan_with_image_declared_volume(self):
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',
Expand Down Expand Up @@ -637,6 +656,64 @@ def test_execute_convergence_plan_when_host_volume_is_removed(self):
)
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',
Expand Down

0 comments on commit b07091a

Please sign in to comment.