From 8e838968fe64ca222c51cd77d2a84f805bada7a9 Mon Sep 17 00:00:00 2001 From: Daniel Nephin <dnephin@docker.com> Date: Fri, 29 Jan 2016 14:41:18 -0500 Subject: [PATCH 1/4] Refactor project network initlization. Signed-off-by: Daniel Nephin <dnephin@docker.com> --- compose/network.py | 64 ++++++++++++++++++++++++++ compose/project.py | 93 ++++++++------------------------------ tests/unit/project_test.py | 8 ++-- 3 files changed, 88 insertions(+), 77 deletions(-) diff --git a/compose/network.py b/compose/network.py index 4f4f55228..4f4e06b22 100644 --- a/compose/network.py +++ b/compose/network.py @@ -104,3 +104,67 @@ def create_ipam_config_from_dict(ipam_dict): for config in ipam_dict.get('config', []) ], ) + + +def build_networks(name, config_data, client): + network_config = config_data.networks or {} + networks = { + network_name: Network( + client=client, project=name, name=network_name, + driver=data.get('driver'), + driver_opts=data.get('driver_opts'), + ipam=data.get('ipam'), + external_name=data.get('external_name'), + ) + for network_name, data in network_config.items() + } + + if 'default' not in networks: + networks['default'] = Network(client, name, 'default') + + return networks + + +class ProjectNetworks(object): + + def __init__(self, networks, use_networking): + self.networks = networks or {} + self.use_networking = use_networking + + @classmethod + def from_services(cls, services, networks, use_networking): + networks = { + network: networks[network] + for service in services + for network in service.get('networks', ['default']) + } + return cls(networks, use_networking) + + def remove(self): + if not self.use_networking: + return + for network in self.networks.values(): + network.remove() + + def initialize(self): + if not self.use_networking: + return + + for network in self.networks.values(): + network.ensure() + + +def get_networks(service_dict, network_definitions): + if 'network_mode' in service_dict: + return [] + + networks = [] + for name in service_dict.pop('networks', ['default']): + network = network_definitions.get(name) + if network: + networks.append(network.full_name) + else: + raise ConfigurationError( + 'Service "{}" uses an undefined network "{}"' + .format(service_dict['name'], name)) + return networks diff --git a/compose/project.py b/compose/project.py index 6411f7cc3..2e9cfb8f3 100644 --- a/compose/project.py +++ b/compose/project.py @@ -19,7 +19,9 @@ from .const import LABEL_ONE_OFF from .const import LABEL_PROJECT from .const import LABEL_SERVICE from .container import Container -from .network import Network +from .network import build_networks +from .network import get_networks +from .network import ProjectNetworks from .service import ContainerNetworkMode from .service import ConvergenceStrategy from .service import NetworkMode @@ -36,15 +38,12 @@ class Project(object): """ A collection of services. """ - def __init__(self, name, services, client, networks=None, volumes=None, - use_networking=False, network_driver=None): + def __init__(self, name, services, client, networks=None, volumes=None): self.name = name self.services = services self.client = client - self.use_networking = use_networking - self.network_driver = network_driver - self.networks = networks or [] self.volumes = volumes or {} + self.networks = networks or ProjectNetworks({}, False) def labels(self, one_off=False): return [ @@ -58,23 +57,12 @@ class Project(object): Construct a Project from a config.Config object. """ use_networking = (config_data.version and config_data.version != V1) - project = cls(name, [], client, use_networking=use_networking) - - network_config = config_data.networks or {} - custom_networks = [ - Network( - client=client, project=name, name=network_name, - driver=data.get('driver'), - driver_opts=data.get('driver_opts'), - ipam=data.get('ipam'), - external_name=data.get('external_name'), - ) - for network_name, data in network_config.items() - ] - - all_networks = custom_networks[:] - if 'default' not in network_config: - all_networks.append(project.default_network) + networks = build_networks(name, config_data, client) + project_networks = ProjectNetworks.from_services( + config_data.services, + networks, + use_networking) + project = cls(name, [], client, project_networks) if config_data.volumes: for vol_name, data in config_data.volumes.items(): @@ -86,13 +74,15 @@ class Project(object): ) for service_dict in config_data.services: + service_dict = dict(service_dict) if use_networking: - networks = get_networks(service_dict, all_networks) + service_networks = get_networks(service_dict, networks) else: - networks = [] + service_networks = [] + service_dict.pop('networks', None) links = project.get_links(service_dict) - network_mode = project.get_network_mode(service_dict, networks) + network_mode = project.get_network_mode(service_dict, service_networks) volumes_from = get_volumes_from(project, service_dict) if config_data.version != V1: @@ -109,17 +99,13 @@ class Project(object): client=client, project=name, use_networking=use_networking, - networks=networks, + networks=service_networks, links=links, network_mode=network_mode, volumes_from=volumes_from, **service_dict) ) - project.networks += custom_networks - if 'default' not in network_config and project.uses_default_network(): - project.networks.append(project.default_network) - return project @property @@ -201,7 +187,7 @@ class Project(object): def get_network_mode(self, service_dict, networks): network_mode = service_dict.pop('network_mode', None) if not network_mode: - if self.use_networking: + if self.networks.use_networking: return NetworkMode(networks[0]) if networks else NetworkMode('none') return NetworkMode(None) @@ -285,7 +271,7 @@ class Project(object): def down(self, remove_image_type, include_volumes): self.stop() self.remove_stopped(v=include_volumes) - self.remove_networks() + self.networks.remove() if include_volumes: self.remove_volumes() @@ -296,33 +282,10 @@ class Project(object): for service in self.get_services(): service.remove_image(remove_image_type) - def remove_networks(self): - if not self.use_networking: - return - for network in self.networks: - network.remove() - def remove_volumes(self): for volume in self.volumes.values(): volume.remove() - def initialize_networks(self): - if not self.use_networking: - return - - for network in self.networks: - network.ensure() - - def uses_default_network(self): - return any( - self.default_network.full_name in service.networks - for service in self.services - ) - - @property - def default_network(self): - return Network(client=self.client, project=self.name, name='default') - def restart(self, service_names=None, **options): containers = self.containers(service_names, stopped=True) parallel.parallel_restart(containers, options) @@ -392,7 +355,7 @@ class Project(object): plans = self._get_convergence_plans(services, strategy) - self.initialize_networks() + self.networks.initialize() self.initialize_volumes() return [ @@ -465,22 +428,6 @@ class Project(object): return acc + dep_services -def get_networks(service_dict, network_definitions): - if 'network_mode' in service_dict: - return [] - - networks = [] - for name in service_dict.pop('networks', ['default']): - matches = [n for n in network_definitions if n.name == name] - if matches: - networks.append(matches[0].full_name) - else: - raise ConfigurationError( - 'Service "{}" uses an undefined network "{}"' - .format(service_dict['name'], name)) - return networks - - def get_volumes_from(project, service_dict): volumes_from = service_dict.pop('volumes_from', None) if not volumes_from: diff --git a/tests/unit/project_test.py b/tests/unit/project_test.py index 21c6be475..bec238de6 100644 --- a/tests/unit/project_test.py +++ b/tests/unit/project_test.py @@ -45,7 +45,7 @@ class ProjectTest(unittest.TestCase): self.assertEqual(project.get_service('web').options['image'], 'busybox:latest') self.assertEqual(project.get_service('db').name, 'db') self.assertEqual(project.get_service('db').options['image'], 'busybox:latest') - self.assertFalse(project.use_networking) + self.assertFalse(project.networks.use_networking) def test_from_config_v2(self): config = Config( @@ -65,7 +65,7 @@ class ProjectTest(unittest.TestCase): ) project = Project.from_config('composetest', config, None) self.assertEqual(len(project.services), 2) - self.assertTrue(project.use_networking) + self.assertTrue(project.networks.use_networking) def test_get_service(self): web = Service( @@ -426,7 +426,7 @@ class ProjectTest(unittest.TestCase): ), ) - assert project.uses_default_network() + assert 'default' in project.networks.networks def test_uses_default_network_false(self): project = Project.from_config( @@ -446,7 +446,7 @@ class ProjectTest(unittest.TestCase): ), ) - assert not project.uses_default_network() + assert 'default' not in project.networks.networks def test_container_without_name(self): self.mock_client.containers.return_value = [ From 0810eeba106f71fc374c791283a84cd9f6725e8f Mon Sep 17 00:00:00 2001 From: Daniel Nephin <dnephin@docker.com> Date: Fri, 29 Jan 2016 16:00:50 -0500 Subject: [PATCH 2/4] Don't initialize networks that aren't used by any services. Signed-off-by: Daniel Nephin <dnephin@docker.com> --- compose/cli/main.py | 2 +- compose/network.py | 11 ++++++++--- compose/project.py | 13 ++++++++----- tests/acceptance/cli_test.py | 16 ++++++++-------- tests/integration/project_test.py | 7 ++++++- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index 93c4729f8..5121d5c32 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -693,7 +693,7 @@ def run_one_off_container(container_options, project, service, options): start_deps=True, strategy=ConvergenceStrategy.never) - project.initialize_networks() + project.initialize() container = service.create_container( quiet=True, diff --git a/compose/network.py b/compose/network.py index 4f4e06b22..36de45026 100644 --- a/compose/network.py +++ b/compose/network.py @@ -133,12 +133,17 @@ class ProjectNetworks(object): @classmethod def from_services(cls, services, networks, use_networking): - networks = { - network: networks[network] + service_networks = { + network: networks.get(network) for service in services for network in service.get('networks', ['default']) } - return cls(networks, use_networking) + unused = set(networks) - set(service_networks) - {'default'} + if unused: + log.warn( + "Some networks were defined but are not used by any service: " + "{}".format(", ".join(unused))) + return cls(service_networks, use_networking) def remove(self): if not self.use_networking: diff --git a/compose/project.py b/compose/project.py index 2e9cfb8f3..291e32b14 100644 --- a/compose/project.py +++ b/compose/project.py @@ -351,13 +351,12 @@ class Project(object): timeout=DEFAULT_TIMEOUT, detached=False): - services = self.get_services_without_duplicate(service_names, include_deps=start_deps) + self.initialize() + services = self.get_services_without_duplicate( + service_names, + include_deps=start_deps) plans = self._get_convergence_plans(services, strategy) - - self.networks.initialize() - self.initialize_volumes() - return [ container for service in services @@ -369,6 +368,10 @@ class Project(object): ) ] + def initialize(self): + self.networks.initialize() + self.initialize_volumes() + def _get_convergence_plans(self, services, strategy): plans = {} diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 447b1e323..6b9a28e58 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -406,7 +406,8 @@ class CLITestCase(DockerClientTestCase): services = self.project.get_services() - networks = self.client.networks(names=[self.project.default_network.full_name]) + network_name = self.project.networks.networks['default'] + networks = self.client.networks(names=[network_name]) self.assertEqual(len(networks), 1) self.assertEqual(networks[0]['Driver'], 'bridge') assert 'com.docker.network.bridge.enable_icc' not in networks[0]['Options'] @@ -439,7 +440,9 @@ class CLITestCase(DockerClientTestCase): self.dispatch(['-f', filename, 'up', '-d'], None) - networks = self.client.networks(names=[self.project.default_network.full_name]) + network_name = self.project.networks.networks['default'] + networks = self.client.networks(names=[network_name]) + assert networks[0]['Options']['com.docker.network.bridge.enable_icc'] == 'false' @v2_only() @@ -586,18 +589,15 @@ class CLITestCase(DockerClientTestCase): n['Name'] for n in self.client.networks() if n['Name'].startswith('{}_'.format(self.project.name)) ] - - assert sorted(network_names) == [ - '{}_{}'.format(self.project.name, name) - for name in ['bar', 'foo'] - ] + assert network_names == [] def test_up_with_links_v1(self): self.base_dir = 'tests/fixtures/links-composefile' self.dispatch(['up', '-d', 'web'], None) # No network was created - networks = self.client.networks(names=[self.project.default_network.full_name]) + network_name = self.project.networks.networks['default'] + networks = self.client.networks(names=[network_name]) assert networks == [] web = self.project.get_service('web') diff --git a/tests/integration/project_test.py b/tests/integration/project_test.py index 180c9df1b..79644e59d 100644 --- a/tests/integration/project_test.py +++ b/tests/integration/project_test.py @@ -565,6 +565,7 @@ class ProjectTest(DockerClientTestCase): 'name': 'web', 'image': 'busybox:latest', 'command': 'top', + 'networks': ['foo', 'bar', 'baz'], }], volumes={}, networks={ @@ -594,7 +595,11 @@ class ProjectTest(DockerClientTestCase): def test_up_with_ipam_config(self): config_data = config.Config( version=V2_0, - services=[], + services=[{ + 'name': 'web', + 'image': 'busybox:latest', + 'networks': ['front'], + }], volumes={}, networks={ 'front': { From e551988616021b50c8069b5d36a808afee5a8653 Mon Sep 17 00:00:00 2001 From: Daniel Nephin <dnephin@docker.com> Date: Fri, 29 Jan 2016 16:30:24 -0500 Subject: [PATCH 3/4] Include networks in the config_hash. Signed-off-by: Daniel Nephin <dnephin@docker.com> --- compose/network.py | 10 +++++++--- compose/project.py | 1 + compose/service.py | 1 + tests/acceptance/cli_test.py | 6 +++--- tests/integration/project_test.py | 3 ++- tests/unit/service_test.py | 8 +++++--- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/compose/network.py b/compose/network.py index 36de45026..82a78f3b5 100644 --- a/compose/network.py +++ b/compose/network.py @@ -136,7 +136,7 @@ class ProjectNetworks(object): service_networks = { network: networks.get(network) for service in services - for network in service.get('networks', ['default']) + for network in get_network_names_for_service(service) } unused = set(networks) - set(service_networks) - {'default'} if unused: @@ -159,12 +159,15 @@ class ProjectNetworks(object): network.ensure() -def get_networks(service_dict, network_definitions): +def get_network_names_for_service(service_dict): if 'network_mode' in service_dict: return [] + return service_dict.get('networks', ['default']) + +def get_networks(service_dict, network_definitions): networks = [] - for name in service_dict.pop('networks', ['default']): + for name in get_network_names_for_service(service_dict): network = network_definitions.get(name) if network: networks.append(network.full_name) @@ -172,4 +175,5 @@ def get_networks(service_dict, network_definitions): raise ConfigurationError( 'Service "{}" uses an undefined network "{}"' .format(service_dict['name'], name)) + return networks diff --git a/compose/project.py b/compose/project.py index 291e32b14..a90ec2c3c 100644 --- a/compose/project.py +++ b/compose/project.py @@ -96,6 +96,7 @@ class Project(object): project.services.append( Service( + service_dict.pop('name'), client=client, project=name, use_networking=use_networking, diff --git a/compose/service.py b/compose/service.py index 2ca0e64d9..e6aad3ae1 100644 --- a/compose/service.py +++ b/compose/service.py @@ -472,6 +472,7 @@ class Service(object): 'image_id': self.image()['Id'], 'links': self.get_link_names(), 'net': self.network_mode.id, + 'networks': self.networks, 'volumes_from': [ (v.source.name, v.mode) for v in self.volumes_from if isinstance(v.source, Service) diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 6b9a28e58..032900d51 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -406,7 +406,7 @@ class CLITestCase(DockerClientTestCase): services = self.project.get_services() - network_name = self.project.networks.networks['default'] + network_name = self.project.networks.networks['default'].full_name networks = self.client.networks(names=[network_name]) self.assertEqual(len(networks), 1) self.assertEqual(networks[0]['Driver'], 'bridge') @@ -440,7 +440,7 @@ class CLITestCase(DockerClientTestCase): self.dispatch(['-f', filename, 'up', '-d'], None) - network_name = self.project.networks.networks['default'] + network_name = self.project.networks.networks['default'].full_name networks = self.client.networks(names=[network_name]) assert networks[0]['Options']['com.docker.network.bridge.enable_icc'] == 'false' @@ -596,7 +596,7 @@ class CLITestCase(DockerClientTestCase): self.dispatch(['up', '-d', 'web'], None) # No network was created - network_name = self.project.networks.networks['default'] + network_name = self.project.networks.networks['default'].full_name networks = self.client.networks(names=[network_name]) assert networks == [] diff --git a/tests/integration/project_test.py b/tests/integration/project_test.py index 79644e59d..45bae2c30 100644 --- a/tests/integration/project_test.py +++ b/tests/integration/project_test.py @@ -832,7 +832,8 @@ class ProjectTest(DockerClientTestCase): ) project = Project.from_config( name='composetest', - config_data=config_data, client=self.client + config_data=config_data, + client=self.client ) with self.assertRaises(config.ConfigurationError) as e: project.initialize_volumes() diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py index 74e9f0f53..f34de3bf1 100644 --- a/tests/unit/service_test.py +++ b/tests/unit/service_test.py @@ -266,7 +266,7 @@ class ServiceTest(unittest.TestCase): self.assertEqual( opts['labels'][LABEL_CONFIG_HASH], - '3c85881a8903b9d73a06c41860c8be08acce1494ab4cf8408375966dccd714de') + 'f8bfa1058ad1f4231372a0b1639f0dfdb574dafff4e8d7938049ae993f7cf1fc') self.assertEqual( opts['environment'], { @@ -417,9 +417,10 @@ class ServiceTest(unittest.TestCase): 'options': {'image': 'example.com/foo'}, 'links': [('one', 'one')], 'net': 'other', + 'networks': [], 'volumes_from': [('two', 'rw')], } - self.assertEqual(config_dict, expected) + assert config_dict == expected def test_config_dict_with_network_mode_from_container(self): self.mock_client.inspect_image.return_value = {'Id': 'abcd'} @@ -437,10 +438,11 @@ class ServiceTest(unittest.TestCase): 'image_id': 'abcd', 'options': {'image': 'example.com/foo'}, 'links': [], + 'networks': [], 'net': 'aaabbb', 'volumes_from': [], } - self.assertEqual(config_dict, expected) + assert config_dict == expected def test_remove_image_none(self): web = Service('web', image='example', client=self.mock_client) From 3d3388d59ba94b074b38418fcec8da1ccddd7b58 Mon Sep 17 00:00:00 2001 From: Daniel Nephin <dnephin@docker.com> Date: Fri, 29 Jan 2016 17:31:27 -0500 Subject: [PATCH 4/4] Extract volume init and removal from project. Signed-off-by: Daniel Nephin <dnephin@docker.com> --- compose/project.py | 72 +++++-------------------------- compose/volume.py | 70 ++++++++++++++++++++++++++++++ tests/integration/project_test.py | 12 +++--- 3 files changed, 86 insertions(+), 68 deletions(-) diff --git a/compose/project.py b/compose/project.py index a90ec2c3c..62e1d2cd3 100644 --- a/compose/project.py +++ b/compose/project.py @@ -6,7 +6,6 @@ import logging from functools import reduce from docker.errors import APIError -from docker.errors import NotFound from . import parallel from .config import ConfigurationError @@ -28,7 +27,7 @@ from .service import NetworkMode from .service import Service from .service import ServiceNetworkMode from .utils import microseconds_from_time_nano -from .volume import Volume +from .volume import ProjectVolumes log = logging.getLogger(__name__) @@ -42,7 +41,7 @@ class Project(object): self.name = name self.services = services self.client = client - self.volumes = volumes or {} + self.volumes = volumes or ProjectVolumes({}) self.networks = networks or ProjectNetworks({}, False) def labels(self, one_off=False): @@ -62,16 +61,8 @@ class Project(object): config_data.services, networks, use_networking) - project = cls(name, [], client, project_networks) - - if config_data.volumes: - for vol_name, data in config_data.volumes.items(): - project.volumes[vol_name] = Volume( - client=client, project=name, name=vol_name, - driver=data.get('driver'), - driver_opts=data.get('driver_opts'), - external_name=data.get('external_name') - ) + volumes = ProjectVolumes.from_config(name, config_data, client) + project = cls(name, [], client, project_networks, volumes) for service_dict in config_data.services: service_dict = dict(service_dict) @@ -86,13 +77,10 @@ class Project(object): volumes_from = get_volumes_from(project, service_dict) if config_data.version != V1: - service_volumes = service_dict.get('volumes', []) - for volume_spec in service_volumes: - if volume_spec.is_named_volume: - declared_volume = project.volumes[volume_spec.external] - service_volumes[service_volumes.index(volume_spec)] = ( - volume_spec._replace(external=declared_volume.full_name) - ) + service_dict['volumes'] = [ + volumes.namespace_spec(volume_spec) + for volume_spec in service_dict.get('volumes', []) + ] project.services.append( Service( @@ -233,49 +221,13 @@ class Project(object): def remove_stopped(self, service_names=None, **options): parallel.parallel_remove(self.containers(service_names, stopped=True), options) - def initialize_volumes(self): - try: - for volume in self.volumes.values(): - if volume.external: - log.debug( - 'Volume {0} declared as external. No new ' - 'volume will be created.'.format(volume.name) - ) - if not volume.exists(): - raise ConfigurationError( - 'Volume {name} declared as external, but could' - ' not be found. Please create the volume manually' - ' using `{command}{name}` and try again.'.format( - name=volume.full_name, - command='docker volume create --name=' - ) - ) - continue - volume.create() - except NotFound: - raise ConfigurationError( - 'Volume %s specifies nonexistent driver %s' % (volume.name, volume.driver) - ) - except APIError as e: - if 'Choose a different volume name' in str(e): - raise ConfigurationError( - 'Configuration for volume {0} specifies driver {1}, but ' - 'a volume with the same name uses a different driver ' - '({3}). If you wish to use the new configuration, please ' - 'remove the existing volume "{2}" first:\n' - '$ docker volume rm {2}'.format( - volume.name, volume.driver, volume.full_name, - volume.inspect()['Driver'] - ) - ) - def down(self, remove_image_type, include_volumes): self.stop() self.remove_stopped(v=include_volumes) self.networks.remove() if include_volumes: - self.remove_volumes() + self.volumes.remove() self.remove_images(remove_image_type) @@ -283,10 +235,6 @@ class Project(object): for service in self.get_services(): service.remove_image(remove_image_type) - def remove_volumes(self): - for volume in self.volumes.values(): - volume.remove() - def restart(self, service_names=None, **options): containers = self.containers(service_names, stopped=True) parallel.parallel_restart(containers, options) @@ -371,7 +319,7 @@ class Project(object): def initialize(self): self.networks.initialize() - self.initialize_volumes() + self.volumes.initialize() def _get_convergence_plans(self, services, strategy): plans = {} diff --git a/compose/volume.py b/compose/volume.py index 469e406a8..2713fd32b 100644 --- a/compose/volume.py +++ b/compose/volume.py @@ -3,8 +3,10 @@ from __future__ import unicode_literals import logging +from docker.errors import APIError from docker.errors import NotFound +from .config import ConfigurationError log = logging.getLogger(__name__) @@ -50,3 +52,71 @@ class Volume(object): if self.external_name: return self.external_name return '{0}_{1}'.format(self.project, self.name) + + +class ProjectVolumes(object): + + def __init__(self, volumes): + self.volumes = volumes + + @classmethod + def from_config(cls, name, config_data, client): + config_volumes = config_data.volumes or {} + volumes = { + vol_name: Volume( + client=client, + project=name, + name=vol_name, + driver=data.get('driver'), + driver_opts=data.get('driver_opts'), + external_name=data.get('external_name')) + for vol_name, data in config_volumes.items() + } + return cls(volumes) + + def remove(self): + for volume in self.volumes.values(): + volume.remove() + + def initialize(self): + try: + for volume in self.volumes.values(): + if volume.external: + log.debug( + 'Volume {0} declared as external. No new ' + 'volume will be created.'.format(volume.name) + ) + if not volume.exists(): + raise ConfigurationError( + 'Volume {name} declared as external, but could' + ' not be found. Please create the volume manually' + ' using `{command}{name}` and try again.'.format( + name=volume.full_name, + command='docker volume create --name=' + ) + ) + continue + volume.create() + except NotFound: + raise ConfigurationError( + 'Volume %s specifies nonexistent driver %s' % (volume.name, volume.driver) + ) + except APIError as e: + if 'Choose a different volume name' in str(e): + raise ConfigurationError( + 'Configuration for volume {0} specifies driver {1}, but ' + 'a volume with the same name uses a different driver ' + '({3}). If you wish to use the new configuration, please ' + 'remove the existing volume "{2}" first:\n' + '$ docker volume rm {2}'.format( + volume.name, volume.driver, volume.full_name, + volume.inspect()['Driver'] + ) + ) + + def namespace_spec(self, volume_spec): + if not volume_spec.is_named_volume: + return volume_spec + + volume = self.volumes[volume_spec.external] + return volume_spec._replace(external=volume.full_name) diff --git a/tests/integration/project_test.py b/tests/integration/project_test.py index 45bae2c30..6bb076a3f 100644 --- a/tests/integration/project_test.py +++ b/tests/integration/project_test.py @@ -749,7 +749,7 @@ class ProjectTest(DockerClientTestCase): name='composetest', config_data=config_data, client=self.client ) - project.initialize_volumes() + project.volumes.initialize() volume_data = self.client.inspect_volume(full_vol_name) self.assertEqual(volume_data['Name'], full_vol_name) @@ -800,7 +800,7 @@ class ProjectTest(DockerClientTestCase): config_data=config_data, client=self.client ) with self.assertRaises(config.ConfigurationError): - project.initialize_volumes() + project.volumes.initialize() @v2_only() def test_initialize_volumes_updated_driver(self): @@ -821,7 +821,7 @@ class ProjectTest(DockerClientTestCase): name='composetest', config_data=config_data, client=self.client ) - project.initialize_volumes() + project.volumes.initialize() volume_data = self.client.inspect_volume(full_vol_name) self.assertEqual(volume_data['Name'], full_vol_name) @@ -836,7 +836,7 @@ class ProjectTest(DockerClientTestCase): client=self.client ) with self.assertRaises(config.ConfigurationError) as e: - project.initialize_volumes() + project.volumes.initialize() assert 'Configuration for volume {0} specifies driver smb'.format( vol_name ) in str(e.exception) @@ -863,7 +863,7 @@ class ProjectTest(DockerClientTestCase): name='composetest', config_data=config_data, client=self.client ) - project.initialize_volumes() + project.volumes.initialize() with self.assertRaises(NotFound): self.client.inspect_volume(full_vol_name) @@ -889,7 +889,7 @@ class ProjectTest(DockerClientTestCase): config_data=config_data, client=self.client ) with self.assertRaises(config.ConfigurationError) as e: - project.initialize_volumes() + project.volumes.initialize() assert 'Volume {0} declared as external'.format( vol_name ) in str(e.exception)