Merge pull request #2743 from aanand/network-mode

Implement network_mode option
This commit is contained in:
Joffrey F 2016-01-25 18:26:35 -08:00
commit 4b84d088e0
21 changed files with 467 additions and 145 deletions

View File

@ -19,7 +19,8 @@ from .errors import CircularReference
from .errors import ComposeFileNotFound from .errors import ComposeFileNotFound
from .errors import ConfigurationError from .errors import ConfigurationError
from .interpolation import interpolate_environment_variables from .interpolation import interpolate_environment_variables
from .sort_services import get_service_name_from_net from .sort_services import get_container_name_from_network_mode
from .sort_services import get_service_name_from_network_mode
from .sort_services import sort_service_dicts from .sort_services import sort_service_dicts
from .types import parse_extra_hosts from .types import parse_extra_hosts
from .types import parse_restart_spec from .types import parse_restart_spec
@ -30,6 +31,7 @@ from .validation import validate_against_fields_schema
from .validation import validate_against_service_schema from .validation import validate_against_service_schema
from .validation import validate_depends_on from .validation import validate_depends_on
from .validation import validate_extends_file_path from .validation import validate_extends_file_path
from .validation import validate_network_mode
from .validation import validate_top_level_object from .validation import validate_top_level_object
from .validation import validate_top_level_service_objects from .validation import validate_top_level_service_objects
from .validation import validate_ulimits from .validation import validate_ulimits
@ -490,10 +492,15 @@ def validate_extended_service_dict(service_dict, filename, service):
"%s services with 'volumes_from' cannot be extended" % error_prefix) "%s services with 'volumes_from' cannot be extended" % error_prefix)
if 'net' in service_dict: if 'net' in service_dict:
if get_service_name_from_net(service_dict['net']) is not None: if get_container_name_from_network_mode(service_dict['net']):
raise ConfigurationError( raise ConfigurationError(
"%s services with 'net: container' cannot be extended" % error_prefix) "%s services with 'net: container' cannot be extended" % error_prefix)
if 'network_mode' in service_dict:
if get_service_name_from_network_mode(service_dict['network_mode']):
raise ConfigurationError(
"%s services with 'network_mode: service' cannot be extended" % error_prefix)
if 'depends_on' in service_dict: if 'depends_on' in service_dict:
raise ConfigurationError( raise ConfigurationError(
"%s services with 'depends_on' cannot be extended" % error_prefix) "%s services with 'depends_on' cannot be extended" % error_prefix)
@ -505,6 +512,7 @@ def validate_service(service_config, service_names, version):
validate_paths(service_dict) validate_paths(service_dict)
validate_ulimits(service_config) validate_ulimits(service_config)
validate_network_mode(service_config, service_names)
validate_depends_on(service_config, service_names) validate_depends_on(service_config, service_names)
if not service_dict.get('image') and has_uppercase(service_name): if not service_dict.get('image') and has_uppercase(service_name):
@ -565,6 +573,14 @@ def finalize_service(service_config, service_names, version):
service_dict['volumes'] = [ service_dict['volumes'] = [
VolumeSpec.parse(v) for v in service_dict['volumes']] VolumeSpec.parse(v) for v in service_dict['volumes']]
if 'net' in service_dict:
network_mode = service_dict.pop('net')
container_name = get_container_name_from_network_mode(network_mode)
if container_name and container_name in service_names:
service_dict['network_mode'] = 'service:{}'.format(container_name)
else:
service_dict['network_mode'] = network_mode
if 'restart' in service_dict: if 'restart' in service_dict:
service_dict['restart'] = parse_restart_spec(service_dict['restart']) service_dict['restart'] = parse_restart_spec(service_dict['restart'])

View File

@ -103,6 +103,7 @@
"mac_address": {"type": "string"}, "mac_address": {"type": "string"},
"mem_limit": {"type": ["number", "string"]}, "mem_limit": {"type": ["number", "string"]},
"memswap_limit": {"type": ["number", "string"]}, "memswap_limit": {"type": ["number", "string"]},
"network_mode": {"type": "string"},
"networks": { "networks": {
"type": "array", "type": "array",

View File

@ -4,14 +4,22 @@ from __future__ import unicode_literals
from compose.config.errors import DependencyError from compose.config.errors import DependencyError
def get_service_name_from_net(net_config): def get_service_name_from_network_mode(network_mode):
if not net_config: return get_source_name_from_network_mode(network_mode, 'service')
def get_container_name_from_network_mode(network_mode):
return get_source_name_from_network_mode(network_mode, 'container')
def get_source_name_from_network_mode(network_mode, source_type):
if not network_mode:
return return
if not net_config.startswith('container:'): if not network_mode.startswith(source_type+':'):
return return
_, net_name = net_config.split(':', 1) _, net_name = network_mode.split(':', 1)
return net_name return net_name
@ -33,7 +41,7 @@ def sort_service_dicts(services):
service for service in services service for service in services
if (name in get_service_names(service.get('links', [])) or if (name in get_service_names(service.get('links', [])) or
name in get_service_names_from_volumes_from(service.get('volumes_from', [])) or name in get_service_names_from_volumes_from(service.get('volumes_from', [])) or
name == get_service_name_from_net(service.get('net')) or name == get_service_name_from_network_mode(service.get('network_mode')) or
name in service.get('depends_on', [])) name in service.get('depends_on', []))
] ]

