Source code for paradrop.backend.exc.plangraph

###################################################################
# Copyright 2013-2015 All Rights Reserved
# Authors: The Paradrop Team
###################################################################


###############################################################################
# PRIORITYFLAGS: Define the priority numbers as constants here
###############################################################################

###############################################################################
# Security Checks
STRUCT_SECURITY_CHECK           = 1
TRAFFIC_SECURITY_CHECK          = 2
RESOURCE_SECURITY_CHECK         = 3
FILES_SECURITY_CHECK            = 4
RUNTIME_SECURITY_CHECK          = 5

###############################################################################
# Configuration Generation
NAME_RESV_PART                  = 9
STRUCT_GET_SYSTEM_DEVICES       = 10
STRUCT_GET_INT_NETWORK          = 11
STRUCT_GET_OS_NETWORK           = 12
STRUCT_GET_OS_WIRELESS          = 13
STRUCT_GET_VIRT_NETWORK         = 14
RESOURCE_GET_VIRT_CPU           = 15
RESOURCE_GET_VIRT_MEM           = 16
RESOURCE_GET_OS_CONFIG          = 17
TRAFFIC_GET_OS_FIREWALL         = 18
TRAFFIC_GET_DEVELOPER_FIREWALL  = 19
RUNTIME_GET_VIRT_PREAMBLE       = 20
RUNTIME_GET_VIRT_DHCP           = 21
DHCP_GET_VIRT_RULES             = 22

###############################################################################
# Write Configuration Data
NAME_SETUP_DIRS                 = 50
STRUCT_SET_SYSTEM_DEVICES       = 51
STRUCT_SET_OS_WIRELESS          = 52
STRUCT_SET_OS_NETWORK           = 53
TRAFFIC_SET_OS_FIREWALL         = 54
RUNTIME_SET_VIRT_SCRIPT         = 56
RESOURCE_SET_VIRT_QOS           = 57
NAME_MNT_PART                   = 58
STATE_SET_VIRT_CONFIG           = 59
STATE_SET_VIRT_SCRIPT           = 60
DHCP_SET_VIRT_RULES             = 62
RUNTIME_SET_VIRT_DHCP           = 61
RUNTIME_RELOAD_CONFIG           = 63

###############################################################################
# Operations On Configuration Changes
STATE_CALL_CFG                  = 77
FILES_COPY_FROM_OS              = 78
FILES_FETCH                     = 79
STATE_FILES_STOP                = 80
FILES_COPY_USER                 = 81
FILES_COPY_TO_MNT               = 82
STARTUP_CALL_EARLY_START        = 83
STATE_SAVE_CFG                  = 84
STRUCT_RELOAD_NETWORK           = 85
STRUCT_RELOAD_WIFI              = 86
TRAFFIC_RELOAD_FIREWALL         = 87
RESOURCE_RELOAD_QOS             = 88
DHCP_RELOAD                     = 89
STATE_MAKE_EXEC                 = 90

###############################################################################
# Manipulate Chute
STATE_CALL_STOP                 = 91
STATE_CALL_UNFREEZE             = 92
STATE_CALL_START                = 93
STATE_CALL_FREEZE               = 94
STATE_FILES_START               = 96

COAP_CHANGE_PROCESSES           = 100


