content: # -*- coding: utf-8 -*-
  # Copyright 2020 CISCO. All rights reserved.
  # Copyright 2021 Kirk Byers. All rights reserved.
  #
  # The contents of this file are licensed under the Apache License, Version 2.0
  # (the "License"); you may not use this file except in compliance with the
  # License. You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  # License for the specific language governing permissions and limitations under
  # the License.
  
  """NETCONF Driver for IOSXR devices."""
  
  from __future__ import unicode_literals
  
  # import stdlib
  import re
  import copy
  import difflib
  import ipaddress
  import logging
  
  # import third party lib
  from ncclient import manager
  from ncclient.xml_ import to_ele
  from ncclient.operations.rpc import RPCError
  from ncclient.operations.errors import TimeoutExpiredError
  from lxml import etree as ETREE
  from lxml.etree import XMLSyntaxError
  
  # import NAPALM base
  from napalm.iosxr_netconf import constants as C
  from napalm.iosxr.utilities import strip_config_header
  from napalm.base.base import NetworkDriver
  import napalm.base.helpers
  from napalm.base.exceptions import ConnectionException
  from napalm.base.exceptions import MergeConfigException
  from napalm.base.exceptions import ReplaceConfigException
  
  logger = logging.getLogger(__name__)
  
  
  class IOSXRNETCONFDriver(NetworkDriver):
      """IOS-XR NETCONF driver class: inherits NetworkDriver from napalm.base."""
  
      def __init__(self, hostname, username, password, timeout=60, optional_args=None):
          """
          Initialize IOSXR driver.
  
          optional_args:
              * config_lock (True/False): lock configuration DB after the
                  connection is established.
              * port (int): custom port
              * key_file (string): SSH key file path
          """
          self.hostname = hostname
          self.username = username
          self.password = password
          self.timeout = timeout
          self.pending_changes = False
          self.replace = False
          self.locked = False
          self.optional_args = optional_args if optional_args else {}
          self.port = self.optional_args.pop("port", 830)
          self.lock_on_connect = self.optional_args.pop("config_lock", False)
          self.key_file = self.optional_args.pop("key_file", None)
          self.config_encoding = self.optional_args.pop("config_encoding", "cli")
          if "ssh_config_file" in self.optional_args:
              self.optional_args["ssh_config"] = self.optional_args.pop("ssh_config_file")
          if self.config_encoding not in C.CONFIG_ENCODINGS:
              raise ValueError(f"config encoding must be one of {C.CONFIG_ENCODINGS}")
  
          self.platform = "iosxr_netconf"
          self.device = None
          self.module_set_ns = []
  
      def open(self):
          """Open the connection with the device."""
          try:
              self.device = manager.connect(
                  host=self.hostname,
                  port=self.port,
                  username=self.username,
                  password=self.password,
                  key_filename=self.key_file,
                  timeout=self.timeout,
                  device_params={"name": "iosxr"},
                  **self.optional_args,
              )
              if self.lock_on_connect:
                  self._lock()
          except Exception as conn_err:
              logger.error(conn_err.args[0])
              raise ConnectionException(conn_err.args[0])
  
          # Retrieve module-set namespaces based on yang library model
          for capability in self.device.server_capabilities:
              if C.NS["ylib"] in capability:
                  rpc_reply = self.device.get(
                      filter=(
                          "subtree",
                          C.YANG_LIB_RPC_REQ_FILTER.format(module_set=C.MODULE_SET),
                      )
                  ).xml
                  # Converts string to tree
                  rpc_reply_etree = ETREE.fromstring(rpc_reply)
  
                  # Retrieves namespaces
                  module_set_tree = rpc_reply_etree.xpath(
                      ".//ylib:yang-library/ylib:module-set/ylib:module/ylib:namespace",
                      namespaces=C.NS,
                  )
                  self.module_set_ns = [n.text for n in module_set_tree]
                  break
  
      def close(self):
          """Close the connection."""
          logger.debug("Closed connection with device %s" % (self.hostname))
          self._unlock()
          self.device.close_session()
  
      def _lock(self):
          """Lock the config DB."""
          if not self.locked:
              self.device.lock()
              self.locked = True
  
      def _unlock(self):
          """Unlock the config DB."""
          if self.locked:
              self.device.unlock()
              self.locked = False
  
      def _load_config(self, filename, config):
          """Edit Configuration."""
          if filename is None:
              configuration = config
          else:
              with open(filename) as f:
                  configuration = f.read()
          self.pending_changes = True
          self._lock()
          return configuration
  
      def _filter_config_tree(self, tree):
          """Return filtered config etree based on YANG module set."""
          if self.module_set_ns:
  
              def unexpected(n):
                  return n not in self.module_set_ns
  
          else:
  
              def unexpected(n):
                  return n.startswith("http://openconfig.net/yang")
  
          for subtree in tree:
              if unexpected(subtree.tag[1:].split("}")[0]):
                  tree.remove(subtree)
          return tree
  
      def _unexpected_modules(self, tree):
          """Return list of unexpected modules based on YANG module set."""
          modules = []
          if self.module_set_ns:
  
              def unexpected(n):
                  return n not in self.module_set_ns
  
          else:
  
              def unexpected(n):
                  return n.startswith("http://openconfig.net/yang")
  
          for subtree in tree:
              namespace = subtree.tag[1:].split("}")[0]
              if unexpected(namespace):
                  modules.append(namespace)
          return modules
  
      def is_alive(self):
          """Return flag with the state of the connection."""
          if self.device is None:
              return {"is_alive": False}
          return {"is_alive": self.device._session.transport.is_active()}
  
      def load_replace_candidate(self, filename=None, config=None):
          """Open the candidate config and replace."""
          self.replace = True
          configuration = self._load_config(filename=filename, config=config)
          if self.config_encoding == "cli":
              configuration = (
                  '<config><cli xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-cli-cfg">'
                  + configuration
                  + "</cli></config>"
              )
          elif self.config_encoding == "xml":
              parser = ETREE.XMLParser(remove_blank_text=True)
              unexpected_modules = self._unexpected_modules(
                  ETREE.XML(configuration, parser=parser)
              )
              if unexpected_modules:
                  raise ReplaceConfigException(
                      f'{C.INVALID_MODEL_REFERENCE} ({", ".join(unexpected_modules)})'
                  )
  
          configuration = "<source>" + configuration + "</source>"
          try:
              self.device.copy_config(source=configuration, target="candidate")
          except (RPCError, XMLSyntaxError) as e:
              self.pending_changes = False
              self.replace = False
              logger.error(e.args[0])
              raise ReplaceConfigException(e)
  
      def load_merge_candidate(self, filename=None, config=None):
          """Open the candidate config and merge."""
          self.replace = False
          configuration = self._load_config(filename=filename, config=config)
          if self.config_encoding == "cli":
              configuration = (
                  '<config><cli xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-cli-cfg">'
                  + configuration
                  + "</cli></config>"
              )
          elif self.config_encoding == "xml":
              parser = ETREE.XMLParser(remove_blank_text=True)
              unexpected_modules = self._unexpected_modules(
                  ETREE.XML(configuration, parser=parser)
              )
              if unexpected_modules:
                  raise MergeConfigException(
                      f'{C.INVALID_MODEL_REFERENCE} ({", ".join(unexpected_modules)})'
                  )
  
          try:
              self.device.edit_config(
                  config=configuration, error_option="rollback-on-error"
              )
          except (RPCError, XMLSyntaxError) as e:
              self.pending_changes = False
              logger.error(e.args[0])
              raise MergeConfigException(e)
  
      def compare_config(self):
          """Compare candidate config with running."""
  
          diff = ""
          encoding = self.config_encoding
          if encoding not in C.CLI_DIFF_RPC_REQ:
              raise NotImplementedError(
                  f"config encoding must be one of {C.CONFIG_ENCODINGS}"
              )
  
          if self.pending_changes:
              parser = ETREE.XMLParser(remove_blank_text=True)
              if encoding == "cli":
                  diff = self.device.dispatch(to_ele(C.CLI_DIFF_RPC_REQ)).xml
                  diff = ETREE.XML(diff, parser=parser)[0].text.strip()
                  diff = strip_config_header(diff)
              elif encoding == "xml":
                  run_conf = self.device.get_config("running").xml
                  can_conf = self.device.get_config("candidate").xml
                  run_conf = ETREE.tostring(
                      self._filter_config_tree(ETREE.XML(run_conf, parser=parser)[0]),
                      pretty_print=True,
                  ).decode()
                  can_conf = ETREE.tostring(
                      self._filter_config_tree(ETREE.XML(can_conf, parser=parser)[0]),
                      pretty_print=True,
                  ).decode()
                  for line in difflib.unified_diff(
                      run_conf.splitlines(1), can_conf.splitlines(1)
                  ):
                      diff += line
  
          return diff
  
      def commit_config(self, message="", revert_in=None):
          """Commit configuration."""
          if revert_in is not None:
              raise NotImplementedError(
                  "Commit confirm has not been implemented on this platform."
              )
          if message:
              raise NotImplementedError(
                  "Commit message not implemented for this platform"
              )
          self.device.commit()
          self.pending_changes = False
          self._unlock()
  
      def discard_config(self):
          """Discard changes."""
          self.device.discard_changes()
          self.pending_changes = False
          self._unlock()
  
      def rollback(self):
          """Rollback to previous commit."""
          self.device.dispatch(to_ele(C.ROLLBACK_RPC_REQ))
  
      def _find_txt(self, xml_tree, path, default=None, namespaces=None):
          """
          Extract the text value from a leaf in an XML tree using XPath.
  
          Will return a default value if leaf path not matched.
          :param xml_tree:the XML Tree object. <type'lxml.etree._Element'>.
          :param path: XPath to be applied in order to extract the desired data.
          :param default:  Value to be returned in case of a no match.
          :param namespaces: namespace dictionary.
          :return: a str value or None if leaf path not matched.
          """
  
          value = None
          xpath_applied = xml_tree.xpath(path, namespaces=namespaces)
          if xpath_applied:
              if not len(xpath_applied[0]):
                  if xpath_applied[0].text is not None:
                      value = xpath_applied[0].text.strip()
                  else:
                      value = ""
          else:
              value = default
  
          return value
  
      def get_facts(self):
          """Return facts of the device."""
          facts = {
              "vendor": "Cisco",
              "os_version": "",
              "hostname": "",
              "uptime": -1.0,
              "serial_number": "",
              "fqdn": "",
              "model": "",
              "interface_list": [],
          }
          interface_list = []
  
          facts_rpc_reply = self.device.dispatch(to_ele(C.FACTS_RPC_REQ)).xml
  
          # Converts string to etree
          facts_rpc_reply_etree = ETREE.fromstring(facts_rpc_reply)
  
          # Retrieves hostname
          hostname = napalm.base.helpers.convert(
              str,
              self._find_txt(
                  facts_rpc_reply_etree,
                  ".//suo:system-time/\
              suo:uptime/suo:host-name",
                  default="",
                  namespaces=C.NS,
              ),
          )
  
          # Retrieves uptime
          uptime = napalm.base.helpers.convert(
              float,
              self._find_txt(
                  facts_rpc_reply_etree,
                  ".//suo:system-time/\
              suo:uptime/suo:uptime",
                  default="",
                  namespaces=C.NS,
              ),
              -1.0,
          )
  
          # Retrieves interfaces name
          interface_tree = facts_rpc_reply_etree.xpath(
              ".//int:interfaces/int:interfaces/int:interface", namespaces=C.NS
          )
          for interface in interface_tree:
              name = self._find_txt(
                  interface, "./int:interface-name", default="", namespaces=C.NS
              )
              interface_list.append(name)
          # Retrieves os version, model, serial number
          basic_info_tree = facts_rpc_reply_etree.xpath(
              ".//imo:inventory/imo:entities/imo:entity/imo:attributes/\
                          imo:inv-basic-bag",
              namespaces=C.NS,
          )
          if basic_info_tree:
              os_version = napalm.base.helpers.convert(
                  str,
                  self._find_txt(
                      basic_info_tree[0],
                      "./imo:software-revision",
                      default="",
                      namespaces=C.NS,
                  ),
              )
              model = napalm.base.helpers.convert(
                  str,
                  self._find_txt(
                      basic_info_tree[0], "./imo:model-name", default="", namespaces=C.NS
                  ),
              )
              serial = napalm.base.helpers.convert(
                  str,
                  self._find_txt(
                      basic_info_tree[0],
                      "./imo:serial-number",
                      default="",
                      namespaces=C.NS,
                  ),
              )
          else:
              os_version = ""
              model = ""
              serial = ""
  
          facts.update(
              {
                  "os_version": os_version,
                  "hostname": hostname,
                  "model": model,
                  "uptime": uptime,
                  "serial_number": serial,
                  "fqdn": hostname,
                  "interface_list": interface_list,
              }
          )
  
          return facts
  
      def get_interfaces(self):
          """Return interfaces details."""
          interfaces = {}
  
          INTERFACE_DEFAULTS = {
              "is_enabled": False,
              "is_up": False,
              "mac_address": "",
              "description": "",
              "speed": -1.0,
              "last_flapped": -1.0,
          }
  
          interfaces_rpc_reply = self.device.get(
              filter=("subtree", C.INT_RPC_REQ_FILTER)
          ).xml
          # Converts string to etree
          interfaces_rpc_reply_etree = ETREE.fromstring(interfaces_rpc_reply)
  
          # Retrieves interfaces details
          for interface_tree, description_tree in zip(
              interfaces_rpc_reply_etree.xpath(
                  ".//int:interfaces/int:interface-xr/int:interface", namespaces=C.NS
              ),
              interfaces_rpc_reply_etree.xpath(
                  ".//int:interfaces/int:interfaces/int:interface", namespaces=C.NS
              ),
          ):
              interface_name = self._find_txt(
                  interface_tree, "./int:interface-name", default="", namespaces=C.NS
              )
              if not interface_name:
                  continue
              is_up = (
                  self._find_txt(
                      interface_tree, "./int:line-state", default="", namespaces=C.NS
                  )
                  == "im-state-up"
              )
              enabled = (
                  self._find_txt(
                      interface_tree, "./int:state", default="", namespaces=C.NS
                  )
                  != "im-state-admin-down"
              )
              raw_mac = self._find_txt(
                  interface_tree,
                  "./int:mac-address/int:address",
                  default="",
                  namespaces=C.NS,
              )
              mac_address = napalm.base.helpers.convert(
                  napalm.base.helpers.mac, raw_mac, raw_mac
              )
              speed = napalm.base.helpers.convert(
                  float,
                  napalm.base.helpers.convert(
                      float,
                      self._find_txt(interface_tree, "./int:bandwidth", namespaces=C.NS),
                      0,
                  )
                  * 1e-3,
              )
              mtu = int(
                  self._find_txt(interface_tree, "./int:mtu", default="", namespaces=C.NS)
              )
              description = self._find_txt(
                  description_tree, "./int:description", default="", namespaces=C.NS
              )
              interfaces[interface_name] = copy.deepcopy(INTERFACE_DEFAULTS)
              interfaces[interface_name].update(
                  {
                      "is_up": is_up,
                      "speed": speed,
                      "mtu": mtu,
                      "is_enabled": enabled,
                      "mac_address": mac_address,
                      "description": description,
                  }
              )
  
          return interfaces
  
      def get_interfaces_counters(self):
          """Return interfaces counters."""
          rpc_reply = self.device.get(
              filter=("subtree", C.INT_COUNTERS_RPC_REQ_FILTER)
          ).xml
          # Converts string to tree
          rpc_reply_etree = ETREE.fromstring(rpc_reply)
  
          interface_counters = {}
  
          # Retrieves interfaces counters details
          interface_xr_tree = rpc_reply_etree.xpath(
              ".//int:interfaces/int:interface-xr/int:interface", namespaces=C.NS
          )
          for interface in interface_xr_tree:
              interface_name = self._find_txt(
                  interface, "./int:interface-name", default="", namespaces=C.NS
              )
              if interface_name[:8] == "Loopback" and interface_name[8:].isdigit():
                  continue
              interface_stats = {}
              if (
                  self._find_txt(
                      interface,
                      "./int:interface-statistics/int:stats-type",
                      default="",
                      namespaces=C.NS,
                  )
                  == "basic"
              ):
                  interface_stats["tx_multicast_packets"] = ""
                  interface_stats["tx_discards"] = ""
                  interface_stats["tx_octets"] = ""
                  interface_stats["tx_errors"] = ""
                  interface_stats["rx_octets"] = ""
                  interface_stats["tx_unicast_packets"] = ""
                  interface_stats["rx_errors"] = ""
                  interface_stats["tx_broadcast_packets"] = ""
                  interface_stats["rx_multicast_packets"] = ""
                  interface_stats["rx_broadcast_packets"] = ""
                  interface_stats["rx_discards"] = ""
                  interface_stats["rx_unicast_packets"] = ""
              else:
                  int_stats_xpath = "./int:interface-statistics/int:full-interface-stats/"
                  interface_stats["tx_multicast_packets"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          interface,
                          int_stats_xpath + "int:multicast-packets-sent",
                          "0",
                          namespaces=C.NS,
                      ),
                  )
                  interface_stats["tx_discards"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          interface,
                          int_stats_xpath + "int:output-drops",
                          "0",
                          namespaces=C.NS,
                      ),
                  )
                  interface_stats["tx_octets"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          interface,
                          int_stats_xpath + "int:bytes-sent",
                          "0",
                          namespaces=C.NS,
                      ),
                  )
                  interface_stats["tx_errors"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          interface,
                          int_stats_xpath + "int:output-errors",
                          "0",
                          namespaces=C.NS,
                      ),
                  )
                  interface_stats["rx_octets"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          interface,
                          int_stats_xpath + "int:bytes-received",
                          "0",
                          namespaces=C.NS,
                      ),
                  )
                  interface_stats["tx_unicast_packets"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          interface,
                          int_stats_xpath + "int:packets-sent",
                          "0",
                          namespaces=C.NS,
                      ),
                  )
                  interface_stats["rx_errors"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          interface,
                          int_stats_xpath + "int:input-errors",
                          "0",
                          namespaces=C.NS,
                      ),
                  )
                  interface_stats["tx_broadcast_packets"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          interface,
                          int_stats_xpath + "int:broadcast-packets-sent",
                          "0",
                          namespaces=C.NS,
                      ),
                  )
                  interface_stats["rx_multicast_packets"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          interface,
                          int_stats_xpath + "int:multicast-packets-received",
                          "0",
                          namespaces=C.NS,
                      ),
                  )
                  interface_stats["rx_broadcast_packets"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          interface,
                          int_stats_xpath + "int:broadcast-packets-received",
                          "0",
                          namespaces=C.NS,
                      ),
                  )
                  interface_stats["rx_discards"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          interface,
                          int_stats_xpath + "int:input-drops",
                          "0",
                          namespaces=C.NS,
                      ),
                  )
                  interface_stats["rx_unicast_packets"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          interface,
                          int_stats_xpath + "int:packets-received",
                          "0",
                          namespaces=C.NS,
                      ),
                  )
  
              interface_counters[interface_name] = interface_stats
  
          return interface_counters
  
      def get_bgp_neighbors(self):
          """Return BGP neighbors details."""
  
          def get_vrf_neighbors(rpc_reply_etree, xpath):
              """Return BGP neighbors details for a given VRF."""
              neighbors = {}
  
              for neighbor in rpc_reply_etree.xpath(xpath, namespaces=C.NS):
                  this_neighbor = {}
                  this_neighbor["local_as"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          neighbor, "./bgp:local-as", default="", namespaces=C.NS
                      ),
                  )
                  this_neighbor["remote_as"] = napalm.base.helpers.convert(
                      int,
                      self._find_txt(
                          neighbor, "./bgp:remote-as", default="", namespaces=C.NS
                      ),
                  )
                  this_neighbor["remote_id"] = napalm.base.helpers.convert(
                      str,
                      self._find_txt(
                          neighbor, "./bgp:router-id", default="", namespaces=C.NS
                      ),
                  )
                  try:
                      this_neighbor["description"] = napalm.base.helpers.convert(
                          str,
                          self._find_txt(
                              neighbor, "./bgp:description", default="", namespaces=C.NS
                          ),
                      )
                  except AttributeError:
                      logger.debug(
                          "No attribute 'description' for neighbor %s"
                          % (this_neighbor["remote_as"])
                      )
                      this_neighbor["description"] = ""
  
                  this_neighbor["is_enabled"] = not (
                      self._find_txt(
                          neighbor,
                          "./bgp:is-administratively-shut-down",
                          default="",
                          namespaces=C.NS,
                      )
                      == "true"
                  )
                  if (
                      str(
                          self._find_txt(
                              neighbor,
                              "./bgp:connection-state",
                              default="",
                              namespaces=C.NS,
                          )
                      )
                      == "bgp-st-estab"
                  ):
                      this_neighbor["is_up"] = True
                      this_neighbor["uptime"] = napalm.base.helpers.convert(
                          int,
                          self._find_txt(
                              neighbor,
                              "./bgp:connection-established-time",
                              default="",
                              namespaces=C.NS,
                          ),
                      )
                  else:
                      this_neighbor["is_up"] = False
                      this_neighbor["uptime"] = -1
  
                  this_neighbor["address_family"] = {}
  
                  if (
                      self._find_txt(
                          neighbor,
                          "./bgp:connection-remote-address/\
                       bgp:afi",
                          default="",
                          namespaces=C.NS,
                      )
                      == "ipv4"
                  ):
                      this_afi = "ipv4"
                  elif (
                      self._find_txt(
                          neighbor,
                          "./bgp:connection-remote-address/bgp:afi",
                          default="",
                          namespaces=C.NS,
                      )
                      == "ipv6"
                  ):
                      this_afi = "ipv6"
                  else:
                      this_afi = self._find_txt(
                          neighbor,
                          "./bgp:connection-remote-address/bgp:afi",
                          default="",
                          namespaces=C.NS,
                      )
  
                  this_neighbor["address_family"][this_afi] = {}
  
                  try:
                      this_neighbor["address_family"][this_afi][
                          "received_prefixes"
                      ] = napalm.base.helpers.convert(
                          int,
                          self._find_txt(
                              neighbor,
                              "./bgp:af-data/bgp:prefixes-accepted",
                              default="",
                              namespaces=C.NS,
                          ),
                          0,
                      ) + napalm.base.helpers.convert(
                          int,
                          self._find_txt(
                              neighbor,
                              "./bgp:af-data/bgp:prefixes-denied",
                              default="",
                              namespaces=C.NS,
                          ),
                          0,
                      )
                      this_neighbor["address_family"][this_afi]["accepted_prefixes"] = (
                          napalm.base.helpers.convert(
                              int,
                              self._find_txt(
                                  neighbor,
                                  "./bgp:af-data/bgp:prefixes-accepted",
                                  default="",
                                  namespaces=C.NS,
                              ),
                              0,
                          )
                      )
                      this_neighbor["address_family"][this_afi]["sent_prefixes"] = (
                          napalm.base.helpers.convert(
                              int,
                              self._find_txt(
                                  neighbor,
                                  "./bgp:af-data/\
                              bgp:prefixes-advertised",
                                  default="",
                                  namespaces=C.NS,
                              ),
                              0,
                          )
                      )
                  except AttributeError:
                      this_neighbor["address_family"][this_afi]["received_prefixes"] = -1
                      this_neighbor["address_family"][this_afi]["accepted_prefixes"] = -1
                      this_neighbor["address_family"][this_afi]["sent_prefixes"] = -1
  
                  neighbor_ip = napalm.base.helpers.ip(
                      self._find_txt(
                          neighbor, "./bgp:neighbor-address", default="", namespaces=C.NS
                      )
                  )
  
                  neighbors[neighbor_ip] = this_neighbor
  
              return neighbors
  
          rpc_reply = self.device.get(filter=("subtree", C.BGP_NEIGHBOR_REQ_FILTER)).xml
          # Converts string to tree
          rpc_reply_etree = ETREE.fromstring(rpc_reply)
          result = {}
          this_vrf = {}
          this_vrf["peers"] = {}
  
          # get neighbors and router id from default(global) VRF
          default_vrf_xpath = """.//bgp:bgp/bgp:instances/bgp:instance/
            bgp:instance-active/bgp:default-vrf/"""
          this_vrf["router_id"] = napalm.base.helpers.convert(
              str,
              self._find_txt(
                  rpc_reply_etree,
                  default_vrf_xpath
                  + "bgp:global-process-info/\
                      bgp:vrf/bgp:router-id",
                  default="",
                  namespaces=C.NS,
              ),
          )
          this_vrf["peers"] = get_vrf_neighbors(
              rpc_reply_etree, default_vrf_xpath + "bgp:neighbors/bgp:neighbor"
          )
          result["global"] = this_vrf
  
          # get neighbors and router id from other VRFs
          vrf_xpath = """.//bgp:bgp/bgp:instances/
                      bgp:instance/bgp:instance-active/bgp:vrfs"""
          for vrf in rpc_reply_etree.xpath(vrf_xpath + "/bgp:vrf", namespaces=C.NS):
              this_vrf = {}
              this_vrf["peers"] = {}
              this_vrf["router_id"] = napalm.base.helpers.convert(
                  str,
                  self._find_txt(
                      vrf,
                      "./bgp:global-process-info/bgp:vrf/\
                                      bgp:router-id",
                      default="",
                      namespaces=C.NS,
                  ),
              )
              vrf_name = self._find_txt(
                  vrf, "./bgp:vrf-name", default="", namespaces=C.NS
              )
              this_vrf["peers"] = get_vrf_neighbors(
  