Implement depends_on to define an order for services in the v2 format.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
Daniel Nephin 2016-01-19 15:41:45 -05:00 committed by Aanand Prasad
parent 33bb7c4e02
commit 6e5c312768
7 changed files with 70 additions and 8 deletions

View File

@ -27,6 +27,7 @@ from .types import VolumeFromSpec
from .types import VolumeSpec
from .validation import validate_against_fields_schema
from .validation import validate_against_service_schema
from .validation import validate_depends_on
from .validation import validate_extends_file_path
from .validation import validate_top_level_object
from .validation import validate_top_level_service_objects
@ -312,9 +313,10 @@ def load_services(working_dir, config_file, service_configs):
resolver = ServiceExtendsResolver(service_config, config_file)
service_dict = process_service(resolver.run())
validate_service(service_dict, service_config.name, config_file.version)
service_config = service_config._replace(config=service_dict)
validate_service(service_config, service_names, config_file.version)
service_dict = finalize_service(
service_config._replace(config=service_dict),
service_config,
service_names,
config_file.version)
return service_dict
@ -481,6 +483,10 @@ def validate_extended_service_dict(service_dict, filename, service):
raise ConfigurationError(
"%s services with 'net: container' cannot be extended" % error_prefix)
if 'depends_on' in service_dict:
raise ConfigurationError(
"%s services with 'depends_on' cannot be extended" % error_prefix)
def validate_ulimits(ulimit_config):
for limit_name, soft_hard_values in six.iteritems(ulimit_config):
@ -491,13 +497,16 @@ def validate_ulimits(ulimit_config):
"than 'hard' value".format(ulimit_config))
def validate_service(service_dict, service_name, version):
def validate_service(service_config, service_names, version):
service_dict, service_name = service_config.config, service_config.name
validate_against_service_schema(service_dict, service_name, version)
validate_paths(service_dict)
if 'ulimits' in service_dict:
validate_ulimits(service_dict['ulimits'])
validate_depends_on(service_config, service_names)
if not service_dict.get('image') and has_uppercase(service_name):
raise ConfigurationError(
"Service '{name}' contains uppercase characters which are not valid "

View File

@ -42,6 +42,7 @@
"cpu_shares": {"type": ["number", "string"]},
"cpu_quota": {"type": ["number", "string"]},
"cpuset": {"type": "string"},
"depends_on": {"$ref": "#/definitions/list_of_strings"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},

View File

@ -33,7 +33,8 @@ def sort_service_dicts(services):
service for service in services
if (name in get_service_names(service.get('links', [])) or
name in get_service_names_from_volumes_from(service.get('volumes_from', [])) or
name == get_service_name_from_net(service.get('net')))
name == get_service_name_from_net(service.get('net')) or
name in service.get('depends_on', []))
]
def visit(n):
@ -42,8 +43,10 @@ def sort_service_dicts(services):
raise DependencyError('A service can not link to itself: %s' % n['name'])
if n['name'] in n.get('volumes_from', []):
raise DependencyError('A service can not mount itself as volume: %s' % n['name'])
else:
raise DependencyError('Circular import between %s' % ' and '.join(temporary_marked))
if n['name'] in n.get('depends_on', []):
raise DependencyError('A service can not depend on itself: %s' % n['name'])
raise DependencyError('Circular dependency between %s' % ' and '.join(temporary_marked))
if n in unmarked:
temporary_marked.add(n['name'])
for m in get_service_dependents(n, services):

View File

@ -123,6 +123,14 @@ def validate_extends_file_path(service_name, extends_options, filename):
)
def validate_depends_on(service_config, service_names):
for dependency in service_config.config.get('depends_on', []):
if dependency not in service_names:
raise ConfigurationError(
"Service '{s.name}' depends on service '{dep}' which is "
"undefined.".format(s=service_config, dep=dependency))
def get_unsupported_config_msg(service_name, error_key):
msg = "Unsupported config option for '{}' service: '{}'".format(service_name, error_key)
if error_key in DOCKER_CONFIG_HINTS:

View File

@ -471,7 +471,8 @@ class Service(object):
net_name = self.net.service_name
return (self.get_linked_service_names() +
self.get_volumes_from_names() +
([net_name] if net_name else []))
([net_name] if net_name else []) +
self.options.get('depends_on', []))
def get_linked_service_names(self):
return [service.name for (service, _) in self.links]

View File

@ -894,9 +894,35 @@ class ConfigTest(unittest.TestCase):
'ext': {'external': True, 'driver': 'foo'}
}
})
with self.assertRaises(ConfigurationError):
with pytest.raises(ConfigurationError):
config.load(config_details)
def test_depends_on_orders_services(self):
config_details = build_config_details({
'version': 2,
'services': {
'one': {'image': 'busybox', 'depends_on': ['three', 'two']},
'two': {'image': 'busybox', 'depends_on': ['three']},
'three': {'image': 'busybox'},
},
})
actual = config.load(config_details)
assert (
[service['name'] for service in actual.services] ==
['three', 'two', 'one']
)
def test_depends_on_unknown_service_errors(self):
config_details = build_config_details({
'version': 2,
'services': {
'one': {'image': 'busybox', 'depends_on': ['three']},
},
})
with pytest.raises(ConfigurationError) as exc:
config.load(config_details)
assert "Service 'one' depends on service 'three'" in exc.exconly()
class PortsTest(unittest.TestCase):
INVALID_PORTS_TYPES = [

View File

@ -1,6 +1,8 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import pytest
from compose.config.errors import DependencyError
from compose.config.sort_services import sort_service_dicts
from compose.config.types import VolumeFromSpec
@ -240,3 +242,15 @@ class SortServiceTest(unittest.TestCase):
self.assertIn('web', e.msg)
else:
self.fail('Should have thrown an DependencyError')
def test_sort_service_dicts_depends_on_self(self):
services = [
{
'depends_on': ['web'],
'name': 'web'
},
]
with pytest.raises(DependencyError) as exc:
sort_service_dicts(services)
assert 'A service can not depend on itself: web' in exc.exconly()