content: # -*- coding: utf-8 -*-
  # Copyright 2015 Spotify AB. 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.
  
  """Driver for JunOS devices."""
  
  # import stdlib
  import re
  import json
  import logging
  import collections
  from copy import deepcopy
  from collections import OrderedDict, defaultdict
  
  # import third party lib
  from lxml.builder import E
  from lxml import etree
  
  from jnpr.junos import Device
  from jnpr.junos.utils.config import Config
  from jnpr.junos.exception import RpcError
  from jnpr.junos.exception import ConfigLoadError
  from jnpr.junos.exception import RpcTimeoutError
  from jnpr.junos.exception import ConnectTimeoutError
  from jnpr.junos.exception import ProbeError
  from jnpr.junos.exception import LockError as JnprLockError
  from jnpr.junos.exception import UnlockError as JnrpUnlockError
  
  # import NAPALM Base
  import napalm.base.helpers
  from napalm.base.base import NetworkDriver
  from napalm.junos import constants as C
  from napalm.base.exceptions import ConnectionException
  from napalm.base.exceptions import MergeConfigException
  from napalm.base.exceptions import CommandErrorException
  from napalm.base.exceptions import ReplaceConfigException
  from napalm.base.exceptions import CommandTimeoutException
  from napalm.base.exceptions import LockError
  from napalm.base.exceptions import UnlockError
  from napalm.base.exceptions import CommitConfirmException
  
  # import local modules
  from napalm.junos.utils import junos_views
  
  log = logging.getLogger(__file__)
  
  
  class JunOSDriver(NetworkDriver):
      """JunOSDriver class - inherits NetworkDriver from napalm.base."""
  
      def __init__(self, hostname, username, password, timeout=60, optional_args=None):
          """
          Initialise JunOS driver.
  
          Optional args:
              * config_lock (True/False): lock configuration DB after the connection is established.
              * lock_disable (True/False): force configuration lock to be disabled (for external lock
                  management).
              * config_private (True/False): juniper configure private command, no DB locking
              * port (int): custom port
              * key_file (string): SSH key file path
              * keepalive (int): Keepalive interval
              * ignore_warning (boolean): not generate warning exceptions
          """
          self.hostname = hostname
          self.username = username
          self.password = password
          self.timeout = timeout
          self.config_replace = False
          self.locked = False
  
          # Get optional arguments
          if optional_args is None:
              optional_args = {}
  
          self.port = optional_args.get("port", 22)
          self.key_file = optional_args.get("key_file", None)
          self.keepalive = optional_args.get("keepalive", 30)
          self.ssh_config_file = optional_args.get("ssh_config_file", None)
          self.ignore_warning = optional_args.get("ignore_warning", False)
          self.auto_probe = optional_args.get("auto_probe", 0)
          self.huge_tree = optional_args.get("huge_tree", False)
  
          # Define locking method
          self.lock_disable = optional_args.get("lock_disable", False)
          self.session_config_lock = optional_args.get("config_lock", False)
          self.config_private = optional_args.get("config_private", False)
  
          # Junos driver specific options
          self.junos_config_database = optional_args.get(
              "junos_config_database", "committed"
          )
          self.junos_config_inheritance = optional_args.get(
              "junos_config_inherit", "inherit"
          )
          self.junos_config_groups = optional_args.get("junos_config_groups", "groups")
          self.junos_config_options = {
              "database": self.junos_config_database,
              "inherit": self.junos_config_inheritance,
              "groups": self.junos_config_groups,
          }
          self.junos_config_options = optional_args.get(
              "junos_config_options", self.junos_config_options
          )
  
          if self.key_file:
              self.device = Device(
                  hostname,
                  user=username,
                  password=password,
                  ssh_private_key_file=self.key_file,
                  ssh_config=self.ssh_config_file,
                  port=self.port,
                  huge_tree=self.huge_tree,
              )
          else:
              self.device = Device(
                  hostname,
                  user=username,
                  password=password,
                  port=self.port,
                  ssh_config=self.ssh_config_file,
                  huge_tree=self.huge_tree,
              )
  
          self.platform = "junos"
          self.profile = [self.platform]
  
      def open(self):
          """Open the connection with the device."""
          try:
              self.device.open(auto_probe=self.auto_probe)
          except (ConnectTimeoutError, ProbeError) as cte:
              raise ConnectionException(cte.msg) from cte
          self.device.timeout = self.timeout
          self.device._conn._session.transport.set_keepalive(self.keepalive)
          if hasattr(self.device, "cu"):
              # make sure to remove the cu attr from previous session
              # ValueError: requested attribute name cu already exists
              del self.device.cu
          self.device.bind(cu=Config)
          if not self.lock_disable and self.session_config_lock:
              self._lock()
  
      def close(self):
          """Close the connection."""
          if not self.lock_disable and self.session_config_lock:
              self._unlock()
          self.device.close()
  
      def _lock(self):
          """Lock the config DB."""
          if not self.locked:
              try:
                  self.device.cu.lock()
                  self.locked = True
              except JnprLockError as jle:
                  raise LockError(str(jle))
  
      def _unlock(self):
          """Unlock the config DB."""
          if self.locked:
              try:
                  self.device.cu.unlock()
                  self.locked = False
              except JnrpUnlockError as jue:
                  raise UnlockError(jue)
  
      def _rpc(self, get, child=None, **kwargs):
          """
          This allows you to construct an arbitrary RPC call to retrieve common stuff. For example:
          Configuration:  get: "<get-configuration/>"
          Interface information:  get: "<get-interface-information/>"
          A particular interface information:
                get: "<get-interface-information/>"
                child: "<interface-name>ge-0/0/0</interface-name>"
          """
          rpc = etree.fromstring(get)
  
          if child:
              rpc.append(etree.fromstring(child))
  
          response = self.device.execute(rpc)
          return etree.tostring(response)
  
      def is_alive(self):
          # evaluate the state of the underlying SSH connection
          # and also the NETCONF status from PyEZ
          return {
              "is_alive": self.device._conn._session.transport.is_active()
              and self.device.connected
          }
  
      @staticmethod
      def _is_json_format(config):
          try:
              _ = json.loads(config)  # noqa
          except (TypeError, ValueError):
              return False
          return True
  
      def _detect_config_format(self, config):
          fmt = "text"
          set_action_matches = [
              "set",
              "activate",
              "deactivate",
              "annotate",
              "copy",
              "delete",
              "insert",
              "protect",
              "rename",
              "unprotect",
              "edit",
              "top",
              "wildcard",
          ]
          if config.strip().startswith("<"):
              return "xml"
          elif config.strip().split(" ")[0] in set_action_matches:
              return "set"
          elif self._is_json_format(config):
              return "json"
          return fmt
  
      def _load_candidate(self, filename, config, overwrite):
          if filename is None:
              configuration = config
          else:
              with open(filename) as f:
                  configuration = f.read()
  
          if (
              not self.lock_disable
              and not self.session_config_lock
              and not self.config_private
          ):
              # if not locked during connection time, will try to lock
              self._lock()
  
          try:
              fmt = self._detect_config_format(configuration)
  
              if fmt == "xml":
                  configuration = etree.XML(configuration)
  
              if self.config_private:
                  try:
                      self.device.rpc.open_configuration(private=True, normalize=True)
                  except RpcError as err:
                      if str(err) == "uncommitted changes will be discarded on exit":
                          pass
  
              self.device.cu.load(
                  configuration,
                  format=fmt,
                  overwrite=overwrite,
                  ignore_warning=self.ignore_warning,
              )
          except ConfigLoadError as e:
              self.discard_config()
              if self.config_replace:
                  raise ReplaceConfigException(e.errs)
              else:
                  raise MergeConfigException(e.errs)
  
      def load_replace_candidate(self, filename=None, config=None):
          """Open the candidate config and merge."""
          self.config_replace = True
          self._load_candidate(filename, config, True)
  
      def load_merge_candidate(self, filename=None, config=None):
          """Open the candidate config and replace."""
          self.config_replace = False
          self._load_candidate(filename, config, False)
  
      def compare_config(self):
          """Compare candidate config with running."""
          diff = self.device.cu.diff(ignore_warning=self.ignore_warning)
  
          if diff is None:
              return ""
          else:
              return diff.strip()
  
      def commit_config(self, message="", revert_in=None):
          """Commit configuration."""
          commit_args = {}
          if revert_in is not None:
              if revert_in % 60 != 0:
                  if not self.lock_disable and not self.session_config_lock:
                      self._unlock()
                  raise CommitConfirmException(
                      "For Junos devices revert_in must be a multiple of 60 (60, 120, 180...)"
                  )
              else:
                  juniper_confirm_time = int(revert_in / 60)
                  commit_args["confirm"] = juniper_confirm_time
  
          if message:
              commit_args["comment"] = message
          self.device.cu.commit(ignore_warning=self.ignore_warning, **commit_args)
  
          if not self.lock_disable and not self.session_config_lock:
              self._unlock()
  
          if self.config_private:
              self.device.rpc.close_configuration()
  
      def has_pending_commit(self):
          """Boolean indicating if there is a commit-confirm in process."""
          pending_commit = self._get_pending_commits()
          if pending_commit:
              return True
          else:
              return False
  
      def _get_pending_commits(self):
          """
          Return a dictionary of commit sequences with pending commit confirms and
          corresponding time when confirm needs to happen by. This is converted to seconds
          since Juniper reports this in minutes.
  
          Example:
          {'re0-1616554286-559': 522}
  
          Will only report on a single commit (the most recent one).
  
          Will return an empty dictionary if there is no pending commit-confirms.
          """
          # show system commit revision detail
          # Command introduced in Junos OS Release 14.1
          try:
              pending_commit = self.device.rpc.get_commit_revision_information(
                  detail=True
              )
          except RpcError:
              msg = "Using commit-confirm with NAPALM requires Junos OS >= 14.1"
              raise CommitConfirmException(msg)
  
          commit_time_element = pending_commit.find("./date-time")
          commit_time = int(commit_time_element.attrib["seconds"])
  
          commit_revision_element = pending_commit.find("./revision")
          commit_revision = commit_revision_element.text
          commit_comment_element = pending_commit.find("./comment")
          if commit_comment_element is None:
              # No commit comment means no commit-confirm
              return {}
          else:
              commit_comment = commit_comment_element.text
  
          sys_uptime_info = self.device.rpc.get_system_uptime_information()
          current_time_element = sys_uptime_info.find(".//current-time/date-time")
          current_time = int(current_time_element.attrib["seconds"])
  
          # Msg from Jnpr: 'commit confirmed, rollback in 5mins'
          if "commit confirmed" in commit_comment and "rollback in" in commit_comment:
              match = re.search(r"rollback in (\d+)mins", commit_comment)
              if match:
                  confirm_time = match.group(1)
                  confirm_time_seconds = int(confirm_time) * 60
                  elapsed_time = current_time - commit_time
                  confirm_time_remaining = confirm_time_seconds - elapsed_time
                  if confirm_time_remaining <= 0:
                      confirm_time_remaining = 0
  
                  return {commit_revision: confirm_time_remaining}
  
          return {}
  
      def confirm_commit(self):
          """Send final commit to confirm an in-proces commit that requires confirmation."""
          self.device.cu.commit(ignore_warning=self.ignore_warning)
  
      def discard_config(self):
          """Discard changes (rollback 0)."""
          self.device.cu.rollback(rb_id=0, ignore_warning=self.ignore_warning)
          if not self.lock_disable and not self.session_config_lock:
              self._unlock()
          if self.config_private:
              self.device.rpc.close_configuration()
  
      def rollback(self):
          """Rollback to previous commit."""
          self.device.cu.rollback(rb_id=1)
          self.commit_config()
  
      def get_facts(self):
          """Return facts of the device."""
          output = self.device.facts
  
          uptime = self.device.uptime or -1
  
          interfaces = junos_views.junos_iface_table(self.device)
          interfaces.get()
          interface_list = interfaces.keys()
  
          return {
              "vendor": "Juniper",
              "model": str(output["model"]),
              "serial_number": str(output["serialnumber"]),
              "os_version": str(output["version"]),
              "hostname": str(output["hostname"]),
              "fqdn": str(output["fqdn"]),
              "uptime": float(uptime),
              "interface_list": interface_list,
          }
  
      def get_interfaces(self):
          """Return interfaces details."""
          result = {}
  
          interfaces = junos_views.junos_iface_table(self.device)
          interfaces.get()
          interfaces_logical = junos_views.junos_logical_iface_table(self.device)
          interfaces_logical.get()
  
          # convert all the tuples to our pre-defined dict structure
          def _convert_to_dict(interfaces):
              # calling .items() here wont work.
              # The dictionary values will end up being tuples instead of dictionaries
              interfaces = dict(interfaces)
              for iface, iface_data in interfaces.items():
                  result[iface] = {
                      "is_up": iface_data["is_up"],
                      # For physical interfaces <admin-status> will always be there, so just
                      # return the value interfaces[iface]['is_enabled']
                      # For logical interfaces if <iff-down> is present interface is disabled,
                      # otherwise interface is enabled
                      "is_enabled": (
                          True
                          if iface_data["is_enabled"] is None
                          else iface_data["is_enabled"]
                      ),
                      "description": (iface_data["description"] or ""),
                      "last_flapped": float((iface_data["last_flapped"] or -1)),
                      "mac_address": napalm.base.helpers.convert(
                          napalm.base.helpers.mac,
                          iface_data["mac_address"],
                          str(iface_data["mac_address"]),
                      ),
                      "speed": -1.0,
                      "mtu": 0,
                  }
                  # result[iface]['last_flapped'] = float(result[iface]['last_flapped'])
  
                  match_mtu = re.search(r"(\w+)", str(iface_data["mtu"]) or "")
                  mtu = napalm.base.helpers.convert(int, match_mtu.group(0), 0)
                  result[iface]["mtu"] = mtu
                  match = re.search(r"(\d+|[Aa]uto)(\w*)", iface_data["speed"] or "")
                  if match and match.group(1).lower() == "auto":
                      match = re.search(
                          r"(\d+)(\w*)", iface_data["negotiated_speed"] or ""
                      )
                  if match is None:
                      continue
                  speed_value = napalm.base.helpers.convert(float, match.group(1), -1.0)
  
                  if speed_value == -1.0:
                      continue
                  speed_unit = match.group(2)
                  if speed_unit.lower() == "gbps":
                      speed_value *= 1000.0
                  result[iface]["speed"] = speed_value
  
              return result
  
          result = _convert_to_dict(interfaces)
          result.update(_convert_to_dict(interfaces_logical))
          return result
  
      def get_interfaces_counters(self):
          """Return interfaces counters."""
          query = junos_views.junos_iface_counter_table(self.device)
          query.get()
          interface_counters = {}
          for interface, counters in query.items():
              _interface_counters = {}
              for k, v in counters:
                  if k == "logical_interfaces":
                      for _interface, _counters in v.items():
                          interface_counters[_interface] = {
                              k: v if v is not None else -1 for k, v in _counters
                          }
                  else:
                      _interface_counters[k] = v if v is not None else -1
  
              interface_counters[interface] = _interface_counters
          return interface_counters
  
      def get_environment(self):
          """Return environment details."""
          if self.device.facts.get("srx_cluster", False):
              environment = junos_views.junos_environment_table_srx_cluster(self.device)
              routing_engine = junos_views.junos_routing_engine_table_srx_cluster(
                  self.device
              )
              temperature_thresholds = (
                  junos_views.junos_temperature_thresholds_srx_cluster(self.device)
              )
          else:
              environment = junos_views.junos_environment_table(self.device)
              routing_engine = junos_views.junos_routing_engine_table(self.device)
              temperature_thresholds = junos_views.junos_temperature_thresholds(
                  self.device
              )
          power_supplies = junos_views.junos_pem_table(self.device)
          environment.get()
          routing_engine.get()
          temperature_thresholds.get()
          environment_data = {}
          current_class = None
  
          for sensor_object, object_data in environment.items():
              structured_object_data = {k: v for k, v in object_data}
  
              if structured_object_data["class"]:
                  # If current object has a 'class' defined, store it for use
                  # on subsequent unlabeled lines.
                  current_class = structured_object_data["class"]
              else:
                  # Juniper doesn't label the 2nd+ lines of a given class with a
                  # class name.  In that case, we use the most recent class seen.
                  structured_object_data["class"] = current_class
  
              if structured_object_data["class"] == "Power":
                  # Make sure naming is consistent
                  sensor_object = sensor_object.replace("PEM", "Power Supply")
  
                  # Create a dict for the 'power' key
                  try:
                      environment_data["power"][sensor_object] = {}
                  except KeyError:
                      environment_data["power"] = {}
                      environment_data["power"][sensor_object] = {}
  
                  environment_data["power"][sensor_object]["capacity"] = -1.0
                  environment_data["power"][sensor_object]["output"] = -1.0
  
              if structured_object_data["class"] == "Fans":
                  # Create a dict for the 'fans' key
                  try:
                      environment_data["fans"][sensor_object] = {}
                  except KeyError:
                      environment_data["fans"] = {}
                      environment_data["fans"][sensor_object] = {}
  
              status = structured_object_data["status"]
              env_class = structured_object_data["class"]
              if status == "OK" and env_class == "Power":
                  # If status is Failed, Absent or Testing, set status to False.
                  environment_data["power"][sensor_object]["status"] = True
  
              elif status != "OK" and env_class == "Power":
                  environment_data["power"][sensor_object]["status"] = False
  
              elif status == "OK" and env_class == "Fans":
                  # If status is Failed, Absent or Testing, set status to False.
                  environment_data["fans"][sensor_object]["status"] = True
  
              elif status != "OK" and env_class == "Fans":
                  environment_data["fans"][sensor_object]["status"] = False
  
              for temperature_object, temperature_data in temperature_thresholds.items():
                  structured_temperature_data = {k: v for k, v in temperature_data}
                  if structured_object_data["class"] == "Temp":
                      # Create a dict for the 'temperature' key
                      try:
                          environment_data["temperature"][sensor_object] = {}
                      except KeyError:
                          environment_data["temperature"] = {}
                          environment_data["temperature"][sensor_object] = {}
                      # Check we have a temperature field in this class (See #66)
                      if structured_object_data["temperature"]:
                          environment_data["temperature"][sensor_object][
                              "temperature"
                          ] = float(structured_object_data["temperature"])
                      # Set a default value (False) to the key is_critical and is_alert
                      environment_data["temperature"][sensor_object]["is_alert"] = False
                      environment_data["temperature"][sensor_object][
                          "is_critical"
                      ] = False
                      # Check if the working temperature is equal to or higher than alerting threshold
                      temp = structured_object_data["temperature"]
                      if temp is not None:
                          if structured_temperature_data["red-alarm"] <= temp:
                              environment_data["temperature"][sensor_object][
                                  "is_critical"
                              ] = True
                              environment_data["temperature"][sensor_object][
                                  "is_alert"
                              ] = True
                          elif structured_temperature_data["yellow-alarm"] <= temp:
                              environment_data["temperature"][sensor_object][
                                  "is_alert"
                              ] = True
                      else:
                          environment_data["temperature"][sensor_object][
                              "temperature"
                          ] = 0.0
  
          # Try to correct Power Supply information
          pem_table = dict()
          try:
              power_supplies.get()
          except RpcError:
              # Not all platforms have support for this
              pass
          else:
              # Format PEM information and correct capacity and output values
              if "power" not in environment_data.keys():
                  # Power supplies were not included from the environment table above
                  # Need to initialize data
                  environment_data["power"] = {}
                  for pem in power_supplies.items():
                      pem_name = pem[0].replace("PEM", "Power Supply")
                      environment_data["power"][pem_name] = {}
                      environment_data["power"][pem_name]["output"] = -1.0
                      environment_data["power"][pem_name]["capacity"] = -1.0
                      environment_data["power"][pem_name]["status"] = False
              for pem in power_supplies.items():
                  pem_name = pem[0].replace("PEM", "Power Supply")
                  pem_table[pem_name] = dict(pem[1])
                  if pem_table[pem_name]["capacity"] is not None:
                      environment_data["power"][pem_name]["capacity"] = pem_table[
                          pem_name
                      ]["capacity"]
                  if pem_table[pem_name]["output"] is not None:
                      environment_data["power"][pem_name]["output"] = pem_table[pem_name][
                          "output"
                      ]
                  environment_data["power"][pem_name]["status"] = pem_table[pem_name][
                      "status"
                  ]
  
          for routing_engine_object, routing_engine_data in routing_engine.items():
              structured_routing_engine_data = {k: v for k, v in routing_engine_data}
              # Create dicts for 'cpu' and 'memory'.
              try:
                  environment_data["cpu"][routing_engine_object] = {}
                  environment_data["memory"] = {}
              except KeyError:
                  environment_data["cpu"] = {}
                  environment_data["cpu"][routing_engine_object] = {}
                  environment_data["memory"] = {}
              # Calculate the CPU usage by using the CPU idle value.
              environment_data["cpu"][routing_engine_object]["%usage"] = (
                  100.0 - structured_routing_engine_data["cpu-idle"]
              )
              try:
                  environment_data["memory"]["available_ram"] = int(
                      structured_routing_engine_data["memory-dram-size"]
                  )
              except ValueError:
                  environment_data["memory"]["available_ram"] = int(
                      "".join(
                          i
                          for i in structured_routing_engine_data["memory-dram-size"]
                          if i.isdigit()
                      )
                  )
              if not structured_routing_engine_data["memory-system-total-used"]:
                  # Junos gives us RAM in %, so calculation has to be made.
                  # Sadly, bacause of this, results are not 100% accurate to the truth.
                  environment_data["memory"]["used_ram"] = int(
                      round(
                          environment_data["memory"]["available_ram"]
                          / 100.0
                          * structured_routing_engine_data["memory-buffer-utilization"]
                      )
                  )
              else:
                  environment_data["memory"]["used_ram"] = structured_routing_engine_data[
                      "memory-system-total-used"
                  ]
  
          return environment_data
  
      @staticmethod
      def _get_address_family(table, instance):
          """
          Function to derive address family from a junos table name.
  
          :params table: The name of the routing table
          :returns: address family
          """
          address_family_mapping = {"inet": "ipv4", "inet6": "ipv6", "inetflow": "flow"}
          if instance == "master":
              family = table.rsplit(".", 1)[-2]
          else:
              family = table.split(".")[-2]
          try:
              address_family = address_family_mapping[family]
          except KeyError:
              address_family = None
          return address_family
  
      def _parse_route_stats(self, neighbor, instance):
          data = {
              "ipv4": {
                  "received_prefixes": -1,
                  "accepted_prefixes": -1,
                  "sent_prefixes": -1,
              },
              "ipv6": {
                  "received_prefixes": -1,
                  "accepted_prefixes": -1,
                  "sent_prefixes": -1,
              },
          }
          if not neighbor["is_up"]:
              return data
          elif isinstance(neighbor["tables"], list):
              if isinstance(neighbor["sent_prefixes"], int):
                  # We expect sent_prefixes to be a list, but sometimes it
                  # is of type int. Therefore convert attribute to list
                  neighbor["sent_prefixes"] = [neighbor["sent_prefixes"]]
              for idx, table in enumerate(neighbor["tables"]):
                  family = self._get_address_family(table, instance)
                  if family is None:
                      # Need to remove counter from sent_prefixes list anyway
                      if "in sync" in neighbor["send-state"][idx]:
                          neighbor["sent_prefixes"].pop(0)
                      continue
                  data[family] = {}
                  data[family]["received_prefixes"] = neighbor["received_prefixes"][idx]
                  data[family]["accepted_prefixes"] = neighbor["accepted_prefixes"][idx]
                  if "in sync" in neighbor["send-state"][idx]:
                      data[family]["sent_prefixes"] = neighbor["sent_prefixes"].pop(0)
                  else:
                      data[family]["sent_prefixes"] = 0
          else:
              family = self._get_address_family(neighbor["tables"], instance)
              if family is not None:
                  data[family] = {}
                  data[family]["received_prefixes"] = neighbor["received_prefixes"]
                  data[family]["accepted_prefixes"] = neighbor["accepted_prefixes"]
                  data[family]["sent_prefixes"] = neighbor["sent_prefixes"]
          return data
  
      @staticmethod
      def _parse_value(value):
          if isinstance(value, str):
              return str(value)
          elif value is None:
              return ""
          else:
              return value
  
      def get_bgp_neighbors(self):
          """Return BGP neighbors details."""
          bgp_neighbor_data = {}
          default_neighbor_details = {
              "local_as": 0,
              "remote_as": 0,
              "remote_id": "",
              "is_up": False,
              "is_enabled": True,
              "description": "",
              "uptime": 0,
              "address_family": {},
          }
          keys = default_neighbor_details.keys()
  
          uptime_table = junos_views.junos_bgp_uptime_table(self.device)
          bgp_neighbors_table = junos_views.junos_bgp_table(self.device)
  
          uptime_table_lookup = {}
  
          def _get_uptime_table(instance):
              if instance not in uptime_table_lookup:
                  uptime_table_lookup[instance] = uptime_table.get(
                      instance=instance
                  ).items()
              return uptime_table_lookup[instance]
  
          def _get_bgp_neighbors_core(
              neighbor_data, instance=None, uptime_table_items=None
          ):
              """
              Make sure to execute a simple request whenever using
              junos > 13. This is a helper used to avoid code redundancy
              and reuse the function also when iterating through the list
              BGP neighbors under a specific routing instance,
              also when the device is capable to return the routing
              instance name at the BGP neighbor level.
              """
              for bgp_neighbor in neighbor_data:
                  peer_ip = napalm.base.helpers.ip(bgp_neighbor[0].split("+")[0])
                  neighbor_details = deepcopy(default_neighbor_details)
                  neighbor_details.update(
                      {
                          elem[0]: elem[1]
                          for elem in bgp_neighbor[1]
                          if elem[1] is not None
                      }
                  )
                  if not instance:
                      # not instance, means newer Junos version,
  