__docformat__ = 'restructuredtext' from category import Category from pattern import Pattern import setcross class StateMachine: """ The state machine contains a list of dependencies between states, and a list of states which are "up." """ def __init__(self): """ Create a new state machine """ self.up = set() self.deps = [] def bring_up(self, cat): """ Move states in the given Category `cat` from down to up. """ found = None for (match, dependency) in self.get_applicable_deps(cat): res = self.get_satisfied_states(match, dependency) if len(res) == 0: return False if found == None: found = [res] else: found.append(res) if found == None: self.add_hold(cat) return True to_add = self.cat_cross(found) for x in to_add: self.add_hold(x) return True def cat_cross(self, found): """ Given a list of sets, where each set contains Category objects, return a set of all categories that can be made by intersecting one element from each set. """ to_add = set() for tup in setcross.cross(*found): orig = tup while len(tup) > 1: newtup = (tup[0].intersect(tup[1]),) if newtup[0] == None: tup = () break tup = newtup + tup[2:len(tup)] if len(tup) == 0 or tup[0] == None: continue to_add.add(tup[0]) return to_add def add_hold(self, cat): """ Add a hold to a state. Does not check dependencies. """ for x in self.up: if cat.subset_of(x): return self.up.add(cat) def bring_down(self, cat): """ Bring a currently "up" state down. """ to_drop = set([ x for x in self.up if x.subset_of(cat) ]) if None in to_drop: to_drop.remove(None) if len(to_drop) == 0: return False for (dependent, dependency) in self.deps: match = set([dependency.intersect(x) for x in to_drop]) if None in match: match.remove(None) if match != None: for item in match: self.bring_down(dependent.fill(item.args)) self.up -= to_drop def get_satisfied_states(self, dependents, dependencies): """ Given that states in `dependents` depend on states in `dependencies`, return a new Category that contains only the states in `dependents` that could match states in `dependencies`. """ retval = [] for cat in self.up: if dependencies.superset_of(cat): retval.append(dependents.fill(cat.args)) return set(retval) | dependents.inverse_set() def get_applicable_deps(self, cat): """ Find dependencies that might apply to members of `cat` """ retval = [] for (x, y) in self.deps: un = cat.intersect(x) if un != None: retval.append((un, y.fill(un.args))) return retval def __str__(self): return "\n".join(["%s" % k for k in self.up]) def __repr__(self): return str(self) def m(*args): return Pattern(True, *args) # The "m" reads "match", so m("foo", "bar") reads 'match foo and bar' def nm(*args): return Pattern(False, *args) # Reads as "don't match." See above def any(): return nm() # Match anything. Implementation reads "don't match nothing" if __name__ == "__main__": sm = StateMachine() sm.deps.append((Category("mounted", type=m("nfs")), Category("network_up"))) sm.deps.append((Category("mounted", uuid=any(), devname=any(), label=any(), type=nm("nfs")), Category("found_disk", uuid=any(), devname=any(), label=any()))) sm.deps.append((Category("mounted", uuid=any(), devname=any(), label=any()), Category("vol_conf", uuid=any(), devname=any(), label=any()))) sm.bring_up(Category("network_up")) sm.bring_up(Category("vol_conf", uuid=any(), devname=any(), label=m("myroot"), type=m("ext3"), mountpoint=m("/"))) sm.bring_up(Category("vol_conf", uuid=any(), devname=m("foosrv.com:/vol/home"), label=any(), type=m("nfs"), mountpoint=m("/home"))) sm.bring_up(Category("vol_conf", uuid=any(), devname=m("foosrv.com:/vol/beefs"), label=any(), type=m("nfs"), mountpoint=m("/beefs"))) sm.bring_up(Category("found_disk", uuid=m("d3adb3ef"), devname=m("/dev/sda"), label=m("myroot"))) sm.bring_up(Category("mounted", uuid=any(), type=any(), devname=any(), label=any(), mountpoint=any())) print sm print "--" sm.bring_down(Category("network_up")) print sm