From 8e1ee8d111b0ec5d7bf6b9e8f1dda833e35d30f2 Mon Sep 17 00:00:00 2001 From: Casey Dahlin Date: Sun, 21 Dec 2008 22:19:24 -0500 Subject: Reorganize code into separate files --- category.py | 102 +++++++++++++++++++++++++++++ state.py | 198 -------------------------------------------------------- statemachine.py | 99 ++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 198 deletions(-) create mode 100644 category.py delete mode 100644 state.py create mode 100644 statemachine.py diff --git a/category.py b/category.py new file mode 100644 index 0000000..51a36b8 --- /dev/null +++ b/category.py @@ -0,0 +1,102 @@ +__docformat__ = 'restructuredtext' + +class Category: + """ + A Category is a "set" of states. The category specifies a name, certain + arguments that must be provided, and constraints for the values of those + arguments. Any possible state which meets these restrictions is in the + category. + """ + + def __init__(self, name, **args): + """ + Categories are constructed with a `name` and a series of keyword + arguments. `name` is a string. Each keyword argument that is specified + is the name of a state argument a state must posess to be considered to + be a member of the category. If the value of the keyword argument is + `None` then the state argument can have any value, otherwise the value is a string + and specifies a mandatory value for the state argument. + """ + self.args = args + self.name = name + + def equiv(self, other): + """ + Returns True if any state in the Category `other` is in this Category. Note that + this is *intransitive*; ``a.equiv(b)`` is not the same as ``b.equiv(a)``. + """ + if self.name != other.name: return False + for key, value in self.args.iteritems(): + if not other.args.has_key(key): return False + if value == None: continue + if other.args[key] == None: continue + if not other.args[key] == value: return False + return True + + def intersect(self, other): + """ + Returns a new category consisting only of states that are in this + category, and the category `other`. + """ + if self.name != other.name: return None + args = {} + for key in list(set(self.args.keys() + other.args.keys())): + if not self.args.has_key(key): + args[key] = other.args[key] + elif not other.args.has_key(key): + args[key] = self.args[key] + elif self.args[key] == None: + args[key] = other.args[key] + elif other.args[key] == None: + args[key] = self.args[key] + elif self.args[key] != other.args[key]: + return None + else: # self.args[key] == other.args[key] + args[key] = self.args[key] + return Category(self.name, **args) + + def __str__(self): + string = self.name + "(" + had = False + for key, val in self.args.iteritems(): + if had: + string += ", " + had = True + if val == None: + string += "%%%s" % key + else: + string += "%s: %s" % (key, val) + return string + ")" + + def __repr__(self): + return self.__str__() + + def __hash__(self): + return hash((self.argstup(), self.name)) + + def __eq__(self, other): + return self.name == other.name and self.argstup() == other.argstup() + + def argstup(self): + """ + Returns arguments as a sorted tuple. + """ + retval = [] + keys = self.args.keys() + keys.sort() + for key in keys: + retval.append((key, self.args[key])) + return tuple(retval) + + def fill(self, info): + """ + Set any arguments matched by this category that don't have explicitly + required values to require the values in `info`. + """ + args = {} + for key, value in self.args.iteritems(): + if value != None or not info.has_key(key): + args[key] = value + else: + args[key] = info[key] + return Category(self.name, **args) diff --git a/state.py b/state.py deleted file mode 100644 index f78fa0a..0000000 --- a/state.py +++ /dev/null @@ -1,198 +0,0 @@ -__docformat__ = 'restructuredtext' - -class Category: - """ - A Category is a "set" of states. The category specifies a name, certain - arguments that must be provided, and constraints for the values of those - arguments. Any possible state which meets these restrictions is in the - category. - """ - - def __init__(self, name, **args): - """ - Categories are constructed with a `name` and a series of keyword - arguments. `name` is a string. Each keyword argument that is specified - is the name of a state argument a state must posess to be considered to - be a member of the category. If the value of the keyword argument is - `None` then the state argument can have any value, otherwise the value is a string - and specifies a mandatory value for the state argument. - """ - self.args = args - self.name = name - - def equiv(self, other): - """ - Returns True if any state in the Category `other` is in this Category. Note that - this is *intransitive*; ``a.equiv(b)`` is not the same as ``b.equiv(a)``. - """ - if self.name != other.name: return False - for key, value in self.args.iteritems(): - if not other.args.has_key(key): return False - if value == None: continue - if other.args[key] == None: continue - if not other.args[key] == value: return False - return True - - def intersect(self, other): - """ - Returns a new category consisting only of states that are in this - category, and the category `other`. - """ - if self.name != other.name: return None - args = {} - for key in list(set(self.args.keys() + other.args.keys())): - if not self.args.has_key(key): - args[key] = other.args[key] - elif not other.args.has_key(key): - args[key] = self.args[key] - elif self.args[key] == None: - args[key] = other.args[key] - elif other.args[key] == None: - args[key] = self.args[key] - elif self.args[key] != other.args[key]: - return None - else: # self.args[key] == other.args[key] - args[key] = self.args[key] - return Category(self.name, **args) - - def __str__(self): - string = self.name + "(" - had = False - for key, val in self.args.iteritems(): - if had: - string += ", " - had = True - if val == None: - string += "%%%s" % key - else: - string += "%s: %s" % (key, val) - return string + ")" - - def __repr__(self): - return self.__str__() - - def __hash__(self): - return hash((self.argstup(), self.name)) - - def __eq__(self, other): - return self.name == other.name and self.argstup() == other.argstup() - - def argstup(self): - """ - Returns arguments as a sorted tuple. - """ - retval = [] - keys = self.args.keys() - keys.sort() - for key in keys: - retval.append((key, self.args[key])) - return tuple(retval) - - def fill(self, info): - """ - Set any arguments matched by this category that don't have explicitly - required values to require the values in `info`. - """ - args = {} - for key, value in self.args.iteritems(): - if value != None or not info.has_key(key): - args[key] = value - else: - args[key] = info[key] - return Category(self.name, **args) - -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.holds = {} - self.deps = [] - - def assert_state(self, cat): - """ - Move states in the given Category `cat` from down to up. - """ - found = None - for dependency in self.get_applicable_deps(cat): - res = self.get_satisfied_states(cat, dependency) - if len(res) == 0: - return False - if found == None: - found = res - else: - found = self.intersect_list(found, res) - if found == None: - self.add_hold(cat) - return True - if len(found) == 0: - return False - for x in found: - self.add_hold(x) - return True - - def intersect_list(self, cats1, cats2): - """ - Given two lists of categories, return a list of categories such that a - state appearing in at least one category in each list will appear in at - least one category in the returned list. - """ - retval = set() - found = set() - for x in cats1: - for y in cats2: - if x == y: continue - inter = x.intersect(y) - if inter != None: - retval.add(inter) - found.add(x) - found.add(y) - if len(found) == 0: - return cats1 & cats2 - return (retval | ((cats1 & cats2) - found)) - - def add_hold(self, cat): - """ - Add a hold to a state. Does not check dependencies. - """ - if self.holds.has_key(cat): - self.holds[cat] = self.holds[cat] + 1 - else: - self.holds[cat] = 1 - - 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 key, val in self.holds.iteritems(): - if dependencies.equiv(key) and val > 0: - retval.append(dependents.fill(key.args)) - return set(retval) - - def get_applicable_deps(self, cat): - """ - Find dependencies that might apply to members of `cat` - """ - retval = [] - for (x, y) in self.deps: - if x.equiv(cat): - retval.append(y.fill(cat.intersect(x).args)) - return retval - -if __name__ == "__main__": - sm = StateMachine() - sm.deps.append((Category("mounted", type="nfs"), Category("network_up"))) - sm.deps.append((Category("mounted", uuid=None, devname=None, label=None), Category("found_disk", uuid=None, devname=None, label=None))) - sm.deps.append((Category("mounted", uuid=None, devname=None, label=None), Category("vol_conf", uuid=None, devname=None, label=None))) - sm.assert_state(Category("vol_conf", uuid=None, devname=None, label="myroot", type="ext3", mountpoint="/")) - sm.assert_state(Category("found_disk", uuid="d3adb3ef", devname="/dev/sda", label="myroot")) - sm.assert_state(Category("mounted", uuid=None, type="ext3", devname=None, label=None, mountpoint=None)) - print sm.holds diff --git a/statemachine.py b/statemachine.py new file mode 100644 index 0000000..5c396eb --- /dev/null +++ b/statemachine.py @@ -0,0 +1,99 @@ +__docformat__ = 'restructuredtext' + +from category import Category + +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.holds = {} + self.deps = [] + + def assert_state(self, cat): + """ + Move states in the given Category `cat` from down to up. + """ + found = None + for dependency in self.get_applicable_deps(cat): + res = self.get_satisfied_states(cat, dependency) + if len(res) == 0: + return False + if found == None: + found = res + else: + found = self.intersect_list(found, res) + if found == None: + self.add_hold(cat) + return True + if len(found) == 0: + return False + for x in found: + self.add_hold(x) + return True + + def intersect_list(self, cats1, cats2): + """ + Given two lists of categories, return a list of categories such that a + state appearing in at least one category in each list will appear in at + least one category in the returned list. + """ + retval = set() + found = set() + for x in cats1: + for y in cats2: + if x == y: continue + inter = x.intersect(y) + if inter != None: + retval.add(inter) + found.add(x) + found.add(y) + if len(found) == 0: + return cats1 & cats2 + return (retval | ((cats1 & cats2) - found)) + + def add_hold(self, cat): + """ + Add a hold to a state. Does not check dependencies. + """ + if self.holds.has_key(cat): + self.holds[cat] = self.holds[cat] + 1 + else: + self.holds[cat] = 1 + + 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 key, val in self.holds.iteritems(): + if dependencies.equiv(key) and val > 0: + retval.append(dependents.fill(key.args)) + return set(retval) + + def get_applicable_deps(self, cat): + """ + Find dependencies that might apply to members of `cat` + """ + retval = [] + for (x, y) in self.deps: + if x.equiv(cat): + retval.append(y.fill(cat.intersect(x).args)) + return retval + +if __name__ == "__main__": + sm = StateMachine() + sm.deps.append((Category("mounted", type="nfs"), Category("network_up"))) + sm.deps.append((Category("mounted", uuid=None, devname=None, label=None), Category("found_disk", uuid=None, devname=None, label=None))) + sm.deps.append((Category("mounted", uuid=None, devname=None, label=None), Category("vol_conf", uuid=None, devname=None, label=None))) + sm.assert_state(Category("vol_conf", uuid=None, devname=None, label="myroot", type="ext3", mountpoint="/")) + sm.assert_state(Category("found_disk", uuid="d3adb3ef", devname="/dev/sda", label="myroot")) + sm.assert_state(Category("mounted", uuid=None, type="ext3", devname=None, label=None, mountpoint=None)) + print sm.holds -- cgit