Beispiel #1
0
def identify_breakout_ports(task, interface_list=None):
    '''Nornir task to identify interfaces created by breakout. Such state
    identified by interface name. For NX-OS there will be 2 '/' symbols, for
    VRPv8 there will be ':' symbol.
    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]
    result = 'Interfaces created by breakout:\n'
    if task.host.platform not in ['nxos', 'huawei_vrpv8']:
        raise UnsupportedNOS('task received unsupported NOS - {}'.format(
            task.host.platform))
    for interface in task.host['interfaces']:
        if task.host.platform == 'nxos' and interface.name.count('/') == 2:
            interface.breakout = True
            result += f'\tInterface {interface.name} created by breakout'
        elif task.host.platform == 'huawei_vrpv8' and interface.name.find(
                ':') != -1:
            interface.breakout = True
            result += f'\tInterface {interface.name} created by breakout'
        else:
            interface.breakout = False
    return Result(host=task.host, result=result)
Beispiel #2
0
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)
Beispiel #3
0
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)
Beispiel #4
0
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)
Beispiel #5
0
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 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 count_macs(nos, mac_table):
     '''Grab number of MACs from output, in case of NX-OS we must count
     lines, for VRPv8 we can found number listed.
     Arguments:
         * nos - NOS name
         * mac_table - CLI output with MAC table
     Returns:
         * number of MACs in table
     '''
     if nos == 'nxos':
         delimeter = mac_table.find('---')
         # Cisco returns empty output if no MACs learned
         if delimeter == -1:
             return 0
         else:
             table_start = mac_table.index('\n', delimeter)
             return len(mac_table[table_start:].strip().split('\n'))
     elif nos == 'huawei_vrpv8':
         return int(re.search(r'Total items: (\d+)', mac_table).group(1))
     else:
         raise UnsupportedNOS(
             'task received unsupported NOS - {}'.format(nos))
Beispiel #8
0
def get_transceiver_stats(task, interface_list=None):
    '''Nornir task to identify interfaces optical module stats, like RX power
    and module type.
    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]
    result = 'Interface transceiver statistics:\n'
    connection = task.host.get_connection('netmiko', None)
    if task.host.platform not in ['nxos', 'huawei_vrpv8']:
        raise UnsupportedNOS('task received unsupported NOS - {}'.format(
            task.host.platform))
    for interface in task.host['interfaces']:
        if task.host.platform == 'nxos':
            pass
        elif task.host.platform == 'huawei_vrpv8':
            transceiver_stats = connection.send_command(
                task.host['vendor_vars'][
                    'show interface transceiver detail'].format(
                        interface.name))
            if f'{interface} transceiver information' not in transceiver_stats:
                interface.transceiver = None
                interface.rx_power = None
                interface.optical_lanes = None
                interface.ddm = None
                interface.module_type = None
                result += (f'Interface {interface.name} has no'
                           f'transceiver inserted or it is copper port')
                continue
            ddm_supported = re.search(
                    r'Digital Diagnostic Monitoring\s+:(YES|NO)',
                    transceiver_stats)
            interface.ddm = True if ddm_supported.group(1) == 'YES' else False
            vendor_name = re.search(r'Vendor Name\s+:(.+)', transceiver_stats)
            transceiver_model = re.search(r'Vendor Part Number\s+:(.+)',
                                          transceiver_stats)
            interface.transceiver = vendor_name.group(
                    1) + ' ' + transceiver_model.group(1)
            interface.module_type = re.search(
                    r'Transceiver Type\s+:(.+)',
                    transceiver_stats).group(1).replace('_', '-')
            if not interface.ddm or 'Current RX Pow' not in transceiver_stats:
                # DDM can be supported, but no stats listed
                interface.optical_lanes = None
                interface.rx_power = None
                result += (f'Interface {interface.name} is of '
                           f'{interface.module_type} type and '
                           f'{interface.transceiver} model, but '
                           f'DDM either not supported or can not be'
                           f'gathered')
                continue
            if 'Lane' not in transceiver_stats:
                interface.optical_lanes = 1
                rx_power = re.search(
                    r'Current RX Power \(dBm\)\s+:(-?\d{1,2}\.\d{1,2})',
                    transceiver_stats)
                interface.rx_power = float(rx_power.group(1))
            else:
                rx_pwr_start = transceiver_stats.index(
                        'Current RX Power (dBm)')
                next_new_line = transceiver_stats.index('\n', rx_pwr_start)
                rx_pwr_end = transceiver_stats.index('Default RX Power',
                                                     rx_pwr_start)
                rx_pwr_chunk = transceiver_stats[next_new_line:rx_pwr_end]
                lanes_rxs = re.findall(r'(-?\d{1,2}\.\d{1,2})', rx_pwr_chunk)
                interface.rx_power = list(map(float, lanes_rxs))
                interface.optical_lanes = len(interface.rx_power)
            result += (
                f'Interface {interface.name} is of {interface.module_type} '
                f'type and {interface.transceiver} model. RX power is: '
                f'{interface.rx_power}, lane number is '
                f'{interface.optical_lanes}')
    return Result(host=task.host, result=result)
