mirror of https://github.com/docker/compose.git
Merge pull request #2708 from dnephin/implement_depends_on
Implement depends_on
This commit is contained in:
commit
e35bf47a7f
|
@ -27,9 +27,11 @@ 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
|
||||
from .validation import validate_ulimits
|
||||
|
||||
|
||||
DOCKER_CONFIG_KEYS = [
|
||||
|
@ -312,9 +314,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,22 +484,18 @@ def validate_extended_service_dict(service_dict, filename, service):
|
|||
raise ConfigurationError(
|
||||
"%s services with 'net: container' cannot be extended" % error_prefix)
|
||||
|
||||
|
||||
def validate_ulimits(ulimit_config):
|
||||
for limit_name, soft_hard_values in six.iteritems(ulimit_config):
|
||||
if isinstance(soft_hard_values, dict):
|
||||
if not soft_hard_values['soft'] <= soft_hard_values['hard']:
|
||||
raise ConfigurationError(
|
||||
"ulimit_config \"{}\" cannot contain a 'soft' value higher "
|
||||
"than 'hard' value".format(ulimit_config))
|
||||
if 'depends_on' in service_dict:
|
||||
raise ConfigurationError(
|
||||
"%s services with 'depends_on' cannot be extended" % error_prefix)
|
||||
|
||||
|
||||
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_ulimits(service_config)
|
||||
validate_depends_on(service_config, service_names)
|
||||
|
||||
if not service_dict.get('image') and has_uppercase(service_name):
|
||||
raise ConfigurationError(
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -110,6 +110,18 @@ def validate_top_level_object(config_file):
|
|||
type(config_file.config)))
|
||||
|
||||
|
||||
def validate_ulimits(service_config):
|
||||
ulimit_config = service_config.config.get('ulimits', {})
|
||||
for limit_name, soft_hard_values in six.iteritems(ulimit_config):
|
||||
if isinstance(soft_hard_values, dict):
|
||||
if not soft_hard_values['soft'] <= soft_hard_values['hard']:
|
||||
raise ConfigurationError(
|
||||
"Service '{s.name}' has invalid ulimit '{ulimit}'. "
|
||||
"'soft' value can not be greater than 'hard' value ".format(
|
||||
s=service_config,
|
||||
ulimit=ulimit_config))
|
||||
|
||||
|
||||
def validate_extends_file_path(service_name, extends_options, filename):
|
||||
"""
|
||||
The service to be extended must either be defined in the config key 'file',
|
||||
|
@ -123,6 +135,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:
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -700,7 +700,7 @@ class ConfigTest(unittest.TestCase):
|
|||
assert "'hard' is a required property" in exc.exconly()
|
||||
|
||||
def test_config_ulimits_soft_greater_than_hard_error(self):
|
||||
expected = "cannot contain a 'soft' value higher than 'hard' value"
|
||||
expected = "'soft' value can not be greater than 'hard' value"
|
||||
|
||||
with pytest.raises(ConfigurationError) as exc:
|
||||
config.load(build_config_details(
|
||||
|
@ -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 = [
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
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
|
||||
from tests import unittest
|
||||
|
||||
|
||||
class SortServiceTest(unittest.TestCase):
|
||||
class TestSortService(object):
|
||||
def test_sort_service_dicts_1(self):
|
||||
services = [
|
||||
{
|
||||
|
@ -23,10 +24,10 @@ class SortServiceTest(unittest.TestCase):
|
|||
]
|
||||
|
||||
sorted_services = sort_service_dicts(services)
|
||||
self.assertEqual(len(sorted_services), 3)
|
||||
self.assertEqual(sorted_services[0]['name'], 'grunt')
|
||||
self.assertEqual(sorted_services[1]['name'], 'redis')
|
||||
self.assertEqual(sorted_services[2]['name'], 'web')
|
||||
assert len(sorted_services) == 3
|
||||
assert sorted_services[0]['name'] == 'grunt'
|
||||
assert sorted_services[1]['name'] == 'redis'
|
||||
assert sorted_services[2]['name'] == 'web'
|
||||
|
||||
def test_sort_service_dicts_2(self):
|
||||
services = [
|
||||
|
@ -44,10 +45,10 @@ class SortServiceTest(unittest.TestCase):
|
|||
]
|
||||
|
||||
sorted_services = sort_service_dicts(services)
|
||||
self.assertEqual(len(sorted_services), 3)
|
||||
self.assertEqual(sorted_services[0]['name'], 'redis')
|
||||
self.assertEqual(sorted_services[1]['name'], 'postgres')
|
||||
self.assertEqual(sorted_services[2]['name'], 'web')
|
||||
assert len(sorted_services) == 3
|
||||
assert sorted_services[0]['name'] == 'redis'
|
||||
assert sorted_services[1]['name'] == 'postgres'
|
||||
assert sorted_services[2]['name'] == 'web'
|
||||
|
||||
def test_sort_service_dicts_3(self):
|
||||
services = [
|
||||
|
@ -65,10 +66,10 @@ class SortServiceTest(unittest.TestCase):
|
|||
]
|
||||
|
||||
sorted_services = sort_service_dicts(services)
|
||||
self.assertEqual(len(sorted_services), 3)
|
||||
self.assertEqual(sorted_services[0]['name'], 'child')
|
||||
self.assertEqual(sorted_services[1]['name'], 'parent')
|
||||
self.assertEqual(sorted_services[2]['name'], 'grandparent')
|
||||
assert len(sorted_services) == 3
|
||||
assert sorted_services[0]['name'] == 'child'
|
||||
assert sorted_services[1]['name'] == 'parent'
|
||||
assert sorted_services[2]['name'] == 'grandparent'
|
||||
|
||||
def test_sort_service_dicts_4(self):
|
||||
services = [
|
||||
|
@ -86,10 +87,10 @@ class SortServiceTest(unittest.TestCase):
|
|||
]
|
||||
|
||||
sorted_services = sort_service_dicts(services)
|
||||
self.assertEqual(len(sorted_services), 3)
|
||||
self.assertEqual(sorted_services[0]['name'], 'child')
|
||||
self.assertEqual(sorted_services[1]['name'], 'parent')
|
||||
self.assertEqual(sorted_services[2]['name'], 'grandparent')
|
||||
assert len(sorted_services) == 3
|
||||
assert sorted_services[0]['name'] == 'child'
|
||||
assert sorted_services[1]['name'] == 'parent'
|
||||
assert sorted_services[2]['name'] == 'grandparent'
|
||||
|
||||
def test_sort_service_dicts_5(self):
|
||||
services = [
|
||||
|
@ -107,10 +108,10 @@ class SortServiceTest(unittest.TestCase):
|
|||
]
|
||||
|
||||
sorted_services = sort_service_dicts(services)
|
||||
self.assertEqual(len(sorted_services), 3)
|
||||
self.assertEqual(sorted_services[0]['name'], 'child')
|
||||
self.assertEqual(sorted_services[1]['name'], 'parent')
|
||||
self.assertEqual(sorted_services[2]['name'], 'grandparent')
|
||||
assert len(sorted_services) == 3
|
||||
assert sorted_services[0]['name'] == 'child'
|
||||
assert sorted_services[1]['name'] == 'parent'
|
||||
assert sorted_services[2]['name'] == 'grandparent'
|
||||
|
||||
def test_sort_service_dicts_6(self):
|
||||
services = [
|
||||
|
@ -128,10 +129,10 @@ class SortServiceTest(unittest.TestCase):
|
|||
]
|
||||
|
||||
sorted_services = sort_service_dicts(services)
|
||||
self.assertEqual(len(sorted_services), 3)
|
||||
self.assertEqual(sorted_services[0]['name'], 'child')
|
||||
self.assertEqual(sorted_services[1]['name'], 'parent')
|
||||
self.assertEqual(sorted_services[2]['name'], 'grandparent')
|
||||
assert len(sorted_services) == 3
|
||||
assert sorted_services[0]['name'] == 'child'
|
||||
assert sorted_services[1]['name'] == 'parent'
|
||||
assert sorted_services[2]['name'] == 'grandparent'
|
||||
|
||||
def test_sort_service_dicts_7(self):
|
||||
services = [
|
||||
|
@ -153,11 +154,11 @@ class SortServiceTest(unittest.TestCase):
|
|||
]
|
||||
|
||||
sorted_services = sort_service_dicts(services)
|
||||
self.assertEqual(len(sorted_services), 4)
|
||||
self.assertEqual(sorted_services[0]['name'], 'one')
|
||||
self.assertEqual(sorted_services[1]['name'], 'two')
|
||||
self.assertEqual(sorted_services[2]['name'], 'three')
|
||||
self.assertEqual(sorted_services[3]['name'], 'four')
|
||||
assert len(sorted_services) == 4
|
||||
assert sorted_services[0]['name'] == 'one'
|
||||
assert sorted_services[1]['name'] == 'two'
|
||||
assert sorted_services[2]['name'] == 'three'
|
||||
assert sorted_services[3]['name'] == 'four'
|
||||
|
||||
def test_sort_service_dicts_circular_imports(self):
|
||||
services = [
|
||||
|
@ -171,13 +172,10 @@ class SortServiceTest(unittest.TestCase):
|
|||
},
|
||||
]
|
||||
|
||||
try:
|
||||
with pytest.raises(DependencyError) as exc:
|
||||
sort_service_dicts(services)
|
||||
except DependencyError as e:
|
||||
self.assertIn('redis', e.msg)
|
||||
self.assertIn('web', e.msg)
|
||||
else:
|
||||
self.fail('Should have thrown an DependencyError')
|
||||
assert 'redis' in exc.exconly()
|
||||
assert 'web' in exc.exconly()
|
||||
|
||||
def test_sort_service_dicts_circular_imports_2(self):
|
||||
services = [
|
||||
|
@ -194,13 +192,10 @@ class SortServiceTest(unittest.TestCase):
|
|||
}
|
||||
]
|
||||
|
||||
try:
|
||||
with pytest.raises(DependencyError) as exc:
|
||||
sort_service_dicts(services)
|
||||
except DependencyError as e:
|
||||
self.assertIn('redis', e.msg)
|
||||
self.assertIn('web', e.msg)
|
||||
else:
|
||||
self.fail('Should have thrown an DependencyError')
|
||||
assert 'redis' in exc.exconly()
|
||||
assert 'web' in exc.exconly()
|
||||
|
||||
def test_sort_service_dicts_circular_imports_3(self):
|
||||
services = [
|
||||
|
@ -218,13 +213,10 @@ class SortServiceTest(unittest.TestCase):
|
|||
}
|
||||
]
|
||||
|
||||
try:
|
||||
with pytest.raises(DependencyError) as exc:
|
||||
sort_service_dicts(services)
|
||||
except DependencyError as e:
|
||||
self.assertIn('a', e.msg)
|
||||
self.assertIn('b', e.msg)
|
||||
else:
|
||||
self.fail('Should have thrown an DependencyError')
|
||||
assert 'a' in exc.exconly()
|
||||
assert 'b' in exc.exconly()
|
||||
|
||||
def test_sort_service_dicts_self_imports(self):
|
||||
services = [
|
||||
|
@ -234,9 +226,18 @@ class SortServiceTest(unittest.TestCase):
|
|||
},
|
||||
]
|
||||
|
||||
try:
|
||||
with pytest.raises(DependencyError) as exc:
|
||||
sort_service_dicts(services)
|
||||
except DependencyError as e:
|
||||
self.assertIn('web', e.msg)
|
||||
else:
|
||||
self.fail('Should have thrown an DependencyError')
|
||||
assert 'web' in exc.exconly()
|
||||
|
||||
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()
|
||||
|
|
Loading…
Reference in New Issue