View File

@ -15,6 +15,7 @@ from jsonschema import RefResolver
from jsonschema import ValidationError from jsonschema import ValidationError
from .errors import ConfigurationError from .errors import ConfigurationError
from .sort_services import get_service_name_from_network_mode
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -147,6 +148,24 @@ def validate_extends_file_path(service_name, extends_options, filename):
) )
def validate_network_mode(service_config, service_names):
network_mode = service_config.config.get('network_mode')
if not network_mode:
return
if 'networks' in service_config.config:
raise ConfigurationError("'network_mode' and 'networks' cannot be combined")
dependency = get_service_name_from_network_mode(network_mode)
if not dependency:
return
if dependency not in service_names:
raise ConfigurationError(
"Service '{s.name}' uses the network stack of service '{dep}' which "
"is undefined.".format(s=service_config, dep=dependency))
def validate_depends_on(service_config, service_names): def validate_depends_on(service_config, service_names):
for dependency in service_config.config.get('depends_on', []): for dependency in service_config.config.get('depends_on', []):
if dependency not in service_names: if dependency not in service_names:

View File

@ -10,7 +10,8 @@ from docker.errors import NotFound
from . import parallel from . import parallel
from .config import ConfigurationError from .config import ConfigurationError
from .config.sort_services import get_service_name_from_net from .config.sort_services import get_container_name_from_network_mode
from .config.sort_services import get_service_name_from_network_mode
from .const import DEFAULT_TIMEOUT from .const import DEFAULT_TIMEOUT
from .const import IMAGE_EVENTS from .const import IMAGE_EVENTS
from .const import LABEL_ONE_OFF from .const import LABEL_ONE_OFF
@ -18,11 +19,11 @@ from .const import LABEL_PROJECT
from .const import LABEL_SERVICE from .const import LABEL_SERVICE
from .container import Container from .container import Container
from .network import Network from .network import Network
from .service import ContainerNet from .service import ContainerNetworkMode
from .service import ConvergenceStrategy from .service import ConvergenceStrategy
from .service import Net from .service import NetworkMode
from .service import Service from .service import Service
from .service import ServiceNet from .service import ServiceNetworkMode
from .utils import microseconds_from_time_nano from .utils import microseconds_from_time_nano
from .volume import Volume from .volume import Volume
@ -86,12 +87,11 @@ class Project(object):
for service_dict in config_data.services: for service_dict in config_data.services:
if use_networking: if use_networking:
networks = get_networks(service_dict, all_networks) networks = get_networks(service_dict, all_networks)
net = Net(networks[0]) if networks else Net("none")
else: else:
networks = [] networks = []
net = project.get_net(service_dict)
links = project.get_links(service_dict) links = project.get_links(service_dict)
network_mode = project.get_network_mode(service_dict, networks)
volumes_from = get_volumes_from(project, service_dict) volumes_from = get_volumes_from(project, service_dict)
if config_data.version == 2: if config_data.version == 2:
@ -110,7 +110,7 @@ class Project(object):
use_networking=use_networking, use_networking=use_networking,
networks=networks, networks=networks,
links=links, links=links,
net=net, network_mode=network_mode,
volumes_from=volumes_from, volumes_from=volumes_from,
**service_dict) **service_dict)
) )
@ -197,27 +197,27 @@ class Project(object):
del service_dict['links'] del service_dict['links']
return links return links
def get_net(self, service_dict): def get_network_mode(self, service_dict, networks):
net = service_dict.pop('net', None) network_mode = service_dict.pop('network_mode', None)
if not net: if not network_mode:
return Net(None) if self.use_networking:
return NetworkMode(networks[0]) if networks else NetworkMode('none')
return NetworkMode(None)
net_name = get_service_name_from_net(net) service_name = get_service_name_from_network_mode(network_mode)
if not net_name: if service_name:
return Net(net) return ServiceNetworkMode(self.get_service(service_name))
container_name = get_container_name_from_network_mode(network_mode)
if container_name:
try: try:
return ServiceNet(self.get_service(net_name)) return ContainerNetworkMode(Container.from_id(self.client, container_name))
except NoSuchService:
pass
try:
return ContainerNet(Container.from_id(self.client, net_name))
except APIError: except APIError:
raise ConfigurationError( raise ConfigurationError(
'Service "%s" is trying to use the network of "%s", ' "Service '{name}' uses the network stack of container '{dep}' which "
'which is not the name of a service or container.' % ( "does not exist.".format(name=service_dict['name'], dep=container_name))
service_dict['name'],
net_name)) return NetworkMode(network_mode)
def start(self, service_names=None, **options): def start(self, service_names=None, **options):
containers = [] containers = []
@ -465,9 +465,12 @@ class Project(object):
def get_networks(service_dict, network_definitions): def get_networks(service_dict, network_definitions):
if 'network_mode' in service_dict:
return []
networks = [] networks = []
for name in service_dict.pop('networks', ['default']): for name in service_dict.pop('networks', ['default']):
if name in ['bridge', 'host']: if name in ['bridge']:
networks.append(name) networks.append(name)
else: else:
matches = [n for n in network_definitions if n.name == name] matches = [n for n in network_definitions if n.name == name]

