def oscall(cmd, get=False): """ This function performs a OS subprocess call. All output is thrown away unless an error has occured or if @get is True Arguments: @cmd: the string command to run [get] : True means return (stdout, stderr) Returns: None if not @get and no error (stdout, retcode, stderr) if @get or yes error """ # Since we are already in a deferred chain, use subprocess to block and make the call to mount right HERE AND NOW proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, errors = proc.communicate() if (proc.returncode or get): return (output, proc.returncode, errors) else: if (output and output != ""): out.verbose('"%s" stdout: "%s"\n' % (cmd, output.rstrip())) if (errors and errors != ""): out.verbose('"%s" stderr: "%s"\n' % (cmd, errors.rstrip())) return None
def generatePlans(update): """ This function looks at a diff of the current Chute (in @chuteStor) and the @newChute, then adds Plan() calls to make the Chute match the @newChute. Returns: True: abort the plan generation process """ out.verbose("%r\n" % (update)) # Generate virt start script, stored in cache (key: 'virtPreamble') update.plans.addPlans(plangraph.RUNTIME_GET_VIRT_PREAMBLE, (dockerconfig.getVirtPreamble, )) # Make sure volume directories exist. update.plans.addPlans(plangraph.CREATE_VOLUME_DIRS, (dockerconfig.createVolumeDirs, ), (dockerconfig.abortCreateVolumeDirs, )) # If the user specifies DHCP then we need to generate the config and store it to disk update.plans.addPlans(plangraph.RUNTIME_GET_VIRT_DHCP, (dhcp.getVirtDHCPSettings, )) update.plans.addPlans(plangraph.RUNTIME_SET_VIRT_DHCP, (dhcp.setVirtDHCPSettings, )) # Reload configuration files todoPlan = (configservice.reloadAll, ) abtPlan = [(osconfig.revertConfig, "dhcp"), (osconfig.revertConfig, "firewall"), (osconfig.revertConfig, "network"), (osconfig.revertConfig, "wireless"), (configservice.reloadAll, )] update.plans.addPlans(plangraph.RUNTIME_RELOAD_CONFIG, todoPlan, abtPlan) return None
def executePlans(update): """ Primary function that actually executes all the functions that were added to plans by all the exc modules. This function can heavily modify the OS/files/etc.. so the return value is very important. Returns: True in error : abortPlans function should be called False otherwise : everything is OK """ out.header('Executing plans %r\n' % (update)) # Finding the functions to call is actually done by a 'iterator' like function in the plangraph module while (True): # This function either returns None or a tuple just like generate added to it p = update.plans.getNextTodo() # No more to do? if (not p): break # Explode tuple otherwise func, args = p # We are in a try-except block so if func isn't callable that will catch it try: out.verbose('Calling %s\n' % (func)) update.progress("Calling {}".format(func.__name__)) # # Call the function from the execution plan # # args may be empty, but we don't want to pass in a tuple if we don't need to. # This below explodes the args so if @args is (), then what is passed is @update skipme = func(*((update, ) + args)) except Exception as e: out.exception(e, True) # plans = str(update.plans)) # Removed because breaks new out.exception call out.warn("Failed to execute plan %s%s" % (func.__name__, args)) update.responses.append({ 'exception': str(e), 'traceback': traceback.format_exc() }) update.failure = str(e) return True # The functions we call here can return other functions, if they do these are functions that should # be skipped later on (for instance a set* function discovering it didn't change anything, later on # we shouldn't call the corresponding reload function) if (skipme): # These functions can return individual functions to skip, or a list of multiple functions if (not isinstance(skipme, list)): skipme = [skipme] for skip in skipme: out.warn('Identified a skipped function: %r\n' % (skip)) update.plans.registerSkip(skip) # Now we are done return False
def generatePlans(update): """ This function looks at a diff of the current Chute (in @chuteStor) and the @newChute, then adds Plan() calls to make the Chute match the @newChute. Returns: True: abort the plan generation process """ out.verbose("%r\n" % (update)) update.plans.addPlans(plangraph.ENFORCE_ACCESS_RIGHTS, (security.enforce_access_rights, )) return None
def generatePlans(update): out.verbose("%r\n" % (update)) if update.updateType == "factoryreset": update.plans.addPlans(plangraph.STATE_CALL_STOP, (dockerapi.removeAllContainers, )) update.plans.addPlans(plangraph.STATE_SAVE_CHUTE, (state.removeAllChutes, )) elif update.updateType == "reboot": update.plans.addPlans(plangraph.STATE_CALL_STOP, (power.reboot, )) elif update.updateType == "shutdown": update.plans.addPlans(plangraph.STATE_CALL_STOP, (power.shutdown, ))
def delConfig(self, config, options): """Finds a match to the config input and removes it from the internal config data structure.""" config = stringify(config) options = stringify(options) # Search through the config array for matches for i, e in enumerate(self.config): c, o = e if(c == config and o == options): break else: # Getting here means we didn't break so no match out.verbose('No match to delete, config: %r\n' % (config)) return del(self.config[i])
def abortPlans(update): """ This function should be called if one of the Plan objects throws an Exception. It takes the PlanMap argument and calls the getNextAbort function just like executePlans does with todoPlans. This dynamically generates an abort plan list based on what plans were originally executed. Returns: True in error : This is really bad False otherwise : we were able to restore system state back to before the executeplans function was called """ out.header('Aborting plans %r\n' % (update.plans)) sameError = False while (True): # This function either returns None or a tuple just like generate added to it p = update.plans.getNextAbort() # No more to do? if (not p): break # Explode tuple otherwise func, args = p # We are in a try-except block so if func isn't callable that will catch it try: out.verbose('Calling {}\n'.format(func)) update.progress('Calling {}'.format(func.__name__)) func(*((update, ) + args)) # If the func is called without exception then clear the @sameError flag for the next function call sameError = False except Exception as e: # Since we are running this in an infinite loop if a major function throws an error # we could loop forever, so check for the error, which is only reset at the end of the loop if (sameError): return True update.responses.append({ 'exception': str(e), 'traceback': traceback.format_exc() }) out.fatal('An abort function raised an exception!!! %r: %s\n%s\n' % (update.plans, str(e), traceback.format_exc())) sameError = True # Getting here we assume the system state has been restored using our abort plan return False
def generatePlans(update): """ This function looks at a diff of the current Chute (in @chuteStor) and the @newChute, then adds Plan() calls to make the Chute match the @newChute. Returns: True: abort the plan generation process """ out.verbose("%r\n" % (update)) update.plans.addPlans(plangraph.RESOURCE_GET_ALLOCATION, (resource.getResourceAllocation, )) update.plans.addPlans(plangraph.RESOURCE_SET_ALLOCATION, (dockerapi.setResourceAllocation, ), (dockerapi.revertResourceAllocation, )) return None
def generatePlans(update): """ This function looks at a diff of the current Chute (in @chuteStor) and the @newChute, then adds Plan() calls to make the Chute match the @newChute. Returns: True: abort the plan generation process """ out.verbose("%r\n" % (update)) #print any warnings from previous update if they exist if hasattr(update, 'pkg') and update.old != None and update.old.warning != None: update.pkg.request.write(update.old.warning + '\n') # TODO: Create a directory for the chute for us to hold onto things (dockerfile, OS config stuff) return None
def generatePlans(update): """ This function looks at a diff of the current Chute (in @chuteStor) and the @newChute, then adds Plan() calls to make the Chute match the @newChute. Returns: True: abort the plan generation process """ out.verbose("%r\n" % (update)) update.plans.addPlans(plangraph.RESOURCE_GET_ALLOCATION, (resource.getResourceAllocation,)) update.plans.addPlans(plangraph.RESOURCE_SET_ALLOCATION, (dockerapi.setResourceAllocation,), (dockerapi.revertResourceAllocation,)) return None
def generatePlans(update): """ This function looks at a diff of the current Chute (in @chuteStor) and the @newChute, then adds Plan() calls to make the Chute match the @newChute. Returns: True: abort the plan generation process """ out.verbose("%r\n" % (update)) # Check for some error conditions that we can detect during the planning # stage, e.g. starting a chute that does not exist. if not validate_change(update): return True update.plans.addPlans(plangraph.STATE_GENERATE_SERVICE_PLANS, (generate_service_plans, )) if update.new.isRunning(): update.plans.addPlans(plangraph.STATE_NET_START, (dockerapi.setup_net_interfaces, ), (dockerapi.cleanup_net_interfaces, )) if update.old is None: update.plans.addPlans(plangraph.STATE_CREATE_BRIDGE, (dockerapi.create_bridge, ), (dockerapi.remove_bridge, )) if update.old is not None and update.old.isRunning(): update.plans.addPlans(plangraph.STATE_NET_STOP, (dockerapi.cleanup_net_interfaces, ), (dockerapi.setup_net_interfaces, )) if update.updateType == "delete": update.plans.addPlans(plangraph.STATE_REMOVE_BRIDGE, (dockerapi.remove_bridge, ), (dockerapi.create_bridge, )) # Save the chute to the chutestorage. update.plans.addPlans(plangraph.STATE_SAVE_CHUTE, (state.saveChute, ), (state.revertChute, )) return None
def abortPlans(update): """ This function should be called if one of the Plan objects throws an Exception. It takes the PlanMap argument and calls the getNextAbort function just like executePlans does with todoPlans. This dynamically generates an abort plan list based on what plans were originally executed. Returns: True in error : This is really bad False otherwise : we were able to restore system state back to before the executeplans function was called """ out.header('Aborting plans %r\n' % (update.plans)) sameError = False while(True): # This function either returns None or a tuple just like generate added to it p = update.plans.getNextAbort() # No more to do? if(not p): break # Explode tuple otherwise func, args = p # We are in a try-except block so if func isn't callable that will catch it try: out.verbose('Calling {}\n'.format(func)) update.progress('Calling {}'.format(func.__name__)) func(*((update, ) + args)) # If the func is called without exception then clear the @sameError flag for the next function call sameError = False except Exception as e: # Since we are running this in an infinite loop if a major function throws an error # we could loop forever, so check for the error, which is only reset at the end of the loop if(sameError): return True update.responses.append({'exception': str(e), 'traceback': traceback.format_exc()}) out.fatal('An abort function raised an exception!!! %r: %s\n%s\n' % (update.plans, str(e), traceback.format_exc())) sameError = True # Getting here we assume the system state has been restored using our abort plan return False
def execute(self): try: proc = subprocess.Popen(self.command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.pid = proc.pid for line in proc.stdout: out.verbose("{} {}: {}".format(self.command[0], self.pid, line)) for line in proc.stderr: out.verbose("{} {}: {}".format(self.command[0], self.pid, line)) self.result = proc.wait() out.info('Command "{}" returned {}\n'.format( " ".join(self.command), self.result)) except Exception as e: out.info('Command "{}" raised exception {}\n'.format( " ".join(self.command), e)) self.result = e if self.parent is not None: self.parent.executed.append(self) return (self.ignoreFailure or self.result == 0)
def execute(self): try: proc = subprocess.Popen(self.command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.pid = proc.pid for line in proc.stdout: out.verbose("{} {}: {}".format(self.command[0], self.pid, line)) for line in proc.stderr: out.verbose("{} {}: {}".format(self.command[0], self.pid, line)) self.result = proc.wait() if self.result == 0: out.verbose('Command "{}" returned {}\n'.format( " ".join(self.command), self.result)) else: out.info('Command "{}" returned {}\n'.format( " ".join(self.command), self.result)) except Exception as e: out.info('Command "{}" raised exception {}\n'.format( " ".join(self.command), e)) self.result = e if self.parent is not None: self.parent.executed.append(self) return (self.ignoreFailure or self.result == 0)
def generatePlans(update): """ This function looks at a diff of the current Chute (in @chuteStor) and the @newChute, then adds Plan() calls to make the Chute match the @newChute. Returns: True: abort the plan generation process """ out.verbose("%r\n" % (update)) # Generate virt start script, stored in cache (key: 'virtPreamble') update.plans.addPlans(plangraph.RUNTIME_GET_VIRT_PREAMBLE, (dockerconfig.getVirtPreamble, )) # Make sure volume directories exist. update.plans.addPlans(plangraph.CREATE_VOLUME_DIRS, (dockerconfig.createVolumeDirs, ), (dockerconfig.abortCreateVolumeDirs, )) # If the user specifies DHCP then we need to generate the config and store it to disk update.plans.addPlans(plangraph.RUNTIME_GET_VIRT_DHCP, (dhcp.getVirtDHCPSettings, )) todoPlan = (dhcp.setVirtDHCPSettings, ) abtPlan = (dhcp.revert_dhcp_settings, ) update.plans.addPlans(plangraph.RUNTIME_SET_VIRT_DHCP, todoPlan, abtPlan) # Reload configuration files todoPlan = (configservice.reloadAll, ) update.plans.addPlans(plangraph.RUNTIME_RELOAD_CONFIG, todoPlan) # Reload configuration files if aborting. This needs to happen at the # right place in the update pipeline such that UCI files have been # restored to their previous contents. todoPlan = (configservice.reload_placeholder, ) abtPlan = (configservice.reloadAll, ) update.plans.addPlans(plangraph.RUNTIME_RELOAD_CONFIG_BACKOUT, todoPlan, abtPlan) return None
def onChallenge(self, challenge): if challenge.method == u"wampcra": out.verbose("WAMP-CRA challenge received: {}".format(challenge)) wampPassword = nexus.core.getKey('apitoken') if u'salt' in challenge.extra: # salted secret key = auth.derive_key(wampPassword, challenge.extra['salt'], challenge.extra['iterations'], challenge.extra['keylen']) else: # plain, unsalted secret key = wampPassword # compute signature for challenge, using the key signature = auth.compute_wcs(key, challenge.extra['challenge']) # return the signature to the router for verification return signature else: raise Exception("Invalid authmethod {}".format(challenge.method))
def generatePlans(update): out.verbose("%r\n" % (update)) update.plans.addPlans(plangraph.SNAP_INSTALL, (config.snap.updateSnap, ))
def executePlans(update): """ Primary function that actually executes all the functions that were added to plans by all the exc modules. This function can heavily modify the OS/files/etc.. so the return value is very important. Returns: True in error : abortPlans function should be called False otherwise : everything is OK """ out.header('Executing plans %r\n' % (update)) # Finding the functions to call is actually done by a 'iterator' like function in the plangraph module while(True): # This function either returns None or a tuple just like generate added to it p = update.plans.getNextTodo() # No more to do? if(not p): break # Explode tuple otherwise func, args = p # We are in a try-except block so if func isn't callable that will catch it try: out.verbose('Calling %s\n' % (func)) update.progress("Calling {}".format(func.__name__)) # # Call the function from the execution plan # # args may be empty, but we don't want to pass in a tuple if we don't need to. # This below explodes the args so if @args is (), then what is passed is @update skipme = func(*((update, ) + args)) except Exception as e: out.exception(e, True) # plans = str(update.plans)) # Removed because breaks new out.exception call out.warn("Failed to execute plan %s%s" % (func.__name__, args)) update.responses.append({'exception': str(e), 'traceback': traceback.format_exc()}) update.failure = str(e) return True # The functions we call here can return other functions, if they do # these are functions that should be skipped later on (for instance a # set* function discovering it didn't change anything, later on we # shouldn't call the corresponding reload function) if skipme is not None: # If the function returned a Deferred, we will drop out of the # execution pipeline and resume later. if isinstance(skipme, Deferred): out.verbose('Function {} returned a Deferred'.format(func)) return skipme # These functions can return individual functions to skip, or a # list of multiple functions elif callable(skipme): skipme = [skipme] for skip in skipme: out.warn('Identified a skipped function: %r\n' % (skip)) update.plans.registerSkip(skip) # Now we are done return False
def generatePlans(update): """ This function looks at a diff of the current Chute (in @chuteStor) and the @newChute, then adds Plan() calls to make the Chute match the @newChute. Returns: True: abort the plan generation process """ out.verbose("%r\n" % (update)) if update.updateType in ['create', 'update']: update.plans.addPlans(plangraph.STATE_BUILD_IMAGE, (dockerapi.buildImage, ), (dockerapi.removeNewImage, )) # If this chute is new (no old version) if (update.old is None): out.verbose('new chute\n') # If it's a stop, start, delete, or restart command go ahead and fail right now since we don't have a record of it if update.updateType in ['stop', 'start', 'delete', 'restart']: update.failure = "No chute found with id: " + update.name return True # If we are now running then everything has to be setup for the first time if (update.new.state == Chute.STATE_RUNNING): update.plans.addPlans(plangraph.STATE_CALL_START, (dockerapi.startChute, ), (dockerapi.removeNewContainer, )) # Check if the state is invalid, we should return bad things in this case (don't do anything) elif (update.new.state == Chute.STATE_INVALID): if (settings.DEBUG_MODE): update.responses.append('Chute state is invalid') return True # Not a new chute else: if update.updateType == 'start': if update.old.state == Chute.STATE_RUNNING: update.failure = update.name + " already running." return True update.plans.addPlans(plangraph.STATE_CALL_START, (dockerapi.restartChute, )) elif update.updateType == 'restart': update.plans.addPlans(plangraph.STATE_CALL_STOP, (dockerapi.removeOldContainer, ), (dockerapi.startOldContainer, )) update.plans.addPlans(plangraph.STATE_CALL_START, (dockerapi.startChute, ), (dockerapi.removeNewContainer, )) elif update.updateType == 'create': update.failure = update.name + " already exists on this device." return True elif update.updateType == 'update': if update.new.version == update.old.version: update.failure = "Version {} already exists on this device.".format( update.new.version) return True elif (update.new.version < update.old.version) and settings.REJECT_DOWNGRADE: update.failure = "A newer version ({}) already exists on this device.".format( update.old.version) return True else: update.plans.addPlans(plangraph.STATE_CALL_STOP, (dockerapi.removeOldContainer, ), (dockerapi.startOldContainer, )) update.plans.addPlans(plangraph.STATE_CALL_START, (dockerapi.startChute, ), (dockerapi.removeNewContainer, )) update.plans.addPlans(plangraph.STATE_CALL_CLEANUP, (dockerapi.removeOldImage, )) elif update.new.state == Chute.STATE_STOPPED: if update.updateType == 'delete': update.plans.addPlans(plangraph.STATE_CALL_STOP, (dockerapi.removeChute, )) if update.updateType == 'stop': if update.old.state == Chute.STATE_STOPPED: update.failure = update.name + " already stopped." return True update.plans.addPlans(plangraph.STATE_CALL_STOP, (dockerapi.stopChute, )) # Save the chute to the chutestorage. update.plans.addPlans(plangraph.STATE_SAVE_CHUTE, (state.saveChute, ), (state.revertChute, )) return None
def generatePlans(update): out.verbose("%r\n" % (update)) update.plans.addPlans(plangraph.SNAP_INSTALL, (snap.updateSnap, ))
def generatePlans(update): out.verbose("%r\n" % (update)) # Detect system devices and set up basic configuration for them (WAN # interface, wireless devices). These steps do not need to be reverted on # abort. # # checkSystemDevices may reboot the machine if expected devices are # missing. # # abortNetworkConfig is added as an abort command here so that it runs when # config.network.getNetworkConfig or just about anything else fails. # # reloadAll is added as an abort command here so that it runs when any of # the set* plans fail and back out. update.plans.addPlans(plangraph.STRUCT_GET_SYSTEM_DEVICES, (devices.getSystemDevices, ), (network.abortNetworkConfig, )) update.plans.addPlans(plangraph.CHECK_SYSTEM_DEVICES, (devices.checkSystemDevices, )) update.plans.addPlans(plangraph.STRUCT_GET_RESERVATIONS, (reservations.getReservations, )) update.plans.addPlans(plangraph.STRUCT_GET_HOST_CONFIG, (hostconfig.getHostConfig, )) # Save current network configuration into chute cache (key: 'networkInterfaces') update.plans.addPlans(plangraph.STRUCT_GET_INT_NETWORK, (network.getNetworkConfig, )) # Start haproxy. This does not depend on the host config, and we want # it setup even in cases where applying the host config failed. update.plans.addPlans(plangraph.RECONFIGURE_PROXY, (haproxy.reconfigureProxy, )) if update.updateType in CONFIG_CHANGING_TYPES: # Save current host configuration to disk. update.plans.addPlans(plangraph.STRUCT_SET_HOST_CONFIG, (hostconfig.setHostConfig, ), (hostconfig.revertHostConfig, )) # Apply host configuration to system configuration. update.plans.addPlans(plangraph.STRUCT_SET_SYSTEM_DEVICES, (devices.setSystemDevices, ), (configservice.reloadAll, )) # Apply zerotier configuration. update.plans.addPlans(plangraph.ZEROTIER_CONFIGURE, (zerotier.configure, )) # Apply Airshark configuration. update.plans.addPlans(plangraph.AIRSHARK_CONFIGURE, (airshark.configure, )) # Configure telemetry service. update.plans.addPlans(plangraph.TELEMETRY_SERVICE, (services.configure_telemetry, )) # Reload configuration files todoPlan = (configservice.reloadAll, ) update.plans.addPlans(plangraph.RUNTIME_RELOAD_CONFIG, todoPlan) # Reload configuration files if aborting. This needs to happen at the # right place in the update pipeline such that UCI files have been # restored to their previous contents. todoPlan = (configservice.reload_placeholder, ) abtPlan = (configservice.reloadAll, ) update.plans.addPlans(plangraph.RUNTIME_RELOAD_CONFIG_BACKOUT, todoPlan, abtPlan)
def generatePlans(update): """ This function looks at a diff of the current Chute (in @chuteStor) and the @newChute, then adds Plan() calls to make the Chute match the @newChute. Returns: True: abort the plan generation process """ out.verbose("%r\n" % (update)) update.plans.addPlans(plangraph.DOWNLOAD_CHUTE_FILES, (files.download_chute_files, ), (files.delete_chute_files, )) update.plans.addPlans(plangraph.LOAD_CHUTE_CONFIGURATION, (files.load_chute_configuration, )) # Detect system devices and set up basic configuration for them (WAN # interface, wireless devices). These steps do not need to be reverted on # abort. # # abortNetworkConfig is added as an abort command here so that it runs when # config.network.getNetworkConfig or just about anything else fails. update.plans.addPlans(plangraph.STRUCT_GET_SYSTEM_DEVICES, (devices.getSystemDevices, ), (network.abortNetworkConfig, )) update.plans.addPlans(plangraph.STRUCT_GET_RESERVATIONS, (reservations.getReservations, )) update.plans.addPlans(plangraph.STRUCT_GET_HOST_CONFIG, (hostconfig.getHostConfig, )) update.plans.addPlans(plangraph.STRUCT_SET_HOST_CONFIG, (hostconfig.setHostConfig, ), (hostconfig.revertHostConfig, )) # Save current network configuration into chute cache (key: 'networkInterfaces') update.plans.addPlans(plangraph.STRUCT_GET_INT_NETWORK, (network.getNetworkConfig, )) # Setup changes to push into OS config files (key: 'osNetworkConfig') update.plans.addPlans(plangraph.STRUCT_GET_OS_NETWORK, (network.getOSNetworkConfig, )) # Setup changes to push into OS config files (key: 'osWirelessConfig') update.plans.addPlans(plangraph.STRUCT_GET_OS_WIRELESS, (wifi.getOSWirelessConfig, )) # Changes for networking todoPlan = (network.setOSNetworkConfig, ) abtPlan = (network.revert_os_network_config, ) update.plans.addPlans(plangraph.STRUCT_SET_OS_NETWORK, todoPlan, abtPlan) # Changes for wifi todoPlan = (wifi.setOSWirelessConfig, ) abtPlan = (wifi.revert_os_wireless_config, ) update.plans.addPlans(plangraph.STRUCT_SET_OS_WIRELESS, todoPlan, abtPlan) # Layer 3 bridging support todoPlan = (network.getL3BridgeConfig, ) update.plans.addPlans(plangraph.STRUCT_GET_L3BRIDGE_CONFIG, todoPlan) todoPlan = (network.setL3BridgeConfig, ) abtPlan = (network.revert_l3_bridge_config, ) update.plans.addPlans(plangraph.STRUCT_SET_L3BRIDGE_CONFIG, todoPlan, abtPlan) return None