Various retooling of the internal code behind inventory pattern matching in support of pending support

for host ranges.
This commit is contained in:
Michael DeHaan 2012-08-11 11:36:59 -04:00
parent 1c81ddf8d4
commit 259f2dc4de
2 changed files with 94 additions and 41 deletions

View file

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

View file

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