Ejemplo n.º 1
0
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")
Ejemplo n.º 2
0
 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
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
def eapi_conn(request):
    connection = pyeapi.connect(
        transport="https",
        host="0.0.0.0",
        username="******",
        password="******",
        port=9002,
    )
    node = Node(connection)
    return node
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
 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)
Ejemplo n.º 8
0
 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
Ejemplo n.º 9
0
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": "",
        }
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
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)
Ejemplo n.º 12
0
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':
            '',
        }
Ejemplo n.º 13
0
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)
Ejemplo n.º 14
0
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)