mirror of https://github.com/docker/compose.git
Avoid rebinding tmpfs data volumes when recreating containers
Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
parent
da78c2c1db
commit
bbdbc35924
|
@ -16,6 +16,7 @@ from docker.errors import NotFound
|
||||||
from docker.types import LogConfig
|
from docker.types import LogConfig
|
||||||
from docker.utils.ports import build_port_bindings
|
from docker.utils.ports import build_port_bindings
|
||||||
from docker.utils.ports import split_port
|
from docker.utils.ports import split_port
|
||||||
|
from docker.utils.utils import convert_tmpfs_mounts
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from . import const
|
from . import const
|
||||||
|
@ -744,6 +745,7 @@ class Service(object):
|
||||||
|
|
||||||
binds, affinity = merge_volume_bindings(
|
binds, affinity = merge_volume_bindings(
|
||||||
container_options.get('volumes') or [],
|
container_options.get('volumes') or [],
|
||||||
|
self.options.get('tmpfs') or [],
|
||||||
previous_container)
|
previous_container)
|
||||||
override_options['binds'] = binds
|
override_options['binds'] = binds
|
||||||
container_options['environment'].update(affinity)
|
container_options['environment'].update(affinity)
|
||||||
|
@ -1126,7 +1128,7 @@ def parse_repository_tag(repo_path):
|
||||||
# Volumes
|
# Volumes
|
||||||
|
|
||||||
|
|
||||||
def merge_volume_bindings(volumes, previous_container):
|
def merge_volume_bindings(volumes, tmpfs, previous_container):
|
||||||
"""Return a list of volume bindings for a container. Container data volumes
|
"""Return a list of volume bindings for a container. Container data volumes
|
||||||
are replaced by those from the previous container.
|
are replaced by those from the previous container.
|
||||||
"""
|
"""
|
||||||
|
@ -1138,7 +1140,7 @@ def merge_volume_bindings(volumes, previous_container):
|
||||||
if volume.external)
|
if volume.external)
|
||||||
|
|
||||||
if previous_container:
|
if previous_container:
|
||||||
old_volumes = get_container_data_volumes(previous_container, volumes)
|
old_volumes = get_container_data_volumes(previous_container, volumes, tmpfs)
|
||||||
warn_on_masked_volume(volumes, old_volumes, previous_container.service)
|
warn_on_masked_volume(volumes, old_volumes, previous_container.service)
|
||||||
volume_bindings.update(
|
volume_bindings.update(
|
||||||
build_volume_binding(volume) for volume in old_volumes)
|
build_volume_binding(volume) for volume in old_volumes)
|
||||||
|
@ -1149,7 +1151,7 @@ def merge_volume_bindings(volumes, previous_container):
|
||||||
return list(volume_bindings.values()), affinity
|
return list(volume_bindings.values()), affinity
|
||||||
|
|
||||||
|
|
||||||
def get_container_data_volumes(container, volumes_option):
|
def get_container_data_volumes(container, volumes_option, tmpfs_option):
|
||||||
"""Find the container data volumes that are in `volumes_option`, and return
|
"""Find the container data volumes that are in `volumes_option`, and return
|
||||||
a mapping of volume bindings for those volumes.
|
a mapping of volume bindings for those volumes.
|
||||||
"""
|
"""
|
||||||
|
@ -1172,6 +1174,10 @@ def get_container_data_volumes(container, volumes_option):
|
||||||
if volume.external:
|
if volume.external:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Attempting to rebind tmpfs volumes breaks: https://github.com/docker/compose/issues/4751
|
||||||
|
if volume.internal in convert_tmpfs_mounts(tmpfs_option).keys():
|
||||||
|
continue
|
||||||
|
|
||||||
mount = container_mounts.get(volume.internal)
|
mount = container_mounts.get(volume.internal)
|
||||||
|
|
||||||
# New volume, doesn't exist in the old container
|
# New volume, doesn't exist in the old container
|
||||||
|
|
|
@ -552,6 +552,24 @@ class ProjectTest(DockerClientTestCase):
|
||||||
self.assertEqual(len(project.get_service('data').containers(stopped=True)), 1)
|
self.assertEqual(len(project.get_service('data').containers(stopped=True)), 1)
|
||||||
self.assertEqual(len(project.get_service('console').containers()), 0)
|
self.assertEqual(len(project.get_service('console').containers()), 0)
|
||||||
|
|
||||||
|
def test_project_up_recreate_with_tmpfs_volume(self):
|
||||||
|
# https://github.com/docker/compose/issues/4751
|
||||||
|
project = Project.from_config(
|
||||||
|
name='composetest',
|
||||||
|
config_data=load_config({
|
||||||
|
'version': '2.1',
|
||||||
|
'services': {
|
||||||
|
'foo': {
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
'tmpfs': ['/dev/shm'],
|
||||||
|
'volumes': ['/dev/shm']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), client=self.client
|
||||||
|
)
|
||||||
|
project.up()
|
||||||
|
project.up(strategy=ConvergenceStrategy.always)
|
||||||
|
|
||||||
def test_unscale_after_restart(self):
|
def test_unscale_after_restart(self):
|
||||||
web = self.create_service('web')
|
web = self.create_service('web')
|
||||||
project = Project('composetest', [web], self.client)
|
project = Project('composetest', [web], self.client)
|
||||||
|
|
|
@ -858,6 +858,7 @@ class ServiceVolumesTest(unittest.TestCase):
|
||||||
'/new/volume',
|
'/new/volume',
|
||||||
'/existing/volume',
|
'/existing/volume',
|
||||||
'named:/named/vol',
|
'named:/named/vol',
|
||||||
|
'/dev/tmpfs'
|
||||||
]]
|
]]
|
||||||
|
|
||||||
self.mock_client.inspect_image.return_value = {
|
self.mock_client.inspect_image.return_value = {
|
||||||
|
@ -903,15 +904,18 @@ class ServiceVolumesTest(unittest.TestCase):
|
||||||
VolumeSpec.parse('imagedata:/mnt/image/data:rw'),
|
VolumeSpec.parse('imagedata:/mnt/image/data:rw'),
|
||||||
]
|
]
|
||||||
|
|
||||||
volumes = get_container_data_volumes(container, options)
|
volumes = get_container_data_volumes(container, options, ['/dev/tmpfs'])
|
||||||
assert sorted(volumes) == sorted(expected)
|
assert sorted(volumes) == sorted(expected)
|
||||||
|
|
||||||
def test_merge_volume_bindings(self):
|
def test_merge_volume_bindings(self):
|
||||||
options = [
|
options = [
|
||||||
VolumeSpec.parse('/host/volume:/host/volume:ro', True),
|
VolumeSpec.parse(v, True) for v in [
|
||||||
VolumeSpec.parse('/host/rw/volume:/host/rw/volume', True),
|
'/host/volume:/host/volume:ro',
|
||||||
VolumeSpec.parse('/new/volume', True),
|
'/host/rw/volume:/host/rw/volume',
|
||||||
VolumeSpec.parse('/existing/volume', True),
|
'/new/volume',
|
||||||
|
'/existing/volume',
|
||||||
|
'/dev/tmpfs'
|
||||||
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
self.mock_client.inspect_image.return_value = {
|
self.mock_client.inspect_image.return_value = {
|
||||||
|
@ -936,7 +940,7 @@ class ServiceVolumesTest(unittest.TestCase):
|
||||||
'existingvolume:/existing/volume:rw',
|
'existingvolume:/existing/volume:rw',
|
||||||
]
|
]
|
||||||
|
|
||||||
binds, affinity = merge_volume_bindings(options, previous_container)
|
binds, affinity = merge_volume_bindings(options, ['/dev/tmpfs'], previous_container)
|
||||||
assert sorted(binds) == sorted(expected)
|
assert sorted(binds) == sorted(expected)
|
||||||
assert affinity == {'affinity:container': '=cdefab'}
|
assert affinity == {'affinity:container': '=cdefab'}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue