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.
  
  # import stdlib
  import re
  import copy
  import ipaddress
  from collections import defaultdict
  import logging
  
  # import third party lib
  from lxml import etree as ETREE
  
  from napalm.pyIOSXR import IOSXR
  from napalm.pyIOSXR.exceptions import ConnectError
  from napalm.pyIOSXR.exceptions import TimeoutError
  from napalm.pyIOSXR.exceptions import InvalidInputError
  from napalm.pyIOSXR.exceptions import XMLCLIError
  
  # import NAPALM base
  import napalm.base.helpers
  from napalm.base.netmiko_helpers import netmiko_args
  from napalm.iosxr import constants as C
  from napalm.base.base import NetworkDriver
  from napalm.base.exceptions import ConnectionException
  from napalm.base.exceptions import MergeConfigException
  from napalm.base.exceptions import ReplaceConfigException
  from napalm.base.exceptions import CommandTimeoutException
  
  logger = logging.getLogger(__name__)
  IP_RIBRoute = "IP_RIBRoute"
  
  
  class IOSXRDriver(NetworkDriver):
      """IOS-XR driver class: inherits NetworkDriver from napalm.base."""
  
      def __init__(self, hostname, username, password, timeout=60, optional_args=None):
          self.hostname = hostname
          self.username = username
          self.password = password
          self.timeout = timeout
          self.pending_changes = False
          self.replace = False
          if optional_args is None:
              optional_args = {}
          self.lock_on_connect = optional_args.get("config_lock", False)
  
          self.netmiko_optional_args = netmiko_args(optional_args)
          try:
              self.port = self.netmiko_optional_args.pop("port")
          except KeyError:
              self.port = 22
  
          self.platform = "iosxr"
          self.device = IOSXR(
              hostname,
              username,
              password,
              timeout=timeout,
              port=self.port,
              lock=self.lock_on_connect,
              **self.netmiko_optional_args,
          )
  
      def open(self):
          try:
              self.device.open()
          except ConnectError as conn_err:
              logger.error(conn_err.args[0])
              raise ConnectionException(conn_err.args[0])
  
      def close(self):
          logger.debug("Closed connection with device %s" % (self.hostname))
          self.device.close()
  
      def is_alive(self):
          """Returns a flag with the state of the connection."""
          if self.device is None:
              return {"is_alive": False}
          # Simply returns the flag from pyIOSXR
          return {"is_alive": self.device.is_alive()}
  
      def load_replace_candidate(self, filename=None, config=None):
          self.pending_changes = True
          self.replace = True
          if not self.lock_on_connect:
              self.device.lock()
  
          try:
              self.device.load_candidate_config(filename=filename, config=config)
          except InvalidInputError as e:
              self.pending_changes = False
              self.replace = False
              logger.error(e.args[0])
              raise ReplaceConfigException(e.args[0])
  
      def load_merge_candidate(self, filename=None, config=None):
          self.pending_changes = True
          self.replace = False
          if not self.lock_on_connect:
              self.device.lock()
  
          try:
              self.device.load_candidate_config(filename=filename, config=config)
          except InvalidInputError as e:
              self.pending_changes = False
              self.replace = False
              logger.error(e.args[0])
              raise MergeConfigException(e.args[0])
  
      def compare_config(self):
          if not self.pending_changes:
              return ""
          elif self.replace:
              return self.device.compare_replace_config().strip()
          else:
              return self.device.compare_config().strip()
  
      def commit_config(self, message="", revert_in=None):
          if revert_in is not None:
              raise NotImplementedError(
                  "Commit confirm has not been implemented on this platform."
              )
          commit_args = {"comment": message} if message else {}
          if self.replace:
              self.device.commit_replace_config(**commit_args)
          else:
              self.device.commit_config(**commit_args)
          self.pending_changes = False
          if not self.lock_on_connect:
              self.device.unlock()
  
      def discard_config(self):
          self.device.discard_config()
          self.pending_changes = False
          if not self.lock_on_connect:
              self.device.unlock()
  
      def rollback(self):
          self.device.rollback()
  
      def get_facts(self):
          facts = {
              "vendor": "Cisco",
              "os_version": "",
              "hostname": "",
              "uptime": -1.0,
              "serial_number": "",
              "fqdn": "",
              "model": "",
              "interface_list": [],
          }
  
          facts_rpc_request = """
  <Get>
    <Operational>
      <SystemTime/>
      <PlatformInventory>
        <RackTable>
          <Rack>
            <Naming>
              <Name>0</Name>
            </Naming>
            <Attributes>
              <BasicInfo/>
            </Attributes>
          </Rack>
        </RackTable>
      </PlatformInventory>
    </Operational>
  </Get>
          """
  
          # IOS-XR 7.3.3 and possibly other 7.X versions have this located in
          # different location in the XML tree
          facts_rpc_request_alt = """
  <Get>
    <Operational>
      <SystemTime/>
      <Inventory>
        <Entities>
          <Entity>
            <Naming>
              <Name>Rack 0</Name>
            </Naming>
            <Attributes>
              <InvBasicBag></InvBasicBag>
            </Attributes>
          </Entity>
        </Entities>
      </Inventory>
    </Operational>
  </Get>
  """
  
          system_time_xpath = ".//SystemTime/Uptime"
          try:
              facts_rpc_reply = ETREE.fromstring(
                  self.device.make_rpc_call(facts_rpc_request)
              )
              platform_attr_xpath = ".//RackTable/Rack/Attributes/BasicInfo"
          except XMLCLIError:
              facts_rpc_reply = ETREE.fromstring(
                  self.device.make_rpc_call(facts_rpc_request_alt)
              )
              platform_attr_xpath = ".//Entities/Entity/Attributes/InvBasicBag"
  
          system_time_tree = facts_rpc_reply.xpath(system_time_xpath)[0]
          try:
              platform_attr_tree = facts_rpc_reply.xpath(platform_attr_xpath)[0]
          except IndexError:
              platform_attr_tree = facts_rpc_reply.xpath(platform_attr_xpath)
  
          hostname = napalm.base.helpers.convert(
              str, napalm.base.helpers.find_txt(system_time_tree, "Hostname")
          )
          uptime = napalm.base.helpers.convert(
              float, napalm.base.helpers.find_txt(system_time_tree, "Uptime"), -1.0
          )
          serial = napalm.base.helpers.convert(
              str, napalm.base.helpers.find_txt(platform_attr_tree, "SerialNumber")
          )
          os_version = napalm.base.helpers.convert(
              str, napalm.base.helpers.find_txt(platform_attr_tree, "SoftwareRevision")
          )
          model = napalm.base.helpers.convert(
              str, napalm.base.helpers.find_txt(platform_attr_tree, "ModelName")
          )
          interface_list = sorted(list(self.get_interfaces().keys()))
  
          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):
          interfaces = {}
  
          INTERFACE_DEFAULTS = {
              "is_enabled": False,
              "is_up": False,
              "mac_address": "",
              "description": "",
              "speed": -1.0,
              "last_flapped": -1.0,
          }
  
          interfaces_rpc_request = "<Get><Operational><Interfaces/></Operational></Get>"
  
          interfaces_rpc_reply = ETREE.fromstring(
              self.device.make_rpc_call(interfaces_rpc_request)
          )
  
          for interface_tree in interfaces_rpc_reply.xpath(
              ".//Interfaces/InterfaceTable/Interface"
          ):
              interface_name = napalm.base.helpers.find_txt(
                  interface_tree, "Naming/InterfaceName"
              )
              if not interface_name:
                  continue
              is_up = (
                  napalm.base.helpers.find_txt(interface_tree, "LineState")
                  == "IM_STATE_UP"
              )
              enabled = (
                  napalm.base.helpers.find_txt(interface_tree, "State")
                  != "IM_STATE_ADMINDOWN"
              )
              raw_mac = napalm.base.helpers.find_txt(interface_tree, "MACAddress/Address")
              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, napalm.base.helpers.find_txt(interface_tree, "Bandwidth"), 0
                  )
                  * 1e-3,
              )
  
              mtu = int(napalm.base.helpers.find_txt(interface_tree, "MTU"))
              description = napalm.base.helpers.find_txt(interface_tree, "Description")
              last_flapped = napalm.base.helpers.convert(
                  float,
                  napalm.base.helpers.find_txt(
                      interface_tree, "LastStateTransitionTime", -1
                  ),
                  -1,
              )
              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,
                      "last_flapped": (
                          last_flapped / 1e9 if last_flapped != -1.0 else -1.0
                      ),
                  }
              )
  
          return interfaces
  
      def get_interfaces_counters(self):
          rpc_command = "<Get><Operational><Interfaces><InterfaceTable></InterfaceTable>\
          </Interfaces></Operational></Get>"
          result_tree = ETREE.fromstring(self.device.make_rpc_call(rpc_command))
  
          interface_counters = {}
  
          for interface in result_tree.xpath(".//Interface"):
              interface_name = napalm.base.helpers.find_txt(interface, "InterfaceHandle")
              interface_stats = {}
              if not interface.xpath("InterfaceStatistics"):
                  continue
              else:
                  interface_stats = {}
                  interface_stats["tx_multicast_packets"] = napalm.base.helpers.convert(
                      int,
                      napalm.base.helpers.find_txt(
                          interface,
                          "InterfaceStatistics/FullInterfaceStats/MulticastPacketsSent",
                      ),
                  )
                  interface_stats["tx_discards"] = napalm.base.helpers.convert(
                      int,
                      napalm.base.helpers.find_txt(
                          interface, "InterfaceStatistics/FullInterfaceStats/OutputDrops"
                      ),
                  )
                  interface_stats["tx_octets"] = napalm.base.helpers.convert(
                      int,
                      napalm.base.helpers.find_txt(
                          interface, "InterfaceStatistics/FullInterfaceStats/BytesSent"
                      ),
                  )
                  interface_stats["tx_errors"] = napalm.base.helpers.convert(
                      int,
                      napalm.base.helpers.find_txt(
                          interface, "InterfaceStatistics/FullInterfaceStats/OutputErrors"
                      ),
                  )
                  interface_stats["rx_octets"] = napalm.base.helpers.convert(
                      int,
                      napalm.base.helpers.find_txt(
                          interface,
                          "InterfaceStatistics/FullInterfaceStats/BytesReceived",
                      ),
                  )
                  interface_stats["tx_unicast_packets"] = napalm.base.helpers.convert(
                      int,
                      napalm.base.helpers.find_txt(
                          interface, "InterfaceStatistics/FullInterfaceStats/PacketsSent"
                      ),
                  )
                  interface_stats["rx_errors"] = napalm.base.helpers.convert(
                      int,
                      napalm.base.helpers.find_txt(
                          interface, "InterfaceStatistics/FullInterfaceStats/InputErrors"
                      ),
                  )
                  interface_stats["tx_broadcast_packets"] = napalm.base.helpers.convert(
                      int,
                      napalm.base.helpers.find_txt(
                          interface,
                          "InterfaceStatistics/FullInterfaceStats/BroadcastPacketsSent",
                      ),
                  )
                  interface_stats["rx_multicast_packets"] = napalm.base.helpers.convert(
                      int,
                      napalm.base.helpers.find_txt(
                          interface,
                          "InterfaceStatistics/FullInterfaceStats/MulticastPacketsReceived",
                      ),
                  )
                  interface_stats["rx_broadcast_packets"] = napalm.base.helpers.convert(
                      int,
                      napalm.base.helpers.find_txt(
                          interface,
                          "InterfaceStatistics/FullInterfaceStats/BroadcastPacketsReceived",
                      ),
                  )
                  interface_stats["rx_discards"] = napalm.base.helpers.convert(
                      int,
                      napalm.base.helpers.find_txt(
                          interface, "InterfaceStatistics/FullInterfaceStats/InputDrops"
                      ),
                  )
                  interface_stats["rx_unicast_packets"] = napalm.base.helpers.convert(
                      int,
                      napalm.base.helpers.find_txt(
                          interface,
                          "InterfaceStatistics/FullInterfaceStats/PacketsReceived",
                      ),
                  )
              interface_counters[interface_name] = interface_stats
  
          return interface_counters
  
      def get_bgp_neighbors(self):
          def generate_vrf_query(vrf_name):
              """
              Helper to provide XML-query for the VRF-type we're interested in.
              """
              if vrf_name == "global":
                  rpc_command = "<Get><Operational><BGP><InstanceTable><Instance><Naming>\
                  <InstanceName>default</InstanceName></Naming><InstanceActive><DefaultVRF>\
                  <GlobalProcessInfo></GlobalProcessInfo><NeighborTable></NeighborTable></DefaultVRF>\
                  </InstanceActive></Instance></InstanceTable></BGP></Operational></Get>"
  
              else:
                  rpc_command = "<Get><Operational><BGP><InstanceTable><Instance><Naming>\
                  <InstanceName>default</InstanceName></Naming><InstanceActive><VRFTable><VRF>\
                  <Naming>{vrf_name}</Naming><GlobalProcessInfo></GlobalProcessInfo><NeighborTable>\
                  </NeighborTable></VRF></VRFTable></InstanceActive></Instance></InstanceTable>\
                  </BGP></Operational></Get>".format(
                      vrf_name=vrf_name
                  )
              return rpc_command
  
          """
          Initial run to figure out what VRF's are available
          Decided to get this one from Configured-section
          because bulk-getting all instance-data to do the same could get ridiculously heavy
          Assuming we're always interested in the DefaultVRF
          """
  
          active_vrfs = ["global"]
  
          rpc_command = "<Get><Operational><BGP><ConfigInstanceTable><ConfigInstance><Naming>\
          <InstanceName>default</InstanceName></Naming><ConfigInstanceVRFTable>\
          </ConfigInstanceVRFTable></ConfigInstance></ConfigInstanceTable></BGP></Operational></Get>"
  
          result_tree = ETREE.fromstring(self.device.make_rpc_call(rpc_command))
  
          for node in result_tree.xpath(".//ConfigVRF"):
              active_vrfs.append(napalm.base.helpers.find_txt(node, "Naming/VRFName"))
  
          result = {}
  
          for vrf in active_vrfs:
              rpc_command = generate_vrf_query(vrf)
              result_tree = ETREE.fromstring(self.device.make_rpc_call(rpc_command))
  
              this_vrf = {}
              this_vrf["peers"] = {}
  
              if vrf == "global":
                  this_vrf["router_id"] = napalm.base.helpers.convert(
                      str,
                      napalm.base.helpers.find_txt(
                          result_tree,
                          "Get/Operational/BGP/InstanceTable/Instance/InstanceActive/DefaultVRF"
                          "/GlobalProcessInfo/VRF/RouterID",
                      ),
                  )
              else:
                  this_vrf["router_id"] = napalm.base.helpers.convert(
                      str,
                      napalm.base.helpers.find_txt(
                          result_tree,
                          "Get/Operational/BGP/InstanceTable/Instance/InstanceActive/VRFTable/VRF"
                          "/GlobalProcessInfo/VRF/RouterID",
                      ),
                  )
  
              neighbors = {}
  
              for neighbor in result_tree.xpath(".//Neighbor"):
                  this_neighbor = {}
                  this_neighbor["local_as"] = napalm.base.helpers.convert(
                      int, napalm.base.helpers.find_txt(neighbor, "LocalAS")
                  )
                  this_neighbor["remote_as"] = napalm.base.helpers.convert(
                      int, napalm.base.helpers.find_txt(neighbor, "RemoteAS")
                  )
                  this_neighbor["remote_id"] = napalm.base.helpers.convert(
                      str, napalm.base.helpers.find_txt(neighbor, "RouterID")
                  )
  
                  if (
                      napalm.base.helpers.find_txt(neighbor, "ConnectionAdminStatus")
                      == "1"
                  ):
                      this_neighbor["is_enabled"] = True
  
                  try:
                      this_neighbor["description"] = napalm.base.helpers.convert(
                          str, napalm.base.helpers.find_txt(neighbor, "Description")
                      )
                  except AttributeError:
                      logger.debug(
                          "No attribute 'description' for neighbor %s"
                          % (this_neighbor["remote_as"])
                      )
                      this_neighbor["description"] = ""
  
                  this_neighbor["is_enabled"] = (
                      napalm.base.helpers.find_txt(neighbor, "ConnectionAdminStatus")
                      == "1"
                  )
  
                  if (
                      str(napalm.base.helpers.find_txt(neighbor, "ConnectionAdminStatus"))
                      == "1"
                  ):
                      this_neighbor["is_enabled"] = True
                  else:
                      this_neighbor["is_enabled"] = False
  
                  if (
                      str(napalm.base.helpers.find_txt(neighbor, "ConnectionState"))
                      == "BGP_ST_ESTAB"
                  ):
                      this_neighbor["is_up"] = True
                      this_neighbor["uptime"] = napalm.base.helpers.convert(
                          int,
                          napalm.base.helpers.find_txt(
                              neighbor, "ConnectionEstablishedTime"
                          ),
                      )
                  else:
                      this_neighbor["is_up"] = False
                      this_neighbor["uptime"] = -1
  
                  this_neighbor["address_family"] = {}
  
                  if (
                      napalm.base.helpers.find_txt(
                          neighbor, "ConnectionRemoteAddress/AFI"
                      )
                      == "IPv4"
                  ):
                      this_afi = "ipv4"
                  elif (
                      napalm.base.helpers.find_txt(
                          neighbor, "ConnectionRemoteAddress/AFI"
                      )
                      == "IPv6"
                  ):
                      this_afi = "ipv6"
                  else:
                      this_afi = napalm.base.helpers.find_txt(
                          neighbor, "ConnectionRemoteAddress/AFI"
                      )
  
                  this_neighbor["address_family"][this_afi] = {}
  
                  try:
                      this_neighbor["address_family"][this_afi][
                          "received_prefixes"
                      ] = napalm.base.helpers.convert(
                          int,
                          napalm.base.helpers.find_txt(
                              neighbor, "AFData/Entry/PrefixesAccepted"
                          ),
                          0,
                      ) + napalm.base.helpers.convert(
                          int,
                          napalm.base.helpers.find_txt(
                              neighbor, "AFData/Entry/PrefixesDenied"
                          ),
                          0,
                      )
                      this_neighbor["address_family"][this_afi]["accepted_prefixes"] = (
                          napalm.base.helpers.convert(
                              int,
                              napalm.base.helpers.find_txt(
                                  neighbor, "AFData/Entry/PrefixesAccepted"
                              ),
                              0,
                          )
                      )
                      this_neighbor["address_family"][this_afi]["sent_prefixes"] = (
                          napalm.base.helpers.convert(
                              int,
                              napalm.base.helpers.find_txt(
                                  neighbor, "AFData/Entry/PrefixesAdvertised"
                              ),
                              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(
                      napalm.base.helpers.find_txt(
                          neighbor, "Naming/NeighborAddress/IPV4Address"
                      )
                      or napalm.base.helpers.find_txt(
                          neighbor, "Naming/NeighborAddress/IPV6Address"
                      )
                  )
  
                  neighbors[neighbor_ip] = this_neighbor
  
              this_vrf["peers"] = neighbors
              result[vrf] = this_vrf
  
          return result
  
      def get_environment(self):
          def get_module_xml_query(module, selection):
              return "<Get><AdminOperational><EnvironmentalMonitoring><RackTable><Rack><Naming>\
              <rack>0</rack></Naming><SlotTable><Slot><Naming><slot>{slot}</slot></Naming>{name}\
              </Slot></SlotTable></Rack></RackTable></EnvironmentalMonitoring></AdminOperational>\
              </Get>".format(
                  slot=module, name=selection
              )
  
          environment_status = {}
          environment_status["fans"] = {}
          environment_status["temperature"] = {}
          environment_status["power"] = {}
          environment_status["cpu"] = {}
          environment_status["memory"] = 0.0
  
          # finding slots with equipment we're interested in
          rpc_command = "<Get><AdminOperational><PlatformInventory><RackTable><Rack><Naming>\
          <Name>0</Name></Naming><SlotTable></SlotTable></Rack></RackTable></PlatformInventory>\
          </AdminOperational></Get>"
  
          result_tree = ETREE.fromstring(self.device.make_rpc_call(rpc_command))
  
          active_modules = defaultdict(list)
  
          for slot in result_tree.xpath(".//Slot"):
              for card in slot.xpath(".//CardTable"):
                  # find enabled slots, figoure out type and save for later
                  if (
                      napalm.base.helpers.find_txt(
                          card, "Card/Attributes/FRUInfo/ModuleAdministrativeState"
                      )
                      == "ADMIN_UP"
                  ):
                      slot_name = napalm.base.helpers.find_txt(slot, "Naming/Name")
                      module_type = re.sub(r"\d+", "", slot_name)
                      if len(module_type) > 0:
                          active_modules[module_type].append(slot_name)
                      else:
                          active_modules["LC"].append(slot_name)
  
          #
          # PSU's
          #
  
          for psu in active_modules["PM"]:
              if psu in ["PM6", "PM7"]:  # Cisco bug, chassis difference V01<->V02
                  continue
  
              rpc_command = get_module_xml_query(psu, "")
              result_tree = ETREE.fromstring(self.device.make_rpc_call(rpc_command))
  
              psu_status = {}
              psu_status["status"] = False
              psu_status["capacity"] = 0.0
              psu_status["output"] = 0.0
  
              for sensor in result_tree.xpath(".//SensorName"):
                  if napalm.base.helpers.find_txt(sensor, "Naming/Name") == "host__VOLT":
                      this_psu_voltage = napalm.base.helpers.convert(
                          float, napalm.base.helpers.find_txt(sensor, "ValueBrief")
                      )
                  elif (
                      napalm.base.helpers.find_txt(sensor, "Naming/Name") == "host__CURR"
                  ):
                      this_psu_current = napalm.base.helpers.convert(
                          float, napalm.base.helpers.find_txt(sensor, "ValueBrief")
                      )
                  elif napalm.base.helpers.find_txt(sensor, "Naming/Name") == "host__PM":
                      this_psu_capacity = napalm.base.helpers.convert(
                          float, napalm.base.helpers.find_txt(sensor, "ValueBrief")
                      )
  
              if this_psu_capacity > 0:
                  psu_status["capacity"] = this_psu_capacity
                  psu_status["status"] = True
  
              if this_psu_current and this_psu_voltage:
                  psu_status["output"] = (
                      this_psu_voltage * this_psu_current
                  ) / 1_000_000.0
  
              environment_status["power"][psu] = psu_status
  
          #
          # Memory
          #
  
          facts = self.get_facts()
          router_model = facts.get("model")
          xr_version = facts.get("os_version")
          major_version = (
              int(xr_version.split(".")[0])
              if xr_version
              and isinstance(xr_version, str)
              and xr_version.split(".")[0].isnumeric()
              else 0
          )
          is_xrv = router_model.lower().startswith("xrv")
          environment_status["memory"] = {"available_ram": 0.0, "used_ram": 0.0}
  
          if not is_xrv:
              if major_version >= 7:
                  rpc_command = "<Get><Operational><MemorySummary>\
                  </MemorySummary></Operational></Get>"
              else:
                  rpc_command = "<Get><AdminOperational><MemorySummary>\
                  </MemorySummary></AdminOperational></Get>"
              result_tree = ETREE.fromstring(self.device.make_rpc_call(rpc_command))
  
              for node in result_tree.xpath(".//Node"):
                  if (
                      napalm.base.helpers.find_txt(node, "Naming/NodeName/Slot")
                      == active_modules["RSP"][0]
                  ):
                      available_ram = napalm.base.helpers.convert(
                          int,
                          napalm.base.helpers.find_txt(node, "Summary/SystemRAMMemory"),
                      )
                      free_ram = napalm.base.helpers.convert(
                          int,
                          napalm.base.helpers.find_txt(
                              node, "Summary/FreeApplicationMemory"
                          ),
                      )
                      break  # we're only looking at one of the RSP's
  
              if available_ram and free_ram:
                  used_ram = available_ram - free_ram
                  memory = {}
                  memory["available_ram"] = available_ram
                  memory["used_ram"] = used_ram
                  environment_status["memory"] = memory
  
              #
              # Fans
              #
  
              for fan in active_modules["FT"]:
                  rpc_command = get_module_xml_query(fan, "")
                  result_tree = ETREE.fromstring(self.device.make_rpc_call(rpc_command))
                  for module in result_tree.xpath(".//Module"):
                      for sensortype in module.xpath(".//SensorType"):
                          for sensorname in sensortype.xpath(".//SensorNameTable"):
                              if (
                                  napalm.base.helpers.find_txt(
                                      sensorname, "SensorName/Naming/Name"
                                  )
                                  == "host__FanSpeed_0"
                              ):
                                  environment_status["fans"][fan] = {
                                      "status": napalm.base.helpers.convert(
                                          int,
                                          napalm.base.helpers.find_txt(
                                              sensorname,
                                              "SensorName/ValueDetailed/Status",
                                          ),
                                      )
                                      == 1
                                  }
  
          #
          # CPU
          #
          cpu = {}
  
          rpc_command = "<Get><Operational><SystemMonitoring></SystemMonitoring></Operational></Get>"
          result_tree = ETREE.fromstring(self.device.make_rpc_call(rpc_command))
  
          for module in result_tree.xpath(".//CPUUtilization"):
              this_cpu = {}
              this_cpu["%usage"] = napalm.base.helpers.convert(
                  float, napalm.base.helpers.find_txt(module, "TotalCPUFiveMinute")
              )
  
              rack = napalm.base.helpers.find_txt(module, "Naming/NodeName/Rack")
              slot = napalm.base.helpers.find_txt(module, "Naming/NodeName/Slot")
              instance = napalm.base.helpers.find_txt(module, "Naming/NodeName/Instance")
              position = "%s/%s/%s" % (rack, slot, instance)
  
              cpu[position] = this_cpu
  
          environment_status["cpu"] = cpu
  
          #
          # Temperature
          #
  
          slot_list = set()
          for category, slot in active_modules.items():
              slot_list |= set(slot)
  
          if not is_xrv:
              for slot in slot_list:
                  rpc_command = get_module_xml_query(slot, "")
                  result_tree = ETREE.fromstring(self.device.make_rpc_call(rpc_command))
                  for sensor in result_tree.xpath(".//SensorName"):
                      if (
                          not napalm.base.helpers.find_txt(sensor, "Naming/Name")
                          == "host__Inlet0"
                      ):
                          continue
                      this_reading = {}
                      this_reading["temperature"] = napalm.base.helpers.convert(
                          float, napalm.base.helpers.find_txt(sensor, "ValueBrief")
                      )
                      threshold_value = [
                          napalm.base.helpers.convert(float, x.text)
                          for x in sensor.xpath("ThresholdTable/Threshold/ValueBrief")
                      ]
                      this_reading["is_alert"] = (
                          threshold_value[2]
                          <= this_reading["temperature"]
                          <= threshold_value[3]
                      )
                      this_reading["is_critical"] = (
                          threshold_value[4]
                          <= this_reading["temperature"]
                          <= threshold_value[5]
                      )
  