<commit_before>'''
Should create a rollback file
'''

# Copyright 2015 Spotify AB. All rights reserved.
#
# The contents of this file are licensed under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

import re
from netmiko import ConnectHandler, FileTransfer
from napalm.base import NetworkDriver
from napalm.exceptions import ReplaceConfigException, MergeConfigException


class IOSDriver(NetworkDriver):
    '''NAPALM Cisco IOS Handler'''
    def __init__(self, hostname, username, password, timeout=60):
        self.hostname = hostname
        self.username = username
        self.password = password
        self.timeout = timeout
        self.first_touch = True
        self.candidate_config = None
        self.candidate_config_commands = None
        self.device = None

        self.config_replace = False

    def open(self):
        """Opens a connection to the device."""
        self.device = ConnectHandler(
            device_type='cisco_ios',
            ip=self.hostname,
            username=self.username,
            password=self.password,
            verbose=False)

    def close(self):
        """Closes the connection to the device."""
        self.device.disconnect()

    def load_replace_candidate(self, filename=None, config=None):
        '''
        SCP file to device filesystem, defaults to flash:/candidate_config.txt

        Returns None (or raise exception if error)
        '''
        self.config_replace = True

        # FIX (hard-coded)
        dest_file_system = 'flash:'
        dest_file = 'candidate_config.txt'
        # FIX - will auto configure SCP by default
        enable_scp = True

        # FIX - straight configuration inline is not currently implemented
        if config:
            raise NotImplementedError
        if filename:
            with FileTransfer(self.device, source_file=filename,
                              dest_file=dest_file, file_system=dest_file_system) as scp_transfer:
                # Check if file already exists and has correct MD5
                if scp_transfer.check_file_exists() and scp_transfer.compare_md5():
                    return None
                if not scp_transfer.verify_space_available():
                    raise ReplaceConfigException("Insufficient space available on remote device")
                # Transfer file
                if enable_scp:
                    scp_transfer.enable_scp()
                scp_transfer.transfer_file()
                if scp_transfer.verify_file():
                    msg="File successfully transferred to remote device"
                else:
                    msg="File transfer to remote device failed"
                    raise ReplaceConfigException(msg)

    def rollback(self):
        '''
        Rollback configuration to previous archive file 'rollback_config.txt'
        or to specified filename.
        '''
        pass

    def load_merge_candidate(self, filename=None, config=None):
        commands = list()
        if self.first_touch:
            commands.append('enable')
            commands.append(self.password)
            commands.append('conf t')

        if filename is not None:
            with open(filename, 'r') as new_config:
                configuration = new_config.readlines()
        else:
            if isinstance(config, list):
                configuration = config
            else:
                configuration = config.splitlines()

        for line in configuration:
            if line.strip() == '' or line.strip() == '!':
                continue
            else:
                commands.append(line.strip())

        self.candidate_config = commands

    def compare_config(self):
        if self.candidate_config is not None:
            if 'enable' in self.candidate_config:
                commands = self.candidate_config[3::]
                for command in commands:
                    print "+ {0}".format(command)
            else:
                for command in self.candidate_config:
                    print "+ {0}".format(command)

    def discard_config(self):
        if self.candidate_config is not None:
            self.candidate_config = None

    def enable_confirm(self):
        '''Enable IOS confirmations on file operations (global config command)'''
        cmd = 'no file prompt quiet'
        self.device.send_config_set([cmd])

    def disable_confirm(self):
        '''Disable IOS confirmations on file operations (global config command)'''
        cmd = 'file prompt quiet'
        self.device.send_config_set([cmd])

    def gen_rollback_cfg(self):
        '''
        Generate a configuration that can be used for rollback
        '''
        # FIX hard-coded to flash:
        cmd = 'copy running-config flash:rollback_config.txt'
        self.disable_confirm()
        output = self.device.send_command(cmd)
        self.enable_confirm()
        print output

    def commit_config(self):
        '''
        Perform 'configure replace' for entire replacement config.

        Perform copy <file> running-config for merge operation.
        '''
        # Always generate a rollback config before performing commit
        self.gen_rollback_cfg()
        if self.config_replace:
            # FIX - hard-coded to flash:
            cmd = 'configure replace flash:/candidate_config.txt force'
        else:
            # FIX for config merge
            cmd = ''
        output = self.device.send_command(cmd)
        print output

    def get_lldp_neighbors(self):
        command = 'show lldp neighbors | begin Device ID'
        lldp = {}

        output = self.device.send_command(command)
        splitted_output = output.split('\n')
        for line in splitted_output:
            neighbor = {}
            if len(line) > 0 and 'Device ID' not in line and 'entries' not in line:
                splitted_line = line.split()
                device_id = unicode(splitted_line[0])
                device_port = unicode(splitted_line[1])
                port_id = unicode(splitted_line[-1])
                neighbor['hostname'] = device_id
                neighbor['port'] = port_id
                lldp[device_port] = neighbor
        return lldp

    def get_facts(self):
        """This function returns a set of facts from the devices."""<commit_msg>Converting over to file based operations<commit_after>'''NAPALM Cisco IOS Handler'''

