Exemple #1
0
    def file_copy(self, src, dest=None, file_system='bootflash:'):
        """Send a local file to the device.

        Args:
            src (str): Path to the local file to send.

        Keyword Args:
            dest (str): The destination file path to be saved on remote flash.
                If none is supplied, the implementing class should use the basename
                of the source path.
            file_system (str): The file system for the
                remote fle. Defaults to bootflash:'.
        """
        fc = FileCopy(self, src, dst=dest, file_system=file_system)
        fc.send()
Exemple #2
0
    def file_copy_remote_exists(self, src, dest=None, file_system='bootflash:'):
        """Check if a remote file exists. A remote file exists if it has the same name
        as supplied dest, and the same md5 hash as the source.

        Args:
            src (str): Path to local file to check.

        Keyword Args:
            dest (str): The destination file path to be saved on remote the remote device.
                If none is supplied, the implementing class should use the basename
                of the source path.
            file_system (str): The file system for the
                remote file. Defaults to 'bootflash:'.

        Returns:
            True if the remote file exists, False if it doesn't.
        """
        fc = FileCopy(self, src, dst=dest, file_system=file_system)
        if fc.remote_file_exists():
            return True
        return False
Exemple #3
0
 def _send_file(self, filename, dest):
     self.fc = FileCopy(self.device, filename, dst=dest.split('/')[-1])
     try:
         if not self.fc.remote_file_exists():
             self.fc.send()
         elif not self.fc.file_already_exists():
             commands = ['terminal dont-ask',
                         'delete {0}'.format(self.fc.dst)]
             self.device.config_list(commands)
             self.fc.send()
     except NXOSFileTransferError as fte:
         raise ReplaceConfigException(fte.message)
