/**
   * Verify the firmware version for the NetworkSystem
   *
   * @param uri - Device URI
   * @throws ControllerException thrown if firmware version is not supported
   */
  public void verifyVersion(URI uri) throws ControllerException {
    // Retrieve the storage device info from the database.
    NetworkSystem networkDev = getDeviceObject(uri);
    NetworkSystemDevice networkDevice = getDevice();
    if (networkDevice == null) {
      throw NetworkDeviceControllerException.exceptions.verifyVersionFailedNull(uri.toString());
    }
    String version = null;
    try {
      version = networkDevice.getVersion(networkDev);
      networkDev.setVersion(version);
      String minimumSupportedVersion =
          VersionChecker.getMinimumSupportedVersion(Type.valueOf(networkDev.getSystemType()));
      _log.info(
          "Verifying version details : Minimum Supported Version {} - Discovered Firmware Version {}",
          minimumSupportedVersion,
          version);

      if (VersionChecker.verifyVersionDetails(minimumSupportedVersion, version) < 0) {
        networkDev.setCompatibilityStatus(
            DiscoveredDataObject.CompatibilityStatus.INCOMPATIBLE.name());
        throw NetworkDeviceControllerException.exceptions.versionNotSupported(
            version, minimumSupportedVersion);
      } else {
        networkDev.setCompatibilityStatus(
            DiscoveredDataObject.CompatibilityStatus.COMPATIBLE.name());
      }
    } catch (Exception ex) {
      Date date = new Date();
      networkDev.setLastDiscoveryStatusMessage(ex.getMessage());
      throw NetworkDeviceControllerException.exceptions.verifyVersionFailed(
          uri.toString(), date.toString(), ex);
    } finally {
      if (networkDev != null) {
        try {
          dbClient.persistObject(networkDev);
        } catch (DatabaseException ex) {
          _log.error("Error while persisting object to DB", ex);
        }
      }
    }
  }
  /**
   * Update the network system physical inventory and creates/updates the discovered FC transport
   * zones as needed. The physical inventory is primarily the FCEndpoints (FCPortConnections), which
   * contains a record for each endpoint logged into the Fiber Channel Nameserver database. The
   * endpoints per fabric (vsan) constitute an FC transport zone which get created/updated based on
   * the FCEndpoints discovered.
   *
   * @param uri - Device URI
   */
  public void updatePhysicalInventory(URI uri) throws ControllerException {
    // Retrieve the storage device info from the database.
    long start = System.currentTimeMillis();
    NetworkSystem networkDev = getDeviceObject(uri);
    String msg = "unknown status";
    NetworkSystemDevice networkDevice = getDevice();
    if (networkDevice == null) {
      throw NetworkDeviceControllerException.exceptions.updatePhysicalInventoryFailedNull(
          uri.toString(), networkDev.getSystemType());
    }
    try {
      // === Reconcile the FCEndpoints of this device ===
      List<FCEndpoint> currentConnections = new ArrayList<FCEndpoint>();
      // IN/OUT parameter to get the routed endpoints map - Fabric-WWN-to-endpoints-WWN
      Map<String, Set<String>> routedEndpoints = new HashMap<String, Set<String>>();
      try {
        currentConnections = networkDevice.getPortConnections(networkDev, routedEndpoints);
        msg =
            MessageFormat.format(
                "Retrieved {0} connections from device {1} at {2}",
                new Integer(currentConnections.size()), uri, new Date());
        _log.info(msg);
      } catch (Exception e) {
        msg =
            MessageFormat.format(
                "Discovery failed getting port connections for Network System : {0}",
                uri.toString());
        throw (e);
      }

      try {
        reconcileFCEndpoints(networkDev, currentConnections);
      } catch (Exception e) {
        msg =
            MessageFormat.format(
                "Discovery failed reconciling FC endpoints for Network System : {0}",
                uri.toString());
        throw (e);
      }

      // ==== Reconcile the discovered transport zones ======
      try {
        reconcileTransportZones(networkDev, routedEndpoints);
      } catch (Exception e) {
        msg =
            MessageFormat.format(
                "Discovery failed reconciling networks for Network System : {0}", uri.toString());
        throw (e);
      }

      try {
        networkDev.setUptime(networkDevice.getUptime(networkDev));
      } catch (Exception e) {
        msg =
            MessageFormat.format(
                "Discovery failed setting version/uptime for Network System : {0}", uri.toString());
        throw (e);
      }

      // discovery succeeds
      msg =
          MessageFormat.format(
              "Discovery completed successfully for Network System : {0}", uri.toString());
    } catch (Exception ex) {
      Date date = new Date();
      throw NetworkDeviceControllerException.exceptions.updatePhysicalInventoryFailedExc(
          uri.toString(), date.toString(), ex);
    } finally {
      if (networkDev != null) {
        try {
          // set detailed message
          networkDev.setLastDiscoveryStatusMessage(msg);
          dbClient.persistObject(networkDev);
          _log.info("Discovery took {}", (System.currentTimeMillis() - start));
        } catch (DatabaseException ex) {
          _log.error("Error while persisting object to DB", ex);
        }
      }
    }
  }