From 259f2dc4dee517f95b72b001eecc18b37b5e6724 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sat, 11 Aug 2012 11:36:59 -0400 Subject: [PATCH] Various retooling of the internal code behind inventory pattern matching in support of pending support for host ranges. --- lib/ansible/inventory/__init__.py | 112 ++++++++++++++++++++++++------ test/TestInventory.py | 23 ++---- 2 files changed, 94 insertions(+), 41 deletions(-) diff --git a/lib/ansible/inventory/__init__.py b/lib/ansible/inventory/__init__.py index 47f01dedc86..76b29b23d2e 100644 --- a/lib/ansible/inventory/__init__.py +++ b/lib/ansible/inventory/__init__.py @@ -89,31 +89,99 @@ class Inventory(object): def _match(self, str, pattern_str): return fnmatch.fnmatch(str, pattern_str) - # TODO: cache this logic so if called a second time the result is not recalculated def get_hosts(self, pattern="all"): - """ Get all host objects matching the pattern """ - hosts = {} + """ + find all host names matching a pattern string, taking into account any inventory restrictions or + applied subsets. + """ + + # process patterns patterns = pattern.replace(";",":").split(":") + positive_patterns = [ p for p in patterns if not p.startswith("!") ] + negative_patterns = [ p for p in patterns if p.startswith("!") ] + + # find hosts matching positive patterns + hosts = self._get_hosts(positive_patterns) + + # exclude hosts mentioned in a negative pattern + if len(negative_patterns): + exclude_hosts = self._get_hosts(negative_patterns) + print "EXCLUDING HOSTS: %s" % exclude_hosts + hosts = [ h for h in hosts if h not in exclude_hosts ] + + # exclude hosts not in a subset, if defined + if self._subset: + positive_subsetp = [ p for p in self._subset if not p.startswith("!") ] + negative_subsetp = [ p for p in self._subset if p.startswith("!") ] + if len(positive_subsetp): + positive_subset = self._get_hosts(positive_subsetp) + hosts = [ h for h in hosts if (h in positive_subset) ] + if len(negative_subsetp): + negative_subset = self._get_hosts(negative_subsetp) + hosts = [ h for h in hosts if (h not in negative_subset)] + + # exclude hosts mentioned in any restriction (ex: failed hosts) + if self._restriction is not None: + hosts = [ h for h in hosts if h.name in self._restriction ] + + return hosts + + def _get_hosts(self, patterns): + """ + finds hosts that postively match a particular list of patterns. Does not + take into account negative matches. + """ + + by_pattern = {} + for p in patterns: + (name, enumeration_details) = self._enumeration_info(p) + by_pattern[p] = self._hosts_in_unenumerated_pattern(name) + + ranged = {} + for (pat, hosts) in by_pattern.iteritems(): + ranged[pat] = self._apply_ranges(pat, hosts) + + results = [] + for (pat, hosts) in ranged.iteritems(): + results.extend(hosts) + + return list(set(results)) + + def _enumeration_info(self, pattern): + """ + returns (pattern, limits) taking a regular pattern and finding out + which parts of it correspond to start/stop offsets. limits is + a tuple of (start, stop) or None + """ + + if not "[" in pattern: + return (pattern, None) + (first, rest) = pattern.split("[") + rest.replace("]","") + if not "-" in rest: + raise errors.AnsibleError("invalid pattern: %s" % pattern) + (left, right) = rest.split("-",1) + return (first, (left, right)) + + def _apply_ranges(self, pat, hosts): + (loose_pattern, limits) = self._enumeration_info(pat) + if not limits: + return hosts + raise Exception("ranges are not yet supported") + + # TODO: cache this logic so if called a second time the result is not recalculated + def _hosts_in_unenumerated_pattern(self, pattern): + """ Get all host names matching the pattern """ + + hosts = {} + # ignore any negative checks here, this is handled elsewhere + pattern = pattern.replace("!","") groups = self.get_groups() - for pat in patterns: - if pat.startswith("!"): - pat = pat[1:] - inverted = True - else: - inverted = False - for group in groups: - for host in group.get_hosts(): - if self._subset and host.name not in self._subset: - continue - if pat == 'all' or self._match(group.name, pat) or self._match(host.name, pat): - # must test explicitly for None because [] means no hosts allowed - if self._restriction==None or host.name in self._restriction: - if inverted: - if host.name in hosts: - del hosts[host.name] - else: - hosts[host.name] = host + for group in groups: + for host in group.get_hosts(): + if pattern == 'all' or self._match(group.name, pattern) or self._match(host.name, pattern): + hosts[host.name] = host return sorted(hosts.values(), key=lambda x: x.name) def get_groups(self): @@ -210,7 +278,7 @@ class Inventory(object): if subset_pattern is None: self._subset = None else: - self._subset = self.list_hosts(subset_pattern) + self._subset = subset_pattern.replace(";",":").split(":") def lift_restriction(self): """ Do not restrict list operations """ diff --git a/test/TestInventory.py b/test/TestInventory.py index 8ce9e979f90..89917f37cef 100644 --- a/test/TestInventory.py +++ b/test/TestInventory.py @@ -111,21 +111,6 @@ class TestInventory(unittest.TestCase): print expected_hosts assert sorted(hosts) == sorted(expected_hosts) - def test_simple_exclude(self): - inventory = self.simple_inventory() - - hosts = inventory.list_hosts("all:!greek") - expected_hosts=['jupiter', 'saturn', 'thor', 'odin', 'loki', - 'thrudgelmir0', 'thrudgelmir1', 'thrudgelmir2', - 'thrudgelmir3', 'thrudgelmir4', 'thrudgelmir5'] - assert sorted(hosts) == sorted(expected_hosts) - - hosts = inventory.list_hosts("all:!norse:!greek") - expected_hosts=['jupiter', 'saturn', - 'thrudgelmir0', 'thrudgelmir1', 'thrudgelmir2', - 'thrudgelmir3', 'thrudgelmir4', 'thrudgelmir5'] - assert sorted(hosts) == sorted(expected_hosts) - def test_simple_vars(self): inventory = self.simple_inventory() vars = inventory.get_variables('thor') @@ -168,12 +153,12 @@ class TestInventory(unittest.TestCase): def test_complex_exclude(self): inventory = self.complex_inventory() - hosts = inventory.list_hosts("nc:!triangle:florida:!orlando") - expected_hosts=['rtp_a', 'rtp_b', 'rtb_c', 'miami'] + hosts = inventory.list_hosts("nc:florida:!triangle:!orlando") + expected_hosts = ['miami', 'rtb_c', 'rtp_a', 'rtp_b'] + print "HOSTS=%s" % sorted(hosts) + print "EXPECTED=%s" % sorted(expected_hosts) assert sorted(hosts) == sorted(expected_hosts) - - ################################################### ### Inventory API tests