mirror of
https://github.com/docker/compose.git
synced 2025-07-27 07:34:10 +02:00
Merge pull request #2564 from aanand/2478-networks-in-compose-file
Declare networks in Compose file
This commit is contained in:
commit
3750811eed
@ -696,8 +696,7 @@ def run_one_off_container(container_options, project, service, options):
|
|||||||
start_deps=True,
|
start_deps=True,
|
||||||
strategy=ConvergenceStrategy.never)
|
strategy=ConvergenceStrategy.never)
|
||||||
|
|
||||||
if project.use_networking:
|
project.initialize_networks()
|
||||||
project.ensure_network_exists()
|
|
||||||
|
|
||||||
container = service.create_container(
|
container = service.create_container(
|
||||||
quiet=True,
|
quiet=True,
|
||||||
@ -705,7 +704,7 @@ def run_one_off_container(container_options, project, service, options):
|
|||||||
**container_options)
|
**container_options)
|
||||||
|
|
||||||
if options['-d']:
|
if options['-d']:
|
||||||
container.start()
|
service.start_container(container)
|
||||||
print(container.name)
|
print(container.name)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -717,6 +716,7 @@ def run_one_off_container(container_options, project, service, options):
|
|||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
dockerpty.start(project.client, container.id, interactive=not options['-T'])
|
dockerpty.start(project.client, container.id, interactive=not options['-T'])
|
||||||
|
service.connect_container_to_networks(container)
|
||||||
exit_code = container.wait()
|
exit_code = container.wait()
|
||||||
except signals.ShutdownException:
|
except signals.ShutdownException:
|
||||||
project.client.stop(container.id)
|
project.client.stop(container.id)
|
||||||
|
@ -139,14 +139,16 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')):
|
|||||||
return {} if self.version == 1 else self.config.get('volumes', {})
|
return {} if self.version == 1 else self.config.get('volumes', {})
|
||||||
|
|
||||||
|
|
||||||
class Config(namedtuple('_Config', 'version services volumes')):
|
class Config(namedtuple('_Config', 'version services volumes networks')):
|
||||||
"""
|
"""
|
||||||
:param version: configuration version
|
:param version: configuration version
|
||||||
:type version: int
|
:type version: int
|
||||||
:param services: List of service description dictionaries
|
:param services: List of service description dictionaries
|
||||||
:type services: :class:`list`
|
:type services: :class:`list`
|
||||||
:param volumes: List of volume description dictionaries
|
:param volumes: Dictionary mapping volume names to description dictionaries
|
||||||
:type volumes: :class:`list`
|
:type volumes: :class:`dict`
|
||||||
|
:param networks: Dictionary mapping network names to description dictionaries
|
||||||
|
:type networks: :class:`dict`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@ -256,39 +258,44 @@ def load(config_details):
|
|||||||
config_details = config_details._replace(config_files=processed_files)
|
config_details = config_details._replace(config_files=processed_files)
|
||||||
|
|
||||||
main_file = config_details.config_files[0]
|
main_file = config_details.config_files[0]
|
||||||
volumes = load_volumes(config_details.config_files)
|
volumes = load_mapping(config_details.config_files, 'volumes', 'Volume')
|
||||||
|
networks = load_mapping(config_details.config_files, 'networks', 'Network')
|
||||||
service_dicts = load_services(
|
service_dicts = load_services(
|
||||||
config_details.working_dir,
|
config_details.working_dir,
|
||||||
main_file.filename,
|
main_file.filename,
|
||||||
[file.get_service_dicts() for file in config_details.config_files],
|
[file.get_service_dicts() for file in config_details.config_files],
|
||||||
main_file.version)
|
main_file.version)
|
||||||
return Config(main_file.version, service_dicts, volumes)
|
return Config(main_file.version, service_dicts, volumes, networks)
|
||||||
|
|
||||||
|
|
||||||
def load_volumes(config_files):
|
def load_mapping(config_files, key, entity_type):
|
||||||
volumes = {}
|
mapping = {}
|
||||||
|
|
||||||
for config_file in config_files:
|
for config_file in config_files:
|
||||||
for name, volume_config in config_file.get_volumes().items():
|
for name, config in config_file.config.get(key, {}).items():
|
||||||
volumes[name] = volume_config or {}
|
mapping[name] = config or {}
|
||||||
if not volume_config:
|
if not config:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
external = volume_config.get('external')
|
external = config.get('external')
|
||||||
if external:
|
if external:
|
||||||
if len(volume_config.keys()) > 1:
|
if len(config.keys()) > 1:
|
||||||
raise ConfigurationError(
|
raise ConfigurationError(
|
||||||
'Volume {0} declared as external but specifies'
|
'{} {} declared as external but specifies'
|
||||||
' additional attributes ({1}). '.format(
|
' additional attributes ({}). '.format(
|
||||||
|
entity_type,
|
||||||
name,
|
name,
|
||||||
', '.join([k for k in volume_config.keys() if k != 'external'])
|
', '.join([k for k in config.keys() if k != 'external'])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if isinstance(external, dict):
|
if isinstance(external, dict):
|
||||||
volume_config['external_name'] = external.get('name')
|
config['external_name'] = external.get('name')
|
||||||
else:
|
else:
|
||||||
volume_config['external_name'] = name
|
config['external_name'] = name
|
||||||
|
|
||||||
return volumes
|
mapping[name] = config
|
||||||
|
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
|
||||||
def load_services(working_dir, filename, service_configs, version):
|
def load_services(working_dir, filename, service_configs, version):
|
||||||
|
@ -17,6 +17,15 @@
|
|||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"networks": {
|
||||||
|
"id": "#/properties/networks",
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^[a-zA-Z0-9._-]+$": {
|
||||||
|
"$ref": "#/definitions/network"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"volumes": {
|
"volumes": {
|
||||||
"id": "#/properties/volumes",
|
"id": "#/properties/volumes",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -30,6 +39,10 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
"network": {
|
||||||
|
"id": "#/definitions/network",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"volume": {
|
"volume": {
|
||||||
"id": "#/definitions/volume",
|
"id": "#/definitions/volume",
|
||||||
"type": ["object", "null"],
|
"type": ["object", "null"],
|
||||||
|
@ -89,7 +89,13 @@
|
|||||||
"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"]},
|
||||||
"net": {"type": "string"},
|
|
||||||
|
"networks": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
|
||||||
"pid": {"type": ["string", "null"]},
|
"pid": {"type": ["string", "null"]},
|
||||||
|
|
||||||
"ports": {
|
"ports": {
|
||||||
|
79
compose/network.py
Normal file
79
compose/network.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from docker.errors import NotFound
|
||||||
|
|
||||||
|
from .config import ConfigurationError
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Network(object):
|
||||||
|
def __init__(self, client, project, name, driver=None, driver_opts=None,
|
||||||
|
external_name=None):
|
||||||
|
self.client = client
|
||||||
|
self.project = project
|
||||||
|
self.name = name
|
||||||
|
self.driver = driver
|
||||||
|
self.driver_opts = driver_opts
|
||||||
|
self.external_name = external_name
|
||||||
|
|
||||||
|
def ensure(self):
|
||||||
|
if self.external_name:
|
||||||
|
try:
|
||||||
|
self.inspect()
|
||||||
|
log.debug(
|
||||||
|
'Network {0} declared as external. No new '
|
||||||
|
'network will be created.'.format(self.name)
|
||||||
|
)
|
||||||
|
except NotFound:
|
||||||
|
raise ConfigurationError(
|
||||||
|
'Network {name} declared as external, but could'
|
||||||
|
' not be found. Please create the network manually'
|
||||||
|
' using `{command} {name}` and try again.'.format(
|
||||||
|
name=self.external_name,
|
||||||
|
command='docker network create'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = self.inspect()
|
||||||
|
if self.driver and data['Driver'] != self.driver:
|
||||||
|
raise ConfigurationError(
|
||||||
|
'Network {} needs to be recreated - driver has changed'
|
||||||
|
.format(self.full_name))
|
||||||
|
if data['Options'] != (self.driver_opts or {}):
|
||||||
|
raise ConfigurationError(
|
||||||
|
'Network {} needs to be recreated - options have changed'
|
||||||
|
.format(self.full_name))
|
||||||
|
except NotFound:
|
||||||
|
driver_name = 'the default driver'
|
||||||
|
if self.driver:
|
||||||
|
driver_name = 'driver "{}"'.format(self.driver)
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
'Creating network "{}" with {}'
|
||||||
|
.format(self.full_name, driver_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client.create_network(
|
||||||
|
self.full_name, self.driver, self.driver_opts
|
||||||
|
)
|
||||||
|
|
||||||
|
def remove(self):
|
||||||
|
# TODO: don't remove external networks
|
||||||
|
log.info("Removing network {}".format(self.full_name))
|
||||||
|
self.client.remove_network(self.full_name)
|
||||||
|
|
||||||
|
def inspect(self):
|
||||||
|
return self.client.inspect_network(self.full_name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_name(self):
|
||||||
|
if self.external_name:
|
||||||
|
return self.external_name
|
||||||
|
return '{0}_{1}'.format(self.project, self.name)
|
@ -17,6 +17,7 @@ from .const import LABEL_ONE_OFF
|
|||||||
from .const import LABEL_PROJECT
|
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 .service import ContainerNet
|
from .service import ContainerNet
|
||||||
from .service import ConvergenceStrategy
|
from .service import ConvergenceStrategy
|
||||||
from .service import Net
|
from .service import Net
|
||||||
@ -33,12 +34,14 @@ class Project(object):
|
|||||||
"""
|
"""
|
||||||
A collection of services.
|
A collection of services.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, services, client, volumes=None, use_networking=False, network_driver=None):
|
def __init__(self, name, services, client, networks=None, volumes=None,
|
||||||
|
use_networking=False, network_driver=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.services = services
|
self.services = services
|
||||||
self.client = client
|
self.client = client
|
||||||
self.use_networking = use_networking
|
self.use_networking = use_networking
|
||||||
self.network_driver = network_driver
|
self.network_driver = network_driver
|
||||||
|
self.networks = networks or []
|
||||||
self.volumes = volumes or []
|
self.volumes = volumes or []
|
||||||
|
|
||||||
def labels(self, one_off=False):
|
def labels(self, one_off=False):
|
||||||
@ -55,23 +58,47 @@ class Project(object):
|
|||||||
use_networking = (config_data.version and config_data.version >= 2)
|
use_networking = (config_data.version and config_data.version >= 2)
|
||||||
project = cls(name, [], client, use_networking=use_networking)
|
project = cls(name, [], client, use_networking=use_networking)
|
||||||
|
|
||||||
if use_networking:
|
custom_networks = []
|
||||||
remove_links(config_data.services)
|
if config_data.networks:
|
||||||
|
for network_name, data in config_data.networks.items():
|
||||||
|
custom_networks.append(
|
||||||
|
Network(
|
||||||
|
client=client, project=name, name=network_name,
|
||||||
|
driver=data.get('driver'),
|
||||||
|
driver_opts=data.get('driver_opts'),
|
||||||
|
external_name=data.get('external_name'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
for service_dict in config_data.services:
|
for service_dict in config_data.services:
|
||||||
links = project.get_links(service_dict)
|
if use_networking:
|
||||||
|
networks = get_networks(
|
||||||
|
service_dict,
|
||||||
|
custom_networks + [project.default_network])
|
||||||
|
net = Net(networks[0]) if networks else Net("none")
|
||||||
|
links = []
|
||||||
|
else:
|
||||||
|
networks = []
|
||||||
|
net = project.get_net(service_dict)
|
||||||
|
links = project.get_links(service_dict)
|
||||||
|
|
||||||
volumes_from = get_volumes_from(project, service_dict)
|
volumes_from = get_volumes_from(project, service_dict)
|
||||||
net = project.get_net(service_dict)
|
|
||||||
|
|
||||||
project.services.append(
|
project.services.append(
|
||||||
Service(
|
Service(
|
||||||
client=client,
|
client=client,
|
||||||
project=name,
|
project=name,
|
||||||
use_networking=use_networking,
|
use_networking=use_networking,
|
||||||
|
networks=networks,
|
||||||
links=links,
|
links=links,
|
||||||
net=net,
|
net=net,
|
||||||
volumes_from=volumes_from,
|
volumes_from=volumes_from,
|
||||||
**service_dict))
|
**service_dict))
|
||||||
|
|
||||||
|
project.networks += custom_networks
|
||||||
|
if project.uses_default_network():
|
||||||
|
project.networks.append(project.default_network)
|
||||||
|
|
||||||
if config_data.volumes:
|
if config_data.volumes:
|
||||||
for vol_name, data in config_data.volumes.items():
|
for vol_name, data in config_data.volumes.items():
|
||||||
project.volumes.append(
|
project.volumes.append(
|
||||||
@ -82,6 +109,7 @@ class Project(object):
|
|||||||
external_name=data.get('external_name')
|
external_name=data.get('external_name')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return project
|
return project
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -124,20 +152,18 @@ class Project(object):
|
|||||||
Raises NoSuchService if any of the named services do not exist.
|
Raises NoSuchService if any of the named services do not exist.
|
||||||
"""
|
"""
|
||||||
if service_names is None or len(service_names) == 0:
|
if service_names is None or len(service_names) == 0:
|
||||||
return self.get_services(
|
service_names = self.service_names
|
||||||
service_names=self.service_names,
|
|
||||||
include_deps=include_deps
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
unsorted = [self.get_service(name) for name in service_names]
|
|
||||||
services = [s for s in self.services if s in unsorted]
|
|
||||||
|
|
||||||
if include_deps:
|
unsorted = [self.get_service(name) for name in service_names]
|
||||||
services = reduce(self._inject_deps, services, [])
|
services = [s for s in self.services if s in unsorted]
|
||||||
|
|
||||||
uniques = []
|
if include_deps:
|
||||||
[uniques.append(s) for s in services if s not in uniques]
|
services = reduce(self._inject_deps, services, [])
|
||||||
return uniques
|
|
||||||
|
uniques = []
|
||||||
|
[uniques.append(s) for s in services if s not in uniques]
|
||||||
|
|
||||||
|
return uniques
|
||||||
|
|
||||||
def get_services_without_duplicate(self, service_names=None, include_deps=False):
|
def get_services_without_duplicate(self, service_names=None, include_deps=False):
|
||||||
services = self.get_services(service_names, include_deps)
|
services = self.get_services(service_names, include_deps)
|
||||||
@ -165,8 +191,6 @@ class Project(object):
|
|||||||
def get_net(self, service_dict):
|
def get_net(self, service_dict):
|
||||||
net = service_dict.pop('net', None)
|
net = service_dict.pop('net', None)
|
||||||
if not net:
|
if not net:
|
||||||
if self.use_networking:
|
|
||||||
return Net(self.default_network_name)
|
|
||||||
return Net(None)
|
return Net(None)
|
||||||
|
|
||||||
net_name = get_service_name_from_net(net)
|
net_name = get_service_name_from_net(net)
|
||||||
@ -251,7 +275,7 @@ class Project(object):
|
|||||||
def down(self, remove_image_type, include_volumes):
|
def down(self, remove_image_type, include_volumes):
|
||||||
self.stop()
|
self.stop()
|
||||||
self.remove_stopped(v=include_volumes)
|
self.remove_stopped(v=include_volumes)
|
||||||
self.remove_network()
|
self.remove_default_network()
|
||||||
|
|
||||||
if include_volumes:
|
if include_volumes:
|
||||||
self.remove_volumes()
|
self.remove_volumes()
|
||||||
@ -262,10 +286,33 @@ class Project(object):
|
|||||||
for service in self.get_services():
|
for service in self.get_services():
|
||||||
service.remove_image(remove_image_type)
|
service.remove_image(remove_image_type)
|
||||||
|
|
||||||
|
def remove_default_network(self):
|
||||||
|
if not self.use_networking:
|
||||||
|
return
|
||||||
|
if self.uses_default_network():
|
||||||
|
self.default_network.remove()
|
||||||
|
|
||||||
def remove_volumes(self):
|
def remove_volumes(self):
|
||||||
for volume in self.volumes:
|
for volume in self.volumes:
|
||||||
volume.remove()
|
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):
|
def restart(self, service_names=None, **options):
|
||||||
containers = self.containers(service_names, stopped=True)
|
containers = self.containers(service_names, stopped=True)
|
||||||
parallel.parallel_restart(containers, options)
|
parallel.parallel_restart(containers, options)
|
||||||
@ -335,9 +382,7 @@ class Project(object):
|
|||||||
|
|
||||||
plans = self._get_convergence_plans(services, strategy)
|
plans = self._get_convergence_plans(services, strategy)
|
||||||
|
|
||||||
if self.use_networking and self.uses_default_network():
|
self.initialize_networks()
|
||||||
self.ensure_network_exists()
|
|
||||||
|
|
||||||
self.initialize_volumes()
|
self.initialize_volumes()
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -395,40 +440,6 @@ class Project(object):
|
|||||||
|
|
||||||
return [c for c in containers if matches_service_names(c)]
|
return [c for c in containers if matches_service_names(c)]
|
||||||
|
|
||||||
def get_network(self):
|
|
||||||
try:
|
|
||||||
return self.client.inspect_network(self.default_network_name)
|
|
||||||
except NotFound:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def ensure_network_exists(self):
|
|
||||||
# TODO: recreate network if driver has changed?
|
|
||||||
if self.get_network() is None:
|
|
||||||
driver_name = 'the default driver'
|
|
||||||
if self.network_driver:
|
|
||||||
driver_name = 'driver "{}"'.format(self.network_driver)
|
|
||||||
|
|
||||||
log.info(
|
|
||||||
'Creating network "{}" with {}'
|
|
||||||
.format(self.default_network_name, driver_name)
|
|
||||||
)
|
|
||||||
self.client.create_network(self.default_network_name, driver=self.network_driver)
|
|
||||||
|
|
||||||
def remove_network(self):
|
|
||||||
if not self.use_networking:
|
|
||||||
return
|
|
||||||
network = self.get_network()
|
|
||||||
if network:
|
|
||||||
log.info("Removing network %s", self.default_network_name)
|
|
||||||
self.client.remove_network(network['Id'])
|
|
||||||
|
|
||||||
def uses_default_network(self):
|
|
||||||
return any(service.net.mode == self.default_network_name for service in self.services)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def default_network_name(self):
|
|
||||||
return '{}_default'.format(self.name)
|
|
||||||
|
|
||||||
def _inject_deps(self, acc, service):
|
def _inject_deps(self, acc, service):
|
||||||
dep_names = service.get_dependency_names()
|
dep_names = service.get_dependency_names()
|
||||||
|
|
||||||
@ -444,24 +455,20 @@ class Project(object):
|
|||||||
return acc + dep_services
|
return acc + dep_services
|
||||||
|
|
||||||
|
|
||||||
def remove_links(service_dicts):
|
def get_networks(service_dict, network_definitions):
|
||||||
services_with_links = [s for s in service_dicts if 'links' in s]
|
networks = []
|
||||||
if not services_with_links:
|
for name in service_dict.pop('networks', ['default']):
|
||||||
return
|
if name in ['bridge', 'host']:
|
||||||
|
networks.append(name)
|
||||||
if len(services_with_links) == 1:
|
else:
|
||||||
prefix = '"{}" defines'.format(services_with_links[0]['name'])
|
matches = [n for n in network_definitions if n.name == name]
|
||||||
else:
|
if matches:
|
||||||
prefix = 'Some services ({}) define'.format(
|
networks.append(matches[0].full_name)
|
||||||
", ".join('"{}"'.format(s['name']) for s in services_with_links))
|
else:
|
||||||
|
raise ConfigurationError(
|
||||||
log.warn(
|
'Service "{}" uses an undefined network "{}"'
|
||||||
'\n{} links, which are not compatible with Docker networking and will be ignored.\n'
|
.format(service_dict['name'], name))
|
||||||
'Future versions of Docker will not support links - you should remove them for '
|
return networks
|
||||||
'forwards-compatibility.\n'.format(prefix))
|
|
||||||
|
|
||||||
for s in services_with_links:
|
|
||||||
del s['links']
|
|
||||||
|
|
||||||
|
|
||||||
def get_volumes_from(project, service_dict):
|
def get_volumes_from(project, service_dict):
|
||||||
|
@ -116,6 +116,7 @@ class Service(object):
|
|||||||
links=None,
|
links=None,
|
||||||
volumes_from=None,
|
volumes_from=None,
|
||||||
net=None,
|
net=None,
|
||||||
|
networks=None,
|
||||||
**options
|
**options
|
||||||
):
|
):
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -125,6 +126,7 @@ class Service(object):
|
|||||||
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.net = net or Net(None)
|
||||||
|
self.networks = networks or []
|
||||||
self.options = options
|
self.options = options
|
||||||
|
|
||||||
def containers(self, stopped=False, one_off=False, filters={}):
|
def containers(self, stopped=False, one_off=False, filters={}):
|
||||||
@ -175,7 +177,7 @@ class Service(object):
|
|||||||
|
|
||||||
def create_and_start(service, number):
|
def create_and_start(service, number):
|
||||||
container = service.create_container(number=number, quiet=True)
|
container = service.create_container(number=number, quiet=True)
|
||||||
container.start()
|
service.start_container(container)
|
||||||
return container
|
return container
|
||||||
|
|
||||||
running_containers = self.containers(stopped=False)
|
running_containers = self.containers(stopped=False)
|
||||||
@ -348,7 +350,7 @@ class Service(object):
|
|||||||
container.attach_log_stream()
|
container.attach_log_stream()
|
||||||
|
|
||||||
if start:
|
if start:
|
||||||
container.start()
|
self.start_container(container)
|
||||||
|
|
||||||
return [container]
|
return [container]
|
||||||
|
|
||||||
@ -406,7 +408,7 @@ class Service(object):
|
|||||||
if attach_logs:
|
if attach_logs:
|
||||||
new_container.attach_log_stream()
|
new_container.attach_log_stream()
|
||||||
if start_new_container:
|
if start_new_container:
|
||||||
new_container.start()
|
self.start_container(new_container)
|
||||||
container.remove()
|
container.remove()
|
||||||
return new_container
|
return new_container
|
||||||
|
|
||||||
@ -415,9 +417,18 @@ class Service(object):
|
|||||||
log.info("Starting %s" % container.name)
|
log.info("Starting %s" % container.name)
|
||||||
if attach_logs:
|
if attach_logs:
|
||||||
container.attach_log_stream()
|
container.attach_log_stream()
|
||||||
container.start()
|
return self.start_container(container)
|
||||||
|
|
||||||
|
def start_container(self, container):
|
||||||
|
container.start()
|
||||||
|
self.connect_container_to_networks(container)
|
||||||
return container
|
return container
|
||||||
|
|
||||||
|
def connect_container_to_networks(self, container):
|
||||||
|
for network in self.networks:
|
||||||
|
log.debug('Connecting "{}" to "{}"'.format(container.name, network))
|
||||||
|
self.client.connect_container_to_network(container.id, network)
|
||||||
|
|
||||||
def remove_duplicate_containers(self, timeout=DEFAULT_TIMEOUT):
|
def remove_duplicate_containers(self, timeout=DEFAULT_TIMEOUT):
|
||||||
for c in self.duplicate_containers():
|
for c in self.duplicate_containers():
|
||||||
log.info('Removing %s' % c.name)
|
log.info('Removing %s' % c.name)
|
||||||
|
@ -103,8 +103,15 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
if self.base_dir:
|
if self.base_dir:
|
||||||
self.project.kill()
|
self.project.kill()
|
||||||
self.project.remove_stopped()
|
self.project.remove_stopped()
|
||||||
|
|
||||||
for container in self.project.containers(stopped=True, one_off=True):
|
for container in self.project.containers(stopped=True, one_off=True):
|
||||||
container.remove(force=True)
|
container.remove(force=True)
|
||||||
|
|
||||||
|
networks = self.client.networks()
|
||||||
|
for n in networks:
|
||||||
|
if n['Name'].startswith('{}_'.format(self.project.name)):
|
||||||
|
self.client.remove_network(n['Name'])
|
||||||
|
|
||||||
super(CLITestCase, self).tearDown()
|
super(CLITestCase, self).tearDown()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -119,6 +126,20 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
proc = start_process(self.base_dir, project_options + options)
|
proc = start_process(self.base_dir, project_options + options)
|
||||||
return wait_on_process(proc, returncode=returncode)
|
return wait_on_process(proc, returncode=returncode)
|
||||||
|
|
||||||
|
def execute(self, container, cmd):
|
||||||
|
# Remove once Hijack and CloseNotifier sign a peace treaty
|
||||||
|
self.client.close()
|
||||||
|
exc = self.client.exec_create(container.id, cmd)
|
||||||
|
self.client.exec_start(exc)
|
||||||
|
return self.client.exec_inspect(exc)['ExitCode']
|
||||||
|
|
||||||
|
def lookup(self, container, service_name):
|
||||||
|
exit_code = self.execute(container, [
|
||||||
|
"nslookup",
|
||||||
|
"{}_{}_1".format(self.project.name, service_name)
|
||||||
|
])
|
||||||
|
return exit_code == 0
|
||||||
|
|
||||||
def test_help(self):
|
def test_help(self):
|
||||||
self.base_dir = 'tests/fixtures/no-composefile'
|
self.base_dir = 'tests/fixtures/no-composefile'
|
||||||
result = self.dispatch(['help', 'up'], returncode=1)
|
result = self.dispatch(['help', 'up'], returncode=1)
|
||||||
@ -350,43 +371,127 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
assert 'simple_1 | simple' in result.stdout
|
assert 'simple_1 | simple' in result.stdout
|
||||||
assert 'another_1 | another' in result.stdout
|
assert 'another_1 | another' in result.stdout
|
||||||
|
|
||||||
def test_up_without_networking(self):
|
def test_up(self):
|
||||||
self.base_dir = 'tests/fixtures/links-composefile'
|
|
||||||
self.dispatch(['up', '-d'], None)
|
|
||||||
|
|
||||||
networks = self.client.networks(names=[self.project.default_network_name])
|
|
||||||
self.assertEqual(len(networks), 0)
|
|
||||||
|
|
||||||
for service in self.project.get_services():
|
|
||||||
containers = service.containers()
|
|
||||||
self.assertEqual(len(containers), 1)
|
|
||||||
self.assertNotEqual(containers[0].get('Config.Hostname'), service.name)
|
|
||||||
|
|
||||||
web_container = self.project.get_service('web').containers()[0]
|
|
||||||
self.assertTrue(web_container.get('HostConfig.Links'))
|
|
||||||
|
|
||||||
def test_up_with_networking(self):
|
|
||||||
self.base_dir = 'tests/fixtures/v2-simple'
|
self.base_dir = 'tests/fixtures/v2-simple'
|
||||||
self.dispatch(['up', '-d'], None)
|
self.dispatch(['up', '-d'], None)
|
||||||
|
|
||||||
services = self.project.get_services()
|
services = self.project.get_services()
|
||||||
|
|
||||||
networks = self.client.networks(names=[self.project.default_network_name])
|
networks = self.client.networks(names=[self.project.default_network.full_name])
|
||||||
for n in networks:
|
|
||||||
self.addCleanup(self.client.remove_network, n['Id'])
|
|
||||||
self.assertEqual(len(networks), 1)
|
self.assertEqual(len(networks), 1)
|
||||||
self.assertEqual(networks[0]['Driver'], 'bridge')
|
self.assertEqual(networks[0]['Driver'], 'bridge')
|
||||||
|
|
||||||
network = self.client.inspect_network(networks[0]['Id'])
|
network = self.client.inspect_network(networks[0]['Id'])
|
||||||
self.assertEqual(len(network['Containers']), len(services))
|
|
||||||
|
|
||||||
for service in services:
|
for service in services:
|
||||||
containers = service.containers()
|
containers = service.containers()
|
||||||
self.assertEqual(len(containers), 1)
|
self.assertEqual(len(containers), 1)
|
||||||
self.assertIn(containers[0].id, network['Containers'])
|
|
||||||
|
|
||||||
web_container = self.project.get_service('simple').containers()[0]
|
container = containers[0]
|
||||||
self.assertFalse(web_container.get('HostConfig.Links'))
|
self.assertIn(container.id, network['Containers'])
|
||||||
|
|
||||||
|
networks = list(container.get('NetworkSettings.Networks'))
|
||||||
|
self.assertEqual(networks, [network['Name']])
|
||||||
|
|
||||||
|
def test_up_with_networks(self):
|
||||||
|
self.base_dir = 'tests/fixtures/networks'
|
||||||
|
self.dispatch(['up', '-d'], None)
|
||||||
|
|
||||||
|
back_name = '{}_back'.format(self.project.name)
|
||||||
|
front_name = '{}_front'.format(self.project.name)
|
||||||
|
|
||||||
|
networks = [
|
||||||
|
n for n in self.client.networks()
|
||||||
|
if n['Name'].startswith('{}_'.format(self.project.name))
|
||||||
|
]
|
||||||
|
|
||||||
|
# Two networks were created: back and front
|
||||||
|
assert sorted(n['Name'] for n in networks) == [back_name, front_name]
|
||||||
|
|
||||||
|
back_network = [n for n in networks if n['Name'] == back_name][0]
|
||||||
|
front_network = [n for n in networks if n['Name'] == front_name][0]
|
||||||
|
|
||||||
|
web_container = self.project.get_service('web').containers()[0]
|
||||||
|
app_container = self.project.get_service('app').containers()[0]
|
||||||
|
db_container = self.project.get_service('db').containers()[0]
|
||||||
|
|
||||||
|
# db and app joined the back network
|
||||||
|
assert sorted(back_network['Containers']) == sorted([db_container.id, app_container.id])
|
||||||
|
|
||||||
|
# web and app joined the front network
|
||||||
|
assert sorted(front_network['Containers']) == sorted([web_container.id, app_container.id])
|
||||||
|
|
||||||
|
# web can see app but not db
|
||||||
|
assert self.lookup(web_container, "app")
|
||||||
|
assert not self.lookup(web_container, "db")
|
||||||
|
|
||||||
|
# app can see db
|
||||||
|
assert self.lookup(app_container, "db")
|
||||||
|
|
||||||
|
def test_up_missing_network(self):
|
||||||
|
self.base_dir = 'tests/fixtures/networks'
|
||||||
|
|
||||||
|
result = self.dispatch(
|
||||||
|
['-f', 'missing-network.yml', 'up', '-d'],
|
||||||
|
returncode=1)
|
||||||
|
|
||||||
|
assert 'Service "web" uses an undefined network "foo"' in result.stderr
|
||||||
|
|
||||||
|
def test_up_predefined_networks(self):
|
||||||
|
filename = 'predefined-networks.yml'
|
||||||
|
|
||||||
|
self.base_dir = 'tests/fixtures/networks'
|
||||||
|
self._project = get_project(self.base_dir, [filename])
|
||||||
|
|
||||||
|
self.dispatch(['-f', filename, 'up', '-d'], None)
|
||||||
|
|
||||||
|
networks = [
|
||||||
|
n for n in self.client.networks()
|
||||||
|
if n['Name'].startswith('{}_'.format(self.project.name))
|
||||||
|
]
|
||||||
|
assert not networks
|
||||||
|
|
||||||
|
for name in ['bridge', 'host', 'none']:
|
||||||
|
container = self.project.get_service(name).containers()[0]
|
||||||
|
assert list(container.get('NetworkSettings.Networks')) == [name]
|
||||||
|
assert container.get('HostConfig.NetworkMode') == name
|
||||||
|
|
||||||
|
def test_up_external_networks(self):
|
||||||
|
filename = 'external-networks.yml'
|
||||||
|
|
||||||
|
self.base_dir = 'tests/fixtures/networks'
|
||||||
|
self._project = get_project(self.base_dir, [filename])
|
||||||
|
|
||||||
|
result = self.dispatch(['-f', filename, 'up', '-d'], returncode=1)
|
||||||
|
assert 'declared as external, but could not be found' in result.stderr
|
||||||
|
|
||||||
|
networks = [
|
||||||
|
n['Name'] for n in self.client.networks()
|
||||||
|
if n['Name'].startswith('{}_'.format(self.project.name))
|
||||||
|
]
|
||||||
|
assert not networks
|
||||||
|
|
||||||
|
network_names = ['{}_{}'.format(self.project.name, n) for n in ['foo', 'bar']]
|
||||||
|
for name in network_names:
|
||||||
|
self.client.create_network(name)
|
||||||
|
|
||||||
|
self.dispatch(['-f', filename, 'up', '-d'])
|
||||||
|
container = self.project.containers()[0]
|
||||||
|
assert sorted(list(container.get('NetworkSettings.Networks'))) == sorted(network_names)
|
||||||
|
|
||||||
|
def test_up_no_services(self):
|
||||||
|
self.base_dir = 'tests/fixtures/no-services'
|
||||||
|
self.dispatch(['up', '-d'], None)
|
||||||
|
|
||||||
|
network_names = [
|
||||||
|
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']
|
||||||
|
]
|
||||||
|
|
||||||
def test_up_with_links_is_invalid(self):
|
def test_up_with_links_is_invalid(self):
|
||||||
self.base_dir = 'tests/fixtures/v2-simple'
|
self.base_dir = 'tests/fixtures/v2-simple'
|
||||||
@ -402,13 +507,48 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
def test_up_with_links_v1(self):
|
def test_up_with_links_v1(self):
|
||||||
self.base_dir = 'tests/fixtures/links-composefile'
|
self.base_dir = 'tests/fixtures/links-composefile'
|
||||||
self.dispatch(['up', '-d', 'web'], None)
|
self.dispatch(['up', '-d', 'web'], None)
|
||||||
|
|
||||||
|
# No network was created
|
||||||
|
networks = self.client.networks(names=[self.project.default_network.full_name])
|
||||||
|
assert networks == []
|
||||||
|
|
||||||
web = self.project.get_service('web')
|
web = self.project.get_service('web')
|
||||||
db = self.project.get_service('db')
|
db = self.project.get_service('db')
|
||||||
console = self.project.get_service('console')
|
console = self.project.get_service('console')
|
||||||
|
|
||||||
|
# console was not started
|
||||||
self.assertEqual(len(web.containers()), 1)
|
self.assertEqual(len(web.containers()), 1)
|
||||||
self.assertEqual(len(db.containers()), 1)
|
self.assertEqual(len(db.containers()), 1)
|
||||||
self.assertEqual(len(console.containers()), 0)
|
self.assertEqual(len(console.containers()), 0)
|
||||||
|
|
||||||
|
# web has links
|
||||||
|
web_container = web.containers()[0]
|
||||||
|
self.assertTrue(web_container.get('HostConfig.Links'))
|
||||||
|
|
||||||
|
def test_up_with_net_is_invalid(self):
|
||||||
|
self.base_dir = 'tests/fixtures/net-container'
|
||||||
|
|
||||||
|
result = self.dispatch(
|
||||||
|
['-f', 'v2-invalid.yml', 'up', '-d'],
|
||||||
|
returncode=1)
|
||||||
|
|
||||||
|
# TODO: fix validation error messages for v2 files
|
||||||
|
# assert "Unsupported config option for service 'web': 'net'" in exc.exconly()
|
||||||
|
assert "Unsupported config option" in result.stderr
|
||||||
|
|
||||||
|
def test_up_with_net_v1(self):
|
||||||
|
self.base_dir = 'tests/fixtures/net-container'
|
||||||
|
self.dispatch(['up', '-d'], None)
|
||||||
|
|
||||||
|
bar = self.project.get_service('bar')
|
||||||
|
bar_container = bar.containers()[0]
|
||||||
|
|
||||||
|
foo = self.project.get_service('foo')
|
||||||
|
foo_container = foo.containers()[0]
|
||||||
|
|
||||||
|
assert foo_container.get('HostConfig.NetworkMode') == \
|
||||||
|
'container:{}'.format(bar_container.id)
|
||||||
|
|
||||||
def test_up_with_no_deps(self):
|
def test_up_with_no_deps(self):
|
||||||
self.base_dir = 'tests/fixtures/links-composefile'
|
self.base_dir = 'tests/fixtures/links-composefile'
|
||||||
self.dispatch(['up', '-d', '--no-deps', 'web'], None)
|
self.dispatch(['up', '-d', '--no-deps', 'web'], None)
|
||||||
@ -698,9 +838,7 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
self.dispatch(['run', 'simple', 'true'], None)
|
self.dispatch(['run', 'simple', 'true'], None)
|
||||||
service = self.project.get_service('simple')
|
service = self.project.get_service('simple')
|
||||||
container, = service.containers(stopped=True, one_off=True)
|
container, = service.containers(stopped=True, one_off=True)
|
||||||
networks = self.client.networks(names=[self.project.default_network_name])
|
networks = self.client.networks(names=[self.project.default_network.full_name])
|
||||||
for n in networks:
|
|
||||||
self.addCleanup(self.client.remove_network, n['Id'])
|
|
||||||
self.assertEqual(len(networks), 1)
|
self.assertEqual(len(networks), 1)
|
||||||
self.assertEqual(container.human_readable_command, u'true')
|
self.assertEqual(container.human_readable_command, u'true')
|
||||||
|
|
||||||
@ -858,7 +996,7 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
def test_restart(self):
|
def test_restart(self):
|
||||||
service = self.project.get_service('simple')
|
service = self.project.get_service('simple')
|
||||||
container = service.create_container()
|
container = service.create_container()
|
||||||
container.start()
|
service.start_container(container)
|
||||||
started_at = container.dictionary['State']['StartedAt']
|
started_at = container.dictionary['State']['StartedAt']
|
||||||
self.dispatch(['restart', '-t', '1'], None)
|
self.dispatch(['restart', '-t', '1'], None)
|
||||||
container.inspect()
|
container.inspect()
|
||||||
|
7
tests/fixtures/net-container/docker-compose.yml
vendored
Normal file
7
tests/fixtures/net-container/docker-compose.yml
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
foo:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
net: "container:bar"
|
||||||
|
bar:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
10
tests/fixtures/net-container/v2-invalid.yml
vendored
Normal file
10
tests/fixtures/net-container/v2-invalid.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
services:
|
||||||
|
foo:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
bar:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
net: "container:foo"
|
19
tests/fixtures/networks/docker-compose.yml
vendored
Normal file
19
tests/fixtures/networks/docker-compose.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
networks: ["front"]
|
||||||
|
app:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
networks: ["front", "back"]
|
||||||
|
db:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
networks: ["back"]
|
||||||
|
|
||||||
|
networks:
|
||||||
|
front: {}
|
||||||
|
back: {}
|
16
tests/fixtures/networks/external-networks.yml
vendored
Normal file
16
tests/fixtures/networks/external-networks.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
networks:
|
||||||
|
- networks_foo
|
||||||
|
- bar
|
||||||
|
|
||||||
|
networks:
|
||||||
|
networks_foo:
|
||||||
|
external: true
|
||||||
|
bar:
|
||||||
|
external:
|
||||||
|
name: networks_bar
|
10
tests/fixtures/networks/missing-network.yml
vendored
Normal file
10
tests/fixtures/networks/missing-network.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
networks: ["foo"]
|
||||||
|
|
||||||
|
networks:
|
||||||
|
bar: {}
|
17
tests/fixtures/networks/predefined-networks.yml
vendored
Normal file
17
tests/fixtures/networks/predefined-networks.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
services:
|
||||||
|
bridge:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
networks: ["bridge"]
|
||||||
|
|
||||||
|
host:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
networks: ["host"]
|
||||||
|
|
||||||
|
none:
|
||||||
|
image: busybox
|
||||||
|
command: top
|
||||||
|
networks: []
|
9
tests/fixtures/no-links-composefile/docker-compose.yml
vendored
Normal file
9
tests/fixtures/no-links-composefile/docker-compose.yml
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
db:
|
||||||
|
image: busybox:latest
|
||||||
|
command: top
|
||||||
|
web:
|
||||||
|
image: busybox:latest
|
||||||
|
command: top
|
||||||
|
console:
|
||||||
|
image: busybox:latest
|
||||||
|
command: top
|
5
tests/fixtures/no-services/docker-compose.yml
vendored
Normal file
5
tests/fixtures/no-services/docker-compose.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
networks:
|
||||||
|
foo: {}
|
||||||
|
bar: {}
|
@ -14,7 +14,6 @@ from compose.const import LABEL_PROJECT
|
|||||||
from compose.container import Container
|
from compose.container import Container
|
||||||
from compose.project import Project
|
from compose.project import Project
|
||||||
from compose.service import ConvergenceStrategy
|
from compose.service import ConvergenceStrategy
|
||||||
from compose.service import Net
|
|
||||||
|
|
||||||
|
|
||||||
def build_service_dicts(service_config):
|
def build_service_dicts(service_config):
|
||||||
@ -104,21 +103,6 @@ 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_get_network_does_not_exist(self):
|
|
||||||
project = Project('composetest', [], self.client)
|
|
||||||
assert project.get_network() is None
|
|
||||||
|
|
||||||
def test_get_network(self):
|
|
||||||
project_name = 'network_does_exist'
|
|
||||||
network_name = '{}_default'.format(project_name)
|
|
||||||
|
|
||||||
project = Project(project_name, [], self.client)
|
|
||||||
self.client.create_network(network_name)
|
|
||||||
self.addCleanup(self.client.remove_network, network_name)
|
|
||||||
|
|
||||||
assert isinstance(project.get_network(), dict)
|
|
||||||
assert project.get_network()['Name'] == network_name
|
|
||||||
|
|
||||||
def test_net_from_service(self):
|
def test_net_from_service(self):
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
@ -473,18 +457,6 @@ 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_with_custom_network(self):
|
|
||||||
network_name = 'composetest-custom'
|
|
||||||
|
|
||||||
self.client.create_network(network_name)
|
|
||||||
self.addCleanup(self.client.remove_network, network_name)
|
|
||||||
|
|
||||||
web = self.create_service('web', net=Net(network_name))
|
|
||||||
project = Project('composetest', [web], self.client, use_networking=True)
|
|
||||||
project.up()
|
|
||||||
|
|
||||||
assert project.get_network() is None
|
|
||||||
|
|
||||||
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)
|
||||||
@ -510,15 +482,50 @@ class ProjectTest(DockerClientTestCase):
|
|||||||
service = project.get_service('web')
|
service = project.get_service('web')
|
||||||
self.assertEqual(len(service.containers()), 1)
|
self.assertEqual(len(service.containers()), 1)
|
||||||
|
|
||||||
|
def test_project_up_networks(self):
|
||||||
|
config_data = config.Config(
|
||||||
|
version=2,
|
||||||
|
services=[{
|
||||||
|
'name': 'web',
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
'command': 'top',
|
||||||
|
}],
|
||||||
|
volumes={},
|
||||||
|
networks={
|
||||||
|
'foo': {'driver': 'bridge'},
|
||||||
|
'bar': {'driver': None},
|
||||||
|
'baz': {},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
project = Project.from_config(
|
||||||
|
client=self.client,
|
||||||
|
name='composetest',
|
||||||
|
config_data=config_data,
|
||||||
|
)
|
||||||
|
project.up()
|
||||||
|
self.assertEqual(len(project.containers()), 1)
|
||||||
|
|
||||||
|
for net_name in ['foo', 'bar', 'baz']:
|
||||||
|
full_net_name = 'composetest_{}'.format(net_name)
|
||||||
|
network_data = self.client.inspect_network(full_net_name)
|
||||||
|
self.assertEqual(network_data['Name'], full_net_name)
|
||||||
|
|
||||||
|
foo_data = self.client.inspect_network('composetest_foo')
|
||||||
|
self.assertEqual(foo_data['Driver'], 'bridge')
|
||||||
|
|
||||||
def test_project_up_volumes(self):
|
def test_project_up_volumes(self):
|
||||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||||
config_data = config.Config(
|
config_data = config.Config(
|
||||||
version=2, services=[{
|
version=2,
|
||||||
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'command': 'top'
|
'command': 'top'
|
||||||
}], volumes={vol_name: {'driver': 'local'}}
|
}],
|
||||||
|
volumes={vol_name: {'driver': 'local'}},
|
||||||
|
networks={},
|
||||||
)
|
)
|
||||||
|
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
@ -587,11 +594,14 @@ class ProjectTest(DockerClientTestCase):
|
|||||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||||
config_data = config.Config(
|
config_data = config.Config(
|
||||||
version=2, services=[{
|
version=2,
|
||||||
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'command': 'top'
|
'command': 'top'
|
||||||
}], volumes={vol_name: {}}
|
}],
|
||||||
|
volumes={vol_name: {}},
|
||||||
|
networks={},
|
||||||
)
|
)
|
||||||
|
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
@ -608,11 +618,14 @@ class ProjectTest(DockerClientTestCase):
|
|||||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||||
config_data = config.Config(
|
config_data = config.Config(
|
||||||
version=2, services=[{
|
version=2,
|
||||||
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'command': 'top'
|
'command': 'top'
|
||||||
}], volumes={vol_name: {}}
|
}],
|
||||||
|
volumes={vol_name: {}},
|
||||||
|
networks={},
|
||||||
)
|
)
|
||||||
|
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
@ -629,11 +642,14 @@ class ProjectTest(DockerClientTestCase):
|
|||||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||||
|
|
||||||
config_data = config.Config(
|
config_data = config.Config(
|
||||||
version=2, services=[{
|
version=2,
|
||||||
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'command': 'top'
|
'command': 'top'
|
||||||
}], volumes={vol_name: {'driver': 'foobar'}}
|
}],
|
||||||
|
volumes={vol_name: {'driver': 'foobar'}},
|
||||||
|
networks={},
|
||||||
)
|
)
|
||||||
|
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
@ -648,11 +664,14 @@ class ProjectTest(DockerClientTestCase):
|
|||||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||||
|
|
||||||
config_data = config.Config(
|
config_data = config.Config(
|
||||||
version=2, services=[{
|
version=2,
|
||||||
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'command': 'top'
|
'command': 'top'
|
||||||
}], volumes={vol_name: {'driver': 'local'}}
|
}],
|
||||||
|
volumes={vol_name: {'driver': 'local'}},
|
||||||
|
networks={},
|
||||||
)
|
)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
@ -683,13 +702,16 @@ class ProjectTest(DockerClientTestCase):
|
|||||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||||
self.client.create_volume(vol_name)
|
self.client.create_volume(vol_name)
|
||||||
config_data = config.Config(
|
config_data = config.Config(
|
||||||
version=2, services=[{
|
version=2,
|
||||||
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'command': 'top'
|
'command': 'top'
|
||||||
}], volumes={
|
}],
|
||||||
|
volumes={
|
||||||
vol_name: {'external': True, 'external_name': vol_name}
|
vol_name: {'external': True, 'external_name': vol_name}
|
||||||
}
|
},
|
||||||
|
networks=None,
|
||||||
)
|
)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
@ -704,13 +726,16 @@ class ProjectTest(DockerClientTestCase):
|
|||||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||||
|
|
||||||
config_data = config.Config(
|
config_data = config.Config(
|
||||||
version=2, services=[{
|
version=2,
|
||||||
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'command': 'top'
|
'command': 'top'
|
||||||
}], volumes={
|
}],
|
||||||
|
volumes={
|
||||||
vol_name: {'external': True, 'external_name': vol_name}
|
vol_name: {'external': True, 'external_name': vol_name}
|
||||||
}
|
},
|
||||||
|
networks=None,
|
||||||
)
|
)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
|
@ -17,7 +17,7 @@ class ResilienceTest(DockerClientTestCase):
|
|||||||
self.project = Project('composetest', [self.db], self.client)
|
self.project = Project('composetest', [self.db], self.client)
|
||||||
|
|
||||||
container = self.db.create_container()
|
container = self.db.create_container()
|
||||||
container.start()
|
self.db.start_container(container)
|
||||||
self.host_path = container.get_mount('/var/db')['Source']
|
self.host_path = container.get_mount('/var/db')['Source']
|
||||||
|
|
||||||
def test_successful_recreate(self):
|
def test_successful_recreate(self):
|
||||||
@ -35,7 +35,7 @@ class ResilienceTest(DockerClientTestCase):
|
|||||||
self.assertEqual(container.get_mount('/var/db')['Source'], self.host_path)
|
self.assertEqual(container.get_mount('/var/db')['Source'], self.host_path)
|
||||||
|
|
||||||
def test_start_failure(self):
|
def test_start_failure(self):
|
||||||
with mock.patch('compose.container.Container.start', crash):
|
with mock.patch('compose.service.Service.start_container', crash):
|
||||||
with self.assertRaises(Crash):
|
with self.assertRaises(Crash):
|
||||||
self.project.up(strategy=ConvergenceStrategy.always)
|
self.project.up(strategy=ConvergenceStrategy.always)
|
||||||
|
|
||||||
|
@ -32,14 +32,7 @@ from compose.service import Service
|
|||||||
|
|
||||||
def create_and_start_container(service, **override_options):
|
def create_and_start_container(service, **override_options):
|
||||||
container = service.create_container(**override_options)
|
container = service.create_container(**override_options)
|
||||||
container.start()
|
return service.start_container(container)
|
||||||
return container
|
|
||||||
|
|
||||||
|
|
||||||
def remove_stopped(service):
|
|
||||||
containers = [c for c in service.containers(stopped=True) if not c.is_running]
|
|
||||||
for container in containers:
|
|
||||||
container.remove()
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceTest(DockerClientTestCase):
|
class ServiceTest(DockerClientTestCase):
|
||||||
@ -88,19 +81,19 @@ class ServiceTest(DockerClientTestCase):
|
|||||||
def test_create_container_with_unspecified_volume(self):
|
def test_create_container_with_unspecified_volume(self):
|
||||||
service = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
|
service = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
|
||||||
container = service.create_container()
|
container = service.create_container()
|
||||||
container.start()
|
service.start_container(container)
|
||||||
assert container.get_mount('/var/db')
|
assert container.get_mount('/var/db')
|
||||||
|
|
||||||
def test_create_container_with_volume_driver(self):
|
def test_create_container_with_volume_driver(self):
|
||||||
service = self.create_service('db', volume_driver='foodriver')
|
service = self.create_service('db', volume_driver='foodriver')
|
||||||
container = service.create_container()
|
container = service.create_container()
|
||||||
container.start()
|
service.start_container(container)
|
||||||
self.assertEqual('foodriver', container.get('HostConfig.VolumeDriver'))
|
self.assertEqual('foodriver', container.get('HostConfig.VolumeDriver'))
|
||||||
|
|
||||||
def test_create_container_with_cpu_shares(self):
|
def test_create_container_with_cpu_shares(self):
|
||||||
service = self.create_service('db', cpu_shares=73)
|
service = self.create_service('db', cpu_shares=73)
|
||||||
container = service.create_container()
|
container = service.create_container()
|
||||||
container.start()
|
service.start_container(container)
|
||||||
self.assertEqual(container.get('HostConfig.CpuShares'), 73)
|
self.assertEqual(container.get('HostConfig.CpuShares'), 73)
|
||||||
|
|
||||||
def test_create_container_with_cpu_quota(self):
|
def test_create_container_with_cpu_quota(self):
|
||||||
@ -113,7 +106,7 @@ class ServiceTest(DockerClientTestCase):
|
|||||||
extra_hosts = ['somehost:162.242.195.82', 'otherhost:50.31.209.229']
|
extra_hosts = ['somehost:162.242.195.82', 'otherhost:50.31.209.229']
|
||||||
service = self.create_service('db', extra_hosts=extra_hosts)
|
service = self.create_service('db', extra_hosts=extra_hosts)
|
||||||
container = service.create_container()
|
container = service.create_container()
|
||||||
container.start()
|
service.start_container(container)
|
||||||
self.assertEqual(set(container.get('HostConfig.ExtraHosts')), set(extra_hosts))
|
self.assertEqual(set(container.get('HostConfig.ExtraHosts')), set(extra_hosts))
|
||||||
|
|
||||||
def test_create_container_with_extra_hosts_dicts(self):
|
def test_create_container_with_extra_hosts_dicts(self):
|
||||||
@ -121,33 +114,33 @@ class ServiceTest(DockerClientTestCase):
|
|||||||
extra_hosts_list = ['somehost:162.242.195.82', 'otherhost:50.31.209.229']
|
extra_hosts_list = ['somehost:162.242.195.82', 'otherhost:50.31.209.229']
|
||||||
service = self.create_service('db', extra_hosts=extra_hosts)
|
service = self.create_service('db', extra_hosts=extra_hosts)
|
||||||
container = service.create_container()
|
container = service.create_container()
|
||||||
container.start()
|
service.start_container(container)
|
||||||
self.assertEqual(set(container.get('HostConfig.ExtraHosts')), set(extra_hosts_list))
|
self.assertEqual(set(container.get('HostConfig.ExtraHosts')), set(extra_hosts_list))
|
||||||
|
|
||||||
def test_create_container_with_cpu_set(self):
|
def test_create_container_with_cpu_set(self):
|
||||||
service = self.create_service('db', cpuset='0')
|
service = self.create_service('db', cpuset='0')
|
||||||
container = service.create_container()
|
container = service.create_container()
|
||||||
container.start()
|
service.start_container(container)
|
||||||
self.assertEqual(container.get('HostConfig.CpusetCpus'), '0')
|
self.assertEqual(container.get('HostConfig.CpusetCpus'), '0')
|
||||||
|
|
||||||
def test_create_container_with_read_only_root_fs(self):
|
def test_create_container_with_read_only_root_fs(self):
|
||||||
read_only = True
|
read_only = True
|
||||||
service = self.create_service('db', read_only=read_only)
|
service = self.create_service('db', read_only=read_only)
|
||||||
container = service.create_container()
|
container = service.create_container()
|
||||||
container.start()
|
service.start_container(container)
|
||||||
self.assertEqual(container.get('HostConfig.ReadonlyRootfs'), read_only, container.get('HostConfig'))
|
self.assertEqual(container.get('HostConfig.ReadonlyRootfs'), read_only, container.get('HostConfig'))
|
||||||
|
|
||||||
def test_create_container_with_security_opt(self):
|
def test_create_container_with_security_opt(self):
|
||||||
security_opt = ['label:disable']
|
security_opt = ['label:disable']
|
||||||
service = self.create_service('db', security_opt=security_opt)
|
service = self.create_service('db', security_opt=security_opt)
|
||||||
container = service.create_container()
|
container = service.create_container()
|
||||||
container.start()
|
service.start_container(container)
|
||||||
self.assertEqual(set(container.get('HostConfig.SecurityOpt')), set(security_opt))
|
self.assertEqual(set(container.get('HostConfig.SecurityOpt')), set(security_opt))
|
||||||
|
|
||||||
def test_create_container_with_mac_address(self):
|
def test_create_container_with_mac_address(self):
|
||||||
service = self.create_service('db', mac_address='02:42:ac:11:65:43')
|
service = self.create_service('db', mac_address='02:42:ac:11:65:43')
|
||||||
container = service.create_container()
|
container = service.create_container()
|
||||||
container.start()
|
service.start_container(container)
|
||||||
self.assertEqual(container.inspect()['Config']['MacAddress'], '02:42:ac:11:65:43')
|
self.assertEqual(container.inspect()['Config']['MacAddress'], '02:42:ac:11:65:43')
|
||||||
|
|
||||||
def test_create_container_with_specified_volume(self):
|
def test_create_container_with_specified_volume(self):
|
||||||
@ -158,7 +151,7 @@ class ServiceTest(DockerClientTestCase):
|
|||||||
'db',
|
'db',
|
||||||
volumes=[VolumeSpec(host_path, container_path, 'rw')])
|
volumes=[VolumeSpec(host_path, container_path, 'rw')])
|
||||||
container = service.create_container()
|
container = service.create_container()
|
||||||
container.start()
|
service.start_container(container)
|
||||||
assert container.get_mount(container_path)
|
assert container.get_mount(container_path)
|
||||||
|
|
||||||
# Match the last component ("host-path"), because boot2docker symlinks /tmp
|
# Match the last component ("host-path"), because boot2docker symlinks /tmp
|
||||||
@ -229,7 +222,7 @@ class ServiceTest(DockerClientTestCase):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
host_container = host_service.create_container()
|
host_container = host_service.create_container()
|
||||||
host_container.start()
|
host_service.start_container(host_container)
|
||||||
self.assertIn(volume_container_1.id + ':rw',
|
self.assertIn(volume_container_1.id + ':rw',
|
||||||
host_container.get('HostConfig.VolumesFrom'))
|
host_container.get('HostConfig.VolumesFrom'))
|
||||||
self.assertIn(volume_container_2.id + ':rw',
|
self.assertIn(volume_container_2.id + ':rw',
|
||||||
@ -248,7 +241,7 @@ class ServiceTest(DockerClientTestCase):
|
|||||||
self.assertEqual(old_container.get('Config.Cmd'), ['-d', '1'])
|
self.assertEqual(old_container.get('Config.Cmd'), ['-d', '1'])
|
||||||
self.assertIn('FOO=1', old_container.get('Config.Env'))
|
self.assertIn('FOO=1', old_container.get('Config.Env'))
|
||||||
self.assertEqual(old_container.name, 'composetest_db_1')
|
self.assertEqual(old_container.name, 'composetest_db_1')
|
||||||
old_container.start()
|
service.start_container(old_container)
|
||||||
old_container.inspect() # reload volume data
|
old_container.inspect() # reload volume data
|
||||||
volume_path = old_container.get_mount('/etc')['Source']
|
volume_path = old_container.get_mount('/etc')['Source']
|
||||||
|
|
||||||
|
@ -12,8 +12,6 @@ from compose.config.types import VolumeFromSpec
|
|||||||
from compose.const import LABEL_SERVICE
|
from compose.const import LABEL_SERVICE
|
||||||
from compose.container import Container
|
from compose.container import Container
|
||||||
from compose.project import Project
|
from compose.project import Project
|
||||||
from compose.service import ContainerNet
|
|
||||||
from compose.service import Net
|
|
||||||
from compose.service import Service
|
from compose.service import Service
|
||||||
|
|
||||||
|
|
||||||
@ -21,35 +19,27 @@ class ProjectTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.mock_client = mock.create_autospec(docker.Client)
|
self.mock_client = mock.create_autospec(docker.Client)
|
||||||
|
|
||||||
def test_from_dict(self):
|
|
||||||
project = Project.from_config('composetest', Config(None, [
|
|
||||||
{
|
|
||||||
'name': 'web',
|
|
||||||
'image': 'busybox:latest'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'db',
|
|
||||||
'image': 'busybox:latest'
|
|
||||||
},
|
|
||||||
], None), None)
|
|
||||||
self.assertEqual(len(project.services), 2)
|
|
||||||
self.assertEqual(project.get_service('web').name, 'web')
|
|
||||||
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')
|
|
||||||
|
|
||||||
def test_from_config(self):
|
def test_from_config(self):
|
||||||
config = Config(None, [
|
config = Config(
|
||||||
{
|
version=None,
|
||||||
'name': 'web',
|
services=[
|
||||||
'image': 'busybox:latest',
|
{
|
||||||
},
|
'name': 'web',
|
||||||
{
|
'image': 'busybox:latest',
|
||||||
'name': 'db',
|
},
|
||||||
'image': 'busybox:latest',
|
{
|
||||||
},
|
'name': 'db',
|
||||||
], None)
|
'image': 'busybox:latest',
|
||||||
project = Project.from_config('composetest', config, None)
|
},
|
||||||
|
],
|
||||||
|
networks=None,
|
||||||
|
volumes=None,
|
||||||
|
)
|
||||||
|
project = Project.from_config(
|
||||||
|
name='composetest',
|
||||||
|
config_data=config,
|
||||||
|
client=None,
|
||||||
|
)
|
||||||
self.assertEqual(len(project.services), 2)
|
self.assertEqual(len(project.services), 2)
|
||||||
self.assertEqual(project.get_service('web').name, 'web')
|
self.assertEqual(project.get_service('web').name, 'web')
|
||||||
self.assertEqual(project.get_service('web').options['image'], 'busybox:latest')
|
self.assertEqual(project.get_service('web').options['image'], 'busybox:latest')
|
||||||
@ -58,16 +48,21 @@ class ProjectTest(unittest.TestCase):
|
|||||||
self.assertFalse(project.use_networking)
|
self.assertFalse(project.use_networking)
|
||||||
|
|
||||||
def test_from_config_v2(self):
|
def test_from_config_v2(self):
|
||||||
config = Config(2, [
|
config = Config(
|
||||||
{
|
version=2,
|
||||||
'name': 'web',
|
services=[
|
||||||
'image': 'busybox:latest',
|
{
|
||||||
},
|
'name': 'web',
|
||||||
{
|
'image': 'busybox:latest',
|
||||||
'name': 'db',
|
},
|
||||||
'image': 'busybox:latest',
|
{
|
||||||
},
|
'name': 'db',
|
||||||
], None)
|
'image': 'busybox:latest',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
networks=None,
|
||||||
|
volumes=None,
|
||||||
|
)
|
||||||
project = Project.from_config('composetest', config, None)
|
project = Project.from_config('composetest', config, None)
|
||||||
self.assertEqual(len(project.services), 2)
|
self.assertEqual(len(project.services), 2)
|
||||||
self.assertTrue(project.use_networking)
|
self.assertTrue(project.use_networking)
|
||||||
@ -161,13 +156,20 @@ class ProjectTest(unittest.TestCase):
|
|||||||
container_id = 'aabbccddee'
|
container_id = 'aabbccddee'
|
||||||
container_dict = dict(Name='aaa', Id=container_id)
|
container_dict = dict(Name='aaa', Id=container_id)
|
||||||
self.mock_client.inspect_container.return_value = container_dict
|
self.mock_client.inspect_container.return_value = container_dict
|
||||||
project = Project.from_config('test', Config(None, [
|
project = Project.from_config(
|
||||||
{
|
name='test',
|
||||||
'name': 'test',
|
client=self.mock_client,
|
||||||
'image': 'busybox:latest',
|
config_data=Config(
|
||||||
'volumes_from': [VolumeFromSpec('aaa', 'rw', 'container')]
|
version=None,
|
||||||
}
|
services=[{
|
||||||
], None), self.mock_client)
|
'name': 'test',
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
'volumes_from': [VolumeFromSpec('aaa', 'rw', 'container')]
|
||||||
|
}],
|
||||||
|
networks=None,
|
||||||
|
volumes=None,
|
||||||
|
),
|
||||||
|
)
|
||||||
assert project.get_service('test')._get_volumes_from() == [container_id + ":rw"]
|
assert project.get_service('test')._get_volumes_from() == [container_id + ":rw"]
|
||||||
|
|
||||||
def test_use_volumes_from_service_no_container(self):
|
def test_use_volumes_from_service_no_container(self):
|
||||||
@ -180,33 +182,51 @@ class ProjectTest(unittest.TestCase):
|
|||||||
"Image": 'busybox:latest'
|
"Image": 'busybox:latest'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
project = Project.from_config('test', Config(None, [
|
project = Project.from_config(
|
||||||
{
|
name='test',
|
||||||
'name': 'vol',
|
client=self.mock_client,
|
||||||
'image': 'busybox:latest'
|
config_data=Config(
|
||||||
},
|
version=None,
|
||||||
{
|
services=[
|
||||||
'name': 'test',
|
{
|
||||||
'image': 'busybox:latest',
|
'name': 'vol',
|
||||||
'volumes_from': [VolumeFromSpec('vol', 'rw', 'service')]
|
'image': 'busybox:latest'
|
||||||
}
|
},
|
||||||
], None), self.mock_client)
|
{
|
||||||
|
'name': 'test',
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
'volumes_from': [VolumeFromSpec('vol', 'rw', 'service')]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
networks=None,
|
||||||
|
volumes=None,
|
||||||
|
),
|
||||||
|
)
|
||||||
assert project.get_service('test')._get_volumes_from() == [container_name + ":rw"]
|
assert project.get_service('test')._get_volumes_from() == [container_name + ":rw"]
|
||||||
|
|
||||||
def test_use_volumes_from_service_container(self):
|
def test_use_volumes_from_service_container(self):
|
||||||
container_ids = ['aabbccddee', '12345']
|
container_ids = ['aabbccddee', '12345']
|
||||||
|
|
||||||
project = Project.from_config('test', Config(None, [
|
project = Project.from_config(
|
||||||
{
|
name='test',
|
||||||
'name': 'vol',
|
client=None,
|
||||||
'image': 'busybox:latest'
|
config_data=Config(
|
||||||
},
|
version=None,
|
||||||
{
|
services=[
|
||||||
'name': 'test',
|
{
|
||||||
'image': 'busybox:latest',
|
'name': 'vol',
|
||||||
'volumes_from': [VolumeFromSpec('vol', 'rw', 'service')]
|
'image': 'busybox:latest'
|
||||||
}
|
},
|
||||||
], None), None)
|
{
|
||||||
|
'name': 'test',
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
'volumes_from': [VolumeFromSpec('vol', 'rw', 'service')]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
networks=None,
|
||||||
|
volumes=None,
|
||||||
|
),
|
||||||
|
)
|
||||||
with mock.patch.object(Service, 'containers') as mock_return:
|
with mock.patch.object(Service, 'containers') as mock_return:
|
||||||
mock_return.return_value = [
|
mock_return.return_value = [
|
||||||
mock.Mock(id=container_id, spec=Container)
|
mock.Mock(id=container_id, spec=Container)
|
||||||
@ -313,12 +333,21 @@ class ProjectTest(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def test_net_unset(self):
|
def test_net_unset(self):
|
||||||
project = Project.from_config('test', Config(None, [
|
project = Project.from_config(
|
||||||
{
|
name='test',
|
||||||
'name': 'test',
|
client=self.mock_client,
|
||||||
'image': 'busybox:latest',
|
config_data=Config(
|
||||||
}
|
version=None,
|
||||||
], None), self.mock_client)
|
services=[
|
||||||
|
{
|
||||||
|
'name': 'test',
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
networks=None,
|
||||||
|
volumes=None,
|
||||||
|
),
|
||||||
|
)
|
||||||
service = project.get_service('test')
|
service = project.get_service('test')
|
||||||
self.assertEqual(service.net.id, None)
|
self.assertEqual(service.net.id, None)
|
||||||
self.assertNotIn('NetworkMode', service._get_container_host_config({}))
|
self.assertNotIn('NetworkMode', service._get_container_host_config({}))
|
||||||
@ -327,13 +356,22 @@ class ProjectTest(unittest.TestCase):
|
|||||||
container_id = 'aabbccddee'
|
container_id = 'aabbccddee'
|
||||||
container_dict = dict(Name='aaa', Id=container_id)
|
container_dict = dict(Name='aaa', Id=container_id)
|
||||||
self.mock_client.inspect_container.return_value = container_dict
|
self.mock_client.inspect_container.return_value = container_dict
|
||||||
project = Project.from_config('test', Config(None, [
|
project = Project.from_config(
|
||||||
{
|
name='test',
|
||||||
'name': 'test',
|
client=self.mock_client,
|
||||||
'image': 'busybox:latest',
|
config_data=Config(
|
||||||
'net': 'container:aaa'
|
version=None,
|
||||||
}
|
services=[
|
||||||
], None), self.mock_client)
|
{
|
||||||
|
'name': 'test',
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
'net': 'container:aaa'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
networks=None,
|
||||||
|
volumes=None,
|
||||||
|
),
|
||||||
|
)
|
||||||
service = project.get_service('test')
|
service = project.get_service('test')
|
||||||
self.assertEqual(service.net.mode, 'container:' + container_id)
|
self.assertEqual(service.net.mode, 'container:' + container_id)
|
||||||
|
|
||||||
@ -347,45 +385,67 @@ class ProjectTest(unittest.TestCase):
|
|||||||
"Image": 'busybox:latest'
|
"Image": 'busybox:latest'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
project = Project.from_config('test', Config(None, [
|
project = Project.from_config(
|
||||||
{
|
name='test',
|
||||||
'name': 'aaa',
|
client=self.mock_client,
|
||||||
'image': 'busybox:latest'
|
config_data=Config(
|
||||||
},
|
version=None,
|
||||||
{
|
services=[
|
||||||
'name': 'test',
|
{
|
||||||
'image': 'busybox:latest',
|
'name': 'aaa',
|
||||||
'net': 'container:aaa'
|
'image': 'busybox:latest'
|
||||||
}
|
},
|
||||||
], None), self.mock_client)
|
{
|
||||||
|
'name': 'test',
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
'net': 'container:aaa'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
networks=None,
|
||||||
|
volumes=None,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
service = project.get_service('test')
|
service = project.get_service('test')
|
||||||
self.assertEqual(service.net.mode, 'container:' + container_name)
|
self.assertEqual(service.net.mode, 'container:' + container_name)
|
||||||
|
|
||||||
def test_uses_default_network_true(self):
|
def test_uses_default_network_true(self):
|
||||||
web = Service('web', project='test', image="alpine", net=Net('test_default'))
|
project = Project.from_config(
|
||||||
db = Service('web', project='test', image="alpine", net=Net('other'))
|
name='test',
|
||||||
project = Project('test', [web, db], None)
|
client=self.mock_client,
|
||||||
|
config_data=Config(
|
||||||
|
version=2,
|
||||||
|
services=[
|
||||||
|
{
|
||||||
|
'name': 'foo',
|
||||||
|
'image': 'busybox:latest'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
networks=None,
|
||||||
|
volumes=None,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
assert project.uses_default_network()
|
assert project.uses_default_network()
|
||||||
|
|
||||||
def test_uses_default_network_custom_name(self):
|
def test_uses_default_network_false(self):
|
||||||
web = Service('web', project='test', image="alpine", net=Net('other'))
|
project = Project.from_config(
|
||||||
project = Project('test', [web], None)
|
name='test',
|
||||||
assert not project.uses_default_network()
|
client=self.mock_client,
|
||||||
|
config_data=Config(
|
||||||
|
version=2,
|
||||||
|
services=[
|
||||||
|
{
|
||||||
|
'name': 'foo',
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
'networks': ['custom']
|
||||||
|
},
|
||||||
|
],
|
||||||
|
networks={'custom': {}},
|
||||||
|
volumes=None,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def test_uses_default_network_host(self):
|
|
||||||
web = Service('web', project='test', image="alpine", net=Net('host'))
|
|
||||||
project = Project('test', [web], None)
|
|
||||||
assert not project.uses_default_network()
|
|
||||||
|
|
||||||
def test_uses_default_network_container(self):
|
|
||||||
container = mock.Mock(id='test')
|
|
||||||
web = Service(
|
|
||||||
'web',
|
|
||||||
project='test',
|
|
||||||
image="alpine",
|
|
||||||
net=ContainerNet(container))
|
|
||||||
project = Project('test', [web], None)
|
|
||||||
assert not project.uses_default_network()
|
assert not project.uses_default_network()
|
||||||
|
|
||||||
def test_container_without_name(self):
|
def test_container_without_name(self):
|
||||||
@ -403,11 +463,16 @@ class ProjectTest(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
'test',
|
name='test',
|
||||||
Config(None, [{
|
client=self.mock_client,
|
||||||
'name': 'web',
|
config_data=Config(
|
||||||
'image': 'busybox:latest',
|
version=None,
|
||||||
}], None),
|
services=[{
|
||||||
self.mock_client,
|
'name': 'web',
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
}],
|
||||||
|
networks=None,
|
||||||
|
volumes=None,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
self.assertEqual([c.id for c in project.containers()], ['1'])
|
self.assertEqual([c.id for c in project.containers()], ['1'])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user