def deviceAudit(device, interfaces, configuration): ''' deviceAudit is called to synchronize configuration between IFC and the device. IFC invokes this call when device recovers from hard fault like reboot, mgmt connectivity loss etc. This call can be made as a result of IFC cluster failover event. Device script is expected to fetch the device configuration and reconcile with the configuration passed by IFC. In this call IFC will pass entire device configuration across all graphs. The device should insert/modify any missing configuration on the device. It should remove any extra configuration found on the device. @param device: dict a device dictionary @param interfaces: list of strings a list of interfaces for this device. e.g. [ 'E0/0', 'E0/1', 'E1/0', 'E1/0' ] @param configuration: dict Entire configuration on the device. @return: Faults dictionary ''' env.debug("[Interfaces argument]\n%s" % pretty_dict(interfaces)) return audit_operation(device, interfaces, configuration)
def deviceHealth(device, interfaces, configuration): ''' This function polls the device. API should return a weighted device health score in range (0-100) @param device: dict a device dictionary @return: int a weighted device health score in range (0-100) Example: Poll CPU usage, free memory, disk usage etc. ''' env.debug("[Interfaces argument]\n%s" % pretty_dict(interfaces)) # Note that there is no root for device configuration, so the returned path # is always an empty list. result = {} version, error = read_asa_version(device) if not version: #fail to connect result['faults'] = [([], 0, error)] result['health'] = [([], 0)] result[ 'state'] = Status.TRANSIENT #same as that from xxxxAudit and xxxxModify else: result['state'] = Status.SUCCESS # check for cluster to see if it is ready cluster_health = ClusterConfig.get_health(device, configuration) if cluster_health: result['health'] = [cluster_health] else: result['health'] = [([], 100)] return result
def deviceValidate(device, version): '''This function validates if the device matches one of the versions supported by the device package. This function should do a compare (regular expression match) the version fetched from the device with version string passed in the param. @param device: dict a device dictionary @param version: str a regular expression for the supported versions of the device. e.g: '1.0' @return: dict {'state': <state>, 'version': <version>} ''' env.debug("[Version argument]\n%s" % version) result = {} asa_version, error = read_asa_version(device) if asa_version: result['version'] = asa_version match = re.compile(version).match(asa_version) result['state'] = Status.SUCCESS if match else Status.PERMANENT if not match: result['faults'] = [(get_config_root_key({}), 0, 'Device running un-supported ASA version %s' % asa_version)] else: #not able to connect result['state'] = Status.TRANSIENT result['faults'] = [(get_config_root_key({}), 0, error)] return result
def deliver_sts_table(device, sts, is_audit): ''' Send STS table to ASA device @param device: dict a device dictionary @param table; dict an STS dictionary @param is_audit: boolean True if this is an audit operation else False ''' table = sts.sts_table if not deliver_clis.enabled: env.debug("[STS table would be delivered]\n\n") return True if not table: return True dispatcher = HttpDispatch(device) if is_audit: messenger = dispatcher.make_sts_audit_write_messenger(sts) else: messenger = dispatcher.make_sts_write_messenger(sts) results = messenger.get_results() if results: faults = [] faults.append(('STS', 0, results)) raise ASACommandError(faults)
def deviceHealth(device, interfaces, configuration): ''' This function polls the device. API should return a weighted device health score in range (0-100) @param device: dict a device dictionary @return: int a weighted device health score in range (0-100) Example: Poll CPU usage, free memory, disk usage etc. ''' env.debug("[Interfaces argument]\n%s" % pretty_dict(interfaces)) # Note that there is no root for device configuration, so the returned path # is always an empty list. result = {} version, error = read_asa_version(device) if not version: #fail to connect result['faults'] = [([], 0, error)] result['health'] = [([], 0)] result['state'] = Status.TRANSIENT #same as that from xxxxAudit and xxxxModify else: result['state'] = Status.SUCCESS # check for cluster to see if it is ready cluster_health = ClusterConfig.get_health(device, configuration) if cluster_health: result['health'] = [cluster_health] else: result['health'] = [([], 100)] return result
def deviceValidate(device, version): '''This function validates if the device matches one of the versions supported by the device package. This function should do a compare (regular expression match) the version fetched from the device with version string passed in the param. @param device: dict a device dictionary @param version: str a regular expression for the supported versions of the device. e.g: '1.0' @return: dict {'state': <state>, 'version': <version>} ''' env.debug("[Version argument]\n%s" % version) result = {} asa_version, error = read_asa_version(device) if asa_version: result['version'] = asa_version match = re.compile(version).match(asa_version) result['state'] = Status.SUCCESS if match else Status.PERMANENT if not match: result['faults'] = [ (get_config_root_key({}), 0, 'Device running un-supported ASA version %s' % asa_version) ] else: #not able to connect result['state'] = Status.TRANSIENT result['faults'] = [(get_config_root_key({}), 0, error)] return result
def __init__(self, dispatch, url): connection = dispatch.create_connection(url) env.debug('Command (' + url + ') started') start_time = time.clock() self.response = connection.open(fullurl=url, timeout=DEFAULT_TIMEOUT) elapsed_time = time.clock() - start_time env.debug('GET ' + url + ' Time=' + str(elapsed_time) + ' seconds')
def deliver_clis(device, clis, transformers=[filter_out_sacred_commands], save_config = True): '''Deliver a list of CLI's to an ASA device @param device: dict a device dictionary @param clis: list of CLIIneraction's @param transformers: list of function that takes one argument and return an object the purpose of a transformer is to transform ASA configuration to a desired format before actually sending them down to the ASA device. The order of the application of the transformer is the reverse given in the parameter, i.e. if the transformers is [a, b], the result will be a(b(config)). a list of CLI objects. @param save_config: boolean indicate if the running-config should be saved to startup-config if the configuration is delivered successfully. @return: True if successful in delivery, or ASACommandError or ConnectionError exception will be raised. ''' if not deliver_clis.enabled: env.debug("[CLIs would be delivered]\n%s\n" % '\n'.join([str(cli) for cli in clis])) return True if not clis: return True; if transformers: for transformer in reversed(transformers): clis = transformer(clis) dispatcher = HttpDispatch(device) def dispatch(clis): 'deliver a list of CLIInteraction, and return list errors if any' messenger = dispatcher.make_command_messenger(clis) results = messenger.get_results() errs = filter(lambda x: x != None and x.err_msg != None and len(x.err_msg.strip()) > 0, results) return errs errs = dispatch(clis) if not errs: def is_cluster_config(clis): return any(str(cli).find('cluster group') >= 0 for cli in clis) if save_config and not is_cluster_config(clis): # 'wr mem' will fail during cluster setup so bypass now. Defer till cluster state is stable. write_mem = CLIInteraction("write mem", response_parser = lambda response: None if '[OK]' in response else response) errs = dispatch([write_mem]) if not errs: return True else: return True faults = [] for err in errs: faults.append((err.model_key, 0, err.err_msg)) raise ASACommandError(faults)
def get_results(self): connection = self.dispatch.create_connection(self.url) request = urllib2.Request(url=self.url, data=self.xml, headers={'Content-Type': 'text/xml'}) env.debug('POST URL = ' + self.url + '\nRequest:\n' + self.xml) start_time = time.clock() response = connection.open(fullurl=request, timeout=DEFAULT_TIMEOUT) body = response.read() elapsed_time = time.clock() - start_time env.debug('Response:\n' + body + '\nResponse time: ' + str(elapsed_time) + ' seconds.') return self.parse_error(body)
def get_results(self): connection = self.dispatch.create_connection(self.url) request = urllib2.Request( url=self.url, data=self.xml, headers={'Content-Type': 'text/xml'}) env.debug('POST URL = ' + self.url + '\nRequest:\n' + self.xml) start_time = time.clock() response = connection.open(fullurl=request, timeout=DEFAULT_TIMEOUT) body = response.read() elapsed_time = time.clock() - start_time env.debug('Response:\n' + body + '\nResponse time: ' + str(elapsed_time) + ' seconds.') return self.parse_error(body)
def deviceModify(device, interfaces, configuration): ''' Update global device configuration @param device: dict a device dictionary @warning: 'creds' attribute should be (name, password) pair?! @param interfaces: list of strings a list of interfaces for this device. e.g. [ 'E0/0', 'E0/1', 'E1/0', 'E1/0' ] @param configuration: dict Device configuration configured by the user. This is configuration that does not depend on a graph/function. Example: syslog server IP HA peer IP HA mode Port-Channel @return: Faults dictionary ''' env.debug("[Interfaces argument]\n%s" % pretty_dict(interfaces)) return modify_operation(device, interfaces, configuration)
def query_sts_audit_table(device, sts): ''' Query STS table from ASA device @param device: dict a device dictionary @param table; dict an STS dictionary ''' table = sts.sts_table if not device: error = "query_sts_table: fails to read response for quering sts table" env.debug(error) return (None, error) dispatcher = HttpDispatch(device) messenger = dispatcher.make_sts_audit_read_messenger(sts) results = messenger.get_results() if results: faults = [] faults.append(('STS', 0, results)) raise ASACommandError(faults)
def query_asa(device, query_cmd): '''Read information back from the given device @param device: dict a device dictionary @param query: str, a show command cli, like "show run access-list bla" @return tuple with: 1) response string from ASA; or None if cannot connect to the device 2) any error or exception string, otherwise None @attention: PLEASE do not use this API to make configuration change on the device ''' if not device: error = "query_asa: fails to read response for command '%s'. Error: %s" % ( query_cmd, 'empty device dictionary') env.debug(error) return (None, error) if not query_cmd.strip().startswith('show'): error = "query_asa: '%s' is not a show command, discarded" % query_cmd env.debug(error) return (None, error) try: dispatcher = HttpDispatch(device) messenger = dispatcher.make_shows_messenger([query_cmd]) result = messenger.read() if result == 'Command failed\n': return None, None if '\nERROR: %' in result: return None, None return result, None except Exception as e: env.debug( "query_asa: fails to read response for command '%s'. Error: %s" % (query_cmd, e)) return (None, str(e))
def query_asa(device, query_cmd): '''Read information back from the given device @param device: dict a device dictionary @param query: str, a show command cli, like "show run access-list bla" @return tuple with: 1) response string from ASA; or None if cannot connect to the device 2) any error or exception string, otherwise None @attention: PLEASE do not use this API to make configuration change on the device ''' if not device: error = "query_asa: fails to read response for command '%s'. Error: %s" % (query_cmd, 'empty device dictionary') env.debug(error) return (None, error) if not query_cmd.strip().startswith('show'): error = "query_asa: '%s' is not a show command, discarded" % query_cmd env.debug(error) return (None, error) try: dispatcher = HttpDispatch(device) messenger = dispatcher.make_shows_messenger([query_cmd]) result = messenger.read() if result == 'Command failed\n': return None, None if '\nERROR: %' in result: return None,None return result, None except Exception as e: env.debug("query_asa: fails to read response for command '%s'. Error: %s" % (query_cmd, e)) return (None, str(e))
def trace(*argv): env.debug("***** API Called: %s" % f.__name__) device = argv[0] env.debug("[Device argument]\n%s" % json.dumps(hide_passwords(copy.deepcopy(device)), indent=3)) config = get_config_dict(argv[1:]) if config: env.debug("[Configuration argument]\n%s" % pretty_dict(config)) return f(*argv)
def deviceCounters(device, interfaces, configuration): ''' This function is called periodically to report statistics associated with the physical interfaces of the device. @param device: a device dictionary @param interfaces: A list of the physical interfaces The format is: { (cifType, '', <interface name>) : { 'state': <state>, 'label': <label> }, ... } @param configuration: dict It contains device configuration. The configuration dictionary follows the format described above. @return: dict The format of the dictionary is as follows { 'state': <state> 'counters': [(path, counters), ...] } path: Identifies the object to which the counter is associated. counters: { 'rxpackets': <rxpackets>, 'rxerrors': <rxerrors>, 'rxdrops': <rxdrops>, 'txpackets': <txpackets>, 'txerrors': <txerrors>, 'txdrops': <txdrops> } ''' env.debug("[Interfaces argument]\n%s" % pretty_dict(interfaces)) result = {'counters': []} if interfaces: cli_holder = [] for interface in interfaces: cli_holder.append(CLIInteraction('show interface ' + interface[2].replace('_', '/'))) dispatcher = HttpDispatch(device) try: messenger = dispatcher.make_command_messenger(cli_holder) cli_results = messenger.get_results() except HTTPError as e: env.debug('deviceCounters: %s' % e) result['state'] = Status.TRANSIENT return result result['state'] = Status.SUCCESS for interface, cli_result in zip(interfaces, cli_results): if cli_result and cli_result.err_type == CLIResult.INFO: path = [(Type.CIF, '', interface[2])] counters = parse_interface_counters(cli_result.err_msg) result['counters'].append((path, counters)) else: # Check if there is connectivity to the device version = read_asa_version(device)[0] result['state'] = Status.SUCCESS if version else Status.TRANSIENT return result
def serviceCounters(device, configuration): ''' This function is called periodically to report statistics associated with the service functions rendered on the device. @param device: a device dictionary @param configuration: dict It contains device configuration, group configuration for a particular graph instance and function configuration. The configuration dictionary follows the format described above. @return: dict The format of the dictionary is as follows { 'state': <state> 'counters': [(path, counters), ...] } path: Identifies the object to which the counter is associated. The path is a list identifying a specific instance of a connector. It includes device name, group name, etc. as shown below: path = [ vdev, vgrp, vfunc, conn ] vdev - Device Name. Passed in the configuration dictionary vgrp - Function Group name passed in the configuration dictionary vfunc - function name passed in configuration dictionary conn - connector name within the function counters: { 'rxpackets': <rxpackets>, 'rxerrors': <rxerrors>, 'rxdrops': <rxdrops>, 'txpackets': <txpackets>, 'txerrors': <txerrors>, 'txdrops': <txdrops> } ''' result = {'counters': []} asa = DeviceModel() ifc_cfg = massage_param_dict(asa, configuration) asa.populate_model(ifc_cfg.keys()[0], ifc_cfg.values()[0]) connectors = get_all_connectors(asa) if connectors: cli_holder = [] for connector in connectors: cli_holder.append(CLIInteraction('show interface ' + connector.get_nameif())) dispatcher = HttpDispatch(device) try: messenger = dispatcher.make_command_messenger(cli_holder) cli_results = messenger.get_results() except HTTPError as e: env.debug('serviceCounters: %s' % e) result['state'] = Status.TRANSIENT return result result['state'] = Status.SUCCESS for connector, cli_result in zip(connectors, cli_results): path = connector.get_config_path() counters = parse_connector_counters(cli_result.err_msg) result['counters'].append((path, counters)) else: # Check if there is connectivity to the device version = read_asa_version(device)[0] result['state'] = Status.SUCCESS if version else Status.TRANSIENT return result
def deliver_clis(device, clis, transformers=[filter_out_sacred_commands], save_config=True): '''Deliver a list of CLI's to an ASA device @param device: dict a device dictionary @param clis: list of CLIIneraction's @param transformers: list of function that takes one argument and return an object the purpose of a transformer is to transform ASA configuration to a desired format before actually sending them down to the ASA device. The order of the application of the transformer is the reverse given in the parameter, i.e. if the transformers is [a, b], the result will be a(b(config)). a list of CLI objects. @param save_config: boolean indicate if the running-config should be saved to startup-config if the configuration is delivered successfully. @return: True if successful in delivery, or ASACommandError or ConnectionError exception will be raised. ''' if not deliver_clis.enabled: env.debug("[CLIs would be delivered]\n%s\n" % '\n'.join([str(cli) for cli in clis])) return True if not clis: return True if transformers: for transformer in reversed(transformers): clis = transformer(clis) dispatcher = HttpDispatch(device) def dispatch(clis): 'deliver a list of CLIInteraction, and return list errors if any' messenger = dispatcher.make_command_messenger(clis) results = messenger.get_results() errs = filter( lambda x: x != None and x.err_msg != None and len(x.err_msg.strip( )) > 0, results) return errs errs = dispatch(clis) if not errs: def is_cluster_config(clis): return any(str(cli).find('cluster group') >= 0 for cli in clis) if save_config and not is_cluster_config(clis): # 'wr mem' will fail during cluster setup so bypass now. Defer till cluster state is stable. write_mem = CLIInteraction("write mem", response_parser=lambda response: None if '[OK]' in response else response) errs = dispatch([write_mem]) if not errs: return True else: return True faults = [] for err in errs: faults.append((err.model_key, 0, err.err_msg)) raise ASACommandError(faults)
def serviceCounters(device, configuration): ''' This function is called periodically to report statistics associated with the service functions rendered on the device. @param device: a device dictionary @param configuration: dict It contains device configuration, group configuration for a particular graph instance and function configuration. The configuration dictionary follows the format described above. @return: dict The format of the dictionary is as follows { 'state': <state> 'counters': [(path, counters), ...] } path: Identifies the object to which the counter is associated. The path is a list identifying a specific instance of a connector. It includes device name, group name, etc. as shown below: path = [ vdev, vgrp, vfunc, conn ] vdev - Device Name. Passed in the configuration dictionary vgrp - Function Group name passed in the configuration dictionary vfunc - function name passed in configuration dictionary conn - connector name within the function counters: { 'rxpackets': <rxpackets>, 'rxerrors': <rxerrors>, 'rxdrops': <rxdrops>, 'txpackets': <txpackets>, 'txerrors': <txerrors>, 'txdrops': <txdrops> } ''' result = {'counters': []} asa = DeviceModel() ifc_cfg = massage_param_dict(asa, configuration) asa.populate_model(ifc_cfg.keys()[0], ifc_cfg.values()[0]) connectors = get_all_connectors(asa) if connectors: cli_holder = [] for connector in connectors: cli_holder.append( CLIInteraction('show interface ' + connector.get_nameif())) dispatcher = HttpDispatch(device) try: messenger = dispatcher.make_command_messenger(cli_holder) cli_results = messenger.get_results() except HTTPError as e: env.debug('serviceCounters: %s' % e) result['state'] = Status.TRANSIENT return result result['state'] = Status.SUCCESS for connector, cli_result in zip(connectors, cli_results): path = connector.get_config_path() counters = parse_connector_counters(cli_result.err_msg) result['counters'].append((path, counters)) else: # Check if there is connectivity to the device version = read_asa_version(device)[0] result['state'] = Status.SUCCESS if version else Status.TRANSIENT return result
def deviceCounters(device, interfaces, configuration): ''' This function is called periodically to report statistics associated with the physical interfaces of the device. @param device: a device dictionary @param interfaces: A list of the physical interfaces The format is: { (cifType, '', <interface name>) : { 'state': <state>, 'label': <label> }, ... } @param configuration: dict It contains device configuration. The configuration dictionary follows the format described above. @return: dict The format of the dictionary is as follows { 'state': <state> 'counters': [(path, counters), ...] } path: Identifies the object to which the counter is associated. counters: { 'rxpackets': <rxpackets>, 'rxerrors': <rxerrors>, 'rxdrops': <rxdrops>, 'txpackets': <txpackets>, 'txerrors': <txerrors>, 'txdrops': <txdrops> } ''' env.debug("[Interfaces argument]\n%s" % pretty_dict(interfaces)) result = {'counters': []} if interfaces: cli_holder = [] for interface in interfaces: cli_holder.append( CLIInteraction('show interface ' + interface[2].replace('_', '/'))) dispatcher = HttpDispatch(device) try: messenger = dispatcher.make_command_messenger(cli_holder) cli_results = messenger.get_results() except HTTPError as e: env.debug('deviceCounters: %s' % e) result['state'] = Status.TRANSIENT return result result['state'] = Status.SUCCESS for interface, cli_result in zip(interfaces, cli_results): if cli_result and cli_result.err_type == CLIResult.INFO: path = [(Type.CIF, '', interface[2])] counters = parse_interface_counters(cli_result.err_msg) result['counters'].append((path, counters)) else: # Check if there is connectivity to the device version = read_asa_version(device)[0] result['state'] = Status.SUCCESS if version else Status.TRANSIENT return result