Skip to content

Commit

Permalink
support deterministic host ordering from group ancestors (ansible#44067)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlanCoding authored and bcoca committed Feb 20, 2019
1 parent 4b37974 commit 62feba3
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 14 deletions.
35 changes: 21 additions & 14 deletions lib/ansible/inventory/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def deserialize(self, data):
g.deserialize(parent_data)
self.parent_groups.append(g)

def _walk_relationship(self, rel):
def _walk_relationship(self, rel, include_self=False, preserve_ordering=False):
'''
Given `rel` that is an iterable property of Group,
consitituting a directed acyclic graph among all groups,
Expand All @@ -98,21 +98,34 @@ def _walk_relationship(self, rel):
'''
seen = set([])
unprocessed = set(getattr(self, rel))
if include_self:
unprocessed.add(self)
if preserve_ordering:
ordered = [self] if include_self else []
ordered.extend(getattr(self, rel))

while unprocessed:
seen.update(unprocessed)
unprocessed = set(chain.from_iterable(
getattr(g, rel) for g in unprocessed
))
unprocessed.difference_update(seen)
new_unprocessed = set([])

for new_item in chain.from_iterable(getattr(g, rel) for g in unprocessed):
new_unprocessed.add(new_item)
if preserve_ordering:
if new_item not in seen:
ordered.append(new_item)

new_unprocessed.difference_update(seen)
unprocessed = new_unprocessed

if preserve_ordering:
return ordered
return seen

def get_ancestors(self):
return self._walk_relationship('parent_groups')

def get_descendants(self):
return self._walk_relationship('child_groups')
def get_descendants(self, **kwargs):
return self._walk_relationship('child_groups', **kwargs)

@property
def host_names(self):
Expand Down Expand Up @@ -215,20 +228,14 @@ def _get_hosts(self):

hosts = []
seen = {}
for kid in self.get_descendants():
for kid in self.get_descendants(include_self=True, preserve_ordering=True):
kid_hosts = kid.hosts
for kk in kid_hosts:
if kk not in seen:
seen[kk] = 1
if self.name == 'all' and kk.implicit:
continue
hosts.append(kk)
for mine in self.hosts:
if mine not in seen:
seen[mine] = 1
if self.name == 'all' and mine.implicit:
continue
hosts.append(mine)
return hosts

def get_vars(self):
Expand Down
27 changes: 27 additions & 0 deletions test/units/plugins/inventory/test_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,33 @@ def test_loop_detection(self):
with self.assertRaises(AnsibleError):
C.add_child_group(A)

def test_direct_host_ordering(self):
"""Hosts are returned in order they are added
"""
group = Group('A')
# host names not added in alphabetical order
host_name_list = ['z', 'b', 'c', 'a', 'p', 'q']
expected_hosts = []
for host_name in host_name_list:
h = Host(host_name)
group.add_host(h)
expected_hosts.append(h)
assert group.get_hosts() == expected_hosts

def test_sub_group_host_ordering(self):
"""With multiple nested groups, asserts that hosts are returned
in deterministic order
"""
top_group = Group('A')
expected_hosts = []
for name in ['z', 'b', 'c', 'a', 'p', 'q']:
child = Group('group_{0}'.format(name))
top_group.add_child_group(child)
host = Host('host_{0}'.format(name))
child.add_host(host)
expected_hosts.append(host)
assert top_group.get_hosts() == expected_hosts

def test_populates_descendant_hosts(self):
A = Group('A')
B = Group('B')
Expand Down

0 comments on commit 62feba3

Please sign in to comment.