Exemple #4
0
class NXOSDriver(NetworkDriver):
    def __init__(self, hostname, username, password, timeout=60, optional_args=None):
        if optional_args is None:
            optional_args = {}
        self.hostname = hostname
        self.username = username
        self.password = password
        self.timeout = timeout
        self.up = False
        self.replace = True
        self.loaded = False
        self.fc = None
        self.changed = False
        self.replace_file = None
        self.merge_candidate = ''

        if optional_args is None:
            optional_args = {}

        # nxos_protocol is there for backwards compatibility, transport is the preferred method
        self.transport = optional_args.get('transport', optional_args.get('nxos_protocol', 'https'))

        if self.transport == 'https':
            self.port = optional_args.get('port', 443)
        elif self.transport == 'http':
            self.port = optional_args.get('port', 80)

    def open(self):
        try:
            self.device = NXOSDevice(self.hostname,
                                     self.username,
                                     self.password,
                                     timeout=self.timeout,
                                     port=self.port,
                                     transport=self.transport)
            self.device.show('show hostname')
            self.up = True
        except (CLIError, ValueError):
            # unable to open connection
            raise ConnectionException('Cannot connect to {}'.format(self.hostname))

    def close(self):
        if self.changed:
            self._delete_file(self.backup_file)
        self.device = None

    @staticmethod
    def _compute_timestamp(stupid_cisco_output):
        """
        Some fields such `uptime` are returned as: 23week(s) 3day(s)
        This method will determine the epoch of the event.
        e.g.: 23week(s) 3day(s) -> 1462248287
        """
        if not stupid_cisco_output or stupid_cisco_output == 'never':
            return -1.0

        if '(s)' in stupid_cisco_output:
            pass
        elif ':' in stupid_cisco_output:
            stupid_cisco_output = stupid_cisco_output.replace(':', 'hour(s) ', 1)
            stupid_cisco_output = stupid_cisco_output.replace(':', 'minute(s) ', 1)
            stupid_cisco_output += 'second(s)'
        else:
            stupid_cisco_output = stupid_cisco_output.replace('d', 'day(s) ')
            stupid_cisco_output = stupid_cisco_output.replace('h', 'hour(s)')

        things = {
            'second(s)': {
                'weight': 1
            },
            'minute(s)': {
                'weight': 60
            },
            'hour(s)': {
                'weight': 3600
            },
            'day(s)': {
                'weight': 24 * 3600
            },
            'week(s)': {
                'weight': 7 * 24 * 3600
            },
            'year(s)': {
                'weight': 365.25 * 24 * 3600
            }
        }

        things_keys = things.keys()
        for part in stupid_cisco_output.split():
            for key in things_keys:
                if key in part:
                    things[key]['count'] = napalm.base.helpers.convert(
                        int, part.replace(key, ''), 0)

        delta = sum([det.get('count', 0) * det.get('weight') for det in things.values()])
        return time.time() - delta

    @staticmethod
    def _get_reply_body(result):
        # useful for debugging
        ret = result.get('ins_api', {}).get('outputs', {}).get('output', {}).get('body', {})
        # Original 'body' entry may have been an empty string, don't return that.
        if not isinstance(ret, dict):
            return {}
        return ret

    @staticmethod
    def _get_table_rows(parent_table, table_name, row_name):
        # because if an inconsistent piece of shit.
        # {'TABLE_intf': [{'ROW_intf': {
        # vs
        # {'TABLE_mac_address': {'ROW_mac_address': [{
        # vs
        # {'TABLE_vrf': {'ROW_vrf': {'TABLE_adj': {'ROW_adj': {
        _table = parent_table.get(table_name)
        _table_rows = []
        if isinstance(_table, list):
            _table_rows = [_table_row.get(row_name) for _table_row in _table]
        elif isinstance(_table, dict):
            _table_rows = _table.get(row_name)
        if not isinstance(_table_rows, list):
            _table_rows = [_table_rows]
        return _table_rows

    @staticmethod
    def fix_checkpoint_string(string, filename):
        # used to generate checkpoint-like files
        pattern = '''!Command: Checkpoint cmd vdc 1'''

        if '!Command' in string:
            return re.sub('!Command.*', pattern.format(filename), string)
        else:
            return "{0}\n{1}".format(pattern.format(filename), string)

    def _get_reply_table(self, result, table_name, row_name):
        return self._get_table_rows(result, table_name, row_name)

    def _get_command_table(self, command, table_name, row_name):
        json_output = self.device.show(command)
        return self._get_reply_table(json_output, table_name, row_name)

    def is_alive(self):
        if self.device:
            return {'is_alive': True}
        else:
            return {'is_alive': False}

    def load_replace_candidate(self, filename=None, config=None):
        self.replace = True
        self.loaded = True

        if not filename and not config:
            raise ReplaceConfigException('filename or config param must be provided.')

        if filename is None:
            temp_file = tempfile.NamedTemporaryFile()
            temp_file.write(config)
            temp_file.flush()
            cfg_filename = temp_file.name
        else:
            cfg_filename = filename

        self.replace_file = cfg_filename

        with open(self.replace_file, 'r') as f:
            file_content = f.read()

        file_content = self.fix_checkpoint_string(file_content, self.replace_file)
        temp_file = tempfile.NamedTemporaryFile()
        temp_file.write(file_content.encode())
        temp_file.flush()
        self.replace_file = cfg_filename

        self._send_file(temp_file.name, cfg_filename)

    def load_merge_candidate(self, filename=None, config=None):
        self.replace = False
        self.loaded = True

        if not filename and not config:
            raise MergeConfigException('filename or config param must be provided.')

        self.merge_candidate += '\n'  # insert one extra line
        if filename is not None:
            with open(filename, "r") as f:
                self.merge_candidate += f.read()
        else:
            self.merge_candidate += config

    def _send_file(self, filename, dest):
        self.fc = FileCopy(self.device, filename, dst=dest.split('/')[-1])
        try:
            if not self.fc.remote_file_exists():
                self.fc.send()
            elif not self.fc.file_already_exists():
                commands = ['terminal dont-ask',
                            'delete {0}'.format(self.fc.dst)]
                self.device.config_list(commands)
                self.fc.send()
        except NXOSFileTransferError as fte:
            raise ReplaceConfigException(fte.message)

    def _create_sot_file(self):
        """Create Source of Truth file to compare."""
        commands = ['terminal dont-ask', 'checkpoint file sot_file']
        self.device.config_list(commands)

    def _get_diff(self, cp_file):
        """Get a diff between running config and a proposed file."""
        diff = []
        self._create_sot_file()
        diff_out = self.device.show(
            'show diff rollback-patch file {0} file {1}'.format(
                'sot_file', self.replace_file.split('/')[-1]), raw_text=True)
        try:
            diff_out = diff_out.split(
                '#Generating Rollback Patch')[1].replace(
                    'Rollback Patch is Empty', '').strip()
            for line in diff_out.splitlines():
                if line:
                    if line[0].strip() != '!':
                        diff.append(line.rstrip(' '))
        except (AttributeError, KeyError):
            raise ReplaceConfigException(
                'Could not calculate diff. It\'s possible the given file doesn\'t exist.')
        return '\n'.join(diff)

    def _get_merge_diff(self):
        diff = []
        running_config = self.get_config(retrieve='running')['running']
        running_lines = running_config.splitlines()
        for line in self.merge_candidate.splitlines():
            if line not in running_lines and line:
                if line[0].strip() != '!':
                    diff.append(line)
        return '\n'.join(diff)
        # the merge diff is not necessarily what needs to be loaded
        # for example under NTP, as the `ntp commit` command might be
        # alread configured, it is mandatory to be sent
        # otherwise it won't take the new configuration - see #59
        # https://github.com/napalm-automation/napalm-nxos/issues/59
        # therefore this method will return the real diff
        # but the merge_candidate will remain unchanged
        # previously: self.merge_candidate = '\n'.join(diff)

    def compare_config(self):
        if self.loaded:
            if not self.replace:
                return self._get_merge_diff()
                # return self.merge_candidate
            diff = self._get_diff(self.fc.dst)
            return diff
        return ''

    def _commit_merge(self):
        commands = [command for command in self.merge_candidate.splitlines() if command]
        self.device.config_list(commands)
        if not self.device.save():
            raise CommandErrorException('Unable to commit config!')

    def _save_config(self, filename):
        """Save the current running config to the given file."""
        self.device.show('checkpoint file {}'.format(filename), raw_text=True)

    def _disable_confirmation(self):
        self.device.config('terminal dont-ask')

    def _load_config(self):
        cmd = 'rollback running file {0}'.format(self.replace_file.split('/')[-1])
        self._disable_confirmation()
        try:
            rollback_result = self.device.config(cmd)
        except ConnectionError:
            # requests will raise an error with verbose warning output
            return True
        except Exception:
            return False
        if 'Rollback failed.' in rollback_result['msg'] or 'ERROR' in rollback_result:
            raise ReplaceConfigException(rollback_result['msg'])
        return True

    def commit_config(self):
        if self.loaded:
            self.backup_file = 'config_' + str(datetime.now()).replace(' ', '_')
            self._save_config(self.backup_file)
            if self.replace:
                if self._load_config() is False:
                    raise ReplaceConfigException
            else:
                try:
                    self._commit_merge()
                    self.merge_candidate = ''  # clear the merge buffer
                except Exception as e:
                    raise MergeConfigException(str(e))

            self.changed = True
            self.loaded = False
        else:
            raise ReplaceConfigException('No config loaded.')

    def _delete_file(self, filename):
        commands = ['terminal dont-ask',
                    'delete {}'.format(filename),
                    'no terminal dont-ask']
        self.device.show_list(commands, raw_text=True)

    def discard_config(self):
        if self.loaded:
            self.merge_candidate = ''  # clear the buffer
        if self.loaded and self.replace:
            try:
                self._delete_file(self.fc.dst)
            except CLIError:
                pass
        self.loaded = False

    def rollback(self):
        if self.changed:
            self.device.rollback(self.backup_file)
            self.device.save()
            self.changed = False

    def get_facts(self):
        pynxos_facts = self.device.facts
        final_facts = {key: value for key, value in pynxos_facts.items() if
                       key not in ['interfaces', 'uptime_string', 'vlans']}

        if pynxos_facts['interfaces']:
            final_facts['interface_list'] = pynxos_facts['interfaces']
        else:
            final_facts['interface_list'] = self.get_interfaces().keys()

        final_facts['vendor'] = 'Cisco'

        hostname_cmd = 'show hostname'
        hostname = self.device.show(hostname_cmd).get('hostname')
        if hostname:
            final_facts['fqdn'] = hostname

        return final_facts

    def get_interfaces(self):
        interfaces = {}
        iface_cmd = 'show interface'
        interfaces_out = self.device.show(iface_cmd)
        interfaces_body = interfaces_out['TABLE_interface']['ROW_interface']

        for interface_details in interfaces_body:
            interface_name = interface_details.get('interface')
            # Earlier version of Nexus returned a list for 'eth_bw' (observed on 7.1(0)N1(1a))
            interface_speed = interface_details.get('eth_bw', 0)
            if isinstance(interface_speed, list):
                interface_speed = interface_speed[0]
            interface_speed = int(interface_speed / 1000)
            if 'admin_state' in interface_details:
                is_up = interface_details.get('admin_state', '') == 'up'
            else:
                is_up = interface_details.get('state', '') == 'up'
            interfaces[interface_name] = {
                'is_up': is_up,
                'is_enabled': (interface_details.get('state') == 'up'),
                'description': py23_compat.text_type(interface_details.get('desc', '').strip('"')),
                'last_flapped': self._compute_timestamp(
                    interface_details.get('eth_link_flapped', '')),
                'speed': interface_speed,
                'mac_address': napalm.base.helpers.convert(
                    napalm.base.helpers.mac, interface_details.get('eth_hw_addr')),
            }
        return interfaces

    def get_lldp_neighbors(self):
        results = {}
        try:
            command = 'show lldp neighbors'
            lldp_raw_output = self.cli([command]).get(command, '')
            lldp_neighbors = napalm.base.helpers.textfsm_extractor(
                                self, 'lldp_neighbors', lldp_raw_output)
        except CLIError:
            lldp_neighbors = []

        for neighbor in lldp_neighbors:
            local_iface = neighbor.get('local_interface')
            if neighbor.get(local_iface) is None:
                if local_iface not in results:
                    results[local_iface] = []

            neighbor_dict = {}
            neighbor_dict['hostname'] = py23_compat.text_type(neighbor.get('neighbor'))
            neighbor_dict['port'] = py23_compat.text_type(neighbor.get('neighbor_interface'))

            results[local_iface].append(neighbor_dict)
        return results

    def get_bgp_neighbors(self):
        results = {}
        bgp_state_dict = {
            'Idle': {'is_up': False, 'is_enabled': True},
            'Active': {'is_up': False, 'is_enabled': True},
            'Open': {'is_up': False, 'is_enabled': True},
            'Established': {'is_up': True, 'is_enabled': True},
            'Closing': {'is_up': True, 'is_enabled': True},
            'Shutdown': {'is_up': False, 'is_enabled': False},
        }

        try:
            cmd = 'show bgp sessions vrf all'
            vrf_list = self._get_command_table(cmd, 'TABLE_vrf', 'ROW_vrf')
        except CLIError:
            vrf_list = []

        for vrf_dict in vrf_list:
            result_vrf_dict = {}
            result_vrf_dict['router_id'] = py23_compat.text_type(vrf_dict['router-id'])
            result_vrf_dict['peers'] = {}
            neighbors_list = vrf_dict.get('TABLE_neighbor', {}).get('ROW_neighbor', [])

            if isinstance(neighbors_list, dict):
                neighbors_list = [neighbors_list]

            for neighbor_dict in neighbors_list:
                neighborid = napalm.base.helpers.ip(neighbor_dict['neighbor-id'])
                remoteas = napalm.base.helpers.as_number(neighbor_dict['remoteas'])
                state = py23_compat.text_type(neighbor_dict['state'])

                bgp_state = bgp_state_dict[state]

                result_peer_dict = {
                    'local_as': int(vrf_dict['local-as']),
                    'remote_as': remoteas,
                    'remote_id': neighborid,
                    'is_enabled': bgp_state['is_enabled'],
                    'uptime': -1,
                    'description': '',
                    'is_up': bgp_state['is_up'],
                }
                result_peer_dict['address_family'] = {
                    'ipv4': {
                        'sent_prefixes': -1,
                        'accepted_prefixes': -1,
                        'received_prefixes': -1
                    }
                }
                result_vrf_dict['peers'][neighborid] = result_peer_dict

            vrf_name = vrf_dict['vrf-name-out']
            if vrf_name == 'default':
                vrf_name = 'global'
            results[vrf_name] = result_vrf_dict
        return results

    def _set_checkpoint(self, filename):
        commands = ['terminal dont-ask', 'checkpoint file {0}'.format(filename)]
        self.device.config_list(commands)

    def _get_checkpoint_file(self):
        filename = 'temp_cp_file_from_napalm'
        self._set_checkpoint(filename)
        cp_out = self.device.show('show file {0}'.format(filename), raw_text=True)
        self._delete_file(filename)
        return cp_out

    def get_lldp_neighbors_detail(self, interface=''):
        lldp_neighbors = {}
        filter = ''
        if interface:
            filter = 'interface {name} '.format(name=interface)

        command = 'show lldp neighbors {filter}detail'.format(filter=filter)
        # seems that some old devices may not return JSON output...

        try:
            lldp_neighbors_table_str = self.cli([command]).get(command)
            # thus we need to take the raw text output
            lldp_neighbors_list = lldp_neighbors_table_str.splitlines()
        except CLIError:
            lldp_neighbors_list = []

        if not lldp_neighbors_list:
            return lldp_neighbors  # empty dict

        CHASSIS_REGEX = r'^(Chassis id:)\s+([a-z0-9\.]+)$'
        PORT_REGEX = r'^(Port id:)\s+([0-9]+)$'
        LOCAL_PORT_ID_REGEX = r'^(Local Port id:)\s+(.*)$'
        PORT_DESCR_REGEX = r'^(Port Description:)\s+(.*)$'
        SYSTEM_NAME_REGEX = r'^(System Name:)\s+(.*)$'
        SYSTEM_DESCR_REGEX = r'^(System Description:)\s+(.*)$'
        SYST_CAPAB_REEGX = r'^(System Capabilities:)\s+(.*)$'
        ENABL_CAPAB_REGEX = r'^(Enabled Capabilities:)\s+(.*)$'
        VLAN_ID_REGEX = r'^(Vlan ID:)\s+(.*)$'

        lldp_neighbor = {}
        interface_name = None

        for line in lldp_neighbors_list:
            chassis_rgx = re.search(CHASSIS_REGEX, line, re.I)
            if chassis_rgx:
                lldp_neighbor = {
                    'remote_chassis_id': napalm.base.helpers.mac(chassis_rgx.groups()[1])
                }
                continue
            lldp_neighbor['parent_interface'] = ''
            port_rgx = re.search(PORT_REGEX, line, re.I)
            if port_rgx:
                lldp_neighbor['parent_interface'] = py23_compat.text_type(port_rgx.groups()[1])
                continue
            local_port_rgx = re.search(LOCAL_PORT_ID_REGEX, line, re.I)
            if local_port_rgx:
                interface_name = local_port_rgx.groups()[1]
                continue
            port_descr_rgx = re.search(PORT_DESCR_REGEX, line, re.I)
            if port_descr_rgx:
                lldp_neighbor['remote_port'] = py23_compat.text_type(port_descr_rgx.groups()[1])
                lldp_neighbor['remote_port_description'] = py23_compat.text_type(
                                                            port_descr_rgx.groups()[1])
                continue
            syst_name_rgx = re.search(SYSTEM_NAME_REGEX, line, re.I)
            if syst_name_rgx:
                lldp_neighbor['remote_system_name'] = py23_compat.text_type(
                                                        syst_name_rgx.groups()[1])
                continue
            syst_descr_rgx = re.search(SYSTEM_DESCR_REGEX, line, re.I)
            if syst_descr_rgx:
                lldp_neighbor['remote_system_description'] = py23_compat.text_type(
                                                                syst_descr_rgx.groups()[1])
                continue
            syst_capab_rgx = re.search(SYST_CAPAB_REEGX, line, re.I)
            if syst_capab_rgx:
                lldp_neighbor['remote_system_capab'] = py23_compat.text_type(
                                                        syst_capab_rgx.groups()[1])
                continue
            syst_enabled_rgx = re.search(ENABL_CAPAB_REGEX, line, re.I)
            if syst_enabled_rgx:
                lldp_neighbor['remote_system_enable_capab'] = py23_compat.text_type(
                                                                syst_enabled_rgx.groups()[1])
                continue
            vlan_rgx = re.search(VLAN_ID_REGEX, line, re.I)
            if vlan_rgx:
                # at the end of the loop
                if interface_name not in lldp_neighbors.keys():
                    lldp_neighbors[interface_name] = []
                lldp_neighbors[interface_name].append(lldp_neighbor)
        return lldp_neighbors

    def cli(self, commands):
        cli_output = {}
        if type(commands) is not list:
            raise TypeError('Please enter a valid list of commands!')

        for command in commands:
            command_output = self.device.show(command, raw_text=True)
            cli_output[py23_compat.text_type(command)] = command_output
        return cli_output

    def get_arp_table(self):
        arp_table = []
        command = 'show ip arp'
        arp_table_vrf = self._get_command_table(command, 'TABLE_vrf', 'ROW_vrf')
        arp_table_raw = self._get_table_rows(arp_table_vrf[0], 'TABLE_adj', 'ROW_adj')

        for arp_table_entry in arp_table_raw:
            raw_ip = arp_table_entry.get('ip-addr-out')
            raw_mac = arp_table_entry.get('mac')
            age = arp_table_entry.get('time-stamp')
            if age == '-':
                age_sec = -1.0
            elif ':' not in age:
                # Cisco sometimes returns a sub second arp time 0.411797
                try:
                    age_sec = float(age)
                except ValueError:
                    age_sec = -1.0
            else:
                fields = age.split(':')
                if len(fields) == 3:
                    try:
                        fields = [float(x) for x in fields]
                        hours, minutes, seconds = fields
                        age_sec = 3600 * hours + 60 * minutes + seconds
                    except ValueError:
                        age_sec = -1.0
            age_sec = round(age_sec, 1)

            interface = py23_compat.text_type(arp_table_entry.get('intf-out'))
            arp_table.append({
                'interface': interface,
                'mac': napalm.base.helpers.convert(
                    napalm.base.helpers.mac, raw_mac, raw_mac),
                'ip': napalm.base.helpers.ip(raw_ip),
                'age': age_sec
            })
        return arp_table

    def _get_ntp_entity(self, peer_type):
        ntp_entities = {}
        command = 'show ntp peers'
        ntp_peers_table = self._get_command_table(command, 'TABLE_peers', 'ROW_peers')

        for ntp_peer in ntp_peers_table:
            if ntp_peer.get('serv_peer', '').strip() != peer_type:
                continue
            peer_addr = napalm.base.helpers.ip(ntp_peer.get('PeerIPAddress').strip())
            ntp_entities[peer_addr] = {}

        return ntp_entities

    def get_ntp_peers(self):
        return self._get_ntp_entity('Peer')

    def get_ntp_servers(self):
        return self._get_ntp_entity('Server')

    def get_ntp_stats(self):
        ntp_stats = []
        command = 'show ntp peer-status'
        ntp_stats_table = self._get_command_table(command, 'TABLE_peersstatus', 'ROW_peersstatus')

        for ntp_peer in ntp_stats_table:
            peer_address = napalm.base.helpers.ip(ntp_peer.get('remote').strip())
            syncmode = ntp_peer.get('syncmode')
            stratum = int(ntp_peer.get('st'))
            hostpoll = int(ntp_peer.get('poll'))
            reachability = int(ntp_peer.get('reach'))
            delay = float(ntp_peer.get('delay'))
            ntp_stats.append({
                'remote': peer_address,
                'synchronized': (syncmode == '*'),
                'referenceid': peer_address,
                'stratum': stratum,
                'type': '',
                'when': '',
                'hostpoll': hostpoll,
                'reachability': reachability,
                'delay': delay,
                'offset': 0.0,
                'jitter': 0.0
            })
        return ntp_stats

    def get_interfaces_ip(self):
        interfaces_ip = {}
        ipv4_command = 'show ip interface'
        ipv4_interf_table_vrf = self._get_command_table(ipv4_command, 'TABLE_intf', 'ROW_intf')

        for interface in ipv4_interf_table_vrf:
            interface_name = py23_compat.text_type(interface.get('intf-name', ''))
            address = napalm.base.helpers.ip(interface.get('prefix'))
            prefix = int(interface.get('masklen', ''))
            if interface_name not in interfaces_ip.keys():
                interfaces_ip[interface_name] = {}
            if 'ipv4' not in interfaces_ip[interface_name].keys():
                interfaces_ip[interface_name]['ipv4'] = {}
            if address not in interfaces_ip[interface_name].get('ipv4'):
                interfaces_ip[interface_name]['ipv4'][address] = {}
            interfaces_ip[interface_name]['ipv4'][address].update({
                'prefix_length': prefix
            })
            secondary_addresses = interface.get('TABLE_secondary_address', {})\
                                           .get('ROW_secondary_address', [])
            if type(secondary_addresses) is dict:
                secondary_addresses = [secondary_addresses]
            for secondary_address in secondary_addresses:
                secondary_address_ip = napalm.base.helpers.ip(secondary_address.get('prefix1'))
                secondary_address_prefix = int(secondary_address.get('masklen1', ''))
                if 'ipv4' not in interfaces_ip[interface_name].keys():
                    interfaces_ip[interface_name]['ipv4'] = {}
                if secondary_address_ip not in interfaces_ip[interface_name].get('ipv4'):
                    interfaces_ip[interface_name]['ipv4'][secondary_address_ip] = {}
                interfaces_ip[interface_name]['ipv4'][secondary_address_ip].update({
                    'prefix_length': secondary_address_prefix
                })

        ipv6_command = 'show ipv6 interface'
        ipv6_interf_table_vrf = self._get_command_table(ipv6_command, 'TABLE_intf', 'ROW_intf')

        for interface in ipv6_interf_table_vrf:
            interface_name = py23_compat.text_type(interface.get('intf-name', ''))
            address = napalm.base.helpers.ip(interface.get('addr', '').split('/')[0])
            prefix = interface.get('prefix', '').split('/')[-1]
            if prefix:
                prefix = int(interface.get('prefix', '').split('/')[-1])
            else:
                prefix = 128
            if interface_name not in interfaces_ip.keys():
                interfaces_ip[interface_name] = {}
            if 'ipv6' not in interfaces_ip[interface_name].keys():
                interfaces_ip[interface_name]['ipv6'] = {}
            if address not in interfaces_ip[interface_name].get('ipv6'):
                interfaces_ip[interface_name]['ipv6'][address] = {}
            interfaces_ip[interface_name]['ipv6'][address].update({
                'prefix_length': prefix
            })
            secondary_addresses = interface.get('TABLE_sec_addr', {}).get('ROW_sec_addr', [])
            if type(secondary_addresses) is dict:
                secondary_addresses = [secondary_addresses]
            for secondary_address in secondary_addresses:
                sec_prefix = secondary_address.get('sec-prefix', '').split('/')
                secondary_address_ip = napalm.base.helpers.ip(sec_prefix[0])
                secondary_address_prefix = int(sec_prefix[-1])
                if 'ipv6' not in interfaces_ip[interface_name].keys():
                    interfaces_ip[interface_name]['ipv6'] = {}
                if secondary_address_ip not in interfaces_ip[interface_name].get('ipv6'):
                    interfaces_ip[interface_name]['ipv6'][secondary_address_ip] = {}
                interfaces_ip[interface_name]['ipv6'][secondary_address_ip].update({
                    'prefix_length': secondary_address_prefix
                })
        return interfaces_ip

    def get_mac_address_table(self):
        mac_table = []
        command = 'show mac address-table'
        mac_table_raw = self._get_command_table(command, 'TABLE_mac_address', 'ROW_mac_address')

        for mac_entry in mac_table_raw:
            raw_mac = mac_entry.get('disp_mac_addr')
            interface = py23_compat.text_type(mac_entry.get('disp_port'))
            vlan = int(mac_entry.get('disp_vlan'))
            active = True
            static = (mac_entry.get('disp_is_static') != '0')
            moves = 0
            last_move = 0.0
            mac_table.append({
                'mac': napalm.base.helpers.mac(raw_mac),
                'interface': interface,
                'vlan': vlan,
                'active': active,
                'static': static,
                'moves': moves,
                'last_move': last_move
            })
        return mac_table

    def get_snmp_information(self):
        snmp_information = {}
        snmp_command = 'show running-config'
        snmp_raw_output = self.cli([snmp_command]).get(snmp_command, '')
        snmp_config = napalm.base.helpers.textfsm_extractor(self, 'snmp_config', snmp_raw_output)

        if not snmp_config:
            return snmp_information

        snmp_information = {
            'contact': py23_compat.text_type(''),
            'location': py23_compat.text_type(''),
            'community': {},
            'chassis_id': py23_compat.text_type('')
        }

        for snmp_entry in snmp_config:
            contact = py23_compat.text_type(snmp_entry.get('contact', ''))
            if contact:
                snmp_information['contact'] = contact
            location = py23_compat.text_type(snmp_entry.get('location', ''))
            if location:
                snmp_information['location'] = location

            community_name = py23_compat.text_type(snmp_entry.get('community', ''))
            if not community_name:
                continue

            if community_name not in snmp_information['community'].keys():
                snmp_information['community'][community_name] = {
                    'acl': py23_compat.text_type(snmp_entry.get('acl', '')),
                    'mode': py23_compat.text_type(snmp_entry.get('mode', '').lower())
                }
            else:
                acl = py23_compat.text_type(snmp_entry.get('acl', ''))
                if acl:
                    snmp_information['community'][community_name]['acl'] = acl
                mode = py23_compat.text_type(snmp_entry.get('mode', '').lower())
                if mode:
                    snmp_information['community'][community_name]['mode'] = mode
        return snmp_information

    def get_users(self):
        _CISCO_TO_CISCO_MAP = {
            'network-admin': 15,
            'network-operator': 5
        }

        _DEFAULT_USER_DICT = {
            'password': '',
            'level': 0,
            'sshkeys': []
        }

        users = {}
        command = 'show running-config'
        section_username_raw_output = self.cli([command]).get(command, '')
        section_username_tabled_output = napalm.base.helpers.textfsm_extractor(
            self, 'users', section_username_raw_output)

        for user in section_username_tabled_output:
            username = user.get('username', '')
            if not username:
                continue
            if username not in users:
                users[username] = _DEFAULT_USER_DICT.copy()

            password = user.get('password', '')
            if password:
                users[username]['password'] = py23_compat.text_type(password.strip())

            level = 0
            role = user.get('role', '')
            if role.startswith('priv'):
                level = int(role.split('-')[-1])
            else:
                level = _CISCO_TO_CISCO_MAP.get(role, 0)
            if level > users.get(username).get('level'):
                # unfortunately on Cisco you can set different priv levels for the same user
                # Good news though: the device will consider the highest level
                users[username]['level'] = level

            sshkeytype = user.get('sshkeytype', '')
            sshkeyvalue = user.get('sshkeyvalue', '')
            if sshkeytype and sshkeyvalue:
                if sshkeytype not in ['ssh-rsa', 'ssh-dsa']:
                    continue
                users[username]['sshkeys'].append(py23_compat.text_type(sshkeyvalue))
        return users

    def traceroute(self,
                   destination,
                   source=c.TRACEROUTE_SOURCE,
                   ttl=c.TRACEROUTE_TTL,
                   timeout=c.TRACEROUTE_TIMEOUT,
                   vrf=c.TRACEROUTE_VRF):
        _HOP_ENTRY_PROBE = [
            r'\s+',
            r'(',  # beginning of host_name (ip_address) RTT group
            r'(',  # beginning of host_name (ip_address) group only
            r'([a-zA-Z0-9\.:-]*)',  # hostname
            r'\s+',
            r'\(?([a-fA-F0-9\.:][^\)]*)\)?'  # IP Address between brackets
            r')?',  # end of host_name (ip_address) group only
            # also hostname/ip are optional -- they can or cannot be specified
            # if not specified, means the current probe followed the same path as the previous
            r'\s+',
            r'(\d+\.\d+)\s+ms',  # RTT
            r'|\*',  # OR *, when non responsive hop
            r')'  # end of host_name (ip_address) RTT group
        ]

        _HOP_ENTRY = [
            r'\s?',  # space before hop index?
            r'(\d+)',  # hop index
        ]

        traceroute_result = {}
        timeout = 5  # seconds
        probes = 3  # 3 probes/jop and this cannot be changed on NXOS!

        version = ''
        try:
            version = '6' if IPAddress(destination).version == 6 else ''
        except AddrFormatError:
            return {'error': 'Destination doest not look like a valid IP Address: {}'.format(
                destination)}

        source_opt = ''
        if source:
            source_opt = 'source {source}'.format(source=source)

        command = 'traceroute{version} {destination} {source_opt}'.format(
            version=version,
            destination=destination,
            source_opt=source_opt
        )

        try:
            traceroute_raw_output = self.cli([command]).get(command)
        except CommandErrorException:
            return {'error': 'Cannot execute traceroute on the device: {}'.format(command)}

        hop_regex = ''.join(_HOP_ENTRY + _HOP_ENTRY_PROBE * probes)
        traceroute_result['success'] = {}
        if traceroute_raw_output:
            for line in traceroute_raw_output.splitlines():
                hop_search = re.search(hop_regex, line)
                if not hop_search:
                    continue
                hop_details = hop_search.groups()
                hop_index = int(hop_details[0])
                previous_probe_host_name = '*'
                previous_probe_ip_address = '*'
                traceroute_result['success'][hop_index] = {'probes': {}}
                for probe_index in range(probes):
                    host_name = hop_details[3+probe_index*5]
                    ip_address_raw = hop_details[4+probe_index*5]
                    ip_address = napalm.base.helpers.convert(
                        napalm.base.helpers.ip, ip_address_raw, ip_address_raw)
                    rtt = hop_details[5+probe_index*5]
                    if rtt:
                        rtt = float(rtt)
                    else:
                        rtt = timeout * 1000.0
                    if not host_name:
                        host_name = previous_probe_host_name
                    if not ip_address:
                        ip_address = previous_probe_ip_address
                    if hop_details[1+probe_index*5] == '*':
                        host_name = '*'
                        ip_address = '*'
                    traceroute_result['success'][hop_index]['probes'][probe_index+1] = {
                        'host_name': py23_compat.text_type(host_name),
                        'ip_address': py23_compat.text_type(ip_address),
                        'rtt': rtt
                    }
                    previous_probe_host_name = host_name
                    previous_probe_ip_address = ip_address
        return traceroute_result

    def get_config(self, retrieve='all'):
        config = {
            'startup': '',
            'running': '',
            'candidate': ''
        }  # default values

        if retrieve.lower() in ('running', 'all'):
            _cmd = 'show running-config'
            config['running'] = py23_compat.text_type(self.cli([_cmd]).get(_cmd))
        if retrieve.lower() in ('startup', 'all'):
            _cmd = 'show startup-config'
            config['startup'] = py23_compat.text_type(self.cli([_cmd]).get(_cmd))
        return config
 def setUp(self, mock_device):
     self.device = mock_device
     self.device.host = 'host'
     self.device.username = '******'
     self.device.password = '******'
     self.fc = FileCopy(self.device, '/path/to/source_file')
