def test_nornsible_print_task_results(capfd): test_result = AggregatedResult("testresult") test_result["localhost"] = MultiResult("testresult") test_result["localhost"].append( Result(host="localhost", result="stuff happening!", failed=False, changed=False) ) print_result(test_result) std_out, std_err = capfd.readouterr() assert "stuff happening" in std_out
def pyez_sec_zones(task: Task) -> Result: device = task.host.get_connection(CONNECTION_NAME, task.nornir.config) data = device.rpc.get_zones_information() data = etree.tostring(data, encoding='unicode', pretty_print=True) parsed = xmltodict.parse(data) clean_parse = json.loads(json.dumps(parsed)) return Result(host=task.host, result=clean_parse)
def task1(task): try: task.run(task=subtask1) except Exception as e: pass task.run(task=subtask2) task.run(task=subtask3) return Result(host=task.host, result=task.name)
def deployConfig(self) -> Result: configs = self._generateConfig() net_connect = self.task.host.get_connection(CONNECTION_NAME, self.task.nornir.config) results = [] for command in configs: result = net_connect.send_command(command) results.append(result) return Result(host=self.task.host, result=results)
def _get_facts(task): logger.info(f"{task.host}: Getting Facts") result = task.run(task=napalm_get, name="Get Facts from device", getters=['facts']) if not result: logger.info(f"{task.host}: Failed to get facts") return Result(host=task.host, failed=True, changed=False) facts = result.result['facts'] logger.info(f"{task.host}: Facts Retrieved") return Result(host=task.host, result=facts, failed=False, changed=False)
def get_vrf_interfaces(task): '''Nornir task to grab all interfaces assigned to VRF on a switch. It will create list of utils.switch_objects.SwitchInterface and assign it to task.host['interfaces']. Task will fail if there are no interfaces assigned to VRF, precluding other tasks run on that host. Arguments: * task - instance or nornir.core.task.Task Returns: * instance of nornir.core.task.Result ''' connection = task.host.get_connection('netmiko', None) output = connection.send_command( task.host['vendor_vars']['show vrf interfaces'].format( task.host['vrf_name'])) if task.host.platform == 'nxos': if task.host['vrf_name'] not in output: interfaces_list = [] else: interfaces_list = [SwitchInterface( x.split(' ')[0], mode='routed') for x in output.strip().split( '\n')[1:]] elif task.host.platform == 'huawei_vrpv8': if 'Interface Number : 0' in output: interfaces_list = [] else: start_mark = 'Interface list : ' start = output.index(start_mark) interfaces_list = [SwitchInterface( x.strip(' ,'), mode='routed') for x in output[start+len( start_mark):].strip().split('\n')] else: raise UnsupportedNOS( 'task received unsupported NOS - {}'.format( task.host.platform)) task.host['interfaces'] = interfaces_list if len(task.host['interfaces']) == 0: return Result(host=task.host, failed=True, result='No interfaces assigned to VRF {}'.format( task.host['vrf_name'])) else: return Result( host=task.host, result='Interfaces bound to VRF {}:\n\t{}'.format( task.host['vrf_name'], '\n\t'.join( [x.name for x in interfaces_list])))
def update_device_status(task: Task) -> Result: """ Update the status of the device on the remote system Args: task: Nornir Task Returns: Result: """ if not config.netbox["status_update"]: logger.debug(f"{task.host.name} | status_update disabled skipping") return Result(host=task.host, result=False) if not task.host.data["obj"].remote: logger.debug(f"{task.host.name} | remote not present skipping") return Result(host=task.host, result=False) new_status = None prev_status = task.host.data["obj"].remote.status.value if task.host.data["status"] == "fail-ip": new_status = config.netbox["status_on_unreachable"] elif "fail" in task.host.data["status"]: new_status = config.netbox["status_on_fail"] else: new_status = config.netbox["status_on_pass"] if new_status not in (None, prev_status): task.host.data["obj"].remote.update(data={"status": new_status}) logger.info( f"{task.host.name} | Updated status on netbox {prev_status} > {new_status}" ) return Result(host=task.host, result=True) logger.debug(f"{task.host.name} | no status update required") return Result(host=task.host, result=False)
def check_interfaces_status(task, interface_list=None): '''Nornir task to get switch interfaces administrative and operational status. If interface list is provided, new list of utils.switch_objects.SwitchInterface will be generated and assigned to task.host['interfaces'], so existed ones would be dropped. Otherwise existed list in task.host['interfaces'] would be used. Arguments: * task - instance or nornir.core.task.Task * interface_list (defaults to None) - list of strings, which represents switch interface names Returns: * instance of nornir.core.task.Result ''' if interface_list: task.host['interfaces'] = [SwitchInterface(x) for x in interface_list] connection = task.host.get_connection('netmiko', None) result = 'Interfaces status:\n' interfaces_brief_output = connection.send_command(task.host['vendor_vars'][ 'show interfaces brief']) for interface in task.host['interfaces']: if task.host.platform == 'nxos': interface_name = cisco_compact_name(interface.name) else: interface_name = interface.name brief_line_start = interfaces_brief_output.index(interface_name) # 'find' will cover end of output (last line) situations brief_line_end = interfaces_brief_output.find('\n', brief_line_start) brief_line = interfaces_brief_output[brief_line_start:brief_line_end] if task.host.platform == 'nxos': if ' up ' in brief_line: interface.admin_status = 'up' interface.oper_status = 'up' elif 'Administratively down' in brief_line: interface.admin_status = 'down' interface.oper_status = 'down' else: interface.admin_status = 'up' interface.oper_status = 'down' elif task.host.platform == 'huawei_vrpv8': phy_status = re.search(r'{}(\(.+\))?\s+(\*?(down|up))'.format( interface.name), brief_line).group(2) if phy_status == '*down': interface.admin_status = 'down' interface.oper_status = 'down' elif phy_status == 'down': interface.admin_status = 'up' interface.oper_status = 'down' else: interface.admin_status = 'up' interface.oper_status = 'up' else: raise UnsupportedNOS('task received unsupported NOS - {}'.format( task.host.platform)) result += '\tInterface {} is in {}/{} state\n'.format( interface.name, interface.admin_status, interface.oper_status) return Result(host=task.host, result=result)
def get_interfaces_mode(task, interface_list=None): '''Nornir task to get switch interfaces mode of operation, which can be either routed (L3) or switched (L2). If interface list is provided, new list of utils.switch_objects.SwitchInterface will be generated and assigned to task.host['interfaces'], so existed ones would be dropped. Otherwise existed list in task.host['interfaces'] would be used. Arguments: * task - instance or nornir.core.task.Task * interface_list (defaults to None) - list of strings, which represents switch interface names Returns: * instance of nornir.core.task.Result ''' if interface_list: task.host['interfaces'] = [SwitchInterface(x) for x in interface_list] connection = task.host.get_connection('netmiko', None) result = 'Interfaces mode:\n' if task.host.platform == 'nxos': interfaces_brief_output = connection.send_command(task.host[ 'vendor_vars']['show interfaces brief']) for interface in task.host['interfaces']: if interface.svi or interface.subinterface: result += 'Interface {} mode: routed (by interface type)'.format( interface.name) continue if task.host.platform == 'nxos': interface_name = cisco_compact_name(interface.name) brief_line_start = interfaces_brief_output.index(interface_name) # 'find' will cover end of output (last line) situations brief_line_end = interfaces_brief_output.find('\n', brief_line_start) brief_line = interfaces_brief_output[ brief_line_start:brief_line_end] if 'routed' in brief_line: interface.mode = 'routed' elif 'trunk' in brief_line or 'access' in brief_line: interface.mode = 'switched' else: raise ValueError('Can not determine interface {} mode'.format( interface.name)) elif task.host.platform == 'huawei_vrpv8': interface_full_output = connection.send_command(task.host[ 'vendor_vars']['show interface'].format(interface.name)) if 'Switch Port' in interface_full_output: interface.mode = 'switched' elif 'Route Port' in interface_full_output: interface.mode = 'routed' else: raise ValueError('Can not determine interface {} mode'.format( interface.name)) else: raise UnsupportedNOS('task received unsupported NOS - {}'.format( task.host.platform)) result += '\tInterface {} mode: {}'.format(interface.name, interface.mode) return Result(host=task.host, result=result)
def run_backup( # pylint: disable=too-many-arguments task: Task, logger, global_settings, remove_regex_dict, replace_regex_dict) -> Result: r"""Backup configurations to disk. Args: task (Task): Nornir task individual object remove_regex_dict (dict): {'cisco_ios': ['^Building\\s+configuration.*\\n', '^Current\\s+configuration.*\\n', '^!\\s+Last\\s+configuration.*'], 'arista_eos': ['.s*']} replace_regex_dict (dict): {'cisco_ios': [{'regex_replacement': '<redacted_config>', 'regex_search': 'username\\s+\\S+\\spassword\\s+5\\s+(\\S+)\\s+role\\s+\\S+'}]} Returns: result (Result): Result from Nornir task """ obj = task.host.data["obj"] backup_obj = GoldenConfig.objects.filter(device=obj).first() if not backup_obj: backup_obj = GoldenConfig.objects.create(device=obj, ) backup_obj.backup_last_attempt_date = task.host.defaults.data["now"] backup_obj.save() backup_directory = get_repository_working_dir("backup", obj, logger, global_settings) backup_path_template_obj = render_jinja_template( obj, logger, global_settings.backup_path_template) backup_file = os.path.join(backup_directory, backup_path_template_obj) if global_settings.backup_test_connectivity is not False: task.run( task=dispatcher, name="TEST CONNECTIVITY", method="check_connectivity", obj=obj, logger=logger, default_drivers_mapping=get_dispatcher(), ) running_config = task.run( task=dispatcher, name="SAVE BACKUP CONFIGURATION TO FILE", method="get_config", obj=obj, logger=logger, backup_file=backup_file, remove_lines=remove_regex_dict.get(obj.platform.slug, []), substitute_lines=replace_regex_dict.get(obj.platform.slug, []), default_drivers_mapping=get_dispatcher(), )[1].result["config"] backup_obj.backup_last_success_date = task.host.defaults.data["now"] backup_obj.backup_config = running_config backup_obj.save() logger.log_success( obj, "Successfully extracted running configuration from device.") return Result(host=task.host, result=running_config)
def napalm_configure(task, config=None, **kwargs): """ Nornir Task function to send confgiuration to devices using ``nornir_napalm.plugins.tasks.napalm_configure`` plugin. :param kwargs: any additional arguments to use with ``nornir_napalm.plugins.tasks.napalm_configure`` plugin :param config: (str or list) configuration string or list of commands to send to device :return result: Nornir result object with task execution results """ # run sanity check if not HAS_NAPALM: return Result( host=task.host, failed=True, exception="No nornir_napalm found, is it installed?", ) # get configuration if "commands" in task.host.data.get("__task__", {}): config = task.host.data["__task__"]["commands"] elif "filename" in task.host.data.get("__task__", {}): config = task.host.data["__task__"]["filename"] # transform configuration to string if list/tuple given if isinstance( config, ( list, tuple, ), ): config = "\n".join(config) # push config to device task.run(task=nornir_napalm_configure, configuration=config, name="napalm_configure", **kwargs) # set skip_results to True, for ResultSerializer to ignore # results for grouped task itself, which are usually None return Result(host=task.host, skip_results=True)
def set_loopbacks(task: Task,): loopbacks = task.host.data['loopbacks'] task.run(task=netmiko_send_config, config_commands = ["interface loopback 99"]) # clock=task.run(task=netmiko_send_command, command_string="show clock") return Result( host=task.host, result=f"{task.host.name} loopbacks configured to - {loopbacks}" )
def _task_group_netmiko_send_commands(task, commands): # run commands import ipdb; ipdb.set_trace() for command in commands: task.run( task=netmiko_send_command, command_string=command, name=command ) return Result(host=task.host)
def test_nornsible_print_task_no_results(): test_result = AggregatedResult("testresult") test_result["localhost"] = MultiResult("testresult") test_result["localhost"].append( Result(host="localhost", result="Task skipped", failed=False, changed=False)) output = print_result(test_result) assert output is None
def pyez_commit(task: Task, ) -> Result: device = task.host.get_connection(CONNECTION_NAME, task.nornir.config) device.timeout = 300 config = Config(device) if config.commit_check() == True: config.commit() else: config.rollback() config.unlock() return Result(host=task.host, result=f"Successfully committed")
def get_version(task: Task) -> Result: # nornir manages the connection automatically using the Connection plugin # To retrieve it you can just call the following method. Note that # CONNETION_NAME needs to match the name we used when registering the plugin device = task.host.get_connection(CONNECTION_NAME, task.nornir.config) # now we are ready to use the library given to us by the vendor version_info = device.get_version() return Result(host=task.host, result=version_info)
def validate_bgp_neighbors(task): missing_neighbors = [] result = task.run(task=networking.napalm_get, getters=["bgp_neighbors"]) for neighbor in task.host.data["expected_state"]["bgp"][ "expected_neighbors"]: if neighbor not in result.result["bgp_neighbors"]["global"]["peers"]: missing_neighbors.append(neighbor) continue if result.result["bgp_neighbors"]["global"]["peers"][neighbor][ "is_up"] is False: missing_neighbors.append(neighbor) task.results.pop() if missing_neighbors: result = f"The following neighbor(s) are missing or down: {missing_neighbors}" return Result(host=task.host, result=result, failed=True, changed=True) result = "All bgp neighbors validated successfully!" return Result(host=task.host, result=result, failed=False, changed=False)
def show_isis_nei(task): response = task.run(task=send_command, command="show isis nei") # print(response.result) data_to_parse = response.result # create parser object and parse data using template: parser = ttp(data=data_to_parse, template=ttp_template) parser.parse() # print result in JSON format results = parser.result(format='json')[0] return Result(host=task.host, result=results)
def BF_assert_no_unusedStructures(task: Task) -> Result: result = bfq.unusedStructures().answer() result = { 'Results': result['answerElements'][0]['rows'], 'Summary': result['answerElements'][0]['summary'], } assert result['Summary']['numResults'] == 0, '\n' + pformat(result) return Result(host=task.host, result=result)
def netconf_commit(task, **kwargs): """ Nornir task to issue a NETCONF commit RPC with optional keyword arguments. On most platforms, this copies the candidate config to the running config. """ conn = task.host.get_connection("netconf", task.nornir.config) result = conn.commit(**kwargs) return Result(host=task.host, result=result)
def my_napalm_get(task: Task) -> Result: result = task.run( task=napalm_get, name='Facts', getters=['facts'] ) return Result( host=task.host, result=f'Task {task.name} on host {task.host} {"failed" if result.failed else "completed succesfully"}' )
def biography(task: Task) -> Result: with open("biography.txt", "w") as bio: task.host.open_connection(connection="ragnarok", configuration=task.host.data) for oneliner in task.host.data["nested_data"]["catch_phrases"]: bio.write(f"{oneliner}\n") result = "Creation of biography.txt was successful" task.host.close_connection( ) # experimentation with manual open / close connections return Result(host=task.host, result=result)
def my_netmiko_command(task: Task) -> Result: result = task.run( task=netmiko_send_command, command_string = "show lldp neighbor", use_textfsm=True ) return Result( host=task.host, result=f'Task {task.name} on host {task.host} {"failed" if result.failed else "completed succesfully"}' )
def wb_sdata( task: Task, workbook: str, sheetname: str, data_only: bool = True, keep_vba: bool = True, ) -> Result: """Loads a specific sheet from a workbook(xlsx file). Creates a list of dictionaries using the first row as the keys. Arguements: workbookfile: Full path to .xlsx file\n sheetname: Worksheet Name\n data_only: Boolean\n keep_vba: Boolean\n Examples: nr.run(task=wb_sdata, workbookfile="example-wb.xlsx', sheetname='ip_data') Returns: Result object with the following attributes set: * result (''list''): list of dictionaries with data from the specific worksheet within the workbook. Notes: There are several flags that can be used in load_workbook. data_only: controls whether cells with formulas have either the formula (default) or the value stored the last time Excel read the sheet. keep_vba: controls whether any Visual Basic elements are preserved or not (default). If they are preserved they are still not editable. """ wb_obj = load_workbook(filename=workbook, keep_vba=keep_vba, data_only=data_only) wsheet = wb_obj[sheetname] data_key = [] for value in wsheet.iter_rows(values_only=True): for key in value: data_key.append(key.strip()) break rows = [] for rows_list in wsheet.iter_rows(values_only=True, min_row=2): row = [] for values in rows_list: row.append(values) results = dict(zip(data_key, row)) rows.append(results) return Result(host=task.host, result=rows)
def get_interfaces_vrf_binding(task, interface_list=None): '''Nornir task to identify if interfaces bound to any VRF instance. If interface is in switched mode or not bound to any VRF it's vrf attribute will be set to None. If interface list is provided, new list of utils.switch_objects.SwitchInterface will be generated and assigned to task.host['interfaces'], so existed ones would be dropped. Otherwise existed list in task.host['interfaces'] would be used. Arguments: * task - instance or nornir.core.task.Task * interface_list (defaults to None) - list of strings, which represents switch interface names Returns: * instance of nornir.core.task.Result ''' if interface_list: task.host['interfaces'] = [SwitchInterface(x) for x in interface_list] connection = task.host.get_connection('netmiko', None) result = 'Interfaces to VRF bindings:\n' vrf_interfaces = connection.send_command( task.host['vendor_vars']['show vrf interfaces'].format('')) if task.host.platform == 'nxos': vrf_interfaces = '\n'.join(vrf_interfaces.strip().split('\n')[1:]) refind = re.findall( r'([0-9A-Za-z/:.]+)\s+([0-9A-Za-z_:.-]+)\s+(\d+|N/A)', vrf_interfaces) vrf_bind_map = {m[0]: m[1] for m in refind} elif task.host.platform == 'huawei_vrpv8': vrf_bind_map = {} vrfs = vrf_interfaces.split('VPN-Instance Name and ID')[1:] for vrf in vrfs: vrf_name = vrf[vrf.index(':')+1:vrf.index(',')].strip() if not re.search(r'interface number\s*:\s*0', vrf, flags=re.I): interfaces_list = vrf[vrf.index( 'Interface list : ')+17:].split('\n') else: interfaces_list = [] vrf_bind_map.update({interface.strip( ', '): vrf_name for interface in interfaces_list}) else: raise UnsupportedNOS('task received unsupported NOS - {}'.format( task.host.platform)) for interface in task.host['interfaces']: if interface.mode == 'switched': interface.vrf = None result += '\tInterface {} is swithed (L2)\n'.format(interface.name) continue if interface.name in vrf_bind_map: interface.vrf = vrf_bind_map[interface.name] result += '\tInterface {} bound to VRF {}\n'.format(interface.name, interface.vrf) else: interface.vrf = None result += '\tInterface {} is not bound to any VRF\n'.format( interface.name) return Result(host=task.host, result=result)
def get_interfaces_ip_neighbors(task, interface_list=None): '''Nornir task to get switch interfaces IP neighbors (both IPv4 (ARP) and IPv6 (NDP)). If interface list is provided, new list of utils.switch_objects.SwitchInterface will be generated and assigned to task.host['interfaces'], so existed ones would be dropped. Otherwise existed list in task.host['interfaces'] would be used. Arguments: * task - instance or nornir.core.task.Task * interface_list (defaults to None) - list of strings, which represents switch interface names Returns: * instance of nornir.core.task.Result ''' if interface_list: task.host['interfaces'] = [SwitchInterface( x, mode='routed') for x in interface_list] connection = task.host.get_connection('netmiko', None) result = 'IP neighbors learned on interfaces:\n' for interface in task.host['interfaces']: result += '\tInterface {} '.format(interface.name) if interface.mode != 'routed': interface.ipv4_neighbors = 0 interface.ipv6_neighbors = 0 result += 'Interface {} is in switched mode'.format(interface.name) continue # must use VRF name in 'non-default' VRF for Cisco, but unnecessary in # any case for Huawei; we force VRF usage on Cisco even for 'default' vrf_name = task.host.get('vrf_name', 'default') ipv4_neighbors = connection.send_command( task.host['vendor_vars']['show ipv4 neighbors interface'].format( interface.name, vrf_name)) ipv6_neighbors = connection.send_command( task.host['vendor_vars']['show ipv6 neighbors interface'].format( interface.name, vrf_name)) if task.host.platform == 'nxos': search_line = r'Total number of entries:\s+(\d+)' elif task.host.platform == 'huawei_vrpv8': search_line = r'Dynamic:(?:\s+)?(\d+)' else: raise UnsupportedNOS('task received unsupported NOS - {}'.format( task.host.platform)) # Huawei returns empty output for 'down' interfaces if not ipv4_neighbors: interface.ipv4_neighbors = 0 else: interface.ipv4_neighbors = int(re.search( search_line, ipv4_neighbors).group(1)) if not ipv6_neighbors: interface.ipv6_neighbors = 0 else: interface.ipv6_neighbors = int(re.search( search_line, ipv6_neighbors).group(1)) result += 'IPv4 neighbors: {}; IPv6 neighbors: {}\n'.format( interface.ipv4_neighbors, interface.ipv6_neighbors) return Result(host=task.host, result=result)
def pyez_int_terse(task: Task, terse: str = True) -> Result: device = task.host.get_connection(CONNECTION_NAME, task.nornir.config) if terse: data = device.rpc.get_interface_information(terse=True) else: data = device.rpc.get_interface_information(terse=False) data = etree.tostring(data, encoding='unicode', pretty_print=True) parsed = xmltodict.parse(data) clean_parse = json.loads(json.dumps(parsed)) return Result(host=task.host, result=clean_parse)
def find_vrf(task): '''Nornir task to detect if VRF exists on a switch. Task will fail if no VRF found, which prevents other tasks on same host from being executed. VRF name contained in task.host['vrf_name']. Arguments: * task - instance or nornir.core.task.Task Returns: * instance of nornir.core.task.Result ''' connection = task.host.get_connection('netmiko', None) output = connection.send_command(task.host['vendor_vars']['show vrf']) if not re.search(task.host['vendor_vars']['vrf regexp'].format( task.host['vrf_name']), output): return Result(host=task.host, failed=True, result='VRF {} is not exist on device'.format( task.host['vrf_name'])) else: return Result(host=task.host, result='VRF {} configured on device'.format( task.host['vrf_name']))
def edit_nc_config_from_yaml(task, feature): with open(f"host_vars/{feature}/{task.host}.yaml") as f: yaml = YAML(typ="safe") data = yaml.load(f) xml = nojinja.dict_to_xml(data, root="config") xml_str = etree.tostring(xml).decode("utf-8") result = task.run(task=netconf_edit_config, target="candidate", config=xml_str) return Result(host=task.host, result=result.result)
def eline(task: Task, **kwargs): service_id = kwargs.pop("_service_id") service_hosts = kwargs.pop("_service_hosts") service_type = kwargs.pop("_service_type") operation = kwargs.pop("_service_operation") data = {} data.update(kwargs) data["service_id"] = service_id if operation.lower() == "replace": data["operation"] = "replace" other_host = "" for h in service_hosts: if h not in task.host.name: other_host = h break saps = kwargs.get("saps") local_epipe = False if isinstance(saps, list): if len(saps) == 2: # local epipe local_epipe = True else: raise ValueError(f"saps must be a list") if not local_epipe: if not other_host: raise Exception(f"Eline {service_id} has not remote host") other_sys_ip = task.nornir.inventory.hosts[other_host]["config"].get( "/router/router-name=Base/interface/interface-name=system", {} ) if not other_sys_ip: raise Exception(f"Cannot get system-ip for {other_host} from inventory") other_sys_ip = other_sys_ip["ipv4"]["primary"]["address"] sdp_info = task.host["config"].get("/service/sdp", {}) sdp_id = "" if sdp_info.get("_count", None): # multiple sdp's for seq, attrs in sdp_info.items(): if str(seq).startswith("_"): continue if attrs.get("far-end", {}).get("ip-address", "") == other_sys_ip: sdp_id = attrs.get("sdp-id") break else: sdp_id = sdp_info.get("sdp-id", "") if not sdp_id: raise Exception(f"Cannot find appropriate SDP to {other_sys_ip}") data["sdp_id"] = sdp_id elif operation.lower() == "delete": data["operation"] = "delete" else: raise Exception(f"unknown action {operation}") return Result(host=task.host, result=data)