def main(): """ Check if BGP/OSPF neighbors for an Arista device are ESTABLISHED/FULL. Anything else is CRITICAL """ # print("STARTING TIME " + str(datetime.datetime.now()) + "\n") args = parse_args() my_password = getpass("Password for user allowed to connect to eAPI: ") check_arista = Arista() connection = pyeapi.connect( transport="https", host=args.ip, username=args.username, password=my_password, port=args.port, ) node = Node(connection) if args.protocol == "bgp": check_arista.bgp_status(node, args.hostname) elif args.protocol == "ospf": check_arista.ospf_status(node, args.hostname, 0) else: print("CRITICAL - Command not supported or wrong")
def __init__(self, host, username, password, transport="http", port=None, timeout=None, **kwargs): super().__init__(host, username, password, device_type="arista_eos_eapi") self.transport = transport self.port = port self.timeout = timeout eapi_args = { "transport": transport, "host": host, "username": username, "password": password, } optional_args = ("port", "timeout") for arg in optional_args: value = getattr(self, arg) if value is not None: eapi_args[arg] = value self.connection = eos_connect(**eapi_args) self.native = EOSNative(self.connection) # _connected indicates Netmiko ssh connection self._connected = False
def open(self): """Implementation of NAPALM method open.""" if self.transport not in TRANSPORTS: raise TypeError('invalid transport specified') klass = TRANSPORTS[self.transport] try: connection = klass( self.hostname, port=self.port, path=self.path, username=self.username, password=self.password, timeout=self.timeout, ) if self.device is None: self.device = EapiNode(connection, enablepwd=self.enablepwd) sw_version = self.device.run_commands(['show version'])[0].get( 'softwareImageVersion', "0.0.0") if LooseVersion(sw_version) < LooseVersion("0.14.1"): raise NotImplementedError( "MOS Software Version 0.14.1 or better required") # This is to get around user mismatch in API/FileCopy if self._ssh is None: self._ssh = ConnectHandler(device_type='cisco_ios', ip=self.hostname, username=self.username, password=self.password) self._ssh.enable() except ConnectionError as ce: raise ConnectionException(ce.message)
def eapi_conn(request): connection = pyeapi.connect( transport="https", host="0.0.0.0", username="******", password="******", port=9002, ) node = Node(connection) return node
def setUp(self): self.node = Node(None) self.node._running_config = self.config self.mock_config = Mock(name='node.config') self.node.config = self.mock_config self.mock_enable = Mock(name='node.enable') self.node.enable = self.mock_enable self.assertIsNotNone(self.instance) self.instance.node = self.node
def open(self): """Implementation of NAPALM method open.""" try: connection = self.transport_class(host=self.hostname, username=self.username, password=self.password, timeout=self.timeout, **self.eapi_kwargs) if self.device is None: self.device = EapiNode(connection, enablepwd=self.enablepwd) sw_version = self.device.run_commands(["show version"])[0].get( "softwareImageVersion", "0.0.0") if LooseVersion(sw_version) < LooseVersion("0.17.9"): raise NotImplementedError( "MOS Software Version 0.17.9 or better required") self._version = LooseVersion(sw_version) except ConnectionError as ce: raise ConnectionException(ce.message)
def __init__(self, host, username, password, transport='http', timeout=60, **kwargs): super(EOSDevice, self).__init__(host, username, password, vendor='arista', device_type='arista_eos_eapi') self.transport = transport self.timeout = timeout self.connection = eos_connect(transport, host=host, username=username, password=password, timeout=timeout) self.native = EOSNative(self.connection)
def __init__(self, host, username, password, transport="http", timeout=60, **kwargs): super().__init__(host, username, password, device_type="arista_eos_eapi") self.transport = transport self.timeout = timeout self.connection = eos_connect(transport, host=host, username=username, password=password, timeout=timeout) self.native = EOSNative(self.connection) # _connected indicates Netmiko ssh connection self._connected = False
class MOSDriver(NetworkDriver): """Napalm driver for Metamako MOS.""" SUPPORTED_OC_MODELS = [] _RE_UPTIME = re.compile( r"^((?P<day>\d+)\s+days?,\s+)?" r"(?P<hour>\d+):(?P<minute>\d+):(?P<second>\d+)", re.VERBOSE, ) _RE_ARP = re.compile( r"^(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" r"\s+\S+\s+" r"(?P<hwAddress>([0-9A-F]{2}[:-]){5}([0-9A-F]{2}))" r"\s+\S+\s+" r"(?P<interface>\S+)$", re.VERBOSE | re.IGNORECASE, ) _RE_NTP_SERVERS = re.compile(r"^ntp server (?P<server>\S+)", re.MULTILINE) _RE_SNMP_COMM = re.compile( r"\s*Community\sname:\s+(?P<community>\S+)\n" r"Community\saccess:\s+(?P<mode>\S+)" r"(\nCommunity\ssource:\s+(?P<v4_acl>\S+))?", re.VERBOSE, ) 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_session = None self._current_config = None self._replace_config = False self._ssh = None self._version = LooseVersion("0") self._process_optional_args(optional_args or {}) def _process_optional_args(self, optional_args): self.enablepwd = optional_args.pop("enable_password", "") self.config_timeout = optional_args.pop("config_timeout", 300) transport = optional_args.get( "transport", optional_args.get("eos_transport", "https")) try: self.transport_class = pyeapi.client.TRANSPORTS[transport] except KeyError: raise ConnectionException("Unknown transport: {}".format( self.transport)) init_args = inspect.getfullargspec(self.transport_class.__init__)[0] init_args.pop(0) # Remove "self" init_args.append( "enforce_verification") # Not an arg for unknown reason filter_args = ["host", "username", "password", "timeout"] self.eapi_kwargs = { k: v for k, v in optional_args.items() if k in init_args and k not in filter_args } def _run_translated_commands(self, commands, **kwargs): """ In 0.22.0+ some commands had their syntax change. This function translates those command syntaxs to their post 0.22.0 version """ if self._version >= LooseVersion("0.22.0"): # Map of translate command syntax to 0.23.0+ syntax translations = { "show snmp chassis-id": "show snmp v2-mib chassis-id", "show snmp location": "show snmp v2-mib location", "show snmp contact": "show snmp v2-mib contact", "show environment all": "show system environment all", } commands = [ i if i not in translations.keys() else translations[i] for i in commands ] return self.device.run_commands(commands, **kwargs) def open(self): """Implementation of NAPALM method open.""" try: connection = self.transport_class(host=self.hostname, username=self.username, password=self.password, timeout=self.timeout, **self.eapi_kwargs) if self.device is None: self.device = EapiNode(connection, enablepwd=self.enablepwd) sw_version = self.device.run_commands(["show version"])[0].get( "softwareImageVersion", "0.0.0") if LooseVersion(sw_version) < LooseVersion("0.17.9"): raise NotImplementedError( "MOS Software Version 0.17.9 or better required") self._version = LooseVersion(sw_version) except ConnectionError as ce: raise ConnectionException(ce.message) def close(self): """Implementation of NAPALM method close.""" if self.config_session is not None: # Only doing this because discard_config is broke self.discard_config() def is_alive(self): return {"is_alive": True} def get_facts(self): """Implementation of NAPALM method get_facts.""" commands_json = ["show version", "show interfaces status"] commands_text = ["show hostname"] result_json = self.device.run_commands(commands_json, encoding="json") result_text = self.device.run_commands(commands_text, encoding="text") version = result_json[0] hostname = result_text[0]["output"].splitlines()[0].split(" ")[-1] fqdn = result_text[0]["output"].splitlines()[1].split(" ")[-1] interfaces = result_json[1]["interfaces"].keys() interfaces = string_parsers.sorted_nicely(interfaces) u_match = re.match(self._RE_UPTIME, version["uptime"]).groupdict() if u_match["day"] is None: u_match["day"] = 0 uptime = timedelta( days=int(u_match["day"]), hours=int(u_match["hour"]), seconds=int(u_match["second"]), ).total_seconds() return { "hostname": hostname, "fqdn": fqdn, "vendor": "Metamako", "model": re.sub(r"^[Mm]etamako ", "", version["device"]), "serial_number": version["serialNumber"], "os_version": version["softwareImageVersion"], "uptime": int(uptime), "interface_list": interfaces, } def _lock(self): if self.config_session is None: self.config_session = "napalm_{}".format( datetime.now().microsecond) commands = [ "copy running-config flash:{}".format(self.config_session) ] self.device.run_commands(commands) if any(k for k in self._get_sessions() if k != self.config_session): self.device.run_commands( ["delete flash:{}".format(self.config_session)]) self.config_session = None raise SessionLockedException( "Session already in use - session file present on flash!") def _unlock(self): if self.config_session is not None: self.device.run_commands( ["delete flash:{}".format(self.config_session)]) self.config_session = None self._replace_config = False def _get_sessions(self): return [ line.split()[-1] for line in self.device.run_commands( ["dir flash:"], encoding="text")[0]["output"].splitlines() if "napalm_" in line.split()[-1] ] def _load_config(self, filename=None, config=None, replace=False): if filename and config: raise ValueError("Cannot simultaneously set filename and config") self._lock() self._candidate = ["copy running-config flash:rollback-0"] if replace: self._candidate.append("copy default-config running-config") self._replace_config = True self._candidate.append("configure terminal") if filename is not None: with open(filename, "r") as f: self._candidate = f.readlines() else: if isinstance(config, list): lines = config else: lines = config.splitlines() for line in lines: if line.strip() == "": continue if line.startswith("!"): continue self._candidate.append(line) self._candidate.append("end") if any("source mac" in line for line in self._candidate) and self._version < LooseVersion("0.19.2"): # Waiting for fixed release raise CommandErrorException( "Cannot set source mac in MOS versions prior to 0.19.2") if any("banner motd" in line for line in self._candidate): raise CommandErrorException("Cannot set banner via JSONRPC API") def _wait_for_reload(self, timeout=None): timeout = timeout or self.config_timeout end_timeout = time.time() + timeout while True: time.sleep(10) try: self.device.run_commands(["show version"]) break except pyeapi.eapilib.ConnectionError: if time.time() > end_timeout: raise def load_merge_candidate(self, filename=None, config=None): self._load_config(filename=filename, config=config, replace=False) def load_replace_candidate(self, filename=None, config=None): self._load_config(filename=filename, config=config, replace=True) def compare_config(self): # There's no good way to do this yet if self._replace_config: cur = self.get_config("running")["running"].splitlines()[4:] return "\n".join(difflib.unified_diff(cur, self._candidate[3:])) else: return "\n".join(self._candidate[2:]) def discard_config(self): if self.config_session is not None: self._candidate = None self._unlock() def commit_config(self, message=""): if message: raise NotImplementedError( "Commit message not implemented for this platform") if self.config_session is not None and self._candidate: if self._replace_config: try: self.device.run_commands( self._candidate + ["copy running-config startup-config"]) except pyeapi.eapilib.ConnectionError: self._wait_for_reload() else: self.device.run_commands( self._candidate + ["copy running-config startup-config"]) self._unlock() def rollback(self): commands = [ "copy flash:rollback-0 running-config", "copy running-config startup-config", ] for command in commands: self.device.run_commands(command) def get_interfaces(self): def _parse_mm_speed(speed): """Parse the Metamako speed string from 'sh int status' into an Mbit/s int""" factormap = {"": 1e-6, "k": 1e-3, "M": 1, "G": 1e3, "T": 1e6} match = re.match(r"^(?P<speed>\d+)(?P<unit>\D)?$", speed) if match: match_dict = match.groupdict("") return int( int(match_dict["speed"]) * factormap[match_dict["unit"]]) return 0 commands = ["show interfaces status", "show interfaces description"] output = self.device.run_commands(commands, encoding="json") descriptions = {d["Port"]: d["Description"] for d in output[1]} interfaces = {} for interface, values in output[0]["interfaces"].items(): interfaces[interface] = {} # A L1 device doesn't really have a line protocol. # Let's say an rx signal means line protocol is up for now. if values["rx"].startswith("up"): interfaces[interface]["is_up"] = True interfaces[interface]["is_enabled"] = True else: interfaces[interface]["is_up"] = False if "shutdown" in values["rx"]: interfaces[interface]["is_enabled"] = False else: interfaces[interface]["is_enabled"] = True interfaces[interface]["description"] = descriptions.get( interface, "") interfaces[interface]["last_flapped"] = 0.0 interfaces[interface]["speed"] = _parse_mm_speed(values["speed"]) interfaces[interface]["mac_address"] = "" # L1 device has no concept of MTU interfaces[interface]["mtu"] = -1 return interfaces def get_lldp_neighbors(self): commands = [] commands.append("show lldp neighbor") output = self.device.run_commands(commands, encoding="json")[0] lldp = {} for n in output: # MOS Has a line for every port, regardless of neighbor if n["Neighbor_Device"] != "" and n["Neighbor_Port"] != "": if n["Port"] not in lldp.keys(): lldp[n["Port"]] = [] lldp[n["Port"]].append({ "hostname": n["Neighbor_Device"], "port": n["Neighbor_Port"] }) return lldp def get_interfaces_counters(self): commands = [ "show interfaces counters", "show interfaces counters errors" ] output = self.device.run_commands(commands, encoding="json") interface_counters = {} errors_dict = output[1]["interfaces"] for interface, counters in output[0]["interfaces"].items(): interface_counters[interface] = {} interface_counters[interface].update( tx_errors=int( errors_dict.get(interface, {}).get("tx", -1).replace(",", "")), rx_errors=int( errors_dict.get(interface, {}).get("tx", -1).replace(",", "")), tx_discards=-1, # Metamako discards? rx_discards=-1, tx_octets=int(counters.get("txoctets", -1).replace(",", "")), rx_octets=int(counters.get("rxoctets", -1).replace(",", "")), tx_unicast_packets=int( counters.get("txucastpkts", -1).replace(",", "")), rx_unicast_packets=int( counters.get("rxucastpkts", -1).replace(",", "")), tx_multicast_packets=int( counters.get("txmcastpkts", -1).replace(",", "")), rx_multicast_packets=int( counters.get("rxmcastpkts", -1).replace(",", "")), tx_broadcast_packets=int( counters.get("txbcastpkts", -1).replace(",", "")), rx_broadcast_packets=int( counters.get("rxbcastpkts", -1).replace(",", "")), ) return interface_counters def get_environment(self): commands = ["show environment all"] output = self._run_translated_commands(commands, encoding="json")[0] environment_counters = { "fans": {}, "temperature": {}, "power": {}, "cpu": {} } # Fans for slot, data in output["systemCooling"]["fans"].items(): environment_counters["fans"][slot] = { "status": False if data["status"] == "NOT WORKING" else True } # Temperature temps = {} for n, v in output["systemTemperature"]["sensors"].items(): # Make sure all the temperatures are numbers, allow floats as well temp = v["temp(C)"] if v["temp(C)"].replace(".", "").isdigit() else -1 alert_thres = (v["alertThreshold"] if v["alertThreshold"].replace( ".", "").isdigit() else -1) crit_thres = (v["criticalThreshold"] if v["criticalThreshold"].replace(".", "").isdigit() else -1) temps[v["description"]] = { "temperature": float(temp), "is_alert": float(temp) > float(alert_thres), "is_critical": float(temp) > float(crit_thres), } environment_counters["temperature"].update(temps) # Power psu_dict = output["systemPower"]["powerSupplies"] for psu, data in output["systemPower"]["powerOutput"].items(): environment_counters["power"][psu] = { "status": float(re.match(r"^([\d.]+)", data["inputVoltage"]).group()) != 0.0, "capacity": float( re.match(r"^([\d.]+)", psu_dict[psu]["capacity"]).group()), "output": float(re.match(r"^([\d.]+)", data["outputPower"]).group()), } # CPU - No CLI command available. Need to be implemented in a different way environment_counters["cpu"][0] = {"%usage": float(-1)} # Memory - No CLI command available. Need to be implemented in a different way environment_counters["memory"] = {"available_ram": -1, "used_ram": -1} return environment_counters def _transform_lldp_capab(self, capabilities): return sorted([ LLDP_CAPAB_TRANFORM_TABLE[c.lower()] for c in capabilities.split(", ") if c ]) def get_lldp_neighbors_detail(self, interface=""): lldp_neighbors_out = {} commands = ["show lldp neighbor {} verbose".format(interface)] neighbors_str = self.device.run_commands(commands, encoding="text")[0]["output"] interfaces_split = re.split(r"^\*\s(\S+)$", neighbors_str, flags=re.MULTILINE)[1:] interface_list = zip(*(iter(interfaces_split), ) * 2) for interface, interface_str in interface_list: lldp_neighbors_out[interface] = [] for neighbor_str in interface_str.strip().split("\n\n"): info_dict = {} for info_line in neighbor_str.strip().splitlines(): try: key, value = info_line.split(":", 1) except ValueError: # Extremely long lines wrap info_dict[key.strip()] = "{} {}".format( info_dict[key.strip()], info_line) info_dict[key.strip()] = value.strip() # System capabilities try: capabilities = ast.literal_eval( info_dict.get("system capability", "{}")) except Exception: capabilities = {} system_capab = capabilities.get("capabilities", "").replace(",", ", ") enabled_capab = capabilities.get("enabled", "").replace(",", ", ") tlv_dict = { "parent_interface": interface, "remote_port": re.sub(r"\s*\([^)]*\)\s*", "", info_dict.get("port id", "")), "remote_port_description": info_dict.get("port description", ""), "remote_chassis_id": re.sub(r"\s*\([^)]*\)\s*", "", info_dict.get("chassis id", "")), "remote_system_name": info_dict.get("system name", ""), "remote_system_description": info_dict.get("system description", ""), "remote_system_capab": self._transform_lldp_capab(system_capab), "remote_system_enable_capab": self._transform_lldp_capab(enabled_capab), } lldp_neighbors_out[interface].append(tlv_dict) return lldp_neighbors_out def cli(self, commands): cli_output = {} if not isinstance(commands, list): raise TypeError("Please enter a valid list of commands!") for command in commands: try: cli_output[command] = self.device.run_commands( [command], encoding="text")[0].get("output") # not quite fair to not exploit rum_commands # but at least can have better control to point to wrong command in case of failure except pyeapi.eapilib.CommandError: # for sure this command failed cli_output[command] = 'Invalid command: "{cmd}"'.format( cmd=command) raise CommandErrorException(str(cli_output)) except Exception as e: # something bad happened msg = 'Unable to execute command "{cmd}": {err}'.format( cmd=command, err=e) cli_output[command] = msg raise CommandErrorException(str(cli_output)) return cli_output def get_arp_table(self, vrf=""): if vrf: raise NotImplementedError( "Metamako MOS does not support multiple VRFs") arp_table = [] commands = ["show arp numeric"] try: output = self.device.run_commands(commands, encoding="text")[0]["output"] except pyeapi.eapilib.CommandError: return [] for line in output.split("\n"): match = self._RE_ARP.match(line) if match: neighbor = match.groupdict() interface = neighbor.get("interface") mac_raw = neighbor.get("hwAddress") ip = neighbor.get("address") age = 0.0 arp_table.append({ "interface": interface, "mac": napalm.base.helpers.mac(mac_raw), "ip": napalm.base.helpers.ip(ip), "age": age, }) return arp_table def get_ntp_servers(self): config = self.get_config(retrieve="running")["running"] servers = self._RE_NTP_SERVERS.findall(config) return {server: {} for server in servers} def get_ntp_stats(self): ntp_stats = [] REGEX = (r"^\s?(\+|\*|x|-)?([a-zA-Z0-9\.+-:]+)" r"\s+([a-zA-Z0-9\.]+)\s+([0-9]{1,2})" r"\s+(-|u)\s+([0-9h-]+)\s+([0-9]+)" r"\s+([0-9]+)\s+([0-9\.]+)\s+([0-9\.-]+)" r"\s+([0-9\.]+)\s?$") commands = [] commands.append("show ntp associations") # output = self.device.run_commands(commands) # pyeapi.eapilib.CommandError: CLI command 2 of 2 'show ntp associations' # failed: unconverted command # JSON output not yet implemented... ntp_assoc = self.device.run_commands(commands, encoding="text")[0].get( "output", "\n\n") ntp_assoc_lines = ntp_assoc.splitlines()[2:] for ntp_assoc in ntp_assoc_lines: line_search = re.search(REGEX, ntp_assoc, re.I) if not line_search: continue # pattern not found line_groups = line_search.groups() try: ntp_stats.append({ "remote": line_groups[1], "synchronized": (line_groups[0] == "*"), "referenceid": line_groups[2], "stratum": int(line_groups[3]), "type": line_groups[4], "when": line_groups[5], "hostpoll": int(line_groups[6]), "reachability": int(line_groups[7]), "delay": float(line_groups[8]), "offset": float(line_groups[9]), "jitter": float(line_groups[10]), }) except Exception: continue # jump to next line return ntp_stats def get_snmp_information(self): """get_snmp_information() for MOS.""" # Default values snmp_dict = { "chassis_id": "", "location": "", "contact": "", "community": {} } commands = [ "show snmp chassis-id", "show snmp location", "show snmp contact", "show snmp community", ] snmp_config = self._run_translated_commands(commands, encoding="text") snmp_dict["chassis_id"] = (snmp_config[0]["output"].replace( "Chassis: ", "").strip()) snmp_dict["location"] = (snmp_config[1]["output"].replace( "Location: ", "").strip()) snmp_dict["contact"] = snmp_config[2]["output"].replace( "Contact: ", "").strip() community_outputs = snmp_config[3]["output"].split("\n\n") for community_output in community_outputs: match = self._RE_SNMP_COMM.search(community_output) if match: matches = match.groupdict("") snmp_dict["community"][match.group("community")] = { "acl": matches["v4_acl"], "mode": matches["mode"], } return snmp_dict def get_optics(self): # THIS NEEDS WORK command = ["show interfaces transceiver"] output = self.device.run_commands(command, encoding="json")[0]["interfaces"] # Formatting data into return data structure optics_detail = {} for port, port_values in output.items(): port_detail = {} port_detail["physical_channels"] = {} port_detail["physical_channels"]["channel"] = [] # Defaulting avg, min, max values to 0.0 since device does not # return these values try: rxpwr = float(port_values["rxPwr"]) except ValueError: rxpwr = 0.0 try: txpwr = float(port_values["txPwr"]) except ValueError: txpwr = 0.0 try: txbias = float(port_values["txBias"]) except ValueError: txbias = 0.0 optic_states = { "index": 0, "state": { "input_power": { "instant": rxpwr, "avg": 0.0, "min": 0.0, "max": 0.0, }, "output_power": { "instant": txpwr, "avg": 0.0, "min": 0.0, "max": 0.0, }, "laser_bias_current": { "instant": txbias, "avg": 0.0, "min": 0.0, "max": 0.0, }, }, } port_detail["physical_channels"]["channel"].append(optic_states) optics_detail[port] = port_detail return optics_detail def get_config(self, retrieve="all", full=False, sanitized=False): """get_config implementation for MOS.""" get_startup = False get_running = False commands = ["#", "#"] if retrieve == "all" or retrieve == "startup": get_startup = True commands[0] = "show startup-config" if retrieve == "all" or retrieve == "running": get_running = True commands[1] = "show running-config" if not get_startup and not get_running: Exception("Wrong retrieve filter: {}".format(retrieve)) output = self.device.run_commands(commands, encoding="text") if sanitized: output = [{ "output": napalm.base.helpers.sanitize_config(config["output"], c.CISCO_SANITIZE_FILTERS) } for config in output] return { "startup": output[0]["output"] if get_startup else "", "running": output[1]["output"] if get_running else "", "candidate": "", }
class EOSDevice(BaseDevice): """Arista EOS Device Implementation.""" vendor = "arista" def __init__(self, host, username, password, transport="http", port=None, timeout=None, **kwargs): super().__init__(host, username, password, device_type="arista_eos_eapi") self.transport = transport self.port = port self.timeout = timeout eapi_args = { "transport": transport, "host": host, "username": username, "password": password, } optional_args = ("port", "timeout") for arg in optional_args: value = getattr(self, arg) if value is not None: eapi_args[arg] = value self.connection = eos_connect(**eapi_args) self.native = EOSNative(self.connection) # _connected indicates Netmiko ssh connection self._connected = False def _file_copy_instance(self, src, dest=None, file_system="flash:"): if dest is None: dest = os.path.basename(src) fc = FileTransfer(self.native_ssh, src, dest, file_system=file_system) return fc def _get_file_system(self): """Determines the default file system or directory for device. Returns: str: The name of the default file system or directory for the device. Raises: FileSystemNotFound: When the module is unable to determine the default file system. """ raw_data = self.show("dir", raw_text=True) try: file_system = re.match(r"\s*.*?(\S+:)", raw_data).group(1) except AttributeError: raise FileSystemNotFoundError(hostname=self.hostname, command="dir") return file_system def _image_booted(self, image_name, **vendor_specifics): version_data = self.show("show boot", raw_text=True) if re.search(image_name, version_data): return True return False def _interfaces_status_list(self): interfaces_list = [] interfaces_status_dictionary = self.show( "show interfaces status")["interfaceStatuses"] for key in interfaces_status_dictionary: interface_dictionary = interfaces_status_dictionary[key] interface_dictionary["interface"] = key interfaces_list.append(interface_dictionary) return convert_list_by_key(interfaces_list, INTERFACES_KM, fill_in=True, whitelist=["interface"]) def _parse_response(self, response, raw_text): if raw_text: return list(x["result"]["output"] for x in response) else: return list(x["result"] for x in response) def _uptime_to_string(self, uptime): days = uptime / (24 * 60 * 60) uptime = uptime % (24 * 60 * 60) hours = uptime / (60 * 60) uptime = uptime % (60 * 60) mins = uptime / 60 uptime = uptime % 60 seconds = uptime return "%02d:%02d:%02d:%02d" % (days, hours, mins, seconds) def _wait_for_device_reboot(self, timeout=3600): start = time.time() while time.time() - start < timeout: try: self.show("show hostname") return except: # noqa E722 # nosec pass raise RebootTimeoutError(hostname=self.hostname, wait_time=timeout) def backup_running_config(self, filename): with open(filename, "w") as f: f.write(self.running_config) @property def boot_options(self): image = self.show("show boot-config")["softwareImage"] image = image.replace("flash:", "") return dict(sys=image) def checkpoint(self, checkpoint_file): self.show("copy running-config %s" % checkpoint_file) def close(self): pass def config(self, commands): """Send configuration commands to a device. Args: commands (str, list): String with single command, or list with multiple commands. Raises: CommandError: Issue with the command provided. CommandListError: Issue with a command in the list provided. """ try: self.native.config(commands) except EOSCommandError as e: if isinstance(commands, str): raise CommandError(commands, e.message) raise CommandListError(commands, e.commands[len(e.commands) - 1], e.message) def config_list(self, commands): """Send configuration commands in list format to a device. DEPRECATED - Use the `config` method. Args: commands (list): List with multiple commands. """ warnings.warn("config_list() is deprecated; use config().", DeprecationWarning) self.config(commands) def enable(self): """Ensure device is in enable mode. Returns: None: Device prompt is set to enable mode. """ # Netmiko reports enable and config mode as being enabled if not self.native_ssh.check_enable_mode(): self.native_ssh.enable() # Ensure device is not in config mode if self.native_ssh.check_config_mode(): self.native_ssh.exit_config_mode() @property def uptime(self): if self._uptime is None: sh_version_output = self.show("show version") self._uptime = int(time.time() - sh_version_output["bootupTimestamp"]) return self._uptime @property def uptime_string(self): if self._uptime_string is None: self._uptime_string = self._uptime_to_string(self.uptime) return self._uptime_string @property def hostname(self): if self._hostname is None: sh_hostname_output = self.show("show hostname") self._hostname = sh_hostname_output["hostname"] return self._hostname @property def interfaces(self): if self._interfaces is None: iface_detailed_list = self._interfaces_status_list() self._interfaces = sorted( list(x["interface"] for x in iface_detailed_list)) return self._interfaces @property def vlans(self): if self._vlans is None: vlans = EOSVlans(self) self._vlans = vlans.get_list() return self._vlans @property def fqdn(self): if self._fqdn is None: sh_hostname_output = self.show("show hostname") self._fqdn = sh_hostname_output["fqdn"] return self._fqdn @property def model(self): if self._model is None: sh_version_output = self.show("show version") self._model = sh_version_output["modelName"] return self._model @property def os_version(self): if self._os_version is None: sh_version_output = self.show("show version") self._os_version = sh_version_output["internalVersion"] return self._os_version @property def serial_number(self): if self._serial_number is None: sh_version_output = self.show("show version") self._serial_number = sh_version_output["serialNumber"] return self._serial_number def file_copy(self, src, dest=None, file_system=None): """[summary] Args: src (string): source file dest (string, optional): Destintion file. Defaults to None. file_system (string, optional): Describes device file system. Defaults to None. Raises: FileTransferError: raise exception if there is an error """ self.open() self.enable() if file_system is None: file_system = self._get_file_system() if not self.file_copy_remote_exists(src, dest, file_system): fc = self._file_copy_instance(src, dest, file_system=file_system) try: fc.enable_scp() fc.establish_scp_conn() fc.transfer_file() except: # noqa E722 raise FileTransferError finally: fc.close_scp_chan() if not self.file_copy_remote_exists(src, dest, file_system): raise FileTransferError( message= "Attempted file copy, but could not validate file existed after transfer" ) # TODO: Make this an internal method since exposing file_copy should be sufficient def file_copy_remote_exists(self, src, dest=None, file_system=None): self.enable() if file_system is None: file_system = self._get_file_system() fc = self._file_copy_instance(src, dest, file_system=file_system) if fc.check_file_exists() and fc.compare_md5(): return True return False def install_os(self, image_name, **vendor_specifics): timeout = vendor_specifics.get("timeout", 3600) if not self._image_booted(image_name): self.set_boot_options(image_name, **vendor_specifics) self.reboot() self._wait_for_device_reboot(timeout=timeout) if not self._image_booted(image_name): raise OSInstallError(hostname=self.hostname, desired_boot=image_name) return True return False def open(self): """Opens ssh connection with Netmiko ConnectHandler to be used with FileTransfer""" if self._connected: try: self.native_ssh.find_prompt() except Exception: self._connected = False if not self._connected: self.native_ssh = ConnectHandler( device_type="arista_eos", ip=self.host, username=self.username, password=self.password, # port=self.port, # global_delay_factor=self.global_delay_factor, # secret=self.secret, verbose=False, ) self._connected = True def reboot(self, timer=0, **kwargs): """ Reload the controller or controller pair. Args: timer (int, optional): The time to wait before reloading. Defaults to 0. Raises: RebootTimeoutError: When the device is still unreachable after the timeout period. Example: >>> device = EOSDevice(**connection_args) >>> device.reboot() >>> """ if kwargs.get("confirm"): warnings.warn("Passing 'confirm' to reboot method is deprecated.", DeprecationWarning) if timer != 0: raise RebootTimerError(self.device_type) self.show("reload now") def rollback(self, rollback_to): try: self.show("configure replace %s force" % rollback_to) except (CommandError, CommandListError): raise RollbackError("Rollback unsuccessful. %s may not exist." % rollback_to) @property def running_config(self): return self.show("show running-config", raw_text=True) def save(self, filename="startup-config"): self.show("copy running-config %s" % filename) return True def set_boot_options(self, image_name, **vendor_specifics): file_system = vendor_specifics.get("file_system") if file_system is None: file_system = self._get_file_system() file_system_files = self.show("dir {0}".format(file_system), raw_text=True) if re.search(image_name, file_system_files) is None: raise NTCFileNotFoundError(hostname=self.hostname, file=image_name, dir=file_system) self.show("install source {0}{1}".format(file_system, image_name)) if self.boot_options["sys"] != image_name: raise CommandError( command="install source {0}".format(image_name), message="Setting install source did not yield expected results", ) def show(self, commands, raw_text=False): """Send configuration commands to a device. Args: commands (str, list): String with single command, or list with multiple commands. raw_text (bool, optional): False if encode should be json, True if encoding is text. Defaults to False. Raises: CommandError: Issue with the command provided. CommandListError: Issue with a command in the list provided. """ if not raw_text: encoding = "json" else: encoding = "text" original_commands_is_str = isinstance(commands, str) if original_commands_is_str: commands = [commands] try: response = self.native.enable(commands, encoding=encoding) response_list = self._parse_response(response, raw_text=raw_text) if original_commands_is_str: return response_list[0] return response_list except EOSCommandError as e: if original_commands_is_str: raise CommandError(e.commands, e.message) raise CommandListError(commands, e.commands[len(e.commands) - 1], e.message) def show_list(self, commands): """Send show commands in list format to a device. DEPRECATED - Use the `show` method. Args: commands (list): List with multiple commands. """ warnings.warn("show_list() is deprecated; use show().", DeprecationWarning) self.show(commands) @property def startup_config(self): return self.show("show startup-config", raw_text=True)
class EOSDevice(BaseDevice): def __init__(self, host, username, password, transport='http', timeout=60, **kwargs): super(EOSDevice, self).__init__(host, username, password, vendor='arista', device_type='arista_eos_eapi') self.transport = transport self.timeout = timeout self.connection = eos_connect(transport, host=host, username=username, password=password, timeout=timeout) self.native = EOSNative(self.connection) def _get_interface_list(self): iface_detailed_list = self._interfaces_status_list() iface_list = sorted(list(x['interface'] for x in iface_detailed_list)) return iface_list def _get_vlan_list(self): vlans = EOSVlans(self) vlan_list = vlans.get_list() return vlan_list def _interfaces_status_list(self): interfaces_list = [] interfaces_status_dictionary = self.show( 'show interfaces status')['interfaceStatuses'] for key in interfaces_status_dictionary: interface_dictionary = interfaces_status_dictionary[key] interface_dictionary['interface'] = key interfaces_list.append(interface_dictionary) return convert_list_by_key(interfaces_list, eos_key_maps.INTERFACES_KM, fill_in=True, whitelist=['interface']) def _parse_response(self, response, raw_text): if raw_text: return list(x['result']['output'] for x in response) else: return list(x['result'] for x in response) def _uptime_to_string(self, uptime): days = uptime / (24 * 60 * 60) uptime = uptime % (24 * 60 * 60) hours = uptime / (60 * 60) uptime = uptime % (60 * 60) mins = uptime / 60 uptime = uptime % 60 seconds = uptime return '%02d:%02d:%02d:%02d' % (days, hours, mins, seconds) def backup_running_config(self, filename): with open(filename, 'w') as f: f.write(self.running_config) def checkpoint(self, checkpoint_file): self.show('copy running-config %s' % checkpoint_file) def close(self): pass def config(self, command): try: self.config_list([command]) except CommandListError as e: raise CommandError(e.command, e.message) def config_list(self, commands): try: self.native.config(commands) except EOSCommandError as e: raise CommandListError(commands, e.commands[len(e.commands) - 1], e.message) @property def facts(self): if hasattr(self, '_facts'): return self._facts facts = {} facts['vendor'] = self.vendor sh_version_output = self.show('show version') facts.update( convert_dict_by_key(sh_version_output, eos_key_maps.BASIC_FACTS_KM)) uptime = int(time.time() - sh_version_output['bootupTimestamp']) facts['uptime'] = uptime facts['uptime_string'] = self._uptime_to_string(uptime) sh_hostname_output = self.show('show hostname') facts.update( convert_dict_by_key(sh_hostname_output, {}, fill_in=True, whitelist=['hostname', 'fqdn'])) facts['interfaces'] = self._get_interface_list() facts['vlans'] = self._get_vlan_list() self._facts = facts return facts def file_copy(self, src, dest=None, **kwargs): fc = EOSFileCopy(self, src, dest) fc.send() def file_copy_remote_exists(self, src, dest=None, **kwargs): fc = EOSFileCopy(self, src, dest) if fc.remote_file_exists(): if fc.already_transfered(): return True return False def get_boot_options(self): image = self.show('show boot-config')['softwareImage'] image = image.replace('flash:', '') return dict(sys=image) def open(self): pass def reboot(self, confirm=False, timer=0): if timer != 0: raise RebootTimerError(self.device_type) if confirm: self.show('reload now') else: print('Need to confirm reboot with confirm=True') def rollback(self, rollback_to): try: self.show('configure replace %s force' % rollback_to) except (CommandError, CommandListError): raise RollbackError('Rollback unsuccessful. %s may not exist.' % rollback_to) @property def running_config(self): return self.show('show running-config', raw_text=True) def save(self, filename='startup-config'): self.show('copy running-config %s' % filename) return True def set_boot_options(self, image_name, **vendor_specifics): self.show('install source %s' % image_name) def show(self, command, raw_text=False): try: response_list = self.show_list([command], raw_text=raw_text) return response_list[0] except CommandListError as e: raise CommandError(e.command, e.message) def show_list(self, commands, raw_text=False): if raw_text: encoding = 'text' else: encoding = 'json' try: return strip_unicode( self._parse_response(self.native.enable(commands, encoding=encoding), raw_text=raw_text)) except EOSCommandError as e: raise CommandListError(commands, e.commands[len(e.commands) - 1], e.message) @property def startup_config(self): return self.show('show startup-config', raw_text=True)
class MOSDriver(NetworkDriver): """Napalm driver for Metamako MOS.""" SUPPORTED_OC_MODELS = [] _RE_UPTIME = re.compile( r"""^((?P<day>\d+)\s+days?,\s+)? (?P<hour>\d+):(?P<minute>\d+):(?P<second>\d+)""", re.VERBOSE) _RE_ARP = re.compile( r"""^(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) \s+\S+\s+ (?P<hwAddress>([0-9A-F]{2}[:-]){5}([0-9A-F]{2})) \s+\S+\s+ (?P<interface>\S+)$""", re.VERBOSE | re.IGNORECASE) _RE_NTP_SERVERS = re.compile(r'^ntp server (?P<server>\S+)', re.MULTILINE) _RE_SNMP_COMM = re.compile( r'''\s*Community\sname:\s+(?P<community>\S+)\n Community\saccess:\s+(?P<mode>\S+) (\nCommunity\ssource:\s+(?P<v4_acl>\S+))?''', re.VERBOSE) 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_session = None self._current_config = None self._replace_config = False self._ssh = None if optional_args is None: optional_args = {} self.transport = optional_args.get('transport', 'https') if self.transport == 'https': self.port = optional_args.get('port', 443) else: self.port = optional_args.get('port', 80) self.path = optional_args.get('path', '/command-api') self.enablepwd = optional_args.get('enable_password', '') def open(self): """Implementation of NAPALM method open.""" if self.transport not in TRANSPORTS: raise TypeError('invalid transport specified') klass = TRANSPORTS[self.transport] try: connection = klass( self.hostname, port=self.port, path=self.path, username=self.username, password=self.password, timeout=self.timeout, ) if self.device is None: self.device = EapiNode(connection, enablepwd=self.enablepwd) sw_version = self.device.run_commands(['show version'])[0].get( 'softwareImageVersion', "0.0.0") if LooseVersion(sw_version) < LooseVersion("0.14.1"): raise NotImplementedError( "MOS Software Version 0.14.1 or better required") # This is to get around user mismatch in API/FileCopy if self._ssh is None: self._ssh = ConnectHandler(device_type='cisco_ios', ip=self.hostname, username=self.username, password=self.password) self._ssh.enable() except ConnectionError as ce: raise ConnectionException(ce.message) def close(self): """Implementation of NAPALM method close.""" if self.config_session is not None: # Only doing this because discard_config is broke self.commit_config() self._ssh.disconnect() self._ssh = None def is_alive(self): """If alive, send keep alive""" if self._ssh is None: return {'is_alive': False} elif self._ssh.remote_conn.transport.is_active(): self._ssh.send_command(chr(0)) return {'is_alive': True} else: return {'is_alive': False} def get_facts(self): """Implementation of NAPALM method get_facts.""" commands_json = ['show version', 'show interfaces status'] commands_text = ['show hostname'] result_json = self.device.run_commands(commands_json, encoding='json') result_text = self.device.run_commands(commands_text, encoding='text') version = result_json[0] hostname = result_text[0]['output'].splitlines()[0].split(" ")[-1] fqdn = result_text[0]['output'].splitlines()[1].split(" ")[-1] interfaces = result_json[1]['interfaces'].keys() interfaces = string_parsers.sorted_nicely(interfaces) u_match = re.match(self._RE_UPTIME, version['uptime']).groupdict() if u_match['day'] is None: u_match['day'] = 0 uptime = timedelta(days=int(u_match['day']), hours=int(u_match['hour']), seconds=int(u_match['second'])).total_seconds() return { 'hostname': hostname, 'fqdn': fqdn, 'vendor': 'Metamako', 'model': re.sub(r'^[Mm]etamako ', '', version['device']), 'serial_number': version['serialNumber'], 'os_version': version['softwareImageVersion'], 'uptime': int(uptime), 'interface_list': interfaces, } def _lock(self): if self.config_session is None: self.config_session = "napalm_{}".format( datetime.now().microsecond) commands = [ "copy running-config flash:{}".format(self.config_session), "show running-config" ] for command in commands: self._ssh.send_command(command) if [k for k in self._get_sessions() if k != self.config_session]: self.device.run_commands( ["delete flash:{}".format(self.config_session)]) self.config_session = None raise SessionLockedException('Session already in use') def _unlock(self): if self.config_session is not None: self._ssh.send_command("bash rm -f /mnt/flash/{}".format( self.config_session)) self.config_session = None self._replace_config = False def _get_sessions(self): return [ l.split()[-1] for l in self.device.run_commands( ["dir flash:"], encoding='text')[0]['output'].splitlines() if "napalm_" in l.split()[-1] ] def _load_config(self, filename=None, config=None, replace=False): if filename and config: raise ValueError("Cannot simultaneously set filename and config") self._lock() if filename is None: with NamedTemporaryFile() as fd: if isinstance(config, list): config.insert(0, "configure") config = "\n".join(config) + "\n" else: config = "configure\n" + config + "\n" fd.write(config.encode('utf-8')) fd.flush() with FileCopy(self, fd.name, '/mnt/flash/{}'.format(self.config_session), 'put') as c: c.put_file() else: with FileCopy(self, filename, '/mnt/flash/{}'.format(self.config_session), 'put') as c: c.put_file() def load_merge_candidate(self, filename=None, config=None): self._load_config(filename=filename, config=config, replace=False) self._replace_config = False def load_replace_candidate(self, filename=None, config=None): self._load_config(filename=filename, config=config, replace=True) self._replace_config = True def compare_config(self): # There's no good way to do this yet if self._replace_config: return self._ssh.send_command( "diff running-config flash:{}".format(self.config_session)) else: return self._ssh.send_command("bash cat /mnt/flash/{}".format( self.config_session)) def discard_config(self): if self.config_session is not None: self._unlock() def commit_config(self): if self.config_session is not None: commands = [ "delete flash:rollback-0", "copy running-config flash:rollback-0" ] if self.compare_config(): if self._replace_config: commands.append("copy flash:{} running-config ".format( self.config_session)) else: commands.append("bash /usr/bin/cli /mnt/flash/{}".format( self.config_session)) commands.append("copy running-config startup-config") for command in commands: self._ssh.send_command(command) self._unlock() def rollback(self): commands = [ "copy flash:rollback-0 running-config", "copy running-config startup-config" ] for command in commands: self._ssh.send_command(command) def get_interfaces(self): def _parse_mm_speed(speed): """Parse the Metamako speed string from 'sh int status' into an Mbit/s int""" factormap = {'': 1e-6, 'k': 1e-3, 'M': 1, 'G': 1000, 'T': 1000000} match = re.match(r'^(?P<speed>\d+)(?P<unit>\D)?$', speed) if match: match_dict = match.groupdict('') return int( int(match_dict['speed']) * factormap[match_dict['unit']]) return 0 commands = [] commands.append('show interfaces status') commands.append('show interfaces description') output = self.device.run_commands(commands, encoding='json') descriptions = {d['Port']: d['Description'] for d in output[1]} interfaces = {} for interface, values in output[0]['interfaces'].items(): interfaces[interface] = {} # A L1 device doesn't really have a line protocol. # Let's say an rx signal means line protocol is up for now. if values['rx'].startswith('up'): interfaces[interface]['is_up'] = True interfaces[interface]['is_enabled'] = True else: interfaces[interface]['is_up'] = False if 'shutdown' in values['rx']: interfaces[interface]['is_enabled'] = False else: interfaces[interface]['is_enabled'] = True interfaces[interface]['description'] = descriptions.get( interface, '') interfaces[interface]['last_flapped'] = 0.0 interfaces[interface]['speed'] = _parse_mm_speed(values['speed']) interfaces[interface]['mac_address'] = '' return interfaces def get_lldp_neighbors(self): commands = [] commands.append('show lldp neighbor') output = self.device.run_commands(commands, encoding='json')[0] lldp = {} for n in output: # MOS Has a line for every port, regardless of neighbor if n['Neighbor_Device'] != '' and n['Neighbor_Port'] != '': if n['Port'] not in lldp.keys(): lldp[n['Port']] = [] lldp[n['Port']].append({ 'hostname': n['Neighbor_Device'], 'port': n['Neighbor_Port'], }) return lldp def get_interfaces_counters(self): commands = [ 'show interfaces counters', 'show interfaces counters errors' ] output = self.device.run_commands(commands, encoding='json') interface_counters = {} errors_dict = output[1]['interfaces'] for interface, counters in output[0]['interfaces'].items(): interface_counters[interface] = {} interface_counters[interface].update( tx_errors=int( errors_dict.get(interface, {}).get('tx', -1).replace(',', '')), rx_errors=int( errors_dict.get(interface, {}).get('tx', -1).replace(',', '')), tx_discards=-1, # Metamako discards? rx_discards=-1, tx_octets=int(counters.get('txoctets', -1).replace(',', '')), rx_octets=int(counters.get('rxoctets', -1).replace(',', '')), tx_unicast_packets=int( counters.get('txucastpkts', -1).replace(',', '')), rx_unicast_packets=int( counters.get('rxucastpkts', -1).replace(',', '')), tx_multicast_packets=int( counters.get('txmcastpkts', -1).replace(',', '')), rx_multicast_packets=int( counters.get('rxmcastpkts', -1).replace(',', '')), tx_broadcast_packets=int( counters.get('txbcastpkts', -1).replace(',', '')), rx_broadcast_packets=int( counters.get('rxbcastpkts', -1).replace(',', '')), ) return interface_counters def get_environment(self): commands = ['show environment all'] output = self.device.run_commands(commands, encoding='json')[0] environment_counters = { 'fans': {}, 'temperature': {}, 'power': {}, 'cpu': {} } # Fans for slot, data in output['systemCooling']['fans'].items(): environment_counters['fans'][slot] = { 'status': data['status'] == 'OK' } # Temperature temps = {} for n, v in output['systemTemperature']['sensors'].items(): temps[v['description']] = { 'temperature': float(v['temp(C)']), 'is_alert': float(v['temp(C)']) > float(v['alertThreshold']), 'is_critical': float(v['temp(C)']) > float(v['criticalThreshold']) } environment_counters['temperature'].update(temps) # Power psu_dict = output['systemPower']['powerSupplies'] for psu, data in output['systemPower']['powerOutput'].items(): environment_counters['power'][psu] = { 'status': float(re.match(r'^([\d.]+)', data['inputVoltage']).group()) != 0.0, 'capacity': float( re.match(r'^([\d.]+)', psu_dict[psu]['capacity']).group()), 'output': float(re.match(r'^([\d.]+)', data['outputPower']).group()) } # CPU - No CLI command available. Need to be implemented in a different way environment_counters['cpu'][0] = {'%usage': float(-1)} # Memory - No CLI command available. Need to be implemented in a different way environment_counters['memory'] = {'available_ram': -1, 'used_ram': -1} return environment_counters def get_lldp_neighbors_detail(self, interface=''): lldp_neighbors_out = {} commands = ['show lldp neighbor {} verbose'.format(interface)] neighbors_str = self.device.run_commands(commands, encoding='text')[0]['output'] interfaces_split = re.split(r'^\*\s(\S+)$', neighbors_str, flags=re.MULTILINE)[1:] interface_list = zip(*(iter(interfaces_split), ) * 2) for interface, interface_str in interface_list: lldp_neighbors_out[interface] = [] for neighbor_str in interface_str.strip().split('\n\n'): info_dict = {} for info_line in neighbor_str.strip().splitlines(): try: key, value = info_line.split(':', 1) except ValueError: # Extremely long lines wrap info_dict[key.strip()] = "{} {}".format( info_dict[key.strip()], info_line) info_dict[key.strip()] = value.strip() # System capabilities try: capabilities = ast.literal_eval( info_dict.get('system capability', '{}')) except Exception: capabilities = {} system_capab = capabilities.get('capabilities', '').replace(',', ', ') enabled_capab = capabilities.get('enabled', '').replace(',', ', ') tlv_dict = { 'parent_interface': py23_compat.text_type(interface), 'remote_port': re.sub(r'\s*\([^)]*\)\s*', '', info_dict.get('port id', '')), 'remote_port_description': info_dict.get('port description', ''), 'remote_chassis_id': re.sub(r'\s*\([^)]*\)\s*', '', info_dict.get('chassis id', '')), 'remote_system_name': info_dict.get('system name', ''), 'remote_system_description': info_dict.get('system description', ''), 'remote_system_capab': system_capab, 'remote_system_enable_capab': enabled_capab } lldp_neighbors_out[interface].append(tlv_dict) return lldp_neighbors_out def cli(self, commands): cli_output = {} if not isinstance(commands, list): raise TypeError('Please enter a valid list of commands!') for command in commands: try: cli_output[py23_compat.text_type( command)] = self.device.run_commands( [command], encoding='text')[0].get('output') # not quite fair to not exploit rum_commands # but at least can have better control to point to wrong command in case of failure except pyeapi.eapilib.CommandError: # for sure this command failed cli_output[py23_compat.text_type( command)] = 'Invalid command: "{cmd}"'.format(cmd=command) raise CommandErrorException(str(cli_output)) except Exception as e: # something bad happened msg = 'Unable to execute command "{cmd}": {err}'.format( cmd=command, err=e) cli_output[py23_compat.text_type(command)] = msg raise CommandErrorException(str(cli_output)) return cli_output def get_arp_table(self): arp_table = [] commands = ['show arp'] try: output = self.device.run_commands(commands, encoding='text')[0]['output'] except pyeapi.eapilib.CommandError: return [] for line in output.split('\n'): match = self._RE_ARP.match(line) if match: neighbor = match.groupdict() interface = py23_compat.text_type(neighbor.get('interface')) mac_raw = neighbor.get('hwAddress') ip = py23_compat.text_type(neighbor.get('address')) age = 0.0 arp_table.append({ 'interface': interface, 'mac': napalm.base.helpers.mac(mac_raw), 'ip': napalm.base.helpers.ip(ip), 'age': age }) return arp_table def get_ntp_servers(self): config = self.get_config(retrieve='running')['running'] servers = self._RE_NTP_SERVERS.findall(config) return {py23_compat.text_type(server): {} for server in servers} def get_ntp_stats(self): ntp_stats = [] REGEX = (r'^\s?(\+|\*|x|-)?([a-zA-Z0-9\.+-:]+)' r'\s+([a-zA-Z0-9\.]+)\s+([0-9]{1,2})' r'\s+(-|u)\s+([0-9h-]+)\s+([0-9]+)' r'\s+([0-9]+)\s+([0-9\.]+)\s+([0-9\.-]+)' r'\s+([0-9\.]+)\s?$') commands = [] commands.append('show ntp associations') # output = self.device.run_commands(commands) # pyeapi.eapilib.CommandError: CLI command 2 of 2 'show ntp associations' # failed: unconverted command # JSON output not yet implemented... ntp_assoc = self.device.run_commands(commands, encoding='text')[0].get( 'output', '\n\n') ntp_assoc_lines = ntp_assoc.splitlines()[2:] for ntp_assoc in ntp_assoc_lines: line_search = re.search(REGEX, ntp_assoc, re.I) if not line_search: continue # pattern not found line_groups = line_search.groups() try: ntp_stats.append({ 'remote': py23_compat.text_type(line_groups[1]), 'synchronized': (line_groups[0] == '*'), 'referenceid': py23_compat.text_type(line_groups[2]), 'stratum': int(line_groups[3]), 'type': py23_compat.text_type(line_groups[4]), 'when': py23_compat.text_type(line_groups[5]), 'hostpoll': int(line_groups[6]), 'reachability': int(line_groups[7]), 'delay': float(line_groups[8]), 'offset': float(line_groups[9]), 'jitter': float(line_groups[10]) }) except Exception: continue # jump to next line return ntp_stats def get_snmp_information(self): """get_snmp_information() for MOS.""" # Default values snmp_dict = { 'chassis_id': '', 'location': '', 'contact': '', 'community': {} } commands = [ 'show snmp chassis-id', 'show snmp location', 'show snmp contact', 'show snmp community' ] snmp_config = self.device.run_commands(commands, encoding='text') snmp_dict['chassis_id'] = snmp_config[0]['output'].replace( 'Chassis: ', '').strip() snmp_dict['location'] = snmp_config[1]['output'].replace( 'Location: ', '').strip() snmp_dict['contact'] = snmp_config[2]['output'].replace( 'Contact: ', '').strip() community_outputs = snmp_config[3]['output'].split('\n\n') for community_output in community_outputs: match = self._RE_SNMP_COMM.search(community_output) if match: matches = match.groupdict('') snmp_dict['community'][match.group('community')] = { 'acl': py23_compat.text_type(matches['v4_acl']), 'mode': py23_compat.text_type(matches['mode']) } return snmp_dict def get_optics(self): # THIS NEEDS WORK command = ['show interfaces transceiver'] output = (self.device.run_commands(command, encoding='json')[0]['interfaces']) # Formatting data into return data structure optics_detail = {} for port, port_values in output.items(): port_detail = {} port_detail['physical_channels'] = {} port_detail['physical_channels']['channel'] = [] # Defaulting avg, min, max values to 0.0 since device does not # return these values optic_states = { 'index': 0, 'state': { 'input_power': { 'instant': (port_values['rxPwr'] if isinstance( port_values.get("rxPwr"), float) else 0.0), 'avg': 0.0, 'min': 0.0, 'max': 0.0 }, 'output_power': { 'instant': (port_values['txPwr'] if isinstance( port_values.get("txPwr"), float) else 0.0), 'avg': 0.0, 'min': 0.0, 'max': 0.0 }, 'laser_bias_current': { 'instant': (port_values['txBias'] if isinstance( port_values.get("txBias"), float) else 0.0), 'avg': 0.0, 'min': 0.0, 'max': 0.0 } } } port_detail['physical_channels']['channel'].append(optic_states) optics_detail[port] = port_detail return optics_detail def get_config(self, retrieve="all"): """get_config implementation for MOS.""" get_startup = False get_running = False commands = ['#', '#'] if retrieve == "all" or retrieve == "startup": get_startup = True commands[0] = 'show startup-config' if retrieve == "all" or retrieve == "running": get_running = True commands[1] = 'show running-config' if not get_startup and not get_running: Exception("Wrong retrieve filter: {}".format(retrieve)) output = self.device.run_commands(commands, encoding="text") return { 'startup': py23_compat.text_type(output[0]['output']) if get_startup else u"", 'running': py23_compat.text_type(output[1]['output']) if get_running else u"", 'candidate': '', }
class EOSDevice(BaseDevice): def __init__(self, host, username, password, transport="http", timeout=60, **kwargs): super(EOSDevice, self).__init__(host, username, password, vendor="arista", device_type="arista_eos_eapi") self.transport = transport self.timeout = timeout self.connection = eos_connect(transport, host=host, username=username, password=password, timeout=timeout) self.native = EOSNative(self.connection) def _get_file_system(self): """Determines the default file system or directory for device. Returns: str: The name of the default file system or directory for the device. Raises: FileSystemNotFound: When the module is unable to determine the default file system. """ raw_data = self.show("dir", raw_text=True) try: file_system = re.match(r"\s*.*?(\S+:)", raw_data).group(1) return file_system except AttributeError: raise FileSystemNotFoundError(hostname=self.facts.get("hostname"), command="dir") def _get_interface_list(self): iface_detailed_list = self._interfaces_status_list() iface_list = sorted(list(x["interface"] for x in iface_detailed_list)) return iface_list def _get_vlan_list(self): vlans = EOSVlans(self) vlan_list = vlans.get_list() return vlan_list def _image_booted(self, image_name, **vendor_specifics): version_data = self.show("show boot", raw_text=True) if re.search(image_name, version_data): return True return False def _interfaces_status_list(self): interfaces_list = [] interfaces_status_dictionary = self.show( "show interfaces status")["interfaceStatuses"] for key in interfaces_status_dictionary: interface_dictionary = interfaces_status_dictionary[key] interface_dictionary["interface"] = key interfaces_list.append(interface_dictionary) return convert_list_by_key(interfaces_list, eos_key_maps.INTERFACES_KM, fill_in=True, whitelist=["interface"]) def _parse_response(self, response, raw_text): if raw_text: return list(x["result"]["output"] for x in response) else: return list(x["result"] for x in response) def _uptime_to_string(self, uptime): days = uptime / (24 * 60 * 60) uptime = uptime % (24 * 60 * 60) hours = uptime / (60 * 60) uptime = uptime % (60 * 60) mins = uptime / 60 uptime = uptime % 60 seconds = uptime return "%02d:%02d:%02d:%02d" % (days, hours, mins, seconds) def _wait_for_device_reboot(self, timeout=3600): start = time.time() while time.time() - start < timeout: try: self.show("show hostname") return except: pass raise RebootTimeoutError(hostname=self.facts["hostname"], wait_time=timeout) def backup_running_config(self, filename): with open(filename, "w") as f: f.write(self.running_config) def checkpoint(self, checkpoint_file): self.show("copy running-config %s" % checkpoint_file) def close(self): pass def config(self, command): try: self.config_list([command]) except CommandListError as e: raise CommandError(e.command, e.message) def config_list(self, commands): try: self.native.config(commands) except EOSCommandError as e: raise CommandListError(commands, e.commands[len(e.commands) - 1], e.message) @property def facts(self): if self._facts is None: sh_version_output = self.show("show version") self._facts = convert_dict_by_key(sh_version_output, eos_key_maps.BASIC_FACTS_KM) self._facts["vendor"] = self.vendor uptime = int(time.time() - sh_version_output["bootupTimestamp"]) self._facts["uptime"] = uptime self._facts["uptime_string"] = self._uptime_to_string(uptime) sh_hostname_output = self.show("show hostname") self._facts.update( convert_dict_by_key(sh_hostname_output, {}, fill_in=True, whitelist=["hostname", "fqdn"])) self._facts["interfaces"] = self._get_interface_list() self._facts["vlans"] = self._get_vlan_list() return self._facts def file_copy(self, src, dest=None, **kwargs): if not self.file_copy_remote_exists(src, dest, **kwargs): fc = EOSFileCopy(self, src, dest) fc.send() if not self.file_copy_remote_exists(src, dest, **kwargs): raise FileTransferError( message= "Attempted file copy, but could not validate file existed after transfer" ) # TODO: Make this an internal method since exposing file_copy should be sufficient def file_copy_remote_exists(self, src, dest=None, **kwargs): fc = EOSFileCopy(self, src, dest) if fc.remote_file_exists() and fc.already_transferred(): return True return False def get_boot_options(self): image = self.show("show boot-config")["softwareImage"] image = image.replace("flash:", "") return dict(sys=image) def install_os(self, image_name, **vendor_specifics): timeout = vendor_specifics.get("timeout", 3600) if not self._image_booted(image_name): self.set_boot_options(image_name, **vendor_specifics) self.reboot(confirm=True) self._wait_for_device_reboot(timeout=timeout) if not self._image_booted(image_name): raise OSInstallError(hostname=self.facts.get("hostname"), desired_boot=image_name) return True return False def open(self): pass def reboot(self, confirm=False, timer=0): if timer != 0: raise RebootTimerError(self.device_type) if confirm: self.show("reload now") else: print("Need to confirm reboot with confirm=True") def rollback(self, rollback_to): try: self.show("configure replace %s force" % rollback_to) except (CommandError, CommandListError): raise RollbackError("Rollback unsuccessful. %s may not exist." % rollback_to) @property def running_config(self): return self.show("show running-config", raw_text=True) def save(self, filename="startup-config"): self.show("copy running-config %s" % filename) return True def set_boot_options(self, image_name, **vendor_specifics): file_system = vendor_specifics.get("file_system") if file_system is None: file_system = self._get_file_system() file_system_files = self.show("dir {0}".format(file_system), raw_text=True) if re.search(image_name, file_system_files) is None: raise NTCFileNotFoundError(hostname=self.facts.get("hostname"), file=image_name, dir=file_system) self.show("install source {0}{1}".format(file_system, image_name)) if self.get_boot_options()["sys"] != image_name: raise CommandError( command="install source {0}".format(image_name), message="Setting install source did not yield expected results", ) def show(self, command, raw_text=False): try: response_list = self.show_list([command], raw_text=raw_text) return response_list[0] except CommandListError as e: raise CommandError(e.command, e.message) def show_list(self, commands, raw_text=False): if raw_text: encoding = "text" else: encoding = "json" try: return strip_unicode( self._parse_response(self.native.enable(commands, encoding=encoding), raw_text=raw_text)) except EOSCommandError as e: raise CommandListError(commands, e.commands[len(e.commands) - 1], e.message) @property def startup_config(self): return self.show("show startup-config", raw_text=True)
class EOSDevice(BaseDevice): """Arista EOS Device Implementation.""" vendor = "arista" def __init__(self, host, username, password, transport="http", timeout=60, **kwargs): super().__init__(host, username, password, device_type="arista_eos_eapi") self.transport = transport self.timeout = timeout self.connection = eos_connect(transport, host=host, username=username, password=password, timeout=timeout) self.native = EOSNative(self.connection) # _connected indicates Netmiko ssh connection self._connected = False def _file_copy_instance(self, src, dest=None, file_system="flash:"): if dest is None: dest = os.path.basename(src) fc = FileTransfer(self.native_ssh, src, dest, file_system=file_system) return fc def _get_file_system(self): """Determines the default file system or directory for device. Returns: str: The name of the default file system or directory for the device. Raises: FileSystemNotFound: When the module is unable to determine the default file system. """ raw_data = self.show("dir", raw_text=True) try: file_system = re.match(r"\s*.*?(\S+:)", raw_data).group(1) except AttributeError: raise FileSystemNotFoundError(hostname=self.facts.get("hostname"), command="dir") return file_system def _get_interface_list(self): iface_detailed_list = self._interfaces_status_list() iface_list = sorted(list(x["interface"] for x in iface_detailed_list)) return iface_list def _get_vlan_list(self): vlans = EOSVlans(self) vlan_list = vlans.get_list() return vlan_list def _image_booted(self, image_name, **vendor_specifics): version_data = self.show("show boot", raw_text=True) if re.search(image_name, version_data): return True return False def _interfaces_status_list(self): interfaces_list = [] interfaces_status_dictionary = self.show( "show interfaces status")["interfaceStatuses"] for key in interfaces_status_dictionary: interface_dictionary = interfaces_status_dictionary[key] interface_dictionary["interface"] = key interfaces_list.append(interface_dictionary) return convert_list_by_key(interfaces_list, INTERFACES_KM, fill_in=True, whitelist=["interface"]) def _parse_response(self, response, raw_text): if raw_text: return list(x["result"]["output"] for x in response) else: return list(x["result"] for x in response) def _uptime_to_string(self, uptime): days = uptime / (24 * 60 * 60) uptime = uptime % (24 * 60 * 60) hours = uptime / (60 * 60) uptime = uptime % (60 * 60) mins = uptime / 60 uptime = uptime % 60 seconds = uptime return "%02d:%02d:%02d:%02d" % (days, hours, mins, seconds) def _wait_for_device_reboot(self, timeout=3600): start = time.time() while time.time() - start < timeout: try: self.show("show hostname") return except: # noqa E722 pass raise RebootTimeoutError(hostname=self.facts["hostname"], wait_time=timeout) def backup_running_config(self, filename): with open(filename, "w") as f: f.write(self.running_config) @property def boot_options(self): image = self.show("show boot-config")["softwareImage"] image = image.replace("flash:", "") return dict(sys=image) def checkpoint(self, checkpoint_file): self.show("copy running-config %s" % checkpoint_file) def close(self): pass def config(self, command): try: self.config_list([command]) except CommandListError as e: raise CommandError(e.command, e.message) def config_list(self, commands): try: self.native.config(commands) except EOSCommandError as e: raise CommandListError(commands, e.commands[len(e.commands) - 1], e.message) def enable(self): """Ensure device is in enable mode. Returns: None: Device prompt is set to enable mode. """ # Netmiko reports enable and config mode as being enabled if not self.native_ssh.check_enable_mode(): self.native_ssh.enable() # Ensure device is not in config mode if self.native_ssh.check_config_mode(): self.native_ssh.exit_config_mode() @property def facts(self): if self._facts is None: sh_version_output = self.show("show version") self._facts = convert_dict_by_key(sh_version_output, BASIC_FACTS_KM) self._facts["vendor"] = self.vendor uptime = int(time.time() - sh_version_output["bootupTimestamp"]) self._facts["uptime"] = uptime self._facts["uptime_string"] = self._uptime_to_string(uptime) sh_hostname_output = self.show("show hostname") self._facts.update( convert_dict_by_key(sh_hostname_output, {}, fill_in=True, whitelist=["hostname", "fqdn"])) self._facts["interfaces"] = self._get_interface_list() self._facts["vlans"] = self._get_vlan_list() return self._facts def file_copy(self, src, dest=None, file_system=None): """[summary] Args: src (string): source file dest (string, optional): Destintion file. Defaults to None. file_system (string, optional): Describes device file system. Defaults to None. Raises: FileTransferError: raise exception if there is an error """ self.open() self.enable() if file_system is None: file_system = self._get_file_system() if not self.file_copy_remote_exists(src, dest, file_system): fc = self._file_copy_instance(src, dest, file_system=file_system) try: fc.enable_scp() fc.establish_scp_conn() fc.transfer_file() except: # noqa E722 raise FileTransferError finally: fc.close_scp_chan() if not self.file_copy_remote_exists(src, dest, file_system): raise FileTransferError( message= "Attempted file copy, but could not validate file existed after transfer" ) # TODO: Make this an internal method since exposing file_copy should be sufficient def file_copy_remote_exists(self, src, dest=None, file_system=None): self.enable() if file_system is None: file_system = self._get_file_system() fc = self._file_copy_instance(src, dest, file_system=file_system) if fc.check_file_exists() and fc.compare_md5(): return True return False def install_os(self, image_name, **vendor_specifics): timeout = vendor_specifics.get("timeout", 3600) if not self._image_booted(image_name): self.set_boot_options(image_name, **vendor_specifics) self.reboot(confirm=True) self._wait_for_device_reboot(timeout=timeout) if not self._image_booted(image_name): raise OSInstallError(hostname=self.facts.get("hostname"), desired_boot=image_name) return True return False def open(self): """Opens ssh connection with Netmiko ConnectHandler to be used with FileTransfer""" if self._connected: try: self.native_ssh.find_prompt() except Exception: self._connected = False if not self._connected: self.native_ssh = ConnectHandler( device_type="arista_eos", ip=self.host, username=self.username, password=self.password, # port=self.port, # global_delay_factor=self.global_delay_factor, # secret=self.secret, verbose=False, ) self._connected = True def reboot(self, confirm=False, timer=0): if timer != 0: raise RebootTimerError(self.device_type) if confirm: self.show("reload now") else: print("Need to confirm reboot with confirm=True") def rollback(self, rollback_to): try: self.show("configure replace %s force" % rollback_to) except (CommandError, CommandListError): raise RollbackError("Rollback unsuccessful. %s may not exist." % rollback_to) @property def running_config(self): return self.show("show running-config", raw_text=True) def save(self, filename="startup-config"): self.show("copy running-config %s" % filename) return True def set_boot_options(self, image_name, **vendor_specifics): file_system = vendor_specifics.get("file_system") if file_system is None: file_system = self._get_file_system() file_system_files = self.show("dir {0}".format(file_system), raw_text=True) if re.search(image_name, file_system_files) is None: raise NTCFileNotFoundError(hostname=self.facts.get("hostname"), file=image_name, dir=file_system) self.show("install source {0}{1}".format(file_system, image_name)) if self.boot_options["sys"] != image_name: raise CommandError( command="install source {0}".format(image_name), message="Setting install source did not yield expected results", ) def show(self, command, raw_text=False): try: response_list = self.show_list([command], raw_text=raw_text) except CommandListError as e: raise CommandError(e.command, e.message) return response_list[0] def show_list(self, commands, raw_text=False): if raw_text: encoding = "text" else: encoding = "json" try: return self._parse_response(self.native.enable(commands, encoding=encoding), raw_text=raw_text) except EOSCommandError as e: raise CommandListError(commands, e.commands[len(e.commands) - 1], e.message) @property def startup_config(self): return self.show("show startup-config", raw_text=True)