Beispiel #9
0
def find_lag_hierarchy(task, interface_list=None):
    '''Nornir task to identify LAG relationship, or which interface is member
    of which LAG. For LAG list of members recorded, or just empty list. For
    physical interfaces it's either a string (LAG inteface name) or 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 = 'LAG interfaces relationship:\n'
    int_brief_output = connection.send_command(
            task.host['vendor_vars']['show interfaces brief'])
    hier = {}
    if task.host.platform == 'nxos':
        in_lag_ints = re.findall(r'^(Eth[0-9/]+).+(\d+)$', int_brief_output,
                                 re.M)
        for match in in_lag_ints:
            lag_name = 'port-channel' + match[1]
            if lag_name not in hier:
                hier[lag_name] = []
            hier[lag_name].append(match[0].replace('Eth', 'Ethernet'))
    elif task.host.platform == 'huawei_vrpv8':
        lags_start = int_brief_output.find('Eth-Trunk')
        if lags_start != -1:
            for line in int_brief_output[lags_start:].split('\n'):
                if line.startswith('Eth-Trunk'):
                    lag = line.split(' ')[0]
                    hier[lag] = []
                elif line.startswith(' '):
                    hier[lag].append(line.strip().split(' ')[0])
                else:
                    break
    else:
        raise UnsupportedNOS('task received unsupported NOS - {}'.format(
            task.host.platform))
    for int_ in task.host['interfaces']:
        if int_.svi or int_.subinterface:
            result += "\tInterface {} can't be in LAG\n".format(int_.name)
            continue
        elif int_.lag:
            int_.members = hier[int_.name] if int_.name in hier else []
            result += "\tLAG {} has members {}\n".format(int_.name,
                                                         int_.members)
        else:
            int_.member = None
            for lag in hier:
                if int_.name in hier[lag]:
                    int_.member = lag
                    break
            if int_.member:
                result += "\tInterface {} is member of {}".format(int_.name,
                                                                  int_.member)
            else:
                result += "\tInterface {} is not member of any LAG".format(
                        int_.name)
    if not hier:
        result = 'No LAG present on a device'
    return Result(host=task.host, result=result)
Beispiel #10
0
def get_interfaces_vlan_list(task, interface_list=None):
    '''Nornir task to get switch interfaces VLAN list and switching mode. Trunk
    and access modes are supported as of now. In any case PVID grabbed, which
    is access VLAN for access interface and native VLAN for trunk, and for
    trunks allowed VLAN list also gathered. 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
    '''
    def int_not_switching(interface):
        '''Set switching attributes to None if interface in routed mode.
        Arguments:
            * interface - instance of utils.SwitchInterface
        Returns string that describes the result, i.e. 'interface not
            switching'
        '''
        interface.switch_mode = None
        interface.pvid = None
        interface.vlan_list = None
        return '\tInterface {} is not switching'.format(interface.name)

    def deaggregate_vlans(vlan_list, separator=' '):
        '''Translate string with VLAN numbers and ranges to list of integers.
        Arguments:
            * vlan_list - string that represents VLAN list, grabbed out of
                switch
            * separator (defaults to ' ') - character, that separates VLAN
                numbers on the list
        Returns list of integers
        '''
        new_list = []
        for num in vlan_list.strip().split(separator):
            # we grub newline characters on Huawei
            if not num or num == '\n':
                continue
            elif '-' not in num:
                new_list.append(int(num))
            else:
                new_list.extend(range(int(num.split('-')[0]),
                                      int(num.split('-')[1])+1))
        return new_list

    if interface_list:
        task.host['interfaces'] = [SwitchInterface(x) for x in interface_list]
    connection = task.host.get_connection('netmiko', None)
    result = 'Interfaces switching attributes:\n'
    for interface in task.host['interfaces']:
        if interface.mode != 'switched':
            result += int_not_switching(interface)
            continue
        switchport_output = connection.send_command(
                task.host['vendor_vars']['show interface switchport'].format(
                    interface.name))
        if task.host.platform == 'nxos':
            if 'switchport: disabled' in switchport_output.lower():
                result += int_not_switching(interface)
                continue
            interface.switch_mode = re.search(
                r'Operational Mode: (trunk|access)',
                switchport_output).group(1)
            if interface.switch_mode == 'access':
                interface.pvid = int(re.search(
                    r'Access Mode VLAN: (\d{1,4})',
                    switchport_output).group(1))
                interface.vlan_list = [interface.pvid]
            elif interface.switch_mode == 'trunk':
                interface.pvid = int(re.search(
                    r'Trunking Native Mode VLAN: (\d{1,4})',
                    switchport_output).group(1))
                interface.vlan_list = deaggregate_vlans(re.search(
                    r'Trunking VLANs Allowed: ([0-9,-]+)',
                    switchport_output).group(1), separator=',')
        elif task.host.platform == 'huawei_vrpv8':
            # Huawei return nothing for non switched port
            if not switchport_output:
                result += int_not_switching(interface)
                continue
            # we need re.S because long VLAN list will be separated by newlines
            vlan_regex = re.compile(r'''
            (?:\d{1,3})?GE\d{1,2}/\d{1,2}/\d{1,2}(?::\d)?\s+# interface name
            (access|trunk)\s+# switchport type
            (\d{1,4})\s+# PVID
            ((?:\d|--).*)# Allowed VLAN list
            ''', re.X | re.S)
            vlan_search = vlan_regex.search(switchport_output)
            interface.switch_mode = vlan_search.group(1)
            interface.pvid = int(vlan_search.group(2))
            if interface.switch_mode == 'access':
                interface.vlan_list = [interface.pvid]
            elif interface.switch_mode == 'trunk':
                interface.vlan_list = deaggregate_vlans(vlan_search.group(3))
        else:
            raise UnsupportedNOS('task received unsupported NOS - {}'.format(
                task.host.platform))
        result += '\tInterface {} is in {} mode, PVID is {}, '.format(
                interface.name, interface.switch_mode, str(interface.pvid))
        result += 'allowed VLANs: {}\n'.format(', '.join(str(
            interface.vlan_list)))
    return Result(host=task.host, result=result)
Beispiel #11
0
def get_interfaces_general_info(task, interface_list=None):
    '''Nornir task to get switch interfaces general information like speed,
    description, MAC address, etc. 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 characteristics:\n'
    for interface in task.host['interfaces']:
        interface_full_output = connection.send_command(
                task.host['vendor_vars']['show interface'].format(
                    interface.name))
        if task.host.platform == 'nxos':
            if 'Description:' not in interface_full_output:
                interface.description = None
            else:
                interface.description = re.search(
                    r'Description: (.+)\n', interface_full_output).group(1)
            interface.mac_address = convert_mac_address(re.search(
                r'address(?:: | is\s+)([a-z0-9.]+)\s',
                interface_full_output).group(1))
            interface.mtu = int(re.search(r'MTU (\d{4}) bytes',
                                          interface_full_output).group(1))
            if getattr(interface, 'oper_status', 'down') == 'down' or \
                    interface.svi or interface.subinterface:
                # SVIs and subinterface doesn't have speed, duplex or load; and
                # this attributes has no meaningful values on down interfaces
                # if 'oper_status' was not set, let's consider it's down
                (interface.speed, interface.duplex, interface.load_in,
                    interface.load_out) = (None, None, None, None)
                continue
            # as it can be full or Full duplex - ignore case
            speed_and_duplex = re.search(r'(full|half)-duplex, (\d{1,3}) gb/s',
                                         interface_full_output, flags=re.I)
            interface.duplex = speed_and_duplex.group(1)
            interface.speed = int(speed_and_duplex.group(2))
            interface.load_in = convert_load(re.search(
                r'input rate (\d+) bits/sec,', interface_full_output).group(1))
            interface.load_out = convert_load(re.search(
                r'output rate (\d+) bits/sec,',
                interface_full_output).group(1))
        elif task.host.platform == 'huawei_vrpv8':
            descr = re.search(r'Description: (.+)\n', interface_full_output)
            interface.description = descr.group(1) if descr else None
            interface.mac_address = convert_mac_address(re.search(
                r'Hardware address is ([a-z0-9-]+)\s',
                interface_full_output).group(1))
            interface.mtu = int(re.search(
                r'Maximum (?:Transmit Unit|Frame Length) is (\d{4})',
                interface_full_output).group(1))
            if getattr(interface, 'oper_status', 'down') == 'down' or \
                    interface.svi or interface.subinterface:
                # SVIs and subinterface doesn't have speed, duplex or load; and
                # this attributes has no meaningful values on down interfaces
                # if 'oper_status' was not set, let's consider it's down
                (interface.speed, interface.duplex, interface.load_in,
                    interface.load_out) = (None, None, None, None)
                continue
            if interface.lag:
                interface.duplex = 'full'
                interface.speed = int(re.search(
                    r'Current BW : (\d+)Gbps,',
                    interface_full_output).group(1))
            else:
                interface.duplex = re.search(
                        r'Duplex:\s+(FULL|HALF),',
                        interface_full_output).group(1).lower()
                interface.speed = int(re.search(
                    r'Speed:\s+(\d+),',
                    interface_full_output).group(1))/1000
            interface.load_in = convert_load(re.search(
                r'input rate:? (\d+) bits/sec,',
                interface_full_output).group(1))
            interface.load_out = convert_load(re.search(
                r'output rate:? (\d+) bits/sec,',
                interface_full_output).group(1))
        else:
            raise UnsupportedNOS('task received unsupported NOS - {}'.format(
                task.host.platform))
        result += '\tInterface {} / {}: MAC address {}, MTU {} bytes,'.format(
                interface.name, interface.description, interface.mac_address,
                interface.mtu)
        result += '\t\t{} Gb/s, {} duplex, {}/{} in/out Gb/s load\n'.format(
                interface.speed, interface.duplex, interface.load_in,
                interface.load_out)
    return Result(host=task.host, result=result)