# Copyright 2015 Spotify AB. All rights reserved.
#
# The contents of this file are licensed under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

import re
from netmiko import ConnectHandler, FileTransfer
from napalm.base import NetworkDriver
from napalm.exceptions import ReplaceConfigException, MergeConfigException


class IOSDriver(NetworkDriver):
    '''NAPALM Cisco IOS Handler'''
    def __init__(self, hostname, username, password, timeout=60):
        self.hostname = hostname
        self.username = username
        self.password = password
        self.timeout = timeout
        self.first_touch = True
        self.candidate_config = None
        self.candidate_config_commands = None
        self.device = None

        self.config_replace = False
        # FIX -- hard-coded
        self.candidate_cfg='candidate_config.txt'
        self.rollback_cfg='rollback_config.txt'
        self.dest_file_system='flash:'

    def open(self):
        """Opens a connection to the device."""
        self.device = ConnectHandler(device_type='cisco_ios', ip=self.hostname,
                                     username=self.username, password=self.password, verbose=False)

    def close(self):
        """Closes the connection to the device."""
        self.device.disconnect()

    def load_replace_candidate(self, filename=None, config=None):
        '''
        SCP file to device filesystem, defaults to flash:/candidate_config.txt

        Returns None (or raise exception if error)
        '''
        self.config_replace = True

        # FIX (hard-coded)
        dest_file_system = 'flash:'
        dest_file = 'candidate_config.txt'
        # FIX - will auto configure SCP by default
        enable_scp = True

        # FIX - straight configuration inline is not currently implemented
        if config:
            raise NotImplementedError
        if filename:
            with FileTransfer(self.device, source_file=filename,
                              dest_file=dest_file, file_system=dest_file_system) as scp_transfer:
                # Check if file already exists and has correct MD5
                if scp_transfer.check_file_exists() and scp_transfer.compare_md5():
                    return None
                if not scp_transfer.verify_space_available():
                    raise ReplaceConfigException("Insufficient space available on remote device")
                # Transfer file
                if enable_scp:
                    scp_transfer.enable_scp()
                scp_transfer.transfer_file()
                if scp_transfer.verify_file():
                    msg="File successfully transferred to remote device"
                else:
                    msg="File transfer to remote device failed"
                    raise ReplaceConfigException(msg)

    def rollback(self, filename=None):
        '''
        Rollback configuration to previous archive file 'rollback_config.txt'
        or to specified filename.
        '''
        if filename is None:
            filename = self.rollback_cfg
        commit_config(filename=filename)

    def load_merge_candidate(self, filename=None, config=None):
        commands = list()
        if self.first_touch:
            commands.append('enable')
            commands.append(self.password)
            commands.append('conf t')

        if filename is not None:
            with open(filename, 'r') as new_config:
                configuration = new_config.readlines()
        else:
            if isinstance(config, list):
                configuration = config
            else:
                configuration = config.splitlines()

        for line in configuration:
            if line.strip() == '' or line.strip() == '!':
                continue
            else:
                commands.append(line.strip())

        self.candidate_config = commands

    def compare_config(self, file1=None, file2=None):
        '''
        show archive config differences <file1> <file2>

        Default operation is to compare self.candidate_cfg to system:running-config
        '''
        # FIX argument passing probably won't work here
        if file1 is None:
            file1 = self.candidate_cfg
        cfg_file1 = '{}/{}'.format(self.dest_file_system, file1)
        if file2 is None:
            file2 = 'system:running-config'
        cfg_file2 = file2
        cmd = 'show archive config differences {} {}'.format(cfg_file1, cfg_file2)
        diff = self.device.send_command(cmd)
        return diff.strip()

    def discard_config(self):
        '''
        Set candidate_cfg to current running-config
        '''
        # FIX hard-coded to flash:
        cmd = 'copy running-config flash:candidate_config.txt'
        self.disable_confirm()
        output = self.device.send_command(cmd)
        self.enable_confirm()

    def enable_confirm(self):
        '''Enable IOS confirmations on file operations (global config command)'''
        cmd = 'no file prompt quiet'
        self.device.send_config_set([cmd])

    def disable_confirm(self):
        '''Disable IOS confirmations on file operations (global config command)'''
        cmd = 'file prompt quiet'
        self.device.send_config_set([cmd])

    def gen_rollback_cfg(self):
        '''
        Generate a configuration that can be used for rollback
        '''
        # FIX hard-coded to flash:
        cmd = 'copy running-config flash:rollback_config.txt'
        self.disable_confirm()
        output = self.device.send_command(cmd)
        self.enable_confirm()
        print output

    def check_file_exists(self, cfg_file):
        '''
        Check that the file exists on remote device using full path

        For example
        # dir flash:/candidate_config.txt
        Directory of flash:/candidate_config.txt

        33  -rw-        5592  Dec 18 2015 10:50:22 -08:00  candidate_config.txt

        return boolean
        '''
        cmd = 'dir {}'.format(cfg_file)
        success_pattern = 'Directory of {}'.format(cfg_file)
        output = self.device.send_command(cmd)
        if 'Error opening' in output:
            return False
        elif success_pattern in output:
            return True
        print output
        return False

    def commit_config(self, filename=None):
        '''
        If replacement operation, perform 'configure replace' for the entire config.
        
        If merge operation, perform copy <file> running-config.
        '''
        if filename is None:
            filename = self.candidate_cfg

        # Always generate a rollback config
        self.gen_rollback_cfg()
        if self.config_replace:
            cfg_file = '{}/{}'.format(self.dest_file_system, filename)
            if not self.check_file_exists(cfg_file):
                raise ReplaceConfigException("Destination candidate config file does not exist")
            cmd = 'configure replace {} force'.format(cfg_file)
            # FIX - use more canonical 'copy run start, but that prompts'
            #cmd2 = 'write mem'
        else:
            # FIX for config merge
            cmd = ''
        output = self.device.send_command(cmd)
        print output

    def get_lldp_neighbors(self):
        command = 'show lldp neighbors | begin Device ID'
        lldp = {}

        output = self.device.send_command(command)
        splitted_output = output.split('\n')
        for line in splitted_output:
            neighbor = {}
            if len(line) > 0 and 'Device ID' not in line and 'entries' not in line:
                splitted_line = line.split()
                device_id = unicode(splitted_line[0])
                device_port = unicode(splitted_line[1])
                port_id = unicode(splitted_line[-1])
                neighbor['hostname'] = device_id
                neighbor['port'] = port_id
                lldp[device_port] = neighbor
        return lldp

    def get_facts(self):
        """This function returns a set of facts from the devices."""
{'fraction_non_alphanumeric': 0.08029006336970014, 'fraction_numerical': 0.004703730319461684, 'mean_word_length': 2.0975313638203157, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2301939', 'n_tokens_mistral': 4001, 'n_tokens_neox': 3825, 'n_words': 1201}