예제 #1
0
def per_device_work(session, enable_pass):
    """
    This function contains the code that should be executed on each device that this script connects to.  It is called
    after establishing a connection to each device in the loop above.

    You can either put your own code here, or if there is a single-device version of a script that performs the correct
    task, it can be imported and called here, essentially making this script connect to all the devices in the chosen
    CSV file and then running a single-device script on each of them.
    """
    script = session.script
    interesting_keys = ['HARDWARE', 'HOSTNAME', 'MODEL', 'VERSION', 'SERIAL', 'UPTIME', 'LAST_REBOOT_REASON', 'IMAGE']

    # Validate device is of a proper OS
    supported_os = ['IOS', 'NXOS', 'ASA']
    session.start_cisco_session(enable_pass=enable_pass)
    session.validate_os(supported_os)

    # Select the appropriate template to process show version data
    if session.os == 'IOS':
        ver_template_file = script.get_template('cisco_ios_show_version.template')
    elif session.os == 'NXOS':
        ver_template_file = script.get_template('cisco_nxos_show_version.template')
    elif session.os == 'ASA':
        ver_template_file = script.get_template('cisco_asa_show_version.template')
    else:
        raise sessions.UnsupportedOSError("{0} isn't a supported OS.".format(session.os))

    # Process Show Version data
    raw_version = session.get_command_output('show version')
    fsm_output = utilities.textfsm_parse_to_dict(raw_version, ver_template_file)

    if len(fsm_output) > 1:
        raise sessions.InteractionError("Received multiple entries from a single device, which should not happen.")
    else:
        ver_info = fsm_output[0]

    # For NXOS get parse 'show inventory' for model and serial number
    if session.os == 'NXOS':
        ver_info['HOSTNAME'] = session.hostname
        logger.debug("<M_SCRIPT> NXOS device, getting 'show inventory'.")
        raw_inv = session.get_command_output('show inventory')
        inv_template_file = script.get_template('cisco_nxos_show_inventory.template')
        inv_info = utilities.textfsm_parse_to_dict(raw_inv, inv_template_file)
        for entry in inv_info:
            if entry['NAME'] == "Chassis":
                logger.debug("<M_SCRIPT> Adding {0} as model number".format(entry['PID']))
                ver_info['MODEL'] = entry['PID']
                logger.debug("<M_SCRIPT> Adding {0} as serial number".format(entry['SN']))
                ver_info['SERIAL'] = entry['SN']
                break
    elif session.os == 'ASA':
        logger.debug("<M_SCRIPT> ASA device, writing 'N/A' for last reboot reason.")
        # For ASA put a N/A reload reason since ASA doesn't have this output
        ver_info['LAST_REBOOT_REASON'] = "N/A"
        # If we don't have a model number in older 'show ver' extract it from the hardware column.
        if not ver_info['MODEL']:
            model = ver_info['HARDWARE'].split(',')[0]
            logger.debug("<M_SCRIPT> ASA device without model, using {0}".format(model))
            ver_info['MODEL'] = model
    elif session.os == 'IOS':
        # Expand multiple serial numbers found in stacks, or just remove lists for serial and model if only 1 device
        logger.debug("<M_SCRIPT> IOS device, writing list of serials/models to separate entries")
        num_in_stack = len(ver_info['SERIAL'])
        if len(ver_info['MODEL']) != num_in_stack:
            # For older IOS, we may not get a model number, but we'll pick up the hardware.  As long as this happens
            # when only 1 serial is detected (not a switch stack), then just use the HARDWARE for the model number.
            if len(ver_info['MODEL']) == 0 and num_in_stack == 1 and ver_info['HARDWARE']:
                ver_info['MODEL'] = [ver_info['HARDWARE']]
            else:
                logger.debug("<M_SCRIPT> List of Serials & Models aren't the same length. Likely TextFSM parsing problem.")
                raise sessions.InteractionError("Received {0} serial nums and only {1} model nums in output."
                                                .format(num_in_stack, len(ver_info['MODEL'])))
        new_output = []
        for x in range(num_in_stack):
            stack_subset = dict((key, ver_info[key]) for key in interesting_keys)
            stack_subset['HOSTNAME'] = "{0}-{1}".format(ver_info['HOSTNAME'], x+1)
            stack_subset['SERIAL'] = ver_info['SERIAL'][x]
            stack_subset['MODEL'] = ver_info['MODEL'][x]
            new_output.append(stack_subset)
            logger.debug("Created an entry for {0}/{1}".format(stack_subset['MODEL'], stack_subset['SERIAL']))
        fsm_output = new_output

    # Create output data structure with only the keys that we need.
    inv_data = []
    logger.debug("Creating list of dictionaries to return, and adding manufacture dates.")
    for entry in fsm_output:
        subset = dict((key, entry[key]) for key in interesting_keys)
        subset['MANUFACTURE_DATE'] = get_manufacture_date(subset['SERIAL'])
        inv_data.append(subset)

    # End session on the Cisco device
    session.end_cisco_session()

    return inv_data
