class DNOS6Driver(NetworkDriver): def __init__(self, hostname, username, password, timeout=60, optional_args=None): self.hostname = hostname self.username = username self.password = password self.timeout = timeout optional_args = optional_args or dict() self.transport = optional_args.get('transport', 'ssh') # Retrieve file names self.candidate_cfg = optional_args.get('candidate_cfg', 'candidate_config.txt') self.merge_cfg = optional_args.get('merge_cfg', 'merge_config.txt') self.rollback_cfg = optional_args.get('rollback_cfg', 'rollback_config.txt') self.inline_transfer = optional_args.get('inline_transfer', False) if self.transport == 'telnet': # Telnet only supports inline_transfer self.inline_transfer = True # None will cause autodetection of dest_file_system self._dest_file_system = optional_args.get('dest_file_system', None) self.auto_rollback_on_error = optional_args.get( 'auto_rollback_on_error', True) # Control automatic toggling of 'file prompt quiet' for file operations self.auto_file_prompt = optional_args.get('auto_file_prompt', True) # Netmiko possible arguments netmiko_argument_map = { 'port': None, 'secret': '', 'verbose': False, 'keepalive': 30, 'global_delay_factor': 1, 'use_keys': False, 'key_file': None, 'ssh_strict': False, 'system_host_keys': False, 'alt_host_keys': False, 'alt_key_file': '', 'ssh_config_file': None, 'allow_agent': False, } # Build dict of any optional Netmiko args self.netmiko_optional_args = {} for k, v in netmiko_argument_map.items(): try: self.netmiko_optional_args[k] = optional_args[k] except KeyError: pass default_port = {'ssh': 22, 'telnet': 23} self.port = optional_args.get('port', default_port[self.transport]) self.device = None self.config_replace = False self.profile = ["dnos6"] self.use_canonical_interface = optional_args.get( 'canonical_int', False) def open(self): """Open a connection to the device.""" device_type = 'dell_dnos6' if self.transport == 'telnet': device_type = 'dell_dnos6_telnet' self.device = ConnectHandler(device_type=device_type, host=self.hostname, username=self.username, password=self.password, **self.netmiko_optional_args) # ensure in enable mode self.device.enable() def _discover_file_system(self): try: return self.device._autodetect_fs() except Exception: msg = "Netmiko _autodetect_fs failed (to workaround specify " \ "dest_file_system in optional_args.)" raise CommandErrorException(msg) def close(self): """Close the connection to the device.""" self.device.disconnect() def _send_command(self, command): """Wrapper for self.device.send.command(). If command is a list will iterate through commands until valid command. """ try: if isinstance(command, list): for cmd in command: output = self.device.send_command(cmd) if "% Invalid" not in output: break else: output = self.device.send_command(command) # return self._send_command_postprocess(output) return output except (socket.error, EOFError) as e: raise ConnectionClosedException(str(e)) def is_alive(self): """Returns a flag with the state of the connection.""" null = chr(0) if self.device is None: return {'is_alive': False} if self.transport == 'telnet': try: # Try sending IAC + NOP (IAC is telnet way of sending command # IAC = Interpret as Command (it comes before the NOP) self.device.write_channel(telnetlib.IAC + telnetlib.NOP) return {'is_alive': True} except UnicodeDecodeError: # Netmiko logging bug (remove after Netmiko >= 1.4.3) return {'is_alive': True} except AttributeError: return {'is_alive': False} else: # SSH try: # Try sending ASCII null byte to maintain the connection alive self.device.write_channel(null) return { 'is_alive': self.device.remote_conn.transport.is_active() } except (socket.error, EOFError): # If unable to send, we can tell for sure that the connection is unusable return {'is_alive': False} return {'is_alive': False} @staticmethod def _create_tmp_file(config): """Write temp file and for use with inline config and SCP.""" tmp_dir = tempfile.gettempdir() rand_fname = py23_compat.text_type(uuid.uuid4()) filename = os.path.join(tmp_dir, rand_fname) with open(filename, 'wt') as fobj: fobj.write(config) return filename def get_config(self, retrieve='all'): """Implementation of get_config for DNOS6. Returns the startup or/and running configuration as dictionary. The keys of the dictionary represent the type of configuration (startup or running). The candidate is always empty string, since IOS does not support candidate configuration. """ configs = { 'startup': '', 'running': '', 'candidate': '', } if retrieve in ('startup', 'all'): command = 'show startup-config' output = self._send_command(command) configs['startup'] = output if retrieve in ('running', 'all'): command = 'show running-config' output = self._send_command(command) configs['running'] = output return configs def get_environment(self): """ Get environment facts. power and fan are currently not implemented cpu is using 1-minute average cpu hard-coded to cpu0 (i.e. only a single CPU) """ environment = {} cpu_cmd = 'show proc cpu' temp_cmd = 'show system temperature' output = self._send_command(cpu_cmd) environment.setdefault('cpu', {}) environment['cpu'][0] = {} environment['cpu'][0]['%usage'] = 0.0 for line in output.splitlines(): if 'Total CPU Utilization' in line: # ['Total', 'CPU', 'Utilization', '9.26%', '9.75%', '9.72%'] oneminute = float(line.split()[4][:-1]) environment['cpu'][0]['%usage'] = float(oneminute) break if 'alloc' in line: used_mem = int(line.split()[1]) if 'free' in line: avail_mem = int(line.split()[1]) environment.setdefault('memory', {}) environment['memory']['used_ram'] = used_mem environment['memory']['available_ram'] = used_mem + avail_mem environment.setdefault('temperature', {}) re_temp_value = re.compile('(.*) Temperature Value') # TODO output = self._send_command(temp_cmd) env_value = { 'is_alert': False, 'is_critical': False, 'temperature': -1.0 } environment['temperature']['invalid'] = env_value # Initialize 'power' and 'fan' to default values (not implemented) environment.setdefault('power', {}) environment['power']['invalid'] = { 'status': True, 'output': -1.0, 'capacity': -1.0 } environment.setdefault('fans', {}) environment['fans']['invalid'] = {'status': True} return environment def get_mac_address_table(self): """ Returns a lists of dictionaries. Each dictionary represents an entry in the MAC Address Table, having the following keys * mac (string) * interface (string) * vlan (int) * active (boolean) * static (boolean) * moves (int) * last_move (float) Format1: Aging time is 300 Sec Vlan Mac Address Type Port -------- --------------------- ----------- --------------------- 1 0025.90C2.88ED Dynamic Gi1/0/48 1 F48E.3841.9628 Management Vl1 """ def _process_mac_fields(vlan, mac, mac_type, interface): """Return proper data for mac address fields.""" if mac_type.lower() in ['management', 'static']: static = True else: static = False if mac_type.lower() in ['dynamic']: active = True else: active = False return { 'mac': napalm.base.helpers.mac(mac), 'interface': self._canonical_int(interface), 'vlan': int(vlan), 'static': static, 'active': active, 'moves': -1, 'last_move': -1.0 } output = self._send_command("show mac address-table") output = re.split(r'^----.*', output, flags=re.M)[1:] output = re.split(r'\n\nTotal.*', output[0], flags=re.M)[0] lines = output.split('\n') entries = [] [ entries.append(_process_mac_fields(*i.split())) for i in lines if i != '' ] return entries def get_arp_table(self): """ Returns a list of dictionaries having the following set of keys: * interface (string) * mac (string) * ip (string) * age (float) Example:: [ { 'interface' : 'MgmtEth0/RSP0/CPU0/0', 'mac' : '5C:5E:AB:DA:3C:F0', 'ip' : '172.17.17.1', 'age' : 1454496274.84 }, { 'interface' : 'MgmtEth0/RSP0/CPU0/0', 'mac' : '5C:5E:AB:DA:3C:FF', 'ip' : '172.17.17.2', 'age' : 1435641582.49 } ] """ def _process_arp_fields(ip, mac, interface, mac_type, h, m='', s=''): if h == 'n/a': age = -1 else: h = int(h[:-1]) m = int(m[:-1]) s = int(s[:-1]) age = h * 3600 + m * 60 + s return { 'interface': self._canonical_int(interface), 'mac': napalm.base.helpers.mac(mac), 'ip': ip, 'age': float(age) } output = self._send_command("show arp") output = re.split(r'^----.*', output, flags=re.M)[1] lines = output.split('\n') entries = [] [ entries.append(_process_arp_fields(*i.split())) for i in lines if i != '' ] return entries def get_interfaces(self): def config_for_iface(iface, configs): for config in configs: if config[0] == iface: iface_config = config[1] m = re.search('description: "(.+?)"', iface_config) descr = '' if m != None: descr = m.group(1) m = re.search('((no )?shutdown)', iface_config) enabled = not bool(m != None and m.group(1) == 'shutdown') return (descr, enabled) return ('', True) config_raw = self._send_command("show running-config") config_ifaces = re.findall("!\ninterface (.*)\n((.|\n)*?)\nexit", config_raw) ifaces_raw = self._send_command("show interfaces") ifaces = ifaces_raw.split('\n\n') iface_list = [] for iface in ifaces: if iface == '': continue name = re.search('Interface Name : \\.+\s(.*)\n', iface).group(1) speed = re.search('Port Speed : \\.+\s(.*)\n', iface).group(1) mac = re.search('L3 MAC Address\\.+\s(.*)\n', iface).group(1) status = re.search('Link Status : \\.+\s(.*)\n', iface).group(1) description, enabled = config_for_iface(name, config_ifaces) if speed == 'Unknown': speed = 0 iface_list.append({ self._canonical_int(name): { 'is_up': bool(status is 'Up'), 'is_enable': enabled, 'description': description, 'last_flapped': -1, 'speed': int(speed), 'mac_address': napalm.base.helpers.mac(mac) } }) return iface_list def get_lldp_neighbors(self): result = collections.defaultdict(list) output = self._send_command("show lldp remote-device all") output = re.split(r'^----.*', output, flags=re.M)[1] lines = output.split('\n') for line in lines: if line == '': continue iface = line[0:10].strip() portid = line[38:55] systemname = line[57:].strip() if systemname == '': systemname = None result[iface].append({'hostname': systemname, 'port': portid}) return dict(result) def _get_lldp_neighbor_detail_iface(self, interface): caps_map = { 'bridge': 'B', 'router': 'R', 'WLAN access point': 'W', 'station only': 'S', } # support empty interface output = self._send_command("show lldp remote-device detail %s" % interface) chassis_id = re.search(r'Chassis ID: (.+)', output).group(1) try: system_name = re.search(r'System Name: (.+)', output).group(1) except: system_name = '' try: port_description = re.search(r'Port Description: (.+)', output).group(1) except: port_description = '' try: caps_supported_output = re.search( r'System Capabilities Supported: (.+)', output).group(1) except: caps_supported_output = '' try: caps_enabled_output = re.search( r'System Capabilities Enabled: (.+)', output).group(1) except: caps_enabled_output = '' caps_supported = caps_supported_output.split(', ') caps_supported = [[caps_maps[cap] for cap in caps_supported]] caps_enabled = caps_enabled_output.split(', ') caps_enabled = [[caps_enabled[cap] for cap in caps_enabled]] return { # 'parent_interface': u'' 'remote_chassis_id': chassis_id, 'remote_system_name': system_name, 'remote_port': port_description, 'remote_port_description': port_description, 'remote_system_description': system_decription, 'remote_system_capab': caps_supported.join(', '), 'remote_system_enable_capab': caps_enabled.join(', ') } def get_lldp_neighbor_detail(self, interface=''): if interface == '': ifaces = [] neighs = self.get_lldp_neighbors() for iface in neighs: ifaces.append(self._get_lldp_neighbor_detail_iface(iface)) return ifaces else: return _get_lldp_neighbor_detail_iface(iface) def get_ntp_peers(self): """ Returns the NTP peers configuration as dictionary. The keys of the dictionary represent the IP Addresses of the peers. Inner dictionaries do not have yet any available keys. Example:: { '192.168.0.1': {}, '17.72.148.53': {}, '37.187.56.220': {}, '162.158.20.18': {} } """ output = self._send_command("show sntp server") output = re.findall('Host Address:\s(\S+)', output) entries = dict() for i in output: entries[i] = {} return entries def getnfacts(self): """ Returns a dictionary containing the following information: * uptime - Uptime of the device in seconds. * vendor - Manufacturer of the device. * model - Device model. * hostname - Hostname of the device * fqdn - Fqdn of the device * os_version - String with the OS version running on the device. * serial_number - Serial number of the device * interface_list - List of the interfaces of the device Example:: { 'uptime': 151005.57332897186, 'vendor': u'Arista', 'os_version': u'4.14.3-2329074.gaatlantarel', 'serial_number': u'SN0123A34AS', 'model': u'vEOS', 'hostname': u'eos-router', 'fqdn': u'eos-router', 'interface_list': [u'Ethernet2', u'Management1', u'Ethernet1', u'Ethernet3'] } """ return { 'uptime': 151005.57332897186, 'vendor': u'Dell', 'os_version': u'4.14.3-2329074.gaatlantarel', 'serial_number': u'SN0123A34AS', 'model': u'vEOS', 'hostname': u'eos-router', 'fqdn': u'eos-router', 'interface_list': [u'Ethernet2', u'Management1', u'Ethernet1', u'Ethernet3'] }
class S350Driver(NetworkDriver): """Napalm driver for S350.""" def __init__(self, hostname, username, password, timeout=60, optional_args=None): """Constructor.""" self.device = None self.hostname = hostname self.username = username self.password = password self.timeout = timeout if optional_args is None: optional_args = {} self._dest_file_system = optional_args.get("dest_file_system", None) # Netmiko possible arguments netmiko_argument_map = { "port": None, "secret": "", "verbose": False, "keepalive": 30, "global_delay_factor": 1, "use_keys": False, "key_file": None, "ssh_strict": False, "system_host_keys": False, "alt_host_keys": False, "alt_key_file": "", "ssh_config_file": None, "allow_agent": False, } # Allow for passing additional Netmiko arguments self.netmiko_optional_args = {} for k, v in netmiko_argument_map.items(): try: self.netmiko_optional_args[k] = optional_args[k] except KeyError: pass self.port = optional_args.get("port", 22) self.device = None self.force_no_enable = optional_args.get("force_no_enable", False) def open(self): """Open a connection to the device.""" self.device = ConnectHandler( device_type="cisco_s300", host=self.hostname, username=self.username, password=self.password, **self.netmiko_optional_args, ) if not self.force_no_enable: self.device.enable() def _discover_file_system(self): try: return self.device._autodetect_fs() except Exception: msg = ( "Netmiko _autodetect_fs failed (to work around specify " "dest_file_system in optional_args)." ) raise CommandErrorException(msg) def close(self): """Close the connection to the device.""" self.device.disconnect() def cli(self, commands): output = {} try: for cmd in commands: output[cmd] = self.device.send_command(cmd) return output except (socket.error, EOFError) as e: raise ConnectionClosedException(str(e)) def _send_command(self, command): """Wrapper for self.device.send.command(). If command is a list will iterate through commands until valid command. """ try: if isinstance(command, list): for cmd in command: output = self.device.send_command(cmd) if "% Invalid" not in output: break else: output = self.device.send_command(command) return output.strip() except (socket.error, EOFError) as e: raise ConnectionClosedException(str(e)) def _parse_uptime(self, uptime_str): """Parse an uptime string into number of seconds""" uptime_str = uptime_str.strip() days, timespec = uptime_str.split(",") hours, minutes, seconds = timespec.split(":") uptime_sec = (int(days) * 86400) + (int(hours) * 3600) + (int(minutes) * 60) + int(seconds) return uptime_sec def get_arp_table(self, vrf=""): """ Get the ARP table, the age isn't readily available so we leave that out for now. vrf is needed for test - no support on s350 """ arp_table = [] output = self._send_command("show arp") for line in output.splitlines(): # A VLAN may not be set for the entry if "vlan" not in line: continue if len(line.split()) == 4: interface, ip, mac, _ = line.split() elif len(line.split()) == 5: if1, if2, ip, mac, _ = line.split() interface = "{} {}".format(if1, if2) elif len(line.split()) == 6: _, _, interface, ip, mac, _ = line.split() else: raise ValueError("Unexpected output: {}".format(line.split())) interface = canonical_interface_name(interface, s350_base_interfaces) entry = { "interface": interface, "mac": napalm.base.helpers.mac(mac), "ip": ip, "age": 0.0, } arp_table.append(entry) return arp_table def get_config(self, retrieve="all", full=False, sanitized=False): """ get_config for S350. Since this firmware doesn't support a candidate configuration we leave it empty. """ configs = { "startup": "", "running": "", "candidate": "", } if retrieve in ("all", "startup"): startup = self._send_command("show startup-config") configs["startup"] = self._get_config_filter(startup) if retrieve in ("all", "running"): # IOS supports "full" only on "show running-config" run_full = " detailed" if full else "" running = self._send_command("show running-config" + run_full) configs["running"] = self._get_config_filter(running) if sanitized: configs = self._get_config_sanitized(configs) return configs def _get_config_filter(self, config): # The output of get_config should be directly usable by load_replace_candidate() # remove header filter_strings = [ r"(?sm)^config-file-header.*^@$", ] for ft in filter_strings: config = re.sub(ft, "", config) return config def _get_config_sanitized(self, configs): # Do not output sensitive information # use Cisco IOS filters configs = napalm.base.helpers.sanitize_configs(configs, C.CISCO_SANITIZE_FILTERS) # defina my own filters s350_filters = { r"^(.* password) (\S+) (\S+) (.*)$": r"\1 \2 <removed> \4", r"^(snmp-server location) (\S+).*$": r"\1 <removed>", } configs = napalm.base.helpers.sanitize_configs(configs, s350_filters) return configs def get_facts(self): """Return a set of facts from the device.""" serial_number, fqdn, os_version, hostname, domainname = ("Unknown",) * 5 # Submit commands to the device. show_ver = self._send_command("show version") show_sys = self._send_command("show system") show_inv = self._send_command("show inventory") show_hosts = self._send_command("show hosts") show_int_st = self._send_command("show interfaces status") os_version = self._get_facts_parse_os_version(show_ver) # hostname hostname = self._get_facts_hostname(show_sys) # special case for SG500 fw v1.4.x if hostname == "Unknown": hostname = self._get_facts_hostname_from_config( self._send_command("show running-config") ) # uptime uptime_str = self._get_facts_uptime(show_sys) uptime = self._parse_uptime(uptime_str) # serial_number and model inventory = self._get_facts_parse_inventory(show_inv)["1"] serial_number = inventory["sn"] model = inventory["pid"] # fqdn domainname = napalm.base.helpers.textfsm_extractor(self, "hosts", show_hosts)[0] domainname = domainname["domain_name"] if domainname == "Domain": domainname = "Unknown" if domainname != "Unknown" and hostname != "Unknown": fqdn = "{0}.{1}".format(hostname, domainname) # interface_list interfaces = [] show_int_st = show_int_st.strip() # remove the header information show_int_st = re.sub( r"(^-.*$|^Port .*$|^Ch .*$)|^\s.*$|^.*Flow.*$", "", show_int_st, flags=re.M ) for line in show_int_st.splitlines(): if not line: continue interface = line.split()[0] interface = canonical_interface_name(interface, s350_base_interfaces) interfaces.append(str(interface)) return { "fqdn": str(fqdn), "hostname": str(hostname), "interface_list": interfaces, "model": str(model), "os_version": str(os_version), "serial_number": str(serial_number), "uptime": uptime, "vendor": "Cisco", } def _get_facts_hostname_from_config(self, show_running): # special case for SG500 fw v1.4.x hostname = "Unknown" for line in show_running.splitlines(): if line.startswith("hostname "): _, hostname = line.split("hostname") hostname = hostname.strip() break return hostname def _get_facts_hostname(self, show_sys): hostname = "Unknown" for line in show_sys.splitlines(): if line.startswith("System Name:"): _, hostname = line.split("System Name:") hostname = hostname.strip() break return hostname def _get_facts_uptime(self, show_sys): i = 0 syslines = [] fields = [] uptime_header_lineNo = None uptime_str = None for line in show_sys.splitlines(): # All models except SG500 fw 1.4.x if line.startswith("System Up Time (days,hour:min:sec):"): _, uptime_str = line.split("System Up Time (days,hour:min:sec):") break line = re.sub(r" *", " ", line, re.M) line = line.strip() fields = line.split(" ") syslines.append(fields) if "Unit" in syslines[i] and "time" in syslines[i]: uptime_header_lineNo = i i += 1 # SG500 fw 1.4.x if not uptime_str: uptime_str = syslines[uptime_header_lineNo + 2][1] return uptime_str def _get_facts_parse_inventory(self, show_inventory): """ inventory can list more modules/devices """ # make 1 module 1 line show_inventory = re.sub(r"\nPID", " PID", show_inventory, re.M) # delete empty lines show_inventory = re.sub(r"^\n", "", show_inventory, re.M) show_inventory = re.sub(r"\n\n", "", show_inventory, re.M) show_inventory = re.sub(r"\n\s*\n", r"\n", show_inventory, re.M) lines = show_inventory.splitlines() modules = {} for line in lines: match = re.search( r""" ^ NAME:\s"(?P<name>\S+)"\s* DESCR:\s"(?P<descr>[^"]+)"\s* PID:\s(?P<pid>\S+)\s* VID:\s(?P<vid>.+\S)\s* SN:\s(?P<sn>\S+)\s* """, line, re.X, ) module = match.groupdict() modules[module["name"]] = module if modules: return modules def _get_facts_parse_os_version(self, show_ver): # os_version # detect os ver > 2 if re.search(r"^Active-image", show_ver): for line in show_ver.splitlines(): # First version line is the active version if re.search(r"Version:", line): _, os_version = line.split("Version: ") break elif re.search(r"^SW version", show_ver): for line in show_ver.splitlines(): if re.search(r"^SW version", line): _, ver = line.split(" ") os_version, _ = ver.split(" (") break else: # show_ver = re.sub(r'^\n', '', show_ver, re.M) for line in show_ver.splitlines(): line = re.sub(r" *", " ", line, re.M) line = line.strip() line_comps = line.split(" ") if line_comps[0] == "1": os_version = line_comps[1] break return os_version def get_interfaces(self): """ get_interfaces() implementation for S350 """ interfaces = {} show_status_output = self._send_command("show interfaces status") show_description_output = self._send_command("show interfaces description") # by documentation SG350 show_jumbo_frame = self._send_command("show ports jumbo-frame") match = re.search(r"Jumbo frames are enabled", show_jumbo_frame, re.M) if match: mtu = 9000 else: mtu = 1518 mac = "0" for status_line in show_status_output.splitlines(): if "Up" in status_line or "Down" in status_line: if "Po" in status_line: interface, _, _, speed, _, _, link_state = status_line.split() else: interface, _, _, speed, _, _, link_state, _, _ = status_line.split() # Since the MAC address for all the local ports are equal, get the address # from the first port and use it everywhere. if mac == "0": show_system_output = self._send_command("show lldp local " + interface) mac = show_system_output.splitlines()[0].split(":", maxsplit=1)[1].strip() if speed == "--": is_enabled = False speed = 0 else: is_enabled = True speed = int(speed) is_up = link_state == "Up" for descr_line in show_description_output.splitlines(): description = 0 if descr_line.startswith(interface): description = " ".join(descr_line.split()[1:]) break # last_flapped can not be get - setting to default entry = { "is_up": is_up, "is_enabled": is_enabled, "speed": speed, "mtu": mtu, "last_flapped": -1.0, "description": description, "mac_address": napalm.base.helpers.mac(mac), } interface = canonical_interface_name(interface, s350_base_interfaces) interfaces[interface] = entry return interfaces def get_interfaces_ip(self): """Returns all configured interface IP addresses.""" interfaces = {} show_ip_int = self._send_command("show ip interface") header = True # cycle trought header for line in show_ip_int.splitlines(): if header: # last line of first header match = re.match(r"^---+ -+ .*$", line) if match: header = False fields_end = self._get_ip_int_fields_end(line) continue # next header, stop processing text if re.match(r"^---+ -+ .*$", line): break line_elems = self._get_ip_int_line_to_fields(line, fields_end) # only valid interfaces # in diferent firmwares there is 'Status' field allwais on last place if line_elems[len(line_elems) - 1] != "Valid": continue cidr = line_elems[0] interface = line_elems[1] ip = netaddr.IPNetwork(cidr) family = "ipv{0}".format(ip.version) interface = canonical_interface_name(interface, s350_base_interfaces) interfaces[interface] = {family: {str(ip.ip): {"prefix_length": ip.prefixlen}}} return interfaces def _get_ip_int_line_to_fields(self, line, fields_end): """ dynamic fields lenghts """ line_elems = {} index = 0 f_start = 0 for f_end in fields_end: line_elems[index] = line[f_start:f_end].strip() index += 1 f_start = f_end return line_elems def _get_ip_int_fields_end(self, dashline): """ fields length are diferent device to device, detect them on horizontal lin """ fields_end = [m.start() for m in re.finditer(" ", dashline.strip())] # fields_position.insert(0,0) fields_end.append(len(dashline)) return fields_end def get_lldp_neighbors(self): """get_lldp_neighbors implementation for s350""" neighbors = {} output = self._send_command("show lldp neighbors") header = True # cycle trought header local_port = "" # keep previous context - multiline syname remote_port = "" remote_name = "" for line in output.splitlines(): if header: # last line of header match = re.match(r"^--------- -+ .*$", line) if match: header = False fields_end = self._get_lldp_neighbors_fields_end(line) continue line_elems = self._get_lldp_neighbors_line_to_fields(line, fields_end) # info owerflow to the other line if line_elems[0] == "" or line_elems[4] == "" or line_elems[5] == "": # complete owerflown fields local_port = local_port + line_elems[0] remote_port = remote_port + line_elems[2] remote_name = remote_name + line_elems[3] # then reuse old values na rewrite previous entry else: local_port = line_elems[0] remote_port = line_elems[2] remote_name = line_elems[3] local_port = canonical_interface_name(local_port, s350_base_interfaces) neighbor = { "hostname": remote_name, "port": remote_port, } neighbor_list = [ neighbor, ] neighbors[local_port] = neighbor_list return neighbors def _get_lldp_neighbors_line_to_fields(self, line, fields_end): """ dynamic fields lenghts """ line_elems = {} index = 0 f_start = 0 for f_end in fields_end: line_elems[index] = line[f_start:f_end].strip() index += 1 f_start = f_end return line_elems def _get_lldp_neighbors_fields_end(self, dashline): """ fields length are diferent device to device, detect them on horizontal lin """ fields_end = [m.start() for m in re.finditer(" ", dashline)] fields_end.append(len(dashline)) return fields_end def _get_lldp_line_value(self, line): """ Safe-ish method to get the value from an 'lldp neighbors $IF' line. """ try: value = line.split(":")[1:][0].strip() except KeyError: value = "N/A" return value def get_lldp_neighbors_detail(self, interface=""): """ get_lldp_neighbors_detail() implementation for s350 """ details = {} # First determine all interfaces with valid LLDP neighbors for local_port in self.get_lldp_neighbors().keys(): if interface: if interface == local_port: entry = self._get_lldp_neighbors_detail_parse(local_port) local_port = canonical_interface_name(local_port, s350_base_interfaces) details[local_port] = [ entry, ] else: entry = self._get_lldp_neighbors_detail_parse(local_port) local_port = canonical_interface_name(local_port, s350_base_interfaces) details[local_port] = [ entry, ] return details def _get_lldp_neighbors_detail_parse(self, local_port): # Set defaults, just in case the remote fails to provide a field. ( remote_port_id, remote_port_description, remote_chassis_id, remote_system_name, remote_system_description, remote_system_capab, remote_system_enable_capab, ) = ("N/A",) * 7 output = self._send_command("show lldp neighbors {}".format(local_port)) for line in output.splitlines(): if line.startswith("Port ID"): remote_port_id = line.split()[-1] elif line.startswith("Device ID"): remote_chassis_id = line.split()[-1] elif line.startswith("Port description"): remote_port_description = self._get_lldp_line_value(line) elif line.startswith("System Name"): remote_system_name = self._get_lldp_line_value(line) elif line.startswith("System description"): remote_system_description = self._get_lldp_line_value(line) elif line.startswith("Capabilities"): caps = self._get_lldp_neighbors_detail_capabilities_parse(line) remote_port_id = canonical_interface_name(remote_port_id, s350_base_interfaces) entry = { "parent_interface": "N/A", "remote_port": remote_port_id, "remote_port_description": remote_port_description, "remote_chassis_id": remote_chassis_id, "remote_system_name": remote_system_name, "remote_system_description": remote_system_description, "remote_system_capab": caps, "remote_system_enable_capab": caps, } return entry def _get_lldp_neighbors_detail_capabilities_parse(self, line): # Only the enabled capabilities are displayed. try: # Split a line like 'Capabilities: Bridge, Router, Wlan-Access-Point' capabilities = line.split(":")[1:][0].split(",") except KeyError: capabilities = [] caps = [] # For all capabilities, except 'Repeater', the shorthand # is the first character. for cap in capabilities: cap = cap.strip() if cap == "Repeater": caps.append("r") else: caps.append(cap[0]) return caps def get_ntp_servers(self): """Returns NTP servers.""" ntp_servers = {} output = self._send_command("show sntp status") servers = re.findall(r"^Server\s*:\s*(\S+)\s*.*$", output, re.M) for server in servers: ntp_servers[server] = {} return ntp_servers def is_alive(self): """Returns an indication of the state of the connection.""" null = chr(0) if self.device is None: return {"is_alive": False} # Send a NUL byte to keep the connection alive. try: self.device.write_channel(null) return {"is_alive": self.device.remote_conn.transport.is_active()} except (socket.error, EOFError): # If we couldn't send it, the connection is not available. return {"is_alive": False} # If we made it here, assume the worst. return {"is_alive": False} @property def dest_file_system(self): # First ensure we have an open connection. if self.device and self._dest_file_system is None: self._dest_file_system = self._discover_file_system() return self._dest_file_system
class S350Driver(NetworkDriver): """Napalm driver for S350.""" def __init__(self, hostname, username, password, timeout=60, optional_args=None): """Constructor.""" self.device = None self.hostname = hostname self.username = username self.password = password self.timeout = timeout self.config_replace = False self.candidate_cfg = optional_args.get("candidate_cfg", "candidate_config.txt") self.merge_cfg = optional_args.get("merge_cfg", "merge_config.txt") self.rollback_cfg = optional_args.get("rollback_cfg", "rollback_config.txt") self.inline_transfer = optional_args.get("inline_transfer", False) self.auto_file_prompt = optional_args.get("auto_file_prompt", True) self.prompt_quiet_configured = None if optional_args is None: optional_args = {} self._dest_file_system = optional_args.get('dest_file_system', None) # Netmiko possible arguments netmiko_argument_map = { 'port': None, 'secret': '', 'verbose': False, 'keepalive': 30, 'global_delay_factor': 1, 'use_keys': False, 'key_file': None, 'ssh_strict': False, 'system_host_keys': False, 'alt_host_keys': False, 'alt_key_file': '', 'ssh_config_file': None, 'allow_agent': False, } # Allow for passing additional Netmiko arguments self.netmiko_optional_args = {} for k, v in netmiko_argument_map.items(): try: self.netmiko_optional_args[k] = optional_args[k] except KeyError: pass self.port = optional_args.get('port', 22) self.device = None def open(self): """Open a connection to the device.""" self.device = ConnectHandler(device_type='cisco_s300', host=self.hostname, username=self.username, password=self.password, **self.netmiko_optional_args) self.device.enable() def _discover_file_system(self): try: return self.device._autodetect_fs() except Exception: msg = "Netmiko _autodetect_fs failed (to work around specify " \ "dest_file_system in optional_args)." raise CommandErrorException(msg) def close(self): """Close the connection to the device.""" self.device.disconnect() def _send_command(self, command): """Wrapper for self.device.send.command(). If command is a list will iterate through commands until valid command. """ try: if isinstance(command, list): for cmd in command: output = self.device.send_command(cmd) if "% Invalid" not in output: break else: output = self.device.send_command(command) return output.strip() except (socket.error, EOFError) as e: raise ConnectionClosedException(str(e)) def _parse_uptime(self, uptime_str): """Parse an uptime string into number of seconds""" uptime_str = uptime_str.strip() days, timespec = uptime_str.split(',') hours, minutes, seconds = timespec.split(':') uptime_sec = (int(days) * 86400) + (int(hours) * 3600) + ( int(minutes) * 60) + int(seconds) return uptime_sec def get_arp_table(self): """ Get the ARP table, the age isn't readily available so we leave that out for now. """ arp_table = [] output = self._send_command('show arp | include (static|dynamic)') for line in output.splitlines(): # A VLAN may not be set for the entry if len(line.split()) == 4: interface, ip, mac, _ = line.split() elif len(line.split()) == 5: _, interface, mac, _ = line.split() else: raise ValueError('Unexpected output: {}'.format(line.split())) entry = { 'interface': interface, 'mac': napalm.base.helpers.mac(mac), 'ip': ip, 'age': 0, } arp_table.append(entry) return arp_table def get_config(self, retrieve='all'): """ get_config for S350. Since this firmware doesn't support a candidate configuration we leave it empty. """ configs = { 'startup': '', 'running': '', 'candidate': '', } if retrieve in ('all', 'startup'): output = self._send_command('show startup-config') configs['startup'] = output if retrieve in ('all', 'running'): output = self._send_command('show running-config') configs['running'] = output return configs def get_facts(self): """Return a set of facts from the device.""" serial_number, fqdn, os_version, hostname, domain_name = ( 'Unknown', ) * 5 # Submit commands to the device. show_ver = self._send_command('show version | include Version') show_sys = self._send_command('show system') show_inv = self._send_command('show inventory') show_hosts = self._send_command( 'show hosts | begin Default Domain Table') show_int_st = self._send_command('show interface status | include gi') # os_version for line in show_ver.splitlines(): # First version line is the active version if 'Version' in line: _, os_version = line.split('Version: ') break # hostname, uptime for line in show_sys.splitlines(): if line.startswith('System Name:'): _, hostname = line.split('System Name:') hostname = hostname.strip() elif line.startswith('System Description:'): _, model = line.split('System Description:') model = model.strip() elif line.startswith('System Up Time (days,hour:min:sec):'): _, uptime_str = line.split( 'System Up Time (days,hour:min:sec):') uptime = self._parse_uptime(uptime_str) # serial_number for line in show_inv.splitlines(): if 'SN:' in line: serial_number = line.split('SN: ')[-1] break # fqdn domain_line = show_hosts.splitlines()[4] domainname = domain_line.split()[0] fqdn = '{0}.{1}'.format(hostname, domainname) # interface_list interfaces = [] for line in show_int_st.splitlines(): interfaces.append(py23_compat.text_type(line.split()[0])) return { 'fqdn': py23_compat.text_type(fqdn), 'hostname': py23_compat.text_type(hostname), 'interface_list': interfaces, 'model': py23_compat.text_type(model), 'os_version': py23_compat.text_type(os_version), 'serial_number': py23_compat.text_type(serial_number), 'uptime': uptime, 'vendor': u'Cisco', } def get_interfaces(self): """ get_interfaces() implementation for S350 """ interfaces = {} show_status_output = self._send_command( 'show interfaces status | include (Up|Down)') show_description_output = self._send_command( 'show interfaces description') # Since the MAC address for all the local ports are equal, get the address # from the first port and use it everywhere. show_system_output = self._send_command( 'show lldp local GigabitEthernet1 | begin Device\ ID') try: mac = show_system_output.splitlines()[0].split( ':', maxsplit=1)[1].strip() except: mac = '0' for status_line in show_status_output.splitlines(): interface, _, _, speed, _, _, link_state, _, _ = status_line.split( ) if speed == '--': is_enabled = False speed = 0 else: is_enabled = True speed = int(speed) is_up = (link_state == 'Up') for descr_line in show_description_output.splitlines(): description = 0 if descr_line.startswith(interface): description = ' '.join(descr_line.split()[1:]) break entry = { 'is_up': is_up, 'is_enabled': is_enabled, 'speed': speed, 'last_flapped': -1, 'description': description, 'mac_address': napalm.base.helpers.mac(mac) } interfaces[interface] = entry return interfaces def get_interfaces_ip(self): """Returns all configured interface IP addresses.""" interfaces = {} show_ip_int = self._send_command('show ip int | include (UP|DOWN)') # Limit to valid interfaces (i.e. ignore vlan 1) valid_interfaces = [ i for i in show_ip_int.splitlines() if i.split()[-1] == 'Valid' ] for interface in valid_interfaces: network, name = interface.split()[:2] ip = netaddr.IPNetwork(network) family = 'ipv{0}'.format(ip.version) interfaces[name] = { family: { str(ip.ip): { 'prefix_length': ip.prefixlen } } } return interfaces def get_lldp_neighbors(self): """get_lldp_neighbors implementation for s350""" neighbors = {} output = self._send_command('show lldp neighbors | begin \ \ Port') for line in output.splitlines()[2:]: line_elems = line.split() local_port = line_elems[0] remote_port = line_elems[2] remote_name = line_elems[3] neighbors[local_port] = { 'hostname': remote_name, 'port': remote_port, } return neighbors def _get_lldp_line_value(self, line): """ Safe-ish method to get the value from an 'lldp neighbors $IF' line. """ try: value = line.split(':')[1:][0].strip() except: value = u'N/A' return value def get_lldp_neighbors_detail(self): """ get_lldp_neighbors_detail() implementation for s350 """ details = {} # First determine all interfaces with valid LLDP neighbors for local_port in self.get_lldp_neighbors().keys(): # Set defaults, just in case the remote fails to provide a field. remote_port_id, remote_port_description, remote_chassis_id, \ remote_system_name, remote_system_description, \ remote_system_capab, remote_system_enable_capab = (u'N/A',) * 7 output = self._send_command( 'show lldp neighbor {}'.format(local_port)) for line in output.splitlines(): if line.startswith('Port ID'): remote_port_id = line.split()[-1] elif line.startswith('Device ID'): remote_chassis_id = line.split()[-1] elif line.startswith('Port description'): remote_port_description = self._get_lldp_line_value(line) elif line.startswith('System Name'): remote_system_name = self._get_lldp_line_value(line) elif line.startswith('System description'): remote_system_description = self._get_lldp_line_value(line) elif line.startswith('Capabilities'): # Only the enabled capabilities are displayed. try: # Split a line like 'Capabilities: Bridge, Router, Wlan-Access-Point' capabilities = line.split(':')[1:][0].split(',') except: capabilities = [] caps = [] # For all capabilities, except 'Repeater', the shorthand # is the first character. for cap in capabilities: cap = cap.strip() if cap == 'Repeater': caps.append('r') else: caps.append(cap[0]) entry = { 'parent_interface': u'N/A', 'remote_port': remote_port_id, 'remote_port_description': remote_port_description, 'remote_chassis_id': remote_chassis_id, 'remote_system_name': remote_system_name, 'remote_system_description': remote_system_description, 'remote_system_capab': u', '.join(caps), 'remote_system_enable_capab': u', '.join(caps), } details[local_port] = entry return details def get_ntp_servers(self): """get_ntp_servers implementation for S350""" ntp_servers = {} output = self._send_command('show sntp status | include Server') for line in output.splitlines(): ntp_servers[line.split()[2]] = {} return ntp_servers def is_alive(self): """Returns an indication of the state of the connection.""" null = chr(0) if self.device is None: return {'is_alive': False} # Send a NUL byte to keep the connection alive. try: self.device.write_channel(null) return {'is_alive': self.device.remote_conn.transport.is_active()} except (socket.error, EOFError): # If we couldn't send it, the connection is not available. return {'is_alive': False} # If we made it here, assume the worst. return {'is_alive': False} @property def dest_file_system(self): # First ensure we have an open connection. if self.device and self._dest_file_system is None: self._dest_file_system = self._discover_file_system() return self._dest_file_system def cli(self, commands): """ Execute a list of commands and return the output in a dictionary format using the command as the key. Example input: ['show clock', 'show calendar'] Output example: { 'show calendar': u'22:02:01 UTC Thu Feb 18 2016', 'show clock': u'*22:01:51.165 UTC Thu Feb 18 2016'} """ cli_output = dict() if type(commands) is not list: raise TypeError("Please enter a valid list of commands!") for command in commands: output = self._send_command(command) cli_output.setdefault(command, {}) cli_output[command] = output return cli_output
class SG350XDriver(NetworkDriver): """Napalm driver for SG350X.""" def __init__(self, hostname, username, password, timeout=60, optional_args=None): """Constructor.""" self.device = None self.hostname = hostname self.username = username self.password = password self.timeout = timeout if optional_args is None: optional_args = {} self._dest_file_system = optional_args.get("dest_file_system", None) # Netmiko possible arguments netmiko_argument_map = { "port": None, "secret": "", "verbose": False, "keepalive": 30, "global_delay_factor": 1, "use_keys": False, "key_file": None, "ssh_strict": False, "system_host_keys": False, "alt_host_keys": False, "alt_key_file": "", "ssh_config_file": None, "allow_agent": False, } # Allow for passing additional Netmiko arguments self.netmiko_optional_args = {} for k, v in netmiko_argument_map.items(): try: self.netmiko_optional_args[k] = optional_args[k] except KeyError: pass self.port = optional_args.get("port", 22) self.device = None def open(self): """Open a connection to the device.""" self.device = ConnectHandler( device_type="cisco_SG350X", host=self.hostname, username=self.username, password=self.password, **self.netmiko_optional_args, ) self.device.enable() def _discover_file_system(self): try: return self.device._autodetect_fs() except Exception: msg = ("Netmiko _autodetect_fs failed (to work around specify " "dest_file_system in optional_args).") raise CommandErrorException(msg) def close(self): """Close the connection to the device.""" self.device.disconnect() def _send_command(self, command): """Wrapper for self.device.send.command(). If command is a list will iterate through commands until valid command. """ try: if isinstance(command, list): for cmd in command: output = self.device.send_command(cmd) if "% Invalid" not in output: break else: output = self.device.send_command(command) return output.strip() except (socket.error, EOFError) as e: raise ConnectionClosedException(str(e)) def _parse_uptime(self, uptime_str): """Parse an uptime string into number of seconds""" uptime_str = uptime_str.strip() days, timespec = uptime_str.split(",") hours, minutes, seconds = timespec.split(":") uptime_sec = ((int(days) * 86400) + (int(hours) * 3600) + (int(minutes) * 60) + int(seconds)) return uptime_sec def get_arp_table(self): """ Get the ARP table, the age isn't readily available so we leave that out for now. """ arp_table = [] output = self._send_command("show arp | include (static|dynamic)") for line in output.splitlines(): if line.startswith("vlan"): if len(line.split()) == 6: # vlan, vlan id, interface, ip, mac, staus _, _, interface, ip, mac, _ = line.split() elif len(len.split() == 5): _, _, ip, mac, _ = line.split() interface = "" else: raise ValueError(f"Unexpected output: {line.split()}") entry = { "interface": interface, "mac": napalm.base.helpers.mac(mac), "ip": ip, "age": 0, } arp_table.append(entry) return arp_table def get_config(self, retrieve="all"): """ get_config for SG350X. Since this firmware doesn't support a candidate configuration we leave it empty. """ configs = { "startup": "", "running": "", "candidate": "", } if retrieve in ("all", "startup"): output = self._send_command("show startup-config") configs["startup"] = output if retrieve in ("all", "running"): output = self._send_command("show running-config") configs["running"] = output return configs def get_enviroment(self): """ Return a dictionary of enviroment factsself. cpu is using 1-minute average cpu hard coded to cpu0 (i.e. only a single CPU) fans status memory - Not easily obtained expensive to run show tech-support memory values are set to """ enviroment.setdefault("cpu", {}) enviroment["cpu"][0] = {} enviroment["cpu"][0]["%usage"] = 0.0 show_fans = self._send_command("show system fans") show_cpu_util = self._send_command("show cpu utilization") show_power_inline = self._send_command("show power inline | include %") show_system_sensors = self._send_command("show system sensors") for line in show_cpu_util.splitlines(): if line.startswith("five seconds:"): cpu_regex = r"one minute: (\d+)%" match = re.search(cpu_regex, line) enviroment["cpu"][0]["%usage"] = float(match.group(1)) break enviroment["fans"] = {} fan_regex = r"(\d+/\d+)\s+\d+\s+(\S+)" for match in re.finditer(fan): if match.group(2) == "OK": eviroment["fans"][match.group(1)]["status"] = True else: enviroment["fans"][match.group(1)]["status"] = False for line in show_power_inline.splitlines(): if line.startwith("Usage threshold:"): threshold = float(line.split(":")[-1].strip("%")) show_power_inline_data = napalm.base.helpers.textfsm_extractor( self, "powerinline", show_power_inline) enviroment.setdefault("power", {}) for item in show_power_inline_data: enviroment["power"][int(item["requiredid"])] = { "status": item["consumedpowerpercent"] < item["threshold"], "capacity": item["nominalpower"], "output": item["consumedpower"], } show_system_sensors_data = napalm.base.helpers.textfsm_extractor( self, "sensor", show_system_sensors) enviroment.setdefault("temperature", {}) for item in show_system_sensors_data: enviroment["temperature"][item["sensor"]] = { "temperature": item["temperature"], "is_alert": item["temperature"] > item["warntemperature"], "is_critical": item["temperature"] > item["crittemperature"], } return enviroment def get_facts(self): """Return a set of facts from the device.""" serial_number, fqdn, os_version, hostname, domain_name = ( "Unknown", ) * 5 # Submit commands to the device. show_ver = self._send_command("show version") show_sys = self._send_command("show system") show_inv = self._send_command("show inventory") show_hosts = self._send_command("show hosts") show_int_st = self._send_command("show interface status") # os_version for line in show_ver.splitlines(): # First version line is the active version if "Version" in line: _, os_version = line.split("Version: ") break # hostname, uptime for line in show_sys.splitlines(): if line.startswith("System Name:"): _, hostname = line.split("System Name:") hostname = hostname.strip() continue elif line.startswith("System Description:"): _, model = line.split("System Description:") model = model.strip() continue elif line.startswith("System Up Time (days,hour:min:sec):"): _, uptime_str = line.split( "System Up Time (days,hour:min:sec):") uptime = self._parse_uptime(uptime_str) # serial_number for line in show_inv.splitlines(): if "SN:" in line: serial_number = line.split("SN: ")[-1] break # fqdn domainname = napalm.base.helpers.textfsm_extractor( self, "hosts", show_hosts)[0] domainname = domainname["domain_name"] if domainname == "Domain" or domainname == "Name": domainname = "" fqdn = f"{hostname}.{domainname}" # interface_list interfaces = [] show_int_st = show_int_st.strip() # remove the header information show_int_st = re.sub(r"(^-.*$|^Port .*$|^Ch .*$)|^\s.*$|^.*Flow.*$", "", show_int_st, flags=re.M) for line in show_int_st.splitlines(): if not line: continue interface = line.split()[0] interfaces.append(str(interface)) return { "fqdn": str(fqdn), "hostname": str(hostname), "interface_list": interfaces, "model": str(model), "os_version": str(os_version), "serial_number": str(serial_number), "uptime": uptime, "vendor": "Cisco", } def get_interfaces(self): """ get_interfaces() implementation for S350 """ interfaces = {} show_status_output = self._send_command( "show interfaces status | include (Up|Down)") show_description_output = self._send_command( "show interfaces description") # Since the MAC address for all the local ports are equal, get the address # from the first port and use it everywhere. show_system_output = self._send_command( "show lldp local g1/0/1 | begin Device\ ID") try: mac = show_system_output.splitlines()[0].split( ":", maxsplit=1)[1].strip() except: mac = "0" for status_line in show_status_output.splitlines(): interface, _, _, speed, _, _, link_state, _, _ = status_line.split( ) if speed == "--": is_enabled = False speed = 0 else: is_enabled = True speed = int(speed) is_up = link_state == "Up" for descr_line in show_description_output.splitlines(): description = 0 if descr_line.startswith(interface): description = " ".join(descr_line.split()[1:]) break entry = { "is_up": is_up, "is_enabled": is_enabled, "speed": speed, "last_flapped": -1, "description": description, "mac_address": napalm.base.helpers.mac(mac), } interfaces[interface] = entry return interfaces def get_interfaces_ip(self): """Returns all configured interface IP addresses.""" interfaces = {} show_ip_int = self._send_command("show ip int | include (UP|DOWN)") # Limit to valid interfaces (i.e. ignore vlan 1) valid_interfaces = [ i for i in show_ip_int.splitlines() if i.split()[-1] == "Valid" ] for interface in valid_interfaces: network, _, name = interface.split()[:3] ip = netaddr.IPNetwork(network) family = f"ipv{ip.version}" interfaces[name] = { family: { str(ip.ip): { "prefix_length": ip.prefixlen } } } return interfaces def get_lldp_neighbors(self): """get_lldp_neighbors implementation for s350""" neighbors = {} output = self._send_command("show lldp neighbors | begin \ \ Port") for line in output.splitlines()[2:]: line_elems = line.split() local_port = line_elems[0] remote_port = line_elems[2] remote_name = line_elems[3] neighbors[local_port] = { "hostname": remote_name, "port": remote_port, } return neighbors def _get_lldp_line_value(self, line): """ Safe-ish method to get the value from an 'lldp neighbors $IF' line. """ try: value = line.split(":")[1:][0].strip() except: value = "N/A" return value def get_lldp_neighbors_detail(self): """ get_lldp_neighbors_detail() implementation for s350 """ details = {} # First determine all interfaces with valid LLDP neighbors for local_port in self.get_lldp_neighbors().keys(): # Set defaults, just in case the remote fails to provide a field. ( remote_port_id, remote_port_description, remote_chassis_id, remote_system_name, remote_system_description, remote_system_capab, remote_system_enable_capab, ) = ("N/A", ) * 7 output = self._send_command( "show lldp neighbor {}".format(local_port)) for line in output.splitlines(): if line.startswith("Port ID"): remote_port_id = line.split()[-1] elif line.startswith("Device ID"): remote_chassis_id = line.split()[-1] elif line.startswith("Port description"): remote_port_description = self._get_lldp_line_value(line) elif line.startswith("System Name"): remote_system_name = self._get_lldp_line_value(line) elif line.startswith("System description"): remote_system_description = self._get_lldp_line_value(line) elif line.startswith("Capabilities"): # Only the enabled capabilities are displayed. try: # Split a line like 'Capabilities: Bridge, Router, Wlan-Access-Point' capabilities = line.split(":")[1:][0].split(",") except: capabilities = [] caps = [] # For all capabilities, except 'Repeater', the shorthand # is the first character. for cap in capabilities: cap = cap.strip() if cap == "Repeater": caps.append("r") else: caps.append(cap[0]) entry = { "parent_interface": "N/A", "remote_port": remote_port_id, "remote_port_description": remote_port_description, "remote_chassis_id": remote_chassis_id, "remote_system_name": remote_system_name, "remote_system_description": remote_system_description, "remote_system_capab": ", ".join(caps), "remote_system_enable_capab": ", ".join(caps), } details[local_port] = entry return details def get_ntp_servers(self): """get_ntp_servers implementation for S350""" ntp_servers = {} output = self._send_command("show sntp status | include Server") for line in output.splitlines(): ntp_servers[line.split()[2]] = {} return ntp_servers def get_vlans(self): """get_vlans implementation for SG350X""" output = self._send_command("show vlan") vlans = {} vlan = None re_min_max = r"(\d+)-(\d+)" # Output has fixed width columns with cells wrapping # vlan num, vlan name, Tagged Ports, UnTagged Ports, created by # skip header for row in output.splitlines()[4:]: t_ports = row[22:41].strip() u_ports = row[41:60].strip() status = row[60:].strip() # Indicates a wrapped cell if row[:5].strip() == "": vlans[vlan]["name"] = " ".join( (vlans[vlan]["name"], row[5:22].strip())) vlans[vlan]["name"] = vlans[vlan]["name"].strip() else: vlan = int(row[:5]) name = row[5:22].strip() vlans[vlan] = {"name": name, "interfaces": []} for item in t_ports.split(","): if item != "": if "-" in item: result = re.search(re_min_max, item) item = item.replace(result.group(0), "") for x in range(int(result.group(1)), int(result.group(2)) + 1): vlans[vlan]["interfaces"].append(f"{item}{x}") else: vlans[vlan]["interfaces"].append(item) for item in u_ports.split(","): if item != "": if "-" in item: result = re.search(re_min_max, item) item = item.replace(result.group(0), "") for x in range(int(result.group(1)), int(result.group(2)) + 1): vlans[vlan]["interfaces"].append(f"{item}{x}") else: vlans[vlan]["interfaces"].append(item) return vlans def is_alive(self): """Returns an indication of the state of the connection.""" null = chr(0) if self.device is None: return {"is_alive": False} # Send a NUL byte to keep the connection alive. try: self.device.write_channel(null) return {"is_alive": self.device.remote_conn.transport.is_active()} except (socket.error, EOFError): # If we couldn't send it, the connection is not available. return {"is_alive": False} # If we made it here, assume the worst. return {"is_alive": False} @property def dest_file_system(self): # First ensure we have an open connection. if self.device and self._dest_file_system is None: self._dest_file_system = self._discover_file_system() return self._dest_file_system