diff --git a/compose/config/config_schema_v2.0.json b/compose/config/config_schema_v2.0.json index 2ad62ac52..8ab451a62 100644 --- a/compose/config/config_schema_v2.0.json +++ b/compose/config/config_schema_v2.0.json @@ -191,7 +191,8 @@ "properties": { "aliases": {"$ref": "#/definitions/list_of_strings"}, "ipv4_address": {"type": "string"}, - "ipv6_address": {"type": "string"} + "ipv6_address": {"type": "string"}, + "priority": {"type": "number"} }, "additionalProperties": false }, diff --git a/compose/config/config_schema_v2.1.json b/compose/config/config_schema_v2.1.json index 15b78e5db..f2ee2ce40 100644 --- a/compose/config/config_schema_v2.1.json +++ b/compose/config/config_schema_v2.1.json @@ -217,7 +217,8 @@ "aliases": {"$ref": "#/definitions/list_of_strings"}, "ipv4_address": {"type": "string"}, "ipv6_address": {"type": "string"}, - "link_local_ips": {"$ref": "#/definitions/list_of_strings"} + "link_local_ips": {"$ref": "#/definitions/list_of_strings"}, + "priority": {"type": "number"} }, "additionalProperties": false }, diff --git a/compose/config/config_schema_v2.2.json b/compose/config/config_schema_v2.2.json index 7a3eed0a9..b25aa71e2 100644 --- a/compose/config/config_schema_v2.2.json +++ b/compose/config/config_schema_v2.2.json @@ -223,7 +223,8 @@ "aliases": {"$ref": "#/definitions/list_of_strings"}, "ipv4_address": {"type": "string"}, "ipv6_address": {"type": "string"}, - "link_local_ips": {"$ref": "#/definitions/list_of_strings"} + "link_local_ips": {"$ref": "#/definitions/list_of_strings"}, + "priority": {"type": "number"} }, "additionalProperties": false }, diff --git a/compose/config/config_schema_v2.3.json b/compose/config/config_schema_v2.3.json index 3a6397a7e..30f00dfe4 100644 --- a/compose/config/config_schema_v2.3.json +++ b/compose/config/config_schema_v2.3.json @@ -226,7 +226,8 @@ "aliases": {"$ref": "#/definitions/list_of_strings"}, "ipv4_address": {"type": "string"}, "ipv6_address": {"type": "string"}, - "link_local_ips": {"$ref": "#/definitions/list_of_strings"} + "link_local_ips": {"$ref": "#/definitions/list_of_strings"}, + "priority": {"type": "number"} }, "additionalProperties": false }, diff --git a/compose/network.py b/compose/network.py index 95e2bf60e..027e7d5a5 100644 --- a/compose/network.py +++ b/compose/network.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from __future__ import unicode_literals import logging +from collections import OrderedDict from docker.errors import NotFound from docker.types import IPAMConfig @@ -286,4 +287,7 @@ def get_networks(service_dict, network_definitions): 'Service "{}" uses an undefined network "{}"' .format(service_dict['name'], name)) - return networks + return OrderedDict(sorted( + networks.items(), + key=lambda t: t[1].get('priority') or 0, reverse=True + )) diff --git a/compose/service.py b/compose/service.py index 420eb3f09..8a2faba95 100644 --- a/compose/service.py +++ b/compose/service.py @@ -6,6 +6,7 @@ import os import re import sys from collections import namedtuple +from collections import OrderedDict from operator import attrgetter import enum @@ -557,18 +558,25 @@ class Service(object): raise OperationFailedError("Cannot start service %s: %s" % (self.name, ex.explanation)) return container + @property + def prioritized_networks(self): + return OrderedDict( + sorted( + self.networks.items(), + key=lambda t: t[1].get('priority') or 0, reverse=True + ) + ) + def connect_container_to_networks(self, container): connected_networks = container.get('NetworkSettings.Networks') - for network, netdefs in self.networks.items(): + for network, netdefs in self.prioritized_networks.items(): if network in connected_networks: if short_id_alias_exists(container, network): continue + self.client.disconnect_container_from_network(container.id, network) - self.client.disconnect_container_from_network( - container.id, - network) - + log.debug('Connecting to {}'.format(network)) self.client.connect_container_to_network( container.id, network, aliases=self._get_aliases(netdefs, container), diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index fa051fecb..ade7d10a9 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -1887,7 +1887,7 @@ class CLITestCase(DockerClientTestCase): result = self.dispatch(['run', 'simple']) if six.PY2: # Can't retrieve output on Py3. See issue #3670 - assert value == result.stdout.strip() + assert value in result.stdout.strip() container = self.project.containers(one_off=OneOffFilter.only, stopped=True)[0] environment = container.get('Config.Env') diff --git a/tests/integration/project_test.py b/tests/integration/project_test.py index a9ca3be61..b0e55f2d0 100644 --- a/tests/integration/project_test.py +++ b/tests/integration/project_test.py @@ -829,6 +829,71 @@ class ProjectTest(DockerClientTestCase): assert ipam_config.get('IPv4Address') == '172.16.100.100' assert ipam_config.get('IPv6Address') == 'fe80::1001:102' + @v2_3_only() + def test_up_with_network_priorities(self): + mac_address = '74:6f:75:68:6f:75' + + def get_config_data(p1, p2, p3): + return build_config( + version=V2_3, + services=[{ + 'name': 'web', + 'image': 'busybox:latest', + 'networks': { + 'n1': { + 'priority': p1, + }, + 'n2': { + 'priority': p2, + }, + 'n3': { + 'priority': p3, + } + }, + 'command': 'top', + 'mac_address': mac_address + }], + networks={ + 'n1': {}, + 'n2': {}, + 'n3': {} + } + ) + + config1 = get_config_data(1000, 1, 1) + config2 = get_config_data(2, 3, 1) + config3 = get_config_data(5, 40, 100) + + project = Project.from_config( + client=self.client, + name='composetest', + config_data=config1 + ) + project.up(detached=True) + service_container = project.get_service('web').containers()[0] + net_config = service_container.inspect()['NetworkSettings']['Networks']['composetest_n1'] + assert net_config['MacAddress'] == mac_address + + project = Project.from_config( + client=self.client, + name='composetest', + config_data=config2 + ) + project.up(detached=True) + service_container = project.get_service('web').containers()[0] + net_config = service_container.inspect()['NetworkSettings']['Networks']['composetest_n2'] + assert net_config['MacAddress'] == mac_address + + project = Project.from_config( + client=self.client, + name='composetest', + config_data=config3 + ) + project.up(detached=True) + service_container = project.get_service('web').containers()[0] + net_config = service_container.inspect()['NetworkSettings']['Networks']['composetest_n3'] + assert net_config['MacAddress'] == mac_address + @v2_1_only() def test_up_with_enable_ipv6(self): self.require_api_version('1.23')