mirror of https://github.com/docker/compose.git
Implement topological sort using Cormen/Tarjan algorithm to handle a->b->c dependencies and detect a->b->c->a cycles.
This commit is contained in:
parent
7faba11245
commit
6431d52a2e
|
@ -7,31 +7,30 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def sort_service_dicts(services):
|
def sort_service_dicts(services):
|
||||||
# Get all services that are dependant on another.
|
# Topological sort (Cormen/Tarjan algorithm).
|
||||||
dependent_services = [s for s in services if s.get('links')]
|
unmarked = services[:]
|
||||||
flatten_links = sum([s['links'] for s in dependent_services], [])
|
temporary_marked = set()
|
||||||
# Get all services that are not linked to and don't link to others.
|
|
||||||
non_dependent_sevices = [s for s in services if s['name'] not in flatten_links and not s.get('links')]
|
|
||||||
sorted_services = []
|
sorted_services = []
|
||||||
# Topological sort.
|
|
||||||
while dependent_services:
|
def visit(n):
|
||||||
n = dependent_services.pop()
|
if n['name'] in temporary_marked:
|
||||||
# Check if a service is dependent on itself, if so raise an error.
|
|
||||||
if n['name'] in n.get('links', []):
|
if n['name'] in n.get('links', []):
|
||||||
raise DependencyError('A service can not link to itself: %s' % n['name'])
|
raise DependencyError('A service can not link to itself: %s' % n['name'])
|
||||||
sorted_services.append(n)
|
else:
|
||||||
for l in n['links']:
|
raise DependencyError('Circular import between %s' % ' and '.join(temporary_marked))
|
||||||
# Get the linked service.
|
if n in unmarked:
|
||||||
linked_service = next(s for s in services if l == s['name'])
|
temporary_marked.add(n['name'])
|
||||||
# Check that there isn't a circular import between services.
|
dependents = [m for m in services if n['name'] in m.get('links', [])]
|
||||||
if n['name'] in linked_service.get('links', []):
|
for m in dependents:
|
||||||
raise DependencyError('Circular import between %s and %s' % (n['name'], linked_service['name']))
|
visit(m)
|
||||||
# Check the linked service has no links and is not already in the
|
temporary_marked.remove(n['name'])
|
||||||
# sorted service list.
|
unmarked.remove(n)
|
||||||
if not linked_service.get('links') and linked_service not in sorted_services:
|
sorted_services.insert(0, n)
|
||||||
sorted_services.insert(0, linked_service)
|
|
||||||
return non_dependent_sevices + sorted_services
|
|
||||||
|
|
||||||
|
while unmarked:
|
||||||
|
visit(unmarked[-1])
|
||||||
|
|
||||||
|
return sorted_services
|
||||||
|
|
||||||
class Project(object):
|
class Project(object):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -44,6 +44,27 @@ class SortServiceTest(unittest.TestCase):
|
||||||
self.assertEqual(sorted_services[1]['name'], 'postgres')
|
self.assertEqual(sorted_services[1]['name'], 'postgres')
|
||||||
self.assertEqual(sorted_services[2]['name'], 'web')
|
self.assertEqual(sorted_services[2]['name'], 'web')
|
||||||
|
|
||||||
|
def test_sort_service_dicts_3(self):
|
||||||
|
services = [
|
||||||
|
{
|
||||||
|
'name': 'child'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'parent',
|
||||||
|
'links': ['child']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'links': ['parent'],
|
||||||
|
'name': 'grandparent'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
def test_sort_service_dicts_circular_imports(self):
|
def test_sort_service_dicts_circular_imports(self):
|
||||||
services = [
|
services = [
|
||||||
{
|
{
|
||||||
|
@ -87,6 +108,30 @@ class SortServiceTest(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
self.fail('Should have thrown an DependencyError')
|
self.fail('Should have thrown an DependencyError')
|
||||||
|
|
||||||
|
def test_sort_service_dicts_circular_imports_3(self):
|
||||||
|
services = [
|
||||||
|
{
|
||||||
|
'links': ['b'],
|
||||||
|
'name': 'a'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'b',
|
||||||
|
'links': ['c']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'c',
|
||||||
|
'links': ['a']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
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')
|
||||||
|
|
||||||
def test_sort_service_dicts_self_imports(self):
|
def test_sort_service_dicts_self_imports(self):
|
||||||
services = [
|
services = [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue