diff options
author | Steve Salevan <ssalevan@marillion.rdu.redhat.com> | 2008-07-08 12:38:20 -0400 |
---|---|---|
committer | Steve Salevan <ssalevan@marillion.rdu.redhat.com> | 2008-07-08 12:38:20 -0400 |
commit | 8770e4dffb3bf4693e01ec761217956fc9f8d355 (patch) | |
tree | 1d67b95c4d48c7ad85dde5819cd2b57627668aab /func | |
parent | 498ee363f87317ec3649e3ab704933e93e8d4216 (diff) | |
download | func-8770e4dffb3bf4693e01ec761217956fc9f8d355.tar.gz func-8770e4dffb3bf4693e01ec761217956fc9f8d355.tar.xz func-8770e4dffb3bf4693e01ec761217956fc9f8d355.zip |
Adding in tree manipulation tools and minion-side delegation module.
Diffstat (limited to 'func')
-rw-r--r-- | func/minion/modules/delegation.py | 35 | ||||
-rwxr-xr-x | func/overlord/client.py | 31 | ||||
-rw-r--r-- | func/overlord/delegation_tools.py | 155 |
3 files changed, 217 insertions, 4 deletions
diff --git a/func/minion/modules/delegation.py b/func/minion/modules/delegation.py new file mode 100644 index 0000000..7af3284 --- /dev/null +++ b/func/minion/modules/delegation.py @@ -0,0 +1,35 @@ +# Copyright 2008, Red Hat, Inc +# Steve Salevan <ssalevan@redhat.com> +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import func_module +import func.overlord.client as fc +from func import utils + +class DelegationModule(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Minion-side module to support delegation on sub-Overlords." + + def run(self,module,method,args,delegation_path): + """ + Delegates commands down the path of delegation + supplied as an argument + """ + + next_hop = delegation_path[0] + overlord = fc.Overlord(next_hop) + if len(delegation_path) == 1: #minion exists under this overlord + meth = "%s.%s" % (module, method) + return getattr(overlord,meth)(*args[:]) + + stripped_list = delegation_path[1:len(delegation_path)] + delegation_results = overlord.delegation.run(module,method,args,stripped_list) + return delegation_results[next_hop] #strip away nested hash data from results diff --git a/func/overlord/client.py b/func/overlord/client.py index d3e7546..74b3ee4 100755 --- a/func/overlord/client.py +++ b/func/overlord/client.py @@ -25,6 +25,7 @@ import sslclient import command import groups +import delegation_tools as dtools import func.forkbomb as forkbomb import func.jobthing as jobthing import func.utils as utils @@ -89,13 +90,16 @@ class CommandAutomagic(object): class Minions(object): def __init__(self, spec, port=51234, noglobs=None, verbose=None, - just_fqdns=False, groups_file=None): + just_fqdns=False, groups_file=None, + delegate=False, minionmap={}): self.spec = spec self.port = port self.noglobs = noglobs self.verbose = verbose self.just_fqdns = just_fqdns + self.delegate = delegate + self.minionmap = minionmap self.config = read_config(CONFIG_FILE, CMConfig) self.group_class = groups.Groups(filename=groups_file) @@ -160,7 +164,7 @@ class Overlord(object): def __init__(self, server_spec, port=DEFAULT_PORT, interactive=False, verbose=False, noglobs=False, nforks=1, config=None, async=False, init_ssl=True, - delegate=False, mapfile=""): + delegate=True, mapfile="/var/lib/func/inventory/map"): """ Constructor. @server_spec -- something like "*.example.org" or "foosball" @@ -257,7 +261,26 @@ class Overlord(object): # ----------------------------------------------- - def run(self, module, method, args, nforks=1): + def run(self, module, method, args, nforks=1, *extraargs, **kwargs): + """ + Invoke a remote method on one or more servers. + Run returns a hash, the keys are server names, the values are the + returns. + + The returns may include exception objects. + If Overlord() was constructed with noglobs=True, the return is instead + just a single value, not a hash. + """ + + #if not self.delegate: #delegation is turned off + # return self.run_nodelegate(module, method, args, nforks) + #print self.minionmap + return self.run_nodelegate(module,method,args,nforks) + + + # ----------------------------------------------- + + def run_nodelegate(self, module, method, args, nforks=1): """ Invoke a remote method on one or more servers. Run returns a hash, the keys are server names, the values are the @@ -313,7 +336,7 @@ class Overlord(object): right = server.rfind(":") server_name = server[left:right] return (server_name, retval) - + if not self.noglobs: if self.nforks > 1 or self.async: # using forkbomb module to distribute job over multiple threads diff --git a/func/overlord/delegation_tools.py b/func/overlord/delegation_tools.py new file mode 100644 index 0000000..4907c9d --- /dev/null +++ b/func/overlord/delegation_tools.py @@ -0,0 +1,155 @@ +## +## func delegation tools +## These are some helper methods to make dealing with delegation +## dictionary trees a little more sane when dealing with delegation +## and related functions. +## +## Copyright 2008, Red Hat, Inc. +## Steve Salevan <ssalevan@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +import fnmatch + +def get_paths_for_glob(glob, minionmap): + """ + Given a glob, returns shortest path to all minions + matching it in the delegation dictionary tree + """ + + pathlist = [] + for elem in match_glob_in_tree(glob,minionmap): + result = get_shortest_path(elem,minionmap) + if result not in pathlist: #prevents duplicates + pathlist.append(result) + return pathlist + +def flatten_list(bumpy_list): + """ + Flattens gnarly nested lists into much + nicer, flat lists + """ + + flat_list = [] + for item in bumpy_list: + if isinstance(item, list): + for elem in flatten_list(item): + flat_list.append(elem) + else: + flat_list.append(item) + return flat_list + +def match_glob_on_toplevel(pattern, minionmap): + """ + Searches through the top level of a dictionary + for all keys (minion FQDNs) matching the given + glob, returns matches + """ + + matched = [] + for k,v in minionmap.iteritems(): + if fnmatch.fnmatch(k,pattern): + matched.append(k) + return matched + +def match_glob_in_tree(pattern, minionmap): + """ + Searches through given tree dictionary for all + keys (minion FQDNs) matching the given glob, + returns matches + """ + + matched = [] + for k,v in minionmap.iteritems(): + for result in match_glob_in_tree(pattern, v): + matched.append(result) + if fnmatch.fnmatch(k,pattern): + matched.append(k) + return matched + +def minion_exists_under_node(minion, minionmap): + """ + A little wrapper around the match_glob_on_toplevel + method that you can use if you want to get a boolean + result denoting minion existence under your current + node + """ + + return len(match_glob_on_toplevel(minion,minionmap)) > 0 + +def get_shortest_path(minion, minionmap): + """ + Given a minion that exists in the given tree, + this method returns all paths from the top + node to the minion in the form of a flat list + """ + + def lensort(a,b): + if len(a) > len(b): + return 1 + return -1 + + results = get_all_paths(minion,minionmap) + results.sort(lensort) + return results[0] + +def get_all_paths(minion, minionmap): + """ + Given a minion that exists in the given tree, + this method returns all paths that exist from the top + node to the minion in the delegation dictionary tree + """ + + #This is an ugly kludge of franken-code. If someone with + #more knowledge of graph theory than myself can improve this + #module, please, please do so. - ssalevan 7/2/08 + seq_list = [] + + if minion_exists_under_node(minion, minionmap): + return [[minion]] #minion found, terminate branch + + if minionmap == {}: + return [[]] #no minion found, terminate branch + + for k,v in minionmap.iteritems(): + branch_list = [] + branch_list.append(k) + + for branchlet in get_all_paths(minion, v): + branch_list.append(branchlet) + + single_branch = flatten_list(branch_list) + if minion in single_branch: + seq_list.append(single_branch) + + return seq_list + +if __name__ == "__main__": + mymap = {'anthony':{'longpath1':{'longpath2':{'longpath3':{}}}}, + 'phil':{'steve':{'longpath3':{}}}, + 'tony':{'mike':{'anthony':{}}}, + 'just_a_minion':{} + } + + print "- Testing an element that exists in multiple lists of varying length:" + for elem in match_glob_in_tree('*path3',mymap): + print "Element: %s, all paths: %s" % (elem, get_all_paths(elem,mymap)) + print "best path: %s" % get_shortest_path(elem, mymap) + + print "- Testing an element that is simply a minion and has no sub-nodes:" + for elem in match_glob_in_tree('*minion',mymap): + print "Element: %s, best path: %s" % (elem, get_shortest_path(elem,mymap)) + + print "- OK, now the whole thing:" + for elem in match_glob_in_tree('*',mymap): + print "Element: %s, best path: %s" % (elem, get_shortest_path(elem,mymap)) + + print "- And finally, with all duplicates removed:" + for elem in get_paths_for_glob('*',mymap): + print "Valid Path: %s" % elem |