class FileCopyTestCase(unittest.TestCase):

    @mock.patch('pynxos.device.Device', autospec=True)
    def setUp(self, mock_device):
        self.device = mock_device
        self.device.host = 'host'
        self.device.username = '******'
        self.device.password = '******'
        self.fc = FileCopy(self.device, '/path/to/source_file')

    def test_init(self):
        self.assertEqual(self.fc.device, self.device)
        self.assertEqual(self.fc.src, '/path/to/source_file')
        self.assertEqual(self.fc.dst, 'source_file')
        self.assertEqual(self.fc.port, 22)
        self.assertEqual(self.fc.file_system, 'bootflash:')

    def test_get_remote_size(self):
        self.device.show.return_value = '       4096    Mar 15 17:06:51 2016  .rpmstore/\n       3651    May 19 18:26:19 2014  20140519_182619_poap_6121_init.log\n       3651    May 19 18:34:38 2014  20140519_183438_poap_5884_init.log\n      23167    Jul 11 19:55:32 2014  20140711_195320_poap_5884_init.log\n       3735    Oct 09 18:00:43 2015  20151009_180036_poap_6291_init.log\n       2826    Oct 12 20:17:32 2015  abc\n       7160    Oct 06 13:49:57 2015  cfg_flowtracker1\n       7123    Oct 08 19:26:48 2015  cfg_flowtracker1_2\n      89620    Oct 09 18:04:41 2015  clean_n9k2_all_cfg\n       2773    Oct 09 18:04:18 2015  clean_n9k2_cfg\n      17339    Oct 09 19:58:44 2015  clean_n9k2_cp\n      18203    Oct 12 19:41:21 2015  clean_n9k2_cp2\n      18118    Oct 12 21:03:57 2015  config_2015-10-12_17:03:46.308598\n      18118    Oct 12 21:03:58 2015  config_2015-10-12_17:03:47.338797\n      18118    Oct 12 21:04:03 2015  config_2015-10-12_17:03:52.012664\n      18118    Oct 12 21:06:17 2015  config_2015-10-12_17:06:05.026284\n      18118    Oct 12 21:07:03 2015  config_2015-10-12_17:06:50.357353\n      18118    Oct 12 21:08:13 2015  config_2015-10-12_17:08:01.145064\n      18118    Oct 12 21:12:55 2015  config_2015-10-12_17:12:43.603017\n      18118    Oct 12 21:13:38 2015  config_2015-10-12_17:13:25.476126\n      18098    Oct 12 21:14:40 2015  config_2015-10-12_17:14:29.411540\n      18118    Oct 12 21:14:43 2015  config_2015-10-12_17:14:32.442546\n      18099    Oct 12 21:14:46 2015  config_2015-10-12_17:14:35.595983\n      18118    Oct 12 21:16:03 2015  config_2015-10-12_17:15:51.501546\n      18118    Oct 12 21:16:20 2015  config_2015-10-12_17:16:09.478200\n      18118    Oct 12 21:16:21 2015  config_2015-10-12_17:16:10.613538\n      18099    Oct 12 21:16:25 2015  config_2015-10-12_17:16:13.730374\n      18118    Oct 12 21:16:30 2015  config_2015-10-12_17:16:18.856276\n      18118    Oct 12 21:16:36 2015  config_2015-10-12_17:16:24.817255\n       4096    Jan 11 20:00:40 2016  configs/\n       5365    Feb 05 15:57:55 2015  configs:jaay.cfg\n       5365    Feb 05 15:51:31 2015  configs:jay.cfg\n      18061    Oct 09 19:12:42 2015  cp_with_shutdown\n        154    Feb 19 21:33:05 2015  eth3.cfg\n         65    Feb 19 21:18:28 2015  eth_1_1.cfg\n       4096    Aug 10 18:54:09 2015  home/\n      18111    Oct 12 20:30:41 2015  initial.conf\n       4096    Mar 15 15:42:22 2016  lost+found/\n  309991424    May 19 18:23:41 2014  n9000-dk9.6.1.2.I2.1.bin\n  353457152    Nov 02 15:14:40 2014  n9000-dk9.6.1.2.I3.1.bin\n   37612335    Nov 02 15:20:00 2014  n9000-epld.6.1.2.I3.1.img\n       9888    Oct 08 18:35:39 2015  n9k1_cfg\n      73970    Oct 09 16:30:54 2015  n9k2_all_cfg\n       7105    Oct 08 19:48:41 2015  n9k2_cfg\n       7142    Oct 08 18:49:19 2015  n9k2_cfg_safe\n      21293    Oct 09 17:16:57 2015  n9k2_cp\n       4096    Aug 10 20:17:35 2015  netmiko/\n      18187    Oct 12 20:31:20 2015  new_typo.conf\n      17927    Oct 12 18:25:40 2015  newcpfile\n  535352320    Mar 15 15:39:31 2016  nxos.7.0.3.I2.1.bin\n       4096    Jan 28 15:33:36 2015  onep/\n       6079    Oct 06 14:46:33 2015  pn9k1_cfg.bak\n   54466560    Jan 28 12:48:30 2015  puppet-1.0.0-nx-os-SPA-k9.ova\n       9698    Sep 19 05:43:12 2014  sart\n       4096    Feb 05 15:15:30 2015  scriaspts/\n       4096    Feb 05 15:09:35 2015  scripts/\n       3345    Feb 19 21:04:50 2015  standardconfig.cfg\n      21994    Oct 23 15:32:18 2015  travis_ping\n      18038    Oct 12 19:32:17 2015  tshootcp\n       4096    Mar 15 15:48:59 2016  virt_strg_pool_bf_vdc_1/\n       4096    Jan 28 15:30:29 2015  virtual-instance/\n        125    Mar 15 15:48:12 2016  virtual-instance.conf\n       2068    Mar 16 09:58:23 2016  vlan.dat\nUsage for bootflash://sup-local\n 2425626624 bytes used\n19439792128 bytes free\n21865418752 bytes total\n'
        result = self.fc.get_remote_size()
        expected = 19439792128

        self.assertEqual(result, expected)
        self.device.show.assert_called_with('dir bootflash:', raw_text=True)

    @mock.patch('os.path.getsize')
    def test_enough_space(self, mock_getsize):
        self.device.show.return_value = '       4096    Mar 15 17:06:51 2016  .rpmstore/\n       3651    May 19 18:26:19 2014  20140519_182619_poap_6121_init.log\n       3651    May 19 18:34:38 2014  20140519_183438_poap_5884_init.log\n      23167    Jul 11 19:55:32 2014  20140711_195320_poap_5884_init.log\n       3735    Oct 09 18:00:43 2015  20151009_180036_poap_6291_init.log\n       2826    Oct 12 20:17:32 2015  abc\n       7160    Oct 06 13:49:57 2015  cfg_flowtracker1\n       7123    Oct 08 19:26:48 2015  cfg_flowtracker1_2\n      89620    Oct 09 18:04:41 2015  clean_n9k2_all_cfg\n       2773    Oct 09 18:04:18 2015  clean_n9k2_cfg\n      17339    Oct 09 19:58:44 2015  clean_n9k2_cp\n      18203    Oct 12 19:41:21 2015  clean_n9k2_cp2\n      18118    Oct 12 21:03:57 2015  config_2015-10-12_17:03:46.308598\n      18118    Oct 12 21:03:58 2015  config_2015-10-12_17:03:47.338797\n      18118    Oct 12 21:04:03 2015  config_2015-10-12_17:03:52.012664\n      18118    Oct 12 21:06:17 2015  config_2015-10-12_17:06:05.026284\n      18118    Oct 12 21:07:03 2015  config_2015-10-12_17:06:50.357353\n      18118    Oct 12 21:08:13 2015  config_2015-10-12_17:08:01.145064\n      18118    Oct 12 21:12:55 2015  config_2015-10-12_17:12:43.603017\n      18118    Oct 12 21:13:38 2015  config_2015-10-12_17:13:25.476126\n      18098    Oct 12 21:14:40 2015  config_2015-10-12_17:14:29.411540\n      18118    Oct 12 21:14:43 2015  config_2015-10-12_17:14:32.442546\n      18099    Oct 12 21:14:46 2015  config_2015-10-12_17:14:35.595983\n      18118    Oct 12 21:16:03 2015  config_2015-10-12_17:15:51.501546\n      18118    Oct 12 21:16:20 2015  config_2015-10-12_17:16:09.478200\n      18118    Oct 12 21:16:21 2015  config_2015-10-12_17:16:10.613538\n      18099    Oct 12 21:16:25 2015  config_2015-10-12_17:16:13.730374\n      18118    Oct 12 21:16:30 2015  config_2015-10-12_17:16:18.856276\n      18118    Oct 12 21:16:36 2015  config_2015-10-12_17:16:24.817255\n       4096    Jan 11 20:00:40 2016  configs/\n       5365    Feb 05 15:57:55 2015  configs:jaay.cfg\n       5365    Feb 05 15:51:31 2015  configs:jay.cfg\n      18061    Oct 09 19:12:42 2015  cp_with_shutdown\n        154    Feb 19 21:33:05 2015  eth3.cfg\n         65    Feb 19 21:18:28 2015  eth_1_1.cfg\n       4096    Aug 10 18:54:09 2015  home/\n      18111    Oct 12 20:30:41 2015  initial.conf\n       4096    Mar 15 15:42:22 2016  lost+found/\n  309991424    May 19 18:23:41 2014  n9000-dk9.6.1.2.I2.1.bin\n  353457152    Nov 02 15:14:40 2014  n9000-dk9.6.1.2.I3.1.bin\n   37612335    Nov 02 15:20:00 2014  n9000-epld.6.1.2.I3.1.img\n       9888    Oct 08 18:35:39 2015  n9k1_cfg\n      73970    Oct 09 16:30:54 2015  n9k2_all_cfg\n       7105    Oct 08 19:48:41 2015  n9k2_cfg\n       7142    Oct 08 18:49:19 2015  n9k2_cfg_safe\n      21293    Oct 09 17:16:57 2015  n9k2_cp\n       4096    Aug 10 20:17:35 2015  netmiko/\n      18187    Oct 12 20:31:20 2015  new_typo.conf\n      17927    Oct 12 18:25:40 2015  newcpfile\n  535352320    Mar 15 15:39:31 2016  nxos.7.0.3.I2.1.bin\n       4096    Jan 28 15:33:36 2015  onep/\n       6079    Oct 06 14:46:33 2015  pn9k1_cfg.bak\n   54466560    Jan 28 12:48:30 2015  puppet-1.0.0-nx-os-SPA-k9.ova\n       9698    Sep 19 05:43:12 2014  sart\n       4096    Feb 05 15:15:30 2015  scriaspts/\n       4096    Feb 05 15:09:35 2015  scripts/\n       3345    Feb 19 21:04:50 2015  standardconfig.cfg\n      21994    Oct 23 15:32:18 2015  travis_ping\n      18038    Oct 12 19:32:17 2015  tshootcp\n       4096    Mar 15 15:48:59 2016  virt_strg_pool_bf_vdc_1/\n       4096    Jan 28 15:30:29 2015  virtual-instance/\n        125    Mar 15 15:48:12 2016  virtual-instance.conf\n       2068    Mar 16 09:58:23 2016  vlan.dat\nUsage for bootflash://sup-local\n 2425626624 bytes used\n19439792128 bytes free\n21865418752 bytes total\n'
        mock_getsize.return_value = 10

        result = self.fc.enough_remote_space()

        self.assertEqual(result, True)
        mock_getsize.assert_called_with('/path/to/source_file')

    @mock.patch('os.path.getsize')
    def test_not_enough_space(self, mock_getsize):
        self.device.show.return_value = '       4096    Mar 15 17:06:51 2016  .rpmstore/\n       3651    May 19 18:26:19 2014  20140519_182619_poap_6121_init.log\n       3651    May 19 18:34:38 2014  20140519_183438_poap_5884_init.log\n      23167    Jul 11 19:55:32 2014  20140711_195320_poap_5884_init.log\n       3735    Oct 09 18:00:43 2015  20151009_180036_poap_6291_init.log\n       2826    Oct 12 20:17:32 2015  abc\n       7160    Oct 06 13:49:57 2015  cfg_flowtracker1\n       7123    Oct 08 19:26:48 2015  cfg_flowtracker1_2\n      89620    Oct 09 18:04:41 2015  clean_n9k2_all_cfg\n       2773    Oct 09 18:04:18 2015  clean_n9k2_cfg\n      17339    Oct 09 19:58:44 2015  clean_n9k2_cp\n      18203    Oct 12 19:41:21 2015  clean_n9k2_cp2\n      18118    Oct 12 21:03:57 2015  config_2015-10-12_17:03:46.308598\n      18118    Oct 12 21:03:58 2015  config_2015-10-12_17:03:47.338797\n      18118    Oct 12 21:04:03 2015  config_2015-10-12_17:03:52.012664\n      18118    Oct 12 21:06:17 2015  config_2015-10-12_17:06:05.026284\n      18118    Oct 12 21:07:03 2015  config_2015-10-12_17:06:50.357353\n      18118    Oct 12 21:08:13 2015  config_2015-10-12_17:08:01.145064\n      18118    Oct 12 21:12:55 2015  config_2015-10-12_17:12:43.603017\n      18118    Oct 12 21:13:38 2015  config_2015-10-12_17:13:25.476126\n      18098    Oct 12 21:14:40 2015  config_2015-10-12_17:14:29.411540\n      18118    Oct 12 21:14:43 2015  config_2015-10-12_17:14:32.442546\n      18099    Oct 12 21:14:46 2015  config_2015-10-12_17:14:35.595983\n      18118    Oct 12 21:16:03 2015  config_2015-10-12_17:15:51.501546\n      18118    Oct 12 21:16:20 2015  config_2015-10-12_17:16:09.478200\n      18118    Oct 12 21:16:21 2015  config_2015-10-12_17:16:10.613538\n      18099    Oct 12 21:16:25 2015  config_2015-10-12_17:16:13.730374\n      18118    Oct 12 21:16:30 2015  config_2015-10-12_17:16:18.856276\n      18118    Oct 12 21:16:36 2015  config_2015-10-12_17:16:24.817255\n       4096    Jan 11 20:00:40 2016  configs/\n       5365    Feb 05 15:57:55 2015  configs:jaay.cfg\n       5365    Feb 05 15:51:31 2015  configs:jay.cfg\n      18061    Oct 09 19:12:42 2015  cp_with_shutdown\n        154    Feb 19 21:33:05 2015  eth3.cfg\n         65    Feb 19 21:18:28 2015  eth_1_1.cfg\n       4096    Aug 10 18:54:09 2015  home/\n      18111    Oct 12 20:30:41 2015  initial.conf\n       4096    Mar 15 15:42:22 2016  lost+found/\n  309991424    May 19 18:23:41 2014  n9000-dk9.6.1.2.I2.1.bin\n  353457152    Nov 02 15:14:40 2014  n9000-dk9.6.1.2.I3.1.bin\n   37612335    Nov 02 15:20:00 2014  n9000-epld.6.1.2.I3.1.img\n       9888    Oct 08 18:35:39 2015  n9k1_cfg\n      73970    Oct 09 16:30:54 2015  n9k2_all_cfg\n       7105    Oct 08 19:48:41 2015  n9k2_cfg\n       7142    Oct 08 18:49:19 2015  n9k2_cfg_safe\n      21293    Oct 09 17:16:57 2015  n9k2_cp\n       4096    Aug 10 20:17:35 2015  netmiko/\n      18187    Oct 12 20:31:20 2015  new_typo.conf\n      17927    Oct 12 18:25:40 2015  newcpfile\n  535352320    Mar 15 15:39:31 2016  nxos.7.0.3.I2.1.bin\n       4096    Jan 28 15:33:36 2015  onep/\n       6079    Oct 06 14:46:33 2015  pn9k1_cfg.bak\n   54466560    Jan 28 12:48:30 2015  puppet-1.0.0-nx-os-SPA-k9.ova\n       9698    Sep 19 05:43:12 2014  sart\n       4096    Feb 05 15:15:30 2015  scriaspts/\n       4096    Feb 05 15:09:35 2015  scripts/\n       3345    Feb 19 21:04:50 2015  standardconfig.cfg\n      21994    Oct 23 15:32:18 2015  travis_ping\n      18038    Oct 12 19:32:17 2015  tshootcp\n       4096    Mar 15 15:48:59 2016  virt_strg_pool_bf_vdc_1/\n       4096    Jan 28 15:30:29 2015  virtual-instance/\n        125    Mar 15 15:48:12 2016  virtual-instance.conf\n       2068    Mar 16 09:58:23 2016  vlan.dat\nUsage for bootflash://sup-local\n 2425626624 bytes used\n19439792128 bytes free\n21865418752 bytes total\n'
        mock_getsize.return_value = 100000000000000000

        result = self.fc.enough_remote_space()

        self.assertEqual(result, False)
        mock_getsize.assert_called_with('/path/to/source_file')

    @mock.patch('os.path.isfile')
    def test_local_file_exists(self, mock_isfile):
        mock_isfile.return_value = True
        result = self.fc.local_file_exists()
        expected = True

        self.assertEqual(result, expected)
        mock_isfile.assert_called_with('/path/to/source_file')

    @mock.patch('os.path.isfile')
    def test_local_file_doesnt_exist(self, mock_isfile):
        mock_isfile.return_value = False
        result = self.fc.local_file_exists()
        expected = False

        self.assertEqual(result, expected)
        mock_isfile.assert_called_with('/path/to/source_file')

    @mock.patch.object(FileCopy, 'get_local_md5')
    def test_file_already_exists(self, mock_local_md5):
        mock_local_md5.return_value = 'b211e79fbaede5859ed2192b0fc5f1d5'
        self.device.show.return_value = {'file_content_md5sum': 'b211e79fbaede5859ed2192b0fc5f1d5\n'}

        result = self.fc.already_transfered()

        self.assertEqual(result, True)
        self.device.show.assert_called_with('show file bootflash:source_file md5sum', raw_text=False)
        mock_local_md5.assert_called_with()

    @mock.patch.object(FileCopy, 'get_local_md5')
    def test_file_doesnt_already_exists(self, mock_local_md5):
        mock_local_md5.return_value = 'abcdef12345'
        self.device.show.return_value = {'file_content_md5sum': 'b211e79fbaede5859ed2192b0fc5f1d5\n'}

        result = self.fc.already_transfered()

        self.assertEqual(result, False)
        self.device.show.assert_called_with('show file bootflash:source_file md5sum', raw_text=False)
        mock_local_md5.assert_called_with()

    def test_remote_file_doesnt_exists(self):
        self.device.show.return_value = 'No such file'

        result = self.fc.remote_file_exists()

        self.assertEqual(result, False)
        self.device.show.assert_called_with('dir bootflash:/source_file', raw_text=True)

    def test_remote_file_exists(self):
        self.device.show.return_value = '          5    Mar 23 00:48:15 2016  smallfile\nUsage for bootflash://sup-local\n 2425630720 bytes used\n19439788032 bytes free\n21865418752 bytes total\n'

        result = self.fc.remote_file_exists()

        self.assertEqual(result, True)
        self.device.show.assert_called_with('dir bootflash:/source_file', raw_text=True)

    @mock.patch('pynxos.features.file_copy.paramiko')
    @mock.patch('pynxos.features.file_copy.SCPClient')
    @mock.patch.object(FileCopy, 'get_local_md5')
    @mock.patch.object(FileCopy, 'get_remote_md5')
    @mock.patch.object(FileCopy, 'local_file_exists')
    @mock.patch.object(FileCopy, 'enough_space')
    def test_send_file(self, mock_enough_space, mock_local_file_exists, mock_remote_md5, mock_local_md5, mock_SCP, mock_paramiko):
        mock_remote_md5.return_value = 'abc'
        mock_local_md5.return_value = 'abc'
        mock_local_file_exists.return_value = True
        mock_enough_space.return_value = True

        mock_ssh = mock_paramiko.SSHClient.return_value

        self.fc.send()

        mock_paramiko.SSHClient.assert_called_with()

        mock_ssh.set_missing_host_key_policy.assert_called_with(mock_paramiko.AutoAddPolicy.return_value)
        mock_ssh.connect.assert_called_with(allow_agent=False,
                                             hostname=self.device.host,
                                             look_for_keys=False,
                                             password=self.device.password,
                                             port=22,
                                             username=self.device.username)

        mock_SCP.assert_called_with(mock_ssh.get_transport.return_value)
        mock_SCP.return_value.put.assert_called_with('/path/to/source_file', 'bootflash:source_file')
        mock_SCP.return_value.close.assert_called_with()


    @mock.patch('pynxos.features.file_copy.paramiko')
    @mock.patch('pynxos.features.file_copy.SCPClient')
    @mock.patch.object(FileCopy, 'get_local_md5')
    @mock.patch.object(FileCopy, 'get_remote_md5')
    @mock.patch.object(FileCopy, 'local_file_exists')
    @mock.patch.object(FileCopy, 'enough_space')
    def test_get_file(self, mock_enough_space, mock_local_file_exists, mock_remote_md5, mock_local_md5, mock_SCP, mock_paramiko):
        mock_remote_md5.return_value = 'abc'
        mock_local_md5.return_value = 'abc'
        mock_local_file_exists.return_value = True
        mock_enough_space.return_value = True

        mock_ssh = mock_paramiko.SSHClient.return_value

        self.fc.get()

        mock_paramiko.SSHClient.assert_called_with()

        mock_ssh.set_missing_host_key_policy.assert_called_with(mock_paramiko.AutoAddPolicy.return_value)
        mock_ssh.connect.assert_called_with(allow_agent=False,
                                             hostname=self.device.host,
                                             look_for_keys=False,
                                             password=self.device.password,
                                             port=22,
                                             username=self.device.username)

        mock_SCP.assert_called_with(mock_ssh.get_transport.return_value)
        mock_SCP.return_value.get.assert_called_with('bootflash:source_file', '/path/to/source_file')
        mock_SCP.return_value.close.assert_called_with()


    @mock.patch('pynxos.features.file_copy.paramiko')
    @mock.patch('pynxos.features.file_copy.SCPClient')
    @mock.patch.object(FileCopy, 'get_local_md5')
    @mock.patch.object(FileCopy, 'get_remote_md5')
    @mock.patch.object(FileCopy, 'local_file_exists')
    @mock.patch.object(FileCopy, 'enough_space')
    def test_send_file_error_local_not_exist(self, mock_enough_space, mock_local_file_exists, mock_remote_md5, mock_local_md5, mock_SCP, mock_paramiko):
        mock_remote_md5.return_value = 'abc'
        mock_local_md5.return_value = 'abc'
        mock_local_file_exists.return_value = False
        mock_enough_space.return_value = True

        mock_ssh = mock_paramiko.SSHClient.return_value

        with self.assertRaises(FileTransferError):
            self.fc.send()

    @mock.patch('pynxos.features.file_copy.paramiko')
    @mock.patch('pynxos.features.file_copy.SCPClient')
    @mock.patch.object(FileCopy, 'get_local_md5')
    @mock.patch.object(FileCopy, 'get_remote_md5')
    @mock.patch.object(FileCopy, 'local_file_exists')
    @mock.patch.object(FileCopy, 'enough_space')
    def test_send_file_error_not_enough_space(self, mock_enough_space, mock_local_file_exists, mock_remote_md5, mock_local_md5, mock_SCP, mock_paramiko):
        mock_remote_md5.return_value = 'abc'
        mock_local_md5.return_value = 'abc'
        mock_local_file_exists.return_value = True
        mock_enough_space.return_value = False

        mock_ssh = mock_paramiko.SSHClient.return_value

        with self.assertRaises(FileTransferError):
            self.fc.send()

    @mock.patch('pynxos.features.file_copy.paramiko')
    @mock.patch('pynxos.features.file_copy.SCPClient')
    @mock.patch.object(FileCopy, 'get_local_md5')
    @mock.patch.object(FileCopy, 'get_remote_md5')
    @mock.patch.object(FileCopy, 'local_file_exists')
    @mock.patch.object(FileCopy, 'enough_space')
    def test_send_file_transfer_error(self, mock_enough_space, mock_local_file_exists, mock_remote_md5, mock_local_md5, mock_SCP, mock_paramiko):
        mock_remote_md5.return_value = 'abc'
        mock_local_md5.return_value = 'abc'
        mock_local_file_exists.return_value = True
        mock_enough_space.return_value = True

        mock_ssh = mock_paramiko.SSHClient.return_value
        mock_SCP.return_value.put.side_effect = Exception

        with self.assertRaises(FileTransferError):
            self.fc.send()

        mock_paramiko.SSHClient.assert_called_with()

        mock_ssh.set_missing_host_key_policy.assert_called_with(mock_paramiko.AutoAddPolicy.return_value)
        mock_ssh.connect.assert_called_with(allow_agent=False,
                                             hostname=self.device.host,
                                             look_for_keys=False,
                                             password=self.device.password,
                                             port=22,
                                             username=self.device.username)

        mock_SCP.assert_called_with(mock_ssh.get_transport.return_value)
        mock_SCP.return_value.put.assert_called_with('/path/to/source_file', 'bootflash:source_file')
        mock_SCP.return_value.close.assert_called_with()