View File

@ -47,7 +47,6 @@ DOCKER_START_KEYS = [
'extra_hosts', 'extra_hosts',
'ipc', 'ipc',
'read_only', 'read_only',
'net',
'log_driver', 'log_driver',
'log_opt', 'log_opt',
'mem_limit', 'mem_limit',
@ -113,7 +112,7 @@ class Service(object):
use_networking=False, use_networking=False,
links=None, links=None,
volumes_from=None, volumes_from=None,
net=None, network_mode=None,
networks=None, networks=None,
**options **options
): ):
@ -123,7 +122,7 @@ class Service(object):
self.use_networking = use_networking self.use_networking = use_networking
self.links = links or [] self.links = links or []
self.volumes_from = volumes_from or [] self.volumes_from = volumes_from or []
self.net = net or Net(None) self.network_mode = network_mode or NetworkMode(None)
self.networks = networks or [] self.networks = networks or []
self.options = options self.options = options
@ -472,7 +471,7 @@ class Service(object):
'options': self.options, 'options': self.options,
'image_id': self.image()['Id'], 'image_id': self.image()['Id'],
'links': self.get_link_names(), 'links': self.get_link_names(),
'net': self.net.id, 'net': self.network_mode.id,
'volumes_from': [ 'volumes_from': [
(v.source.name, v.mode) (v.source.name, v.mode)
for v in self.volumes_from if isinstance(v.source, Service) for v in self.volumes_from if isinstance(v.source, Service)
@ -480,7 +479,7 @@ class Service(object):
} }
def get_dependency_names(self): def get_dependency_names(self):
net_name = self.net.service_name net_name = self.network_mode.service_name
return (self.get_linked_service_names() + return (self.get_linked_service_names() +
self.get_volumes_from_names() + self.get_volumes_from_names() +
([net_name] if net_name else []) + ([net_name] if net_name else []) +
@ -636,7 +635,7 @@ class Service(object):
binds=options.get('binds'), binds=options.get('binds'),
volumes_from=self._get_volumes_from(), volumes_from=self._get_volumes_from(),
privileged=options.get('privileged', False), privileged=options.get('privileged', False),
network_mode=self.net.mode, network_mode=self.network_mode.mode,
devices=options.get('devices'), devices=options.get('devices'),
dns=options.get('dns'), dns=options.get('dns'),
dns_search=options.get('dns_search'), dns_search=options.get('dns_search'),
@ -774,22 +773,22 @@ class Service(object):
log.error(six.text_type(e)) log.error(six.text_type(e))
class Net(object): class NetworkMode(object):
"""A `standard` network mode (ex: host, bridge)""" """A `standard` network mode (ex: host, bridge)"""
service_name = None service_name = None
def __init__(self, net): def __init__(self, network_mode):
self.net = net self.network_mode = network_mode
@property @property
def id(self): def id(self):
return self.net return self.network_mode
mode = id mode = id
class ContainerNet(object): class ContainerNetworkMode(object):
"""A network mode that uses a container's network stack.""" """A network mode that uses a container's network stack."""
service_name = None service_name = None
@ -806,7 +805,7 @@ class ContainerNet(object):
return 'container:' + self.container.id return 'container:' + self.container.id
class ServiceNet(object): class ServiceNetworkMode(object):
"""A network mode that uses a service's network stack.""" """A network mode that uses a service's network stack."""
def __init__(self, service): def __init__(self, service):

View File

@ -437,14 +437,29 @@ Specify logging options as key-value pairs. An example of `syslog` options:
### net ### net
> [Version 1 file format](#version-1) only. In version 2, use > [Version 1 file format](#version-1) only. In version 2, use
> [networks](#networks). > [network_mode](#network_mode).
Networking mode. Use the same values as the docker client `--net` parameter. Network mode. Use the same values as the docker client `--net` parameter.
The `container:...` form can take a service name instead of a container name or
id.
net: "bridge" net: "bridge"
net: "none"
net: "container:[name or id]"
net: "host" net: "host"
net: "none"
net: "container:[service name or container name/id]"
### network_mode
> [Version 2 file format](#version-1) only. In version 1, use [net](#net).
Network mode. Use the same values as the docker client `--net` parameter, plus
the special form `service:[service name]`.
network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"
### networks ### networks
@ -457,8 +472,8 @@ Networks to join, referencing entries under the
- some-network - some-network
- other-network - other-network
The values `bridge`, `host` and `none` can also be used, and are equivalent to The value `bridge` can also be used to make containers join the pre-defined
`net: "bridge"`, `net: "host"` or `net: "none"` in version 1. `bridge` network.
There is no equivalent to `net: "container:[name or id]"`. There is no equivalent to `net: "container:[name or id]"`.
@ -918,16 +933,22 @@ It's more complicated if you're using particular configuration features:
your service's containers to an your service's containers to an
[external network](networking.md#using-a-pre-existing-network). [external network](networking.md#using-a-pre-existing-network).
- `net`: If you're using `host`, `bridge` or `none`, this is now replaced by - `net`: This is now replaced by [network_mode](#network_mode):
`networks`:
net: host -> networks: ["host"] net: host -> network_mode: host
net: bridge -> networks: ["bridge"] net: bridge -> network_mode: bridge
net: none -> networks: ["none"] net: none -> network_mode: none
If you're using `net: "container:<name>"`, there is no equivalent to this in If you're using `net: "container:[service name]"`, you must now use
version 2 - you should use [Docker networks](networking.md) for `network_mode: "service:[service name]"` instead.
communication instead.
net: "container:web" -> network_mode: "service:web"
If you're using `net: "container:[container name/id]"`, the value does not
need to change.
net: "container:cont-name" -> network_mode: "container:cont-name"
net: "container:abc12345" -> network_mode: "container:abc12345"
## Variable substitution ## Variable substitution

View File

@ -144,15 +144,3 @@ If you want your containers to join a pre-existing network, use the [`external`
name: my-pre-existing-network name: my-pre-existing-network
Instead of attemping to create a network called `[projectname]_default`, Compose will look for a network called `my-pre-existing-network` and connect your app's containers to it. Instead of attemping to create a network called `[projectname]_default`, Compose will look for a network called `my-pre-existing-network` and connect your app's containers to it.
## Custom container network modes
The `docker` CLI command allows you to specify a custom network mode for a container with the `--net` option - for example, `--net=host` specifies that the container should use the same network namespace as the Docker host, and `--net=none` specifies that it should have no networking capabilities.
To make use of this in Compose, specify a `networks` list with a single item `host`, `bridge` or `none`:
app:
build: ./app
networks: ["host"]
There is no equivalent to `--net=container:CONTAINER_NAME` in the v2 Compose file format. You should instead use networks to enable communication.

View File

@ -496,8 +496,29 @@ class CLITestCase(DockerClientTestCase):
assert 'Service "web" uses an undefined network "foo"' in result.stderr assert 'Service "web" uses an undefined network "foo"' in result.stderr
@v2_only() @v2_only()
def test_up_predefined_networks(self): def test_up_with_bridge_network_plus_default(self):
filename = 'predefined-networks.yml' filename = 'bridge.yml'
self.base_dir = 'tests/fixtures/networks'
self._project = get_project(self.base_dir, [filename])
self.dispatch(['-f', filename, 'up', '-d'], None)
container = self.project.containers()[0]
assert sorted(list(container.get('NetworkSettings.Networks'))) == sorted([
'bridge',
self.project.default_network.full_name,
])
@v2_only()
def test_up_with_network_mode(self):
c = self.client.create_container('busybox', 'top', name='composetest_network_mode_container')
self.addCleanup(self.client.remove_container, c, force=True)
self.client.start(c)
container_mode_source = 'container:{}'.format(c['Id'])
filename = 'network-mode.yml'
self.base_dir = 'tests/fixtures/networks' self.base_dir = 'tests/fixtures/networks'
self._project = get_project(self.base_dir, [filename]) self._project = get_project(self.base_dir, [filename])
@ -515,6 +536,16 @@ class CLITestCase(DockerClientTestCase):
assert list(container.get('NetworkSettings.Networks')) == [name] assert list(container.get('NetworkSettings.Networks')) == [name]
assert container.get('HostConfig.NetworkMode') == name assert container.get('HostConfig.NetworkMode') == name
service_mode_source = 'container:{}'.format(
self.project.get_service('bridge').containers()[0].id)
service_mode_container = self.project.get_service('service').containers()[0]
assert not service_mode_container.get('NetworkSettings.Networks')
assert service_mode_container.get('HostConfig.NetworkMode') == service_mode_source
container_mode_container = self.project.get_service('container').containers()[0]
assert not container_mode_container.get('NetworkSettings.Networks')
assert container_mode_container.get('HostConfig.NetworkMode') == container_mode_source
@v2_only() @v2_only()
def test_up_external_networks(self): def test_up_external_networks(self):
filename = 'external-networks.yml' filename = 'external-networks.yml'

View File

@ -1,6 +1,7 @@
web: web:
image: busybox image: busybox
command: /bin/true command: /bin/true
net: host
environment: environment:
- FOO=1 - FOO=1
- BAR=1 - BAR=1

View File

@ -11,6 +11,7 @@ myweb:
BAR: "2" BAR: "2"
# add BAZ # add BAZ
BAZ: "2" BAZ: "2"
net: bridge
mydb: mydb:
image: busybox image: busybox
command: top command: top

View File

@ -0,0 +1,12 @@
version: 2
services:
myweb:
build: '.'
extends:
service: web
command: top
web:
build: '.'
network_mode: "service:net"
net:
build: '.'

9
tests/fixtures/networks/bridge.yml vendored Normal file
View File

@ -0,0 +1,9 @@
version: 2
services:
web:
image: busybox
command: top
networks:
- bridge
- default

View File

@ -0,0 +1,27 @@
version: 2
services:
bridge:
image: busybox
command: top
network_mode: bridge
service:
image: busybox
command: top
network_mode: "service:bridge"
container:
image: busybox
command: top
network_mode: "container:composetest_network_mode_container"
host:
image: busybox
command: top
network_mode: host
none:
image: busybox
command: top
network_mode: none

View File

@ -1,17 +0,0 @@
version: 2
services:
bridge:
image: busybox
command: top
networks: ["bridge"]
host:
image: busybox
command: top
networks: ["host"]
none:
image: busybox
command: top
networks: []

View File

@ -4,10 +4,12 @@ from __future__ import unicode_literals
import random import random
import py import py
import pytest
from docker.errors import NotFound from docker.errors import NotFound
from .testcases import DockerClientTestCase from .testcases import DockerClientTestCase
from compose.config import config from compose.config import config
from compose.config import ConfigurationError
from compose.config.types import VolumeFromSpec from compose.config.types import VolumeFromSpec
from compose.config.types import VolumeSpec from compose.config.types import VolumeSpec
from compose.const import LABEL_PROJECT from compose.const import LABEL_PROJECT
@ -104,7 +106,71 @@ class ProjectTest(DockerClientTestCase):
db = project.get_service('db') db = project.get_service('db')
self.assertEqual(db._get_volumes_from(), [data_container.id + ':rw']) self.assertEqual(db._get_volumes_from(), [data_container.id + ':rw'])
def test_net_from_service(self): @v2_only()
def test_network_mode_from_service(self):
project = Project.from_config(
name='composetest',
client=self.client,
config_data=build_service_dicts({
'version': 2,
'services': {
'net': {
'image': 'busybox:latest',
'command': ["top"]
},
'web': {
'image': 'busybox:latest',
'network_mode': 'service:net',
'command': ["top"]
},
},
}),
)
project.up()
web = project.get_service('web')
net = project.get_service('net')
self.assertEqual(web.network_mode.mode, 'container:' + net.containers()[0].id)
@v2_only()
def test_network_mode_from_container(self):
def get_project():
return Project.from_config(
name='composetest',
config_data=build_service_dicts({
'version': 2,
'services': {
'web': {
'image': 'busybox:latest',
'network_mode': 'container:composetest_net_container'
},
},
}),
client=self.client,
)
with pytest.raises(ConfigurationError) as excinfo:
get_project()
assert "container 'composetest_net_container' which does not exist" in excinfo.exconly()
net_container = Container.create(
self.client,
image='busybox:latest',
name='composetest_net_container',
command='top',
labels={LABEL_PROJECT: 'composetest'},
)
net_container.start()
project = get_project()
project.up()
web = project.get_service('web')
self.assertEqual(web.network_mode.mode, 'container:' + net_container.id)
def test_net_from_service_v1(self):
project = Project.from_config( project = Project.from_config(
name='composetest', name='composetest',
config_data=build_service_dicts({ config_data=build_service_dicts({
@ -125,19 +191,11 @@ class ProjectTest(DockerClientTestCase):
web = project.get_service('web') web = project.get_service('web')
net = project.get_service('net') net = project.get_service('net')
self.assertEqual(web.net.mode, 'container:' + net.containers()[0].id) self.assertEqual(web.network_mode.mode, 'container:' + net.containers()[0].id)
def test_net_from_container(self): def test_net_from_container_v1(self):
net_container = Container.create( def get_project():
self.client, return Project.from_config(
image='busybox:latest',
name='composetest_net_container',
command='top',
labels={LABEL_PROJECT: 'composetest'},
)
net_container.start()
project = Project.from_config(
name='composetest', name='composetest',
config_data=build_service_dicts({ config_data=build_service_dicts({
'web': { 'web': {
@ -148,10 +206,25 @@ class ProjectTest(DockerClientTestCase):
client=self.client, client=self.client,
) )
with pytest.raises(ConfigurationError) as excinfo:
get_project()
assert "container 'composetest_net_container' which does not exist" in excinfo.exconly()
net_container = Container.create(
self.client,
image='busybox:latest',
name='composetest_net_container',
command='top',
labels={LABEL_PROJECT: 'composetest'},
)
net_container.start()
project = get_project()
project.up() project.up()
web = project.get_service('web') web = project.get_service('web')
self.assertEqual(web.net.mode, 'container:' + net_container.id) self.assertEqual(web.network_mode.mode, 'container:' + net_container.id)
def test_start_pause_unpause_stop_kill_remove(self): def test_start_pause_unpause_stop_kill_remove(self):
web = self.create_service('web') web = self.create_service('web')

View File

@ -26,7 +26,7 @@ from compose.const import LABEL_VERSION
from compose.container import Container from compose.container import Container
from compose.service import ConvergencePlan from compose.service import ConvergencePlan
from compose.service import ConvergenceStrategy from compose.service import ConvergenceStrategy
from compose.service import Net from compose.service import NetworkMode
from compose.service import Service from compose.service import Service
@ -752,17 +752,17 @@ class ServiceTest(DockerClientTestCase):
assert len(service.containers(stopped=True)) == 2 assert len(service.containers(stopped=True)) == 2
def test_network_mode_none(self): def test_network_mode_none(self):
service = self.create_service('web', net=Net('none')) service = self.create_service('web', network_mode=NetworkMode('none'))
container = create_and_start_container(service) container = create_and_start_container(service)
self.assertEqual(container.get('HostConfig.NetworkMode'), 'none') self.assertEqual(container.get('HostConfig.NetworkMode'), 'none')
def test_network_mode_bridged(self): def test_network_mode_bridged(self):
service = self.create_service('web', net=Net('bridge')) service = self.create_service('web', network_mode=NetworkMode('bridge'))
container = create_and_start_container(service) container = create_and_start_container(service)
self.assertEqual(container.get('HostConfig.NetworkMode'), 'bridge') self.assertEqual(container.get('HostConfig.NetworkMode'), 'bridge')
def test_network_mode_host(self): def test_network_mode_host(self):
service = self.create_service('web', net=Net('host')) service = self.create_service('web', network_mode=NetworkMode('host'))
container = create_and_start_container(service) container = create_and_start_container(service)
self.assertEqual(container.get('HostConfig.NetworkMode'), 'host') self.assertEqual(container.get('HostConfig.NetworkMode'), 'host')

View File

@ -1015,6 +1015,126 @@ class ConfigTest(unittest.TestCase):
assert "Service 'one' depends on service 'three'" in exc.exconly() assert "Service 'one' depends on service 'three'" in exc.exconly()
class NetworkModeTest(unittest.TestCase):
def test_network_mode_standard(self):
config_data = config.load(build_config_details({
'version': 2,
'services': {
'web': {
'image': 'busybox',
'command': "top",
'network_mode': 'bridge',
},
},
}))
assert config_data.services[0]['network_mode'] == 'bridge'
def test_network_mode_standard_v1(self):
config_data = config.load(build_config_details({
'web': {
'image': 'busybox',
'command': "top",
'net': 'bridge',
},
}))
assert config_data.services[0]['network_mode'] == 'bridge'
assert 'net' not in config_data.services[0]
def test_network_mode_container(self):
config_data = config.load(build_config_details({
'version': 2,
'services': {
'web': {
'image': 'busybox',
'command': "top",
'network_mode': 'container:foo',
},
},
}))
assert config_data.services[0]['network_mode'] == 'container:foo'
def test_network_mode_container_v1(self):
config_data = config.load(build_config_details({
'web': {
'image': 'busybox',
'command': "top",
'net': 'container:foo',
},
}))
assert config_data.services[0]['network_mode'] == 'container:foo'
def test_network_mode_service(self):
config_data = config.load(build_config_details({
'version': 2,
'services': {
'web': {
'image': 'busybox',
'command': "top",
'network_mode': 'service:foo',
},
'foo': {
'image': 'busybox',
'command': "top",
},
},
}))
assert config_data.services[1]['network_mode'] == 'service:foo'
def test_network_mode_service_v1(self):
config_data = config.load(build_config_details({
'web': {
'image': 'busybox',
'command': "top",
'net': 'container:foo',
},
'foo': {
'image': 'busybox',
'command': "top",
},
}))
assert config_data.services[1]['network_mode'] == 'service:foo'
def test_network_mode_service_nonexistent(self):
with pytest.raises(ConfigurationError) as excinfo:
config.load(build_config_details({
'version': 2,
'services': {
'web': {
'image': 'busybox',
'command': "top",
'network_mode': 'service:foo',
},
},
}))
assert "service 'foo' which is undefined" in excinfo.exconly()
def test_network_mode_plus_networks_is_invalid(self):
with pytest.raises(ConfigurationError) as excinfo:
config.load(build_config_details({
'version': 2,
'services': {
'web': {
'image': 'busybox',
'command': "top",
'network_mode': 'bridge',
'networks': ['front'],
},
},
'networks': {
'front': None,
}
}))
assert "'network_mode' and 'networks' cannot be combined" in excinfo.exconly()
class PortsTest(unittest.TestCase): class PortsTest(unittest.TestCase):
INVALID_PORTS_TYPES = [ INVALID_PORTS_TYPES = [
{"1": "8000"}, {"1": "8000"},
@ -1642,6 +1762,7 @@ class ExtendsTest(unittest.TestCase):
'name': 'myweb', 'name': 'myweb',
'image': 'busybox', 'image': 'busybox',
'command': 'top', 'command': 'top',
'network_mode': 'bridge',
'links': ['mydb:db'], 'links': ['mydb:db'],
'environment': { 'environment': {
"FOO": "1", "FOO": "1",
@ -1659,6 +1780,7 @@ class ExtendsTest(unittest.TestCase):
'name': 'web', 'name': 'web',
'image': 'busybox', 'image': 'busybox',
'command': '/bin/true', 'command': '/bin/true',
'network_mode': 'host',
'environment': { 'environment': {
"FOO": "2", "FOO": "2",
"BAR": "1", "BAR": "1",
@ -1677,6 +1799,7 @@ class ExtendsTest(unittest.TestCase):
'name': 'myweb', 'name': 'myweb',
'image': 'busybox', 'image': 'busybox',
'command': '/bin/true', 'command': '/bin/true',
'network_mode': 'host',
'environment': { 'environment': {
"FOO": "2", "FOO": "2",
"BAR": "2", "BAR": "2",
@ -1867,11 +1990,18 @@ class ExtendsTest(unittest.TestCase):
load_from_filename('tests/fixtures/extends/invalid-volumes.yml') load_from_filename('tests/fixtures/extends/invalid-volumes.yml')
def test_invalid_net_in_extended_service(self): def test_invalid_net_in_extended_service(self):
expected_error_msg = "services with 'net: container' cannot be extended" with pytest.raises(ConfigurationError) as excinfo:
load_from_filename('tests/fixtures/extends/invalid-net-v2.yml')
with self.assertRaisesRegexp(ConfigurationError, expected_error_msg): assert 'network_mode: service' in excinfo.exconly()
assert 'cannot be extended' in excinfo.exconly()
with pytest.raises(ConfigurationError) as excinfo:
load_from_filename('tests/fixtures/extends/invalid-net.yml') load_from_filename('tests/fixtures/extends/invalid-net.yml')
assert 'net: container' in excinfo.exconly()
assert 'cannot be extended' in excinfo.exconly()
@mock.patch.dict(os.environ) @mock.patch.dict(os.environ)
def test_load_config_runs_interpolation_in_extended_service(self): def test_load_config_runs_interpolation_in_extended_service(self):
os.environ.update(HOSTNAME_VALUE="penguin") os.environ.update(HOSTNAME_VALUE="penguin")

View File

@ -100,7 +100,7 @@ class TestSortService(object):
}, },
{ {
'name': 'parent', 'name': 'parent',
'net': 'container:child' 'network_mode': 'service:child'
}, },
{ {
'name': 'child' 'name': 'child'
@ -137,7 +137,7 @@ class TestSortService(object):
def test_sort_service_dicts_7(self): def test_sort_service_dicts_7(self):
services = [ services = [
{ {
'net': 'container:three', 'network_mode': 'service:three',
'name': 'four' 'name': 'four'
}, },
{ {

View File

@ -349,7 +349,7 @@ class ProjectTest(unittest.TestCase):
), ),
) )
service = project.get_service('test') service = project.get_service('test')
self.assertEqual(service.net.id, None) self.assertEqual(service.network_mode.id, None)
self.assertNotIn('NetworkMode', service._get_container_host_config({})) self.assertNotIn('NetworkMode', service._get_container_host_config({}))
def test_use_net_from_container(self): def test_use_net_from_container(self):
@ -365,7 +365,7 @@ class ProjectTest(unittest.TestCase):
{ {
'name': 'test', 'name': 'test',
'image': 'busybox:latest', 'image': 'busybox:latest',
'net': 'container:aaa' 'network_mode': 'container:aaa'
}, },
], ],
networks=None, networks=None,
@ -373,7 +373,7 @@ class ProjectTest(unittest.TestCase):
), ),
) )
service = project.get_service('test') service = project.get_service('test')
self.assertEqual(service.net.mode, 'container:' + container_id) self.assertEqual(service.network_mode.mode, 'container:' + container_id)
def test_use_net_from_service(self): def test_use_net_from_service(self):
container_name = 'test_aaa_1' container_name = 'test_aaa_1'
@ -398,7 +398,7 @@ class ProjectTest(unittest.TestCase):
{ {
'name': 'test', 'name': 'test',
'image': 'busybox:latest', 'image': 'busybox:latest',
'net': 'container:aaa' 'network_mode': 'service:aaa'
}, },
], ],
networks=None, networks=None,
@ -407,7 +407,7 @@ class ProjectTest(unittest.TestCase):
) )
service = project.get_service('test') service = project.get_service('test')
self.assertEqual(service.net.mode, 'container:' + container_name) self.assertEqual(service.network_mode.mode, 'container:' + container_name)
def test_uses_default_network_true(self): def test_uses_default_network_true(self):
project = Project.from_config( project = Project.from_config(

View File

@ -15,16 +15,16 @@ from compose.const import LABEL_SERVICE
from compose.container import Container from compose.container import Container
from compose.service import build_ulimits from compose.service import build_ulimits
from compose.service import build_volume_binding from compose.service import build_volume_binding
from compose.service import ContainerNet from compose.service import ContainerNetworkMode
from compose.service import get_container_data_volumes from compose.service import get_container_data_volumes
from compose.service import ImageType from compose.service import ImageType
from compose.service import merge_volume_bindings from compose.service import merge_volume_bindings
from compose.service import NeedsBuildError from compose.service import NeedsBuildError
from compose.service import Net from compose.service import NetworkMode
from compose.service import NoSuchImageError from compose.service import NoSuchImageError
from compose.service import parse_repository_tag from compose.service import parse_repository_tag
from compose.service import Service from compose.service import Service
from compose.service import ServiceNet from compose.service import ServiceNetworkMode
from compose.service import warn_on_masked_volume from compose.service import warn_on_masked_volume
@ -407,7 +407,7 @@ class ServiceTest(unittest.TestCase):
'foo', 'foo',
image='example.com/foo', image='example.com/foo',
client=self.mock_client, client=self.mock_client,
net=ServiceNet(Service('other')), network_mode=ServiceNetworkMode(Service('other')),
links=[(Service('one'), 'one')], links=[(Service('one'), 'one')],
volumes_from=[VolumeFromSpec(Service('two'), 'rw', 'service')]) volumes_from=[VolumeFromSpec(Service('two'), 'rw', 'service')])
@ -421,7 +421,7 @@ class ServiceTest(unittest.TestCase):
} }
self.assertEqual(config_dict, expected) self.assertEqual(config_dict, expected)
def test_config_dict_with_net_from_container(self): def test_config_dict_with_network_mode_from_container(self):
self.mock_client.inspect_image.return_value = {'Id': 'abcd'} self.mock_client.inspect_image.return_value = {'Id': 'abcd'}
container = Container( container = Container(
self.mock_client, self.mock_client,
@ -430,7 +430,7 @@ class ServiceTest(unittest.TestCase):
'foo', 'foo',
image='example.com/foo', image='example.com/foo',
client=self.mock_client, client=self.mock_client,
net=container) network_mode=ContainerNetworkMode(container))
config_dict = service.config_dict() config_dict = service.config_dict()
expected = { expected = {
@ -589,20 +589,20 @@ class BuildUlimitsTestCase(unittest.TestCase):
class NetTestCase(unittest.TestCase): class NetTestCase(unittest.TestCase):
def test_net(self): def test_network_mode(self):
net = Net('host') network_mode = NetworkMode('host')
self.assertEqual(net.id, 'host') self.assertEqual(network_mode.id, 'host')
self.assertEqual(net.mode, 'host') self.assertEqual(network_mode.mode, 'host')
self.assertEqual(net.service_name, None) self.assertEqual(network_mode.service_name, None)
def test_net_container(self): def test_network_mode_container(self):
container_id = 'abcd' container_id = 'abcd'
net = ContainerNet(Container(None, {'Id': container_id})) network_mode = ContainerNetworkMode(Container(None, {'Id': container_id}))
self.assertEqual(net.id, container_id) self.assertEqual(network_mode.id, container_id)
self.assertEqual(net.mode, 'container:' + container_id) self.assertEqual(network_mode.mode, 'container:' + container_id)
self.assertEqual(net.service_name, None) self.assertEqual(network_mode.service_name, None)
def test_net_service(self): def test_network_mode_service(self):
container_id = 'bbbb' container_id = 'bbbb'
service_name = 'web' service_name = 'web'
mock_client = mock.create_autospec(docker.Client) mock_client = mock.create_autospec(docker.Client)
@ -611,23 +611,23 @@ class NetTestCase(unittest.TestCase):
] ]
service = Service(name=service_name, client=mock_client) service = Service(name=service_name, client=mock_client)
net = ServiceNet(service) network_mode = ServiceNetworkMode(service)
self.assertEqual(net.id, service_name) self.assertEqual(network_mode.id, service_name)
self.assertEqual(net.mode, 'container:' + container_id) self.assertEqual(network_mode.mode, 'container:' + container_id)
self.assertEqual(net.service_name, service_name) self.assertEqual(network_mode.service_name, service_name)
def test_net_service_no_containers(self): def test_network_mode_service_no_containers(self):
service_name = 'web' service_name = 'web'
mock_client = mock.create_autospec(docker.Client) mock_client = mock.create_autospec(docker.Client)
mock_client.containers.return_value = [] mock_client.containers.return_value = []
service = Service(name=service_name, client=mock_client) service = Service(name=service_name, client=mock_client)
net = ServiceNet(service) network_mode = ServiceNetworkMode(service)
self.assertEqual(net.id, service_name) self.assertEqual(network_mode.id, service_name)
self.assertEqual(net.mode, None) self.assertEqual(network_mode.mode, None)
self.assertEqual(net.service_name, service_name) self.assertEqual(network_mode.service_name, service_name)
def build_mount(destination, source, mode='rw'): def build_mount(destination, source, mode='rw'):