Merge pull request #2708 from dnephin/implement_depends_on

Implement depends_on
This commit is contained in:
Aanand Prasad 2016-01-20 17:46:10 +00:00
commit e35bf47a7f
7 changed files with 124 additions and 73 deletions

View File

@ -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(

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

@ -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:

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

@ -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 = [

View File

@ -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()