summaryrefslogtreecommitdiffstats
path: root/func/overlord/delegation_tools.py
blob: 0f3b43eba4f8f9c2979d6acb402a4f0b1882814f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
##
## 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

class groupby(object):
    """
    Borrowing the groupby iterator class directly 
    from the Python API as it does not exist in Pythons < 2.4
    """
    
    def __init__(self, iterable, key=None):
        if key is None:
            key = lambda x: x
        self.keyfunc = key
        self.it = iter(iterable)
        self.tgtkey = self.currkey = self.currvalue = xrange(0)
    def __iter__(self):
        return self
    def next(self): 
        while self.currkey == self.tgtkey:
            self.currvalue = self.it.next() # Exit on StopIteration
            self.currkey = self.keyfunc(self.currvalue)
        self.tgtkey = self.currkey
        return (self.currkey, self._grouper(self.tgtkey))
    def _grouper(self, tgtkey):
        while self.currkey == tgtkey:
            yield self.currvalue
            self.currvalue = self.it.next() # Exit on StopIteration
            self.currkey = self.keyfunc(self.currvalue)

def group_paths(ungrouped_list):
    """
    Given a list of multi-element path lists, 
    groups them together into a list of single-element paths (which
    exist directly under the current overlord) and a dictionary of paths
    to send to next hops in the delegation chain, containing a list of lists
    keyed by their common next hop.
    """
    
    single_paths = [path[0] for path in ungrouped_list if len(path) == 1]
    non_single_paths = [path for path in ungrouped_list if len(path) > 1]
    path_group = dict([(key,[path[1:len(path)] for path in list(gen)]) 
                       for key, gen in groupby(non_single_paths,
                                               key=lambda x:x[0])])
    
    return (single_paths,path_group)
                                          
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 list_all_minions(minionmap):
    """
    Given a minion map, returns a flat list of all minions
    contained within it
    """
    minionlist = []
    for minion in minionmap.keys():
        if minion not in minionlist:
            minionlist.append(minion)
        for minion in list_all_minions(minionmap[minion]):
            if minion not in minionlist:
                minionlist.append(minion)
    return minionlist

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
        
    print "- And grouped together:"
    print group_paths(get_paths_for_glob('*',mymap))