예제 #2
0
def update_helpers(session, check_mode, old_helpers, new_helpers,
                   remove_old_helpers):
    script = session.script
    # A list of supported OSes that this script is configured to handle.
    supported_os = ["IOS", "NXOS"]

    # Create data structure to record helper IPs that we find that aren't in our list that we are either looking for
    # or adding.
    unrecognized_helpers = [["Hostname", "Interface", "Helper IP"]]

    if session.os not in supported_os:
        logger.debug(
            "<UPDATE_HELPER> OS is {0}, which is not in supported OS list of {1}"
            .format(session.os, supported_os))
        raise sessions.UnsupportedOSError(
            "This device's OS is {0}, which is not a supported OS for this script which "
            "only supports: {1}).".format(session.os, supported_os))

    # Save our "Before" configuration.
    before_filename = session.create_output_filename("1-show-run-BEFORE")
    session.write_output_to_file("show run", before_filename)

    # Open the "Before" configuration and parse it with TextFSM to find helper-addresses
    with open(before_filename, 'r') as config_file:
        run_config = config_file.read()

    if session.os == "IOS":
        template_file = script.get_template(
            "cisco_ios_show_run_helper.template")
    else:
        template_file = script.get_template(
            "cisco_nxos_show_run_dhcp_relay.template")

    result = utilities.textfsm_parse_to_list(run_config, template_file)

    if check_mode:
        os.remove(before_filename)

    # Create a dictionary that will let us get a set of configured helpers under each interface.
    intfs_with_helpers = {}
    for entry in result:
        interface = entry[0]
        helper = entry[1]
        vrf = entry[2]
        if interface in intfs_with_helpers:
            intfs_with_helpers[interface]["helpers"].add(helper)
        else:
            intfs_with_helpers[interface] = {
                "vrf": "{}".format(vrf),
                "helpers": {helper}
            }

        # Check if helper is unrecognized and needs to be recorded
        if helper not in old_helpers and helper not in new_helpers:
            unknown_line = [session.hostname, interface, helper, vrf]
            unrecognized_helpers.append(unknown_line)
            logger.debug("<UPDATE_HELPER> Adding {} to unknown helpers".format(
                str(unknown_line)))

    logger.debug("<UPDATE_HELPER> Interfaces with helpers:\n{}".format(
        str(intfs_with_helpers)))

    # Figure out which interfaces need additional helpers
    need_to_update = []
    for interface in intfs_with_helpers:
        configured_helpers = intfs_with_helpers[interface]["helpers"]
        vrf = intfs_with_helpers[interface]["vrf"]
        helper_matches = configured_helpers.intersection(old_helpers)
        if helper_matches:
            needed_new_helpers = set(new_helpers).difference(
                configured_helpers)
            if remove_old_helpers:
                need_to_update.append(
                    (interface, vrf, needed_new_helpers, helper_matches))
            else:
                need_to_update.append((interface, vrf, needed_new_helpers, {}))

    logger.debug("<UPDATE_HELPER> Required Updates:\n{}".format(
        str(need_to_update)))

    # If we have anything we need to update, build out required config commands, depending on device OS.
    update_commands = []
    if session.os == "IOS":
        for entry in need_to_update:
            interface = entry[0]
            vrf = entry[1]
            helpers_to_add = entry[2]
            helpers_to_remove = entry[3]
            if helpers_to_add or helpers_to_remove:
                update_commands.append("interface {}".format(interface))
                for helper in helpers_to_add:
                    if vrf == "":
                        update_commands.append(
                            "ip helper-address {}".format(helper))
                    elif vrf == "global":
                        update_commands.append(
                            "ip helper-address global {}".format(helper))
                    else:
                        update_commands.append(
                            "ip helper-address vrf {} {}".format(vrf, helper))
                for helper in helpers_to_remove:
                    if vrf == "":
                        update_commands.append(
                            "no ip helper-address {}".format(helper))
                    elif vrf == "global":
                        update_commands.append(
                            "no ip helper-address global {}".format(helper))
                    else:
                        update_commands.append(
                            "no ip helper-address vrf {} {}".format(
                                vrf, helper))
    else:
        for entry in need_to_update:
            interface = entry[0]
            vrf = entry[1]
            helpers_to_add = entry[2]
            helpers_to_remove = entry[3]
            if helpers_to_add or helpers_to_remove:
                update_commands.append("interface {}".format(interface))
                for helper in helpers_to_add:
                    if vrf == "":
                        update_commands.append(
                            "ip dhcp relay address {}".format(helper))
                    else:
                        update_commands.append(
                            "ip dhcp relay address {} use-vrf {}".format(
                                helper, vrf))
                for helper in helpers_to_remove:
                    if vrf == "":
                        update_commands.append(
                            "no ip dhcp relay address {}".format(helper))
                    else:
                        update_commands.append(
                            "no ip dhcp relay address {} use-vrf {}".format(
                                helper, vrf))

    # Send config commands to the device and save the session.
    if update_commands:
        if check_mode:
            # If in Check Mode, only generate config updates and write to a file.
            logger.debug("<UPDATE_HELPER> CHECK MODE: Generating config")
            command_string = ""
            command_string += "configure terminal\n"
            for command in update_commands:
                command_string += "{}\n".format(command.strip())
            command_string += "end\n"

            config_filename = session.create_output_filename("PROPOSED_CONFIG")
            with open(config_filename, 'w') as output_file:
                output_file.write(command_string)
        else:
            config_filename = session.create_output_filename(
                "2-CONFIG_RESULTS")
            session.send_config_commands(update_commands,
                                         output_filename=config_filename)
            session.save()

            # Save our "After" configuration.
            after_filename = session.create_output_filename("3-show-run-AFTER")
            session.write_output_to_file("show run", after_filename)