Beispiel #12
0
def get_interfaces_ip_addresses(task, interface_list=None):
    '''Nornir task to get switch interfaces IP addresses (both IPv4 and IPv6).
    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]
    result = 'IP addresses on interfaces:\n'
    connection = task.host.get_connection('netmiko', None)
    for interface in task.host['interfaces']:
        ipv4_status = connection.send_command(
            task.host['vendor_vars']['show ipv4 interface'].format(
                interface.name))
        ipv6_status = connection.send_command(
            task.host['vendor_vars']['show ipv6 interface'].format(
                interface.name))
        if task.host.platform == 'nxos':
            if 'IP is disabled' not in ipv4_status:
                pattern = re.compile(
                        r'IP address: '
                        r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
                        r', '
                        r'IP subnet: \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
                        r'/(\d{1,2})( secondary)?')
                match = pattern.findall(ipv4_status)
                for address in match:
                    interface.ipv4_addresses.append(IPAddress(
                            address[0], address[1], address[2]))
            if 'IPv6 is disabled' not in ipv6_status:
                primary_address = re.search(r'IPv6 address: (\S+)',
                                            ipv6_status).group(1)
                primary_prefix_length = re.search(
                        r'IPv6 subnet:  \S+/(\d{1,3})', ipv6_status).group(1)
                link_local = re.search(r'IPv6 link-local address: (\S+)',
                                       ipv6_status).group(1)
                interface.ipv6_addresses.append(IPAddress(
                    primary_address, primary_prefix_length))
                interface.ipv6_addresses.append(IPAddress(
                    link_local, '64', True))
                sec_start = ipv6_status.find('Secondary configured addresses')
                if sec_start != -1:
                    sec_end = ipv6_status.index('IPv6 link-local address')
                    match = re.findall(r'([0-9A-Fa-f:]+)/(\d{1,3})',
                                       ipv6_status[sec_start:sec_end])
                    for address in match:
                        interface.ipv6_addresses.append(IPAddress(
                            address[0], address[1], True))
        elif task.host.platform == 'huawei_vrpv8':
            if 'Internet Address is' in ipv4_status:
                pattern = re.compile(r'Internet Address is '
                                     r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
                                     r'/(\d{1,2})( Sub)?')
                match = pattern.findall(ipv4_status)
                for address in match:
                    interface.ipv4_addresses.append(IPAddress(
                        address[0], address[1], address[2]))
            if 'The IPv6 address does not exist' not in ipv6_status:
                interface.ipv6_addresses.append(IPAddress(re.search(
                    r'link-local address is ([0-9A-Fa-f:]+)',
                    ipv6_status).group(1), '64', True))
                match = re.findall(
                        r'([0-9A-Fa-f:]+), subnet is [0-9A-Fa-f:]+/(\d{1,3})',
                        ipv6_status)
                for address in match:
                    # There is no primary IPv6 address concept for Huawei,
                    # unlike Cisco
                    interface.ipv6_addresses.append(IPAddress(
                        address[0], address[1], True))
        else:
            raise UnsupportedNOS('task received unsupported NOS - {}'.format(
                task.host.platform))
        result += 'Interface {} IP addresses:\n'.format(interface.name)
        if len(interface.ipv4_addresses) == 0:
            result += '\tNo IPv4 addresses\n'
        else:
            result += '\tIPv4: {}\n'.format(', '.join(
                [str(x) for x in interface.ipv4_addresses]))
        if len(interface.ipv6_addresses) == 0:
            result += '\tNo IPv6 addresses\n'
        else:
            result += '\tIPv6: {}\n'.format(', '.join(
                [str(x) for x in interface.ipv6_addresses]))
    return Result(host=task.host, result=result)
def check_vrf(task, vrf_name):
    '''Nornir task that execute different subtasks to get an high level
    overview of VRF operational state on a ToR switch. Criterias for final
    rating are opinionated. In theory task may run well on other types of
    switches.
    Arguments:
        * task - instance of nornir.core.task.Task
        * vrf_name - name of VRF to check for
    Returns:
        * instance of nornir.core.task.Result
    '''
    with open('operations/vendor_vars.json', 'r', encoding='utf-8') as jsonf:
        vendor_vars = json.load(jsonf)
    if task.host.platform == 'nxos':
        task.host['vendor_vars'] = vendor_vars['Cisco Nexus']
        nos_name = 'Cisco NX-OS'
    elif task.host.platform == 'huawei_vrpv8':
        task.host['vendor_vars'] = vendor_vars['Huawei CE']
        nos_name = 'Huawei VRPv8'
    else:
        raise UnsupportedNOS('{} is unsupported or bogus'.format(
            task.host.platform))
    task.host['vrf_name'] = vrf_name
    task.run(task=check_vrf_status.find_vrf,
             name='Check if VRF exists on node')
    task.run(task=check_vrf_status.get_vrf_interfaces,
             name='Get VRF interfaces list')
    task.run(task=check_interfaces.check_interfaces_status,
             name='Check interfaces status for VRF')
    task.run(task=check_interfaces.get_interfaces_ip_addresses,
             name='Gather IP addresses for interfaces in VRF')
    task.run(task=check_interfaces.get_interfaces_ip_neighbors,
             name='Gather IP neighbors for interfaces in VRF')
    task.run(task=check_mac_table.get_interfaces_macs,
             name='Gather learned MAC for interfaces in VRF')
    task.run(task=check_vrf_status.check_vrf_bgp_neighbors,
             name='Get BGP neighbors in VRF and they state')
    result = 'Host {} running {}, VRF {} status:\n'.format(
            task.host.name, nos_name, task.host['vrf_name'])
    oper_up_interfaces = [x for x in task.host[
        'interfaces'] if x.oper_status == 'up']
    result += '\t{} interfaces in VRF, {} of them operationally up\n'.format(
            len(task.host['interfaces']), len(oper_up_interfaces))
    ipv4_addresses = []
    ipv6_addresses = []
    for interface in task.host['interfaces']:
        ipv4_addresses.extend(interface.ipv4_addresses)
        ipv6_addresses.extend([x for x in interface.ipv6_addresses
                              if not x.address.is_link_local])
    result += '\t{}/{} v4/v6 addresses present (except link-locals)\n'.format(
            len(ipv4_addresses), len(ipv6_addresses))
    v4_neighbors = sum([x.ipv4_neighbors for x in task.host['interfaces']])
    v6_neighbors = sum([x.ipv6_neighbors for x in task.host['interfaces']])
    result += '\t{}/{} v4/v6 neighbors learned on interfaces\n'.format(
            v4_neighbors, v6_neighbors)
    learned_macs = sum([x.macs_learned for x in task.host['interfaces']])
    result += '\t{} MAC addresses learned in VRF VLANs\n'.format(learned_macs)
    num_neighbors = len(task.host['bgp_neighbors'])
    established_neighbors = len([x for x in task.host[
        'bgp_neighbors'].values() if x.state == 'established'])
    neighbors_with_prefixes = []
    for neighbor in task.host['bgp_neighbors'].values():
        if neighbor.af['ipv4'] and neighbor.af['ipv4'].learned_routes:
            neighbors_with_prefixes.append(neighbor)
            continue
        elif neighbor.af['ipv6'] and neighbor.af['ipv6'].learned_routes:
            neighbors_with_prefixes.append(neighbor)
    result += '\t{} BGP neighbors configured, {} of them '.format(
        num_neighbors, established_neighbors)
    result += 'in established state, {} of them sent prefixes\n'.format(
        len(neighbors_with_prefixes))
    if not any([num_neighbors, established_neighbors,
                neighbors_with_prefixes]):
        overall_status = 'No VRF connectivity'
    elif not learned_macs:
        overall_status = 'No MACs learned, probably no devices connected'
    elif not any([v4_neighbors, v6_neighbors]):
        overall_status = 'No IP neighbors learned, devices may not be \
                properly configured'
    elif not any([ipv4_addresses, ipv6_addresses]):
        overall_status = 'No IP address configured in VRF'
    elif not oper_up_interfaces:
        overall_status = 'All interfaces down'
    elif not task.host['interfaces']:
        overall_status = 'No interfaces configured'
    else:
        overall_status = 'VRF looks good!'
    result += overall_status
    return Result(task.host, result=result)
def check_switch_interfaces(task, interface_names):
    '''Nornir task that execute different subtasks to get an high level
    overview of interfaces state and characteristics on switch. This binding
    didn't target execution on multiple hosts (because, obiously, you supply
    interface names to it), but in some cases it can work.
    Arguments:
        * task - instance of nornir.core.task.Task
        * interface_names - names of interfaces to check for
    Returns:
        * instance of nornir.core.task.Result
    '''
    with open('operations/vendor_vars.json', 'r', encoding='utf-8') as jsonf:
        vendor_vars = json.load(jsonf)
    if task.host.platform == 'nxos':
        task.host['vendor_vars'] = vendor_vars['Cisco Nexus']
    elif task.host.platform == 'huawei_vrpv8':
        task.host['vendor_vars'] = vendor_vars['Huawei CE']
    else:
        raise UnsupportedNOS('{} is unsupported or bogus'.format(
            task.host.platform))
    task.run(task=check_interfaces.sanitize_interface_list,
             name='Check provided interface names to be valid',
             interface_list=interface_names)
    task.run(task=check_interfaces.check_interfaces_status,
             name='Check for interfaces operational status')
    task.run(task=check_interfaces.get_interfaces_mode,
             name='Check for interfaces mode (L2/L3)')
    task.run(task=check_interfaces.get_interfaces_general_info,
             name='Get interfaces characteristics')
    task.run(task=check_interfaces.get_interfaces_ip_addresses,
             name='Get interfaces IP addresses')
    task.run(task=check_interfaces.get_interfaces_ip_neighbors,
             name='Get interfaces IP neighbors (ARP/ND)')
    task.run(task=check_mac_table.get_interfaces_macs,
             name='Get number of MACs learned on interfaces')
    task.run(task=check_interfaces.get_interfaces_vlan_list,
             name='Get interfaces switchport configuration')
    task.run(task=check_interfaces.get_interfaces_vrf_binding,
             name='Check if interfaces bound to VRF')
    task.run(task=check_interfaces.find_lag_hierarchy,
             name='Discover LAG relationships')
    task.run(task=check_interfaces.identify_breakout_ports,
             name='Identify if interface created by breakout')
    result = 'Interfaces state and characteristics:\n'
    for interface in task.host['interfaces']:
        result += '\tInterface {} with "{}" description\n'.format(
            interface.name, interface.description)
        result += '\t\tadmin status: {}, operational status:{}\n'.format(
            interface.admin_status, interface.oper_status)
        result += '\t\t{} mode, MAC address {}, MTU {} bytes\n'.format(
            interface.mode, interface.mac_address, interface.mtu)
        if interface.breakout:
            result += '\t\tInterface created by breakout (port-split)\n'
        if interface.oper_status == 'up' and not (interface.svi
                                                  or interface.subinterface):
            result += '\t\t{} Gb/s, {} duplex, '.format(
                interface.speed, interface.duplex)
            result += '{}/{} in/out Gb/s load\n'.format(
                interface.load_in, interface.load_out)
        if interface.mode == 'routed':
            if interface.ipv4_addresses:
                result += '\t\tIPv4 addresses on interface: {}\n'.format(
                    ', '.join([str(x) for x in interface.ipv4_addresses]))
            else:
                result += '\t\tNo IPv4 addresses\n'
            if interface.ipv6_addresses:
                result += '\t\tIPv6 addresses on interface: {}\n'.format(
                    ', '.join([str(x) for x in interface.ipv6_addresses]))
            else:
                result += '\t\tNo IPv6 addresses\n'
            result += '\t\tNumber or neighbors learned (ARP/NDP): '
            result += '{}/{}\n'.format(interface.ipv4_neighbors,
                                       interface.ipv6_neighbors)
            if interface.vrf:
                result += '\t\tInterface is bound to VRF {}\n'.format(
                    interface.vrf)
        if interface.mode == 'switched' or interface.svi:
            result += '\t\tMAC addresses learned on interface: {}\n'.format(
                interface.macs_learned)
        if interface.mode == 'switched':
            result += '\t\tVLANs configuration: {} mode, PVID - {}'.format(
                interface.switch_mode, interface.pvid)
            if interface.switch_mode == 'trunk':
                result += ', allowed list - {}'.format(interface.vlan_list)
            result += '\n'
        if interface.lag:
            result += '\t\tLAG members are: [{}]\n'.format(', '.join(
                interface.members))
        elif not interface.svi and not interface.subinterface:
            if interface.member:
                result += '\t\tInterface is a member of LAG: {}\n'.format(
                    interface.member)
    return Result(task.host, result=result)
def check_vrf_bgp_neighbors(task, af='both'):
    '''Nornir task to check state of BGP sessions with neighbors and grab they
    essintial parameters (ASN, session type, routed ID and number of prefixes
    learned for IPv4 unicast and/or IPv6 unicast.
    Arguments:
        * task - instance or nornir.core.task.Task
        * af (defaults to 'both') - which AF we are interested in: both, v4 or
            v6
    Returns:
        * instance of nornir.core.task.Result
    '''
    def get_n_record(host, raw_address):
        '''Check if utils.switch_objects.BGPNeighbor instance already exist in
        task.host['bgp_neighbors'] and grab it. If not create new and assign to
        that list.
        Arguments:
            * host - instance or nornir.core.task.Host
            * raw_address - string, which represents neighbor IP address
        Returns:
            * instance of utils.switch_objects.BGPNeighbor
        '''
        address = ipaddress.ip_address(raw_address).compressed
        if address not in host['bgp_neighbors'].keys():
            neighbor = BGPNeighbor(raw_address)
            host['bgp_neighbors'][neighbor.address.compressed] = neighbor
        else:
            neighbor = host['bgp_neighbors'][address]
        return neighbor

    connection = task.host.get_connection('netmiko', None)
    result = 'BGP neighbors in VRF {}:\n'.format(task.host['vrf_name'])
    if 'bgp_neighbors' not in task.host.keys():
        task.host['bgp_neighbors'] = {}
    check_af_v4 = True
    check_af_v6 = True
    if af == 'v4':
        check_af_v6 = False
    elif af == 'v6':
        check_af_v4 = False
    if check_af_v4:
        v4_output = connection.send_command(task.host['vendor_vars'][
            'show bgp ipv4 vrf neighbors'].format(task.host['vrf_name']))
    else:
        v4_output = None
    if check_af_v6:
        v6_output = connection.send_command(task.host['vendor_vars'][
            'show bgp ipv6 vrf neighbors'].format(task.host['vrf_name']))
    else:
        v6_output = None
    for output, af_name in zip([v4_output, v6_output], ['v4', 'v6']):
        if task.host.platform == 'nxos':
            if not output or 'BGP neighbor is' not in output:
                continue
            neighbors = output.strip().split('BGP neighbor is ')
            for neighbor in neighbors:
                if not neighbor:
                    continue
                n_record = get_n_record(task.host,
                                        neighbor[:neighbor.index(',')])
                n_record.state = re.search(r'BGP state = (\w+),',
                                           neighbor).group(1).lower()
                n_record.as_number = re.search(r'remote AS (\d+(?:\.\d+)?),',
                                               neighbor).group(1)
                n_record.router_id = ipaddress.ip_address(re.search(
                    r'remote router ID (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})',
                    neighbor).group(1))
                if 'ebgp link' in neighbor[:neighbor.index('Peer index')]:
                    n_record._type = 'external'
                elif 'ibgp link' in neighbor[:neighbor.index('Peer index')]:
                    n_record._type = 'internal'
                routes_count_start = neighbor.index(
                    'For address family: IP{} Unicast'.format(af_name))
                routes_count_end = neighbor.index('sent paths',
                                                  routes_count_start)
                # +10 will retain 'sent paths' words
                routes_count = neighbor[routes_count_start:routes_count_end+10]
                new_af = AddressFamily(af_name)
                new_af.learned_routes = int(re.search(
                    r'(\d+) accepted paths', routes_count).group(1))
                new_af.sent_routes = int(re.search(
                    r'(\d+) sent paths', routes_count).group(1))
                n_record.af['ip{}'.format(af_name)] = new_af
        elif task.host.platform == 'huawei_vrpv8':
            if not output or 'BGP Peer is' not in output:
                continue
            neighbors = output.strip().split('BGP Peer is ')
            for neighbor in neighbors:
                if not neighbor:
                    continue
                n_record = get_n_record(task.host,
                                        neighbor[:neighbor.index(',')])
                n_record.state = re.search(r'BGP current state: (\w+),',
                                           neighbor).group(1).lower()
                n_record.as_number = re.search(r'remote AS (\d+(?:\.\d+)?)',
                                               neighbor).group(1)
                n_record.router_id = ipaddress.ip_address(re.search(
                    r'Remote router ID (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})',
                    neighbor).group(1))
                if 'EBGP link' in neighbor[:neighbor.index('BGP version')]:
                    n_record._type = 'external'
                elif 'IBGP link' in neighbor[:neighbor.index('BGP version')]:
                    n_record._type = 'internal'
                task.host['bgp_neighbors'][
                        n_record.address.compressed] = n_record
                new_af = AddressFamily(af_name)
                new_af.learned_routes = int(re.search(
                    r'Received total routes: (\d+)', neighbor).group(1))
                new_af.sent_routes = int(re.search(
                    r'Advertised total routes: (\d+)', neighbor).group(1))
                n_record.af['ip{}'.format(af_name)] = new_af
        else:
            raise UnsupportedNOS(
                    'task received unsupported NOS - {}'.format(
                        task.host.platform))
    for neighbor in task.host['bgp_neighbors'].values():
        result += '\t{} AS {} (router ID {}) of type {} is {}'.format(
                neighbor.address, neighbor.as_number, neighbor.router_id,
                neighbor._type, neighbor.state)
        if neighbor.state != 'established':
            continue
        result += ': '
        result += ', '.join(['{} learned/sent routes: {}/{}'.format(
            x, neighbor.af[x].learned_routes, neighbor.af[x].sent_routes)
            for x in neighbor.af if neighbor.af[x]])
        result += '\n'
    return Result(host=task.host, result=result)