[docs]class Plan: """ Helper class to hold onto the actual plan data associated with each plan """ def __init__(self, func, *args): """ Takes a tuple of (function, (args)) of actions to perform at some point. """ self.func = func self.args = args def __repr__(self): return "<%r>" % (self.func) def __eq__(self, o): """Implementing equal function so we can do 'if a in b' logic""" if(not self.func is o.func): return False if(self.args != o.args): return False return True
[docs]class PlanMap: """ This class helps build a dependency graph required to determine what steps are required to update a Chute from a previous version of its configuration. """ def __init__(self, name): """Hold onto a name object so we know what we are referencing with this PlanMap.""" self.name = name # Hold onto plans here self.plans = [] self.planPtr = 0 self.abtPtr = None self.abtPlans = [] self.skipFunctions = []
[docs] def addPlans(self, priority, todoPlan, abortPlan=[]): """ Adds new Plan objects into the list of plans for this PlanMap. Arguments: @priority : The priority number (1 is done first, 99 done last - see PRIORITYFLAGS section at top of this file) @todoPlan : A tuple of (function, (args)), this is the function that completes the actual task requested the args can either be a single variable, a tuple of variables, or None. @abortPlan : A tuple of (function, (args)) or None. This is what should be called if a plan somewhere in the chain fails and we need to undo the work we did here - this function is only called if a higher priority function fails (ie we were called, then something later on fails that would cause us to undo everything we did to setup/change the Chute). """ # They can provide multiple abortPlans in a list, or 1 in a tuple, or none at all empty list # But internally the abortPlan should be either None or a list (even if the list is size 1) if(isinstance(abortPlan, tuple)): abortP = [Plan(*abortPlan)] elif(isinstance(abortPlan, list)): # See if there is no plans at all if(len(abortPlan) == 0): abortP = None else: abortP = [Plan(*a) for a in abortPlan] else: raise Exception('BadAbortPlan', 'Plan should be tuple, list of tuples, or empty') todoP = Plan(*todoPlan) # Now add into the set self.plans.append((priority, todoP, abortP))
[docs] def addMap(self, other): """ Takes another PlanMap object and appends whatever the plans are into this plans object. """ # Make sure to extend NOT append these new plans! self.plans.extend(other.plans)
[docs] def sort(self): """ Sorts the plans based on priority. """ # Sort by the Priority (first index in tuple) self.plans = sorted(self.plans, key=lambda tup: tup[0])
[docs] def registerSkip(self, func): """ Register this function as one to skip execution on, if provided it shouldn't return the (func, args) tuple as a result from the getNextTodo function. """ self.skipFunctions.append(func)
[docs] def getNextTodo(self): """ Like an iterator function, it returns each element in the list of plans in order. Returns: (function, args) : Each todo is returned just how the user first added it None : None is returned when there are no more todo's """ # Are there more plans? if(self.planPtr == len(self.plans)): return None else: prio, todo, abt = self.plans[self.planPtr] self.planPtr += 1 # After we updated the pointer, check if this is a skipped function # if so then we should just call getNextTodo again! if(todo.func in self.skipFunctions): return self.getNextTodo() else: return (todo.func, todo.args)
[docs] def getNextAbort(self): """ Like an iterator function, it returns each element in the list of abort plans in order. Returns: (function, args) : Each todo is returned just how the user first added it None : None is returned when there are no more todo's """ # Check if this is the first time calling the function if(self.abtPtr == None): ################################################################################### # If so we generate the abort list right now # The reason we do this is that depending on how far along the plan got, we have # to abort different things, and some functions will exist in multiple abort plans # so we need to call stuff in the proper order ################################################################################### # So start at one minus the todoPlan that failed startPoint = self.planPtr - 1 for prio, todo, abt in self.plans[startPoint::-1]: # If nothing keep looking if(not abt): continue # This should be a list of Plan() objects for a in abt: # See if we are already going to do this Plan() if(a not in self.abtPlans): self.abtPlans.append(a) # Lastly set the abtPtr so we know we can start self.abtPtr = 0 # Here occurs after the first time call check # Now we actually return each new Plan from abtPlans if(self.abtPtr == len(self.abtPlans)): return None else: abt = self.abtPlans[self.abtPtr] self.abtPtr += 1 return (abt.func, abt.args)
def __repr__(self): return "<%s %r: %d Plans>" % (self.__class__.__name__, self.name, len(self.plans)) def __str__(self): ret = "%r:\n" % self.name for p, todopl, abortpl in self.plans: ret += " %d: %r || %r\n" % (p, todopl, abortpl) return ret
################################################################################################### ## Unit test ################################################################################################### if(__name__ == "__main__"): class Output(object): pass def f(x): print(x) out = Output() out.info = f out.warn = f out.err = f class TestChute(object): pass def exceptionFunc(x): raise Exception('test5') def security0(x): out.info(x) def security1(x): out.info(x) def get0(x): out.info(x) def get1(x): out.info(x) def set0(x): out.info(x) return reload0 def set1(x): out.info(x) def revertSet0(x): out.warn(x) def revertSet1(x): out.warn(x) def reload0(x): out.info(x) def reload1(x): out.info(x) def revertReload0(x): out.warn(x) def revertReload1(x): out.warn(x) # # Setup new map # pm = PlanMap('test') ch = TestChute() ch.guid = 'TESTCHUTE' # # Generate plans portion # reload1 = exceptionFunc # Category zere stuff pm.addPlans(ch, 0, (lambda x: out.info(x), 'category 0'), (lambda x: out.warn(x), 'reverting category 0')) # Category one stuff abtPlans = [] pm.addPlans(ch, 1, (security0, 'sec0')) pm.addPlans(ch, 11, (get0, 'get0')) abtPlans.append((revertSet0, 'reverting set 0')) pm.addPlans(ch, 21, (set0, 'set0'), abtPlans) abtPlans.append((revertReload0, 'reverting reload 0')) pm.addPlans(ch, 31, (reload0, 'reload0'), abtPlans) # Category two stuff abtPlans = [] pm.addPlans(ch, 2, (security1, 'sec1')) pm.addPlans(ch, 12, (get1, 'get1')) abtPlans.append((revertSet1, 'reverting set 1')) pm.addPlans(ch, 22, (set1, 'set1'), abtPlans) abtPlans.append((revertReload1, 'reverting reload 1')) pm.addPlans(ch, 32, (reload1, 'reload1'), abtPlans) # Category three stuff pm.addPlans(ch, 43, (lambda x: out.info(x), 'category 3'), (lambda x: out.warn(x), 'reverting category 3')) out.info(pm) # # Aggregate plans portion # pm.sort() # # Execute Plans portion # doAbort = False while(True): p = pm.getNextTodo() if(not p): break c, f, a = p try: s = f(a) if(s): out.info('Got skip function: %s' % s.__module__) pm.registerSkip(s) except Exception as e: doAbort = True break # # Abort plans portion # if(doAbort): while(True): p = pm.getNextAbort() if(not p): break c, f, a = p try: f(a) except Exception as e: out.err(e) break