content: // Licensed to the Apache Software Foundation (ASF) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The ASF licenses this file
  // to you 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.
  package org.apache.cloudstack.storage.datastore.util;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Random;
  import java.util.Set;
  import java.util.StringTokenizer;
  import java.util.UUID;
  
  import org.apache.commons.lang3.ArrayUtils;
  import org.apache.commons.lang3.StringUtils;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  
  import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
  import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
  import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
  import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
  
  import com.cloud.dc.ClusterVO;
  import com.cloud.dc.dao.ClusterDao;
  import com.cloud.host.Host;
  import com.cloud.host.HostVO;
  import com.cloud.host.dao.HostDao;
  import com.cloud.user.AccountDetailVO;
  import com.cloud.user.AccountDetailsDao;
  import com.cloud.utils.db.GlobalLock;
  import com.cloud.utils.exception.CloudRuntimeException;
  
  import com.google.common.base.Preconditions;
  import com.google.common.primitives.Longs;
  
  import com.solidfire.client.ElementFactory;
  import com.solidfire.element.api.Account;
  import com.solidfire.element.api.AddAccountRequest;
  import com.solidfire.element.api.AddInitiatorsToVolumeAccessGroupRequest;
  import com.solidfire.element.api.AddVolumesToVolumeAccessGroupRequest;
  import com.solidfire.element.api.CloneVolumeRequest;
  import com.solidfire.element.api.CloneVolumeResult;
  import com.solidfire.element.api.CreateSnapshotRequest;
  import com.solidfire.element.api.CreateVolumeAccessGroupRequest;
  import com.solidfire.element.api.CreateVolumeRequest;
  import com.solidfire.element.api.DeleteSnapshotRequest;
  import com.solidfire.element.api.DeleteVolumeRequest;
  import com.solidfire.element.api.GetAccountByIDRequest;
  import com.solidfire.element.api.GetAccountByNameRequest;
  import com.solidfire.element.api.GetAsyncResultRequest;
  import com.solidfire.element.api.ListSnapshotsRequest;
  import com.solidfire.element.api.ListVolumeAccessGroupsRequest;
  import com.solidfire.element.api.ListVolumesRequest;
  import com.solidfire.element.api.ModifyVolumeRequest;
  import com.solidfire.element.api.QoS;
  import com.solidfire.element.api.RemoveInitiatorsFromVolumeAccessGroupRequest;
  import com.solidfire.element.api.RemoveVolumesFromVolumeAccessGroupRequest;
  import com.solidfire.element.api.RollbackToSnapshotRequest;
  import com.solidfire.element.api.Snapshot;
  import com.solidfire.element.api.SolidFireElement;
  import com.solidfire.element.api.Volume;
  import com.solidfire.element.api.VolumeAccessGroup;
  import com.solidfire.jsvcgen.javautil.Optional;
  
  import static org.apache.commons.lang.ArrayUtils.toPrimitive;
  
  public class SolidFireUtil {
      protected static Logger LOGGER = LogManager.getLogger(SolidFireUtil.class);
  
      public static final String PROVIDER_NAME = "SolidFire";
      public static final String SHARED_PROVIDER_NAME = "SolidFireShared";
  
      private static final Random RANDOM = new Random(System.nanoTime());
      public static final int LOCK_TIME_IN_SECONDS = 300;
  
      public static final String LOGGER_PREFIX = "SolidFire: ";
  
      public static final String MANAGEMENT_VIP = "mVip";
      public static final String STORAGE_VIP = "sVip";
  
      public static final String MANAGEMENT_PORT = "mPort";
      public static final String STORAGE_PORT = "sPort";
  
      public static final String CLUSTER_ADMIN_USERNAME = "clusterAdminUsername";
      public static final String CLUSTER_ADMIN_PASSWORD = "clusterAdminPassword";
  
      // these three variables should only be used for the SolidFire plug-in with the name SolidFireUtil.PROVIDER_NAME
      public static final String CLUSTER_DEFAULT_MIN_IOPS = "clusterDefaultMinIops";
      public static final String CLUSTER_DEFAULT_MAX_IOPS = "clusterDefaultMaxIops";
      public static final String CLUSTER_DEFAULT_BURST_IOPS_PERCENT_OF_MAX_IOPS = "clusterDefaultBurstIopsPercentOfMaxIops";
  
      // these three variables should only be used for the SolidFire plug-in with the name SolidFireUtil.SHARED_PROVIDER_NAME
      public static final String MIN_IOPS = "minIops";
      public static final String MAX_IOPS = "maxIops";
      public static final String BURST_IOPS = "burstIops";
  
      private static final String ACCOUNT_ID = "accountId";
  
      public static final String VOLUME_ID = "volumeId";
      public static final String TEMP_VOLUME_ID = "tempVolumeId";
      public static final String SNAPSHOT_ID = "snapshotId";
  
      public static final String CloudStackVolumeId = "CloudStackVolumeId";
      public static final String CloudStackVolumeSize = "CloudStackVolumeSize";
      public static final String CloudStackSnapshotId = "CloudStackSnapshotId";
      public static final String CloudStackSnapshotSize = "CloudStackSnapshotSize";
      public static final String CloudStackTemplateId = "CloudStackTemplateId";
      public static final String CloudStackTemplateSize = "CloudStackTemplateSize";
  
      public static final String ORIG_CS_VOLUME_ID = "originalCloudStackVolumeId";
  
      public static final String VOLUME_SIZE = "sfVolumeSize";
  
      public static final String STORAGE_POOL_ID = "sfStoragePoolId";
  
      public static final String DATACENTER = "datacenter";
  
      public static final String DATASTORE_NAME = "datastoreName";
      public static final String IQN = "iqn";
  
      private static final String SF_CS_ACCOUNT_PREFIX = "CloudStack_";
  
      public static final long MIN_VOLUME_SIZE = 1000000000;
  
      public static final long MIN_IOPS_PER_VOLUME = 100;
      public static final long MAX_MIN_IOPS_PER_VOLUME = 15000;
      public static final long MAX_IOPS_PER_VOLUME = 100000;
  
      private static final int DEFAULT_MANAGEMENT_PORT = 443;
      private static final int DEFAULT_STORAGE_PORT = 3260;
  
      private static final int MAX_NUM_VAGS_PER_VOLUME = 4;
      private static final int MAX_NUM_INITIATORS_PER_VAG = 64;
  
      public static class SolidFireConnection {
          private final String _managementVip;
          private final int _managementPort;
          private final String _clusterAdminUsername;
          private final String _clusterAdminPassword;
  
          public SolidFireConnection(String managementVip, int managementPort, String clusterAdminUsername, String clusterAdminPassword) {
              if (managementVip == null) {
                  throw new CloudRuntimeException("The management VIP cannot be 'null'.");
              }
  
              if (managementPort <= 0) {
                  throw new CloudRuntimeException("The management port must be a positive integer.");
              }
  
              if (clusterAdminUsername == null) {
                  throw new CloudRuntimeException("The cluster admin username cannot be 'null'.");
              }
  
              if (clusterAdminPassword == null) {
                  throw new CloudRuntimeException("The cluster admin password cannot be 'null'.");
              }
  
              _managementVip = managementVip;
              _managementPort = managementPort;
              _clusterAdminUsername = clusterAdminUsername;
              _clusterAdminPassword = clusterAdminPassword;
          }
  
          String getManagementVip() {
              return _managementVip;
          }
  
          int getManagementPort() {
              return _managementPort;
          }
  
          String getClusterAdminUsername() {
              return _clusterAdminUsername;
          }
  
          private String getClusterAdminPassword() {
              return _clusterAdminPassword;
          }
  
          @Override
          public int hashCode() {
              return _managementVip.hashCode();
          }
  
          @Override
          public boolean equals(Object obj) {
              if (!(obj instanceof SolidFireConnection)) {
                  return false;
              }
  
              SolidFireConnection sfConnection = (SolidFireConnection)obj;
  
              return _managementVip.equals(sfConnection.getManagementVip());
          }
      }
  
      public static SolidFireConnection getSolidFireConnection(long storagePoolId, StoragePoolDetailsDao storagePoolDetailsDao) {
          StoragePoolDetailVO storagePoolDetail = storagePoolDetailsDao.findDetail(storagePoolId, SolidFireUtil.MANAGEMENT_VIP);
  
          String mVip = storagePoolDetail.getValue();
  
          storagePoolDetail = storagePoolDetailsDao.findDetail(storagePoolId, SolidFireUtil.MANAGEMENT_PORT);
  
          int mPort = Integer.parseInt(storagePoolDetail.getValue());
  
          storagePoolDetail = storagePoolDetailsDao.findDetail(storagePoolId, SolidFireUtil.CLUSTER_ADMIN_USERNAME);
  
          String clusterAdminUsername = storagePoolDetail.getValue();
  
          storagePoolDetail = storagePoolDetailsDao.findDetail(storagePoolId, SolidFireUtil.CLUSTER_ADMIN_PASSWORD);
  
          String clusterAdminPassword = storagePoolDetail.getValue();
  
          return new SolidFireConnection(mVip, mPort, clusterAdminUsername, clusterAdminPassword);
      }
  
      private static SolidFireElement getSolidFireElement(SolidFireConnection sfConnection) {
          return ElementFactory.create(sfConnection.getManagementVip(), sfConnection.getClusterAdminUsername(), sfConnection.getClusterAdminPassword());
      }
  
      // used to parse the "url" parameter when creating primary storage that's based on the SolidFire plug-in with the
      // name SolidFireUtil.PROVIDER_NAME (as opposed to the SolidFire plug-in with the name SolidFireUtil.SHARED_PROVIDER_NAME)
      // return a String instance that contains at most the MVIP and SVIP info
      public static String getModifiedUrl(String originalUrl) {
          StringBuilder sb = new StringBuilder();
  
          String delimiter = ";";
  
          StringTokenizer st = new StringTokenizer(originalUrl, delimiter);
  
          while (st.hasMoreElements()) {
              String token = st.nextElement().toString().toUpperCase();
  
              if (token.startsWith(SolidFireUtil.MANAGEMENT_VIP.toUpperCase()) || token.startsWith(SolidFireUtil.STORAGE_VIP.toUpperCase())) {
                  sb.append(token).append(delimiter);
              }
          }
  
          String modifiedUrl = sb.toString();
          int lastIndexOf = modifiedUrl.lastIndexOf(delimiter);
  
          if (lastIndexOf == (modifiedUrl.length() - delimiter.length())) {
              return modifiedUrl.substring(0, lastIndexOf);
          }
  
          return modifiedUrl;
      }
  
      public static String getManagementVip(String url) {
          return getVip(SolidFireUtil.MANAGEMENT_VIP, url);
      }
  
      public static String getStorageVip(String url) {
          return getVip(SolidFireUtil.STORAGE_VIP, url);
      }
  
      public static int getManagementPort(String url) {
          return getPort(SolidFireUtil.MANAGEMENT_VIP, url, DEFAULT_MANAGEMENT_PORT);
      }
  
      public static int getStoragePort(String url) {
          return getPort(SolidFireUtil.STORAGE_VIP, url, DEFAULT_STORAGE_PORT);
      }
  
      public static String getValue(String keyToMatch, String url) {
          return getValue(keyToMatch, url, true);
      }
  
      public static String getValue(String keyToMatch, String url, boolean throwExceptionIfNotFound) {
          String delimiter1 = ";";
          String delimiter2 = "=";
  
          StringTokenizer st = new StringTokenizer(url, delimiter1);
  
          while (st.hasMoreElements()) {
              String token = st.nextElement().toString();
  
              int index = token.indexOf(delimiter2);
  
              if (index == -1) {
                  throw new RuntimeException("Invalid URL format");
              }
  
              String key = token.substring(0, index);
  
              if (key.equalsIgnoreCase(keyToMatch)) {
                  return token.substring(index + delimiter2.length());
              }
          }
  
          if (throwExceptionIfNotFound) {
              throw new RuntimeException("Key not found in URL");
          }
  
          return null;
      }
  
      public static String getSolidFireAccountName(String csAccountUuid, long csAccountId) {
          return SF_CS_ACCOUNT_PREFIX + csAccountUuid + "_" + csAccountId;
      }
  
      public static void updateCsDbWithSolidFireIopsInfo(long storagePoolId, PrimaryDataStoreDao primaryDataStoreDao,
                                                         StoragePoolDetailsDao storagePoolDetailsDao, long minIops, long maxIops, long burstIops) {
          Map<String, String> existingDetails = storagePoolDetailsDao.listDetailsKeyPairs(storagePoolId);
          Set<String> existingKeys = existingDetails.keySet();
  
          Map<String, String> existingDetailsToKeep = new HashMap<>();
  
          for (String existingKey : existingKeys) {
              String existingValue = existingDetails.get(existingKey);
  
              if (!SolidFireUtil.MIN_IOPS.equalsIgnoreCase(existingValue) &&
                      !SolidFireUtil.MAX_IOPS.equalsIgnoreCase(existingValue) &&
                      !SolidFireUtil.BURST_IOPS.equalsIgnoreCase(existingValue)) {
                  existingDetailsToKeep.put(existingKey, existingValue);
              }
          }
  
          existingDetailsToKeep.put(SolidFireUtil.MIN_IOPS, String.valueOf(minIops));
          existingDetailsToKeep.put(SolidFireUtil.MAX_IOPS, String.valueOf(maxIops));
          existingDetailsToKeep.put(SolidFireUtil.BURST_IOPS, String.valueOf(burstIops));
  
          primaryDataStoreDao.updateDetails(storagePoolId, existingDetailsToKeep);
      }
  
      public static void updateCsDbWithSolidFireAccountInfo(long csAccountId, SolidFireUtil.SolidFireAccount sfAccount,
                                                            long storagePoolId, AccountDetailsDao accountDetailsDao) {
          AccountDetailVO accountDetail = new AccountDetailVO(csAccountId,
                  SolidFireUtil.getAccountKey(storagePoolId),
                  String.valueOf(sfAccount.getId()));
  
          accountDetailsDao.persist(accountDetail);
      }
  
      public static SolidFireAccount getAccount(SolidFireConnection sfConnection, String sfAccountName) {
          try {
              return getAccountByName(sfConnection, sfAccountName);
          } catch (Exception ex) {
              return null;
          }
      }
  
      private static boolean isCloudStackOnlyVag(SolidFireConnection sfConnection, SolidFireVag sfVag) {
          long[] volumeIds = sfVag.getVolumeIds();
  
          if (ArrayUtils.isEmpty(volumeIds)) {
              // We count this situation as being "CloudStack only" because the reason we call this method is to determine
              // if we can remove a host from a VAG (we only want to allow the host to be removed from the VAG if there are
              // no non-CloudStack volumes in it).
              return true;
          }
  
          List<Long> knownSfAccountsForCs = new ArrayList<>();
  
          for (long volumeId : volumeIds) {
              SolidFireVolume sfVolume = getVolume(sfConnection, volumeId);
              long sfAccountId = sfVolume.getAccountId();
  
              if (!knownSfAccountsForCs.contains(sfAccountId)) {
                  SolidFireAccount sfAccount = getAccountById(sfConnection, sfAccountId);
  
                  if (sfAccount.getName().startsWith(SF_CS_ACCOUNT_PREFIX)) {
                      knownSfAccountsForCs.add(sfAccountId);
                  }
                  else {
                      return false;
                  }
              }
          }
  
          return true;
      }
  
      private static boolean isStorageApplicableToZoneOrCluster(StoragePoolVO storagePoolVO, long clusterId, ClusterDao clusterDao) {
          if (storagePoolVO.getClusterId() != null) {
              if (storagePoolVO.getClusterId() == clusterId) {
                  return true;
              }
          }
          else {
              List<ClusterVO> clustersInZone = clusterDao.listClustersByDcId(storagePoolVO.getDataCenterId());
  
              if (clustersInZone != null) {
                  for (ClusterVO clusterInZone : clustersInZone) {
                      if (clusterInZone.getId() == clusterId) {
                          return true;
                      }
                  }
              }
          }
  
          return false;
      }
  
      public static void hostRemovedFromCluster(long hostId, long clusterId, String storageProvider, ClusterDao clusterDao, HostDao hostDao,
                                                PrimaryDataStoreDao storagePoolDao, StoragePoolDetailsDao storagePoolDetailsDao) {
          HostVO hostVO = hostDao.findByIdIncludingRemoved(hostId);
  
          Preconditions.checkArgument(hostVO != null, "Could not locate host for ID: " + hostId);
  
          ClusterVO cluster = clusterDao.findById(clusterId);
  
          GlobalLock lock = GlobalLock.getInternLock(cluster.getUuid());
  
          if (!lock.lock(LOCK_TIME_IN_SECONDS)) {
              String errMsg = "Couldn't lock the DB on the following string: " + cluster.getUuid();
  
              LOGGER.warn(errMsg);
  
              throw new CloudRuntimeException(errMsg);
          }
  
          try {
              List<StoragePoolVO> storagePools = storagePoolDao.findPoolsByProvider(storageProvider);
  
              if (storagePools != null && storagePools.size() > 0) {
                  List<SolidFireUtil.SolidFireConnection> sfConnections = new ArrayList<>();
  
                  for (StoragePoolVO storagePool : storagePools) {
                      if (!isStorageApplicableToZoneOrCluster(storagePool, clusterId, clusterDao)) {
                          continue;
                      }
  
                      SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePool.getId(), storagePoolDetailsDao);
  
                      if (!sfConnections.contains(sfConnection)) {
                          sfConnections.add(sfConnection);
  
                          List<SolidFireUtil.SolidFireVag> sfVags = SolidFireUtil.getAllVags(sfConnection);
                          SolidFireVag sfVag = getVolumeAccessGroup(hostVO.getStorageUrl(), sfVags);
  
                          if (sfVag != null && isCloudStackOnlyVag(sfConnection, sfVag)) {
                              removeInitiatorsFromSolidFireVag(sfConnection, sfVag.getId(), new String[] { hostVO.getStorageUrl() });
                          }
                      }
                  }
              }
          }
          finally {
              lock.unlock();
              lock.releaseRef();
          }
      }
  
      public static void hostAddedToCluster(long hostId, long clusterId, String storageProvider, ClusterDao clusterDao, HostDao hostDao,
                                            PrimaryDataStoreDao storagePoolDao, StoragePoolDetailsDao storagePoolDetailsDao) {
          HostVO hostVO = hostDao.findById(hostId);
  
          Preconditions.checkArgument(hostVO != null, "Could not locate host for ID: " + hostId);
  
          ClusterVO cluster = clusterDao.findById(clusterId);
  
          GlobalLock lock = GlobalLock.getInternLock(cluster.getUuid());
  
          if (!lock.lock(LOCK_TIME_IN_SECONDS)) {
              String errMsg = "Couldn't lock the DB on the following string: " + cluster.getUuid();
  
              LOGGER.warn(errMsg);
  
              throw new CloudRuntimeException(errMsg);
          }
  
          try {
              List<StoragePoolVO> storagePools = storagePoolDao.findPoolsByProvider(storageProvider);
  
              if (storagePools != null && storagePools.size() > 0) {
                  List<SolidFireUtil.SolidFireConnection> sfConnections = new ArrayList<>();
  
                  for (StoragePoolVO storagePool : storagePools) {
                      if (!isStorageApplicableToZoneOrCluster(storagePool, clusterId, clusterDao)) {
                          continue;
                      }
  
                      SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePool.getId(), storagePoolDetailsDao);
  
                      if (!sfConnections.contains(sfConnection)) {
                          sfConnections.add(sfConnection);
  
                          List<SolidFireUtil.SolidFireVag> sfVags = SolidFireUtil.getAllVags(sfConnection);
                          SolidFireVag sfVag = getVolumeAccessGroup(hostVO.getStorageUrl(), sfVags);
  
                          if (sfVag != null) {
                              placeVolumeIdsInVag(sfConnection, sfVags, sfVag, hostVO, hostDao);
                          } else {
                              handleVagForHost(sfConnection, sfVags, hostVO, hostDao, clusterDao);
                          }
                      }
                  }
              }
          }
          finally {
              lock.unlock();
              lock.releaseRef();
          }
      }
  
      // Put the host in an existing VAG or create a new one (only create a new one if all existing VAGs are full (i.e. 64 hosts max per VAG) and if
      // creating a new VAG won't exceed 4 VAGs for the computer cluster).
      // If none of the hosts in the cluster are in a VAG, then leave this host out of a VAG.
      // Place applicable volume IDs in VAG, if need be (account of volume starts with SF_CS_ACCOUNT_PREFIX).
      private static void handleVagForHost(SolidFireUtil.SolidFireConnection sfConnection, List<SolidFireUtil.SolidFireVag> sfVags, Host host, HostDao hostDao, ClusterDao clusterDao) {
          List<HostVO> hostVOs = hostDao.findByClusterId(host.getClusterId());
  
          if (hostVOs != null) {
              int numVags = 0;
  
              addInitiatorsToExistingVag(clusterDao, host, sfVags, sfConnection);
  
              Collections.shuffle(hostVOs, RANDOM);
  
              for (HostVO hostVO : hostVOs) {
                  if (hostVO.getId() != host.getId()) {
                      SolidFireVag sfVag = getVolumeAccessGroup(hostVO.getStorageUrl(), sfVags);
  
                      if (sfVag != null) {
                          numVags++;
  
                          // A volume should be visible to all hosts that are in the same compute cluster. That being the case, you
                          // can use MAX_NUM_VAGS_PER_VOLUME here. This is to limit the number of VAGs being used in a compute cluster
                          // to MAX_NUM_VAGS_PER_VOLUME.
                          if (numVags > MAX_NUM_VAGS_PER_VOLUME) {
                              throw new CloudRuntimeException("Can support at most four volume access groups per compute cluster (>)");
                          }
  
                          if (sfVag.getInitiators().length < MAX_NUM_INITIATORS_PER_VAG) {
                              if (!hostSupports_iScsi(host)) {
                                  String errMsg = String.format("Host %s does not support iSCSI.", host);
  
                                  LOGGER.warn(errMsg);
  
                                  throw new CloudRuntimeException(errMsg);
                              }
  
                              if(!isInitiatorInSfVag(host.getStorageUrl(),sfVag)) {
                                  addInitiatorsToSolidFireVag(sfConnection, sfVag.getId(), new String[]{host.getStorageUrl()});
                              }
                              return;
                          }
                      }
                  }
              }
  
              if (numVags == MAX_NUM_VAGS_PER_VOLUME) {
                  throw new CloudRuntimeException("Can support at most four volume access groups per compute cluster (==)");
              }
  
              if (numVags > 0) {
                  if (!hostSupports_iScsi(host)) {
                      String errMsg = String.format("Host %s does not support iSCSI.", host);
  
                      LOGGER.warn(errMsg);
  
                      throw new CloudRuntimeException(errMsg);
                  }
  
                  SolidFireUtil.createVag(sfConnection, "CloudStack-" + UUID.randomUUID().toString(),
                          new String[]{host.getStorageUrl()}, getVolumeIds(sfConnection, sfVags, host, hostDao));
              }
          }
      }
  
      private static void addInitiatorsToExistingVag(ClusterDao clusterDao, Host host, List<SolidFireUtil.SolidFireVag> sfVags, SolidFireUtil.SolidFireConnection sfConnection){
          String clusterUuId = clusterDao.findById(host.getClusterId()).getUuid();
          SolidFireVag sfVagMatchingClusterId = sfVags.stream().filter(vag -> vag.getName().equals("CloudStack-"+clusterUuId)).findFirst().orElse(null);
          if (sfVagMatchingClusterId != null && sfVagMatchingClusterId.getInitiators().length < MAX_NUM_INITIATORS_PER_VAG) {
              addInitiatorsToSolidFireVag(sfConnection, sfVagMatchingClusterId.getId(), new String[]{host.getStorageUrl()});
          }
      }
  
      /**
       * Make use of the volume access group (VAG) of a random host in the cluster. With this VAG, collect all of its volume IDs that are for
       * volumes that are in SolidFire accounts that are for CloudStack.
       */
      private static long[] getVolumeIds(SolidFireUtil.SolidFireConnection sfConnection, List<SolidFireUtil.SolidFireVag> sfVags,
                                         Host host, HostDao hostDao) {
          List<Long> volumeIdsToReturn = new ArrayList<>();
  
          SolidFireVag sfVagForRandomHostInCluster = getVagForRandomHostInCluster(sfVags, host, hostDao);
  
          if (sfVagForRandomHostInCluster != null) {
              long[] volumeIds = sfVagForRandomHostInCluster.getVolumeIds();
  
              if (volumeIds != null) {
                  List<Long> knownSfAccountsForCs = new ArrayList<>();
                  List<Long> knownSfAccountsNotForCs = new ArrayList<>();
  
                  for (long volumeId : volumeIds) {
                      SolidFireVolume sfVolume = getVolume(sfConnection, volumeId);
                      long sfAccountId = sfVolume.getAccountId();
  
                      if (knownSfAccountsForCs.contains(sfAccountId)) {
                          volumeIdsToReturn.add(volumeId);
                      }
                      else if (!knownSfAccountsNotForCs.contains(sfAccountId)) {
                          SolidFireAccount sfAccount = getAccountById(sfConnection, sfAccountId);
  
                          if (sfAccount.getName().startsWith(SF_CS_ACCOUNT_PREFIX)) {
                              knownSfAccountsForCs.add(sfAccountId);
  
                              volumeIdsToReturn.add(volumeId);
                          }
                          else {
                              knownSfAccountsNotForCs.add(sfAccountId);
                          }
                      }
                  }
              }
          }
  
          return volumeIdsToReturn.stream().mapToLong(l -> l).toArray();
      }
  
      private static void placeVolumeIdsInVag(SolidFireUtil.SolidFireConnection sfConnection, List<SolidFireUtil.SolidFireVag> sfVags,
                                              SolidFireVag sfVag, Host host, HostDao hostDao) {
          SolidFireVag sfVagForRandomHostInCluster = getVagForRandomHostInCluster(sfVags, host, hostDao);
  
          if (sfVagForRandomHostInCluster != null) {
              long[] volumeIds = sfVagForRandomHostInCluster.getVolumeIds();
  
              if (volumeIds != null) {
                  List<Long> knownSfAccountsForCs = new ArrayList<>();
                  List<Long> knownSfAccountsNotForCs = new ArrayList<>();
  
                  List<Long> newVolumeIds = new ArrayList<>();
  
                  for (long volumeId : volumeIds) {
                      SolidFireVolume sfVolume = getVolume(sfConnection, volumeId);
                      long sfAccountId = sfVolume.getAccountId();
  
                      if (knownSfAccountsForCs.contains(sfAccountId)) {
                          addVolumeIdToSolidFireVag(volumeId, sfVag, newVolumeIds);
                      }
                      else if (!knownSfAccountsNotForCs.contains(sfAccountId)) {
                          SolidFireAccount sfAccount = getAccountById(sfConnection, sfAccountId);
  
                          if (sfAccount.getName().startsWith(SF_CS_ACCOUNT_PREFIX)) {
                              knownSfAccountsForCs.add(sfAccountId);
  
                              addVolumeIdToSolidFireVag(volumeId, sfVag, newVolumeIds);
                          }
                          else {
                              knownSfAccountsNotForCs.add(sfAccountId);
                          }
                      }
                  }
  
                  if (newVolumeIds.size() > 0) {
                      addVolumeIdsToSolidFireVag(sfConnection, sfVag.getId(), newVolumeIds.toArray(new Long[0]));
                  }
              }
          }
      }
  
      private static void addVolumeIdToSolidFireVag(long volumeId, SolidFireVag sfVag, List<Long> newVolumeIds) {
          List<Long> existingVolumeIds = Longs.asList(sfVag.getVolumeIds());
  
          if (!existingVolumeIds.contains(volumeId) && !newVolumeIds.contains(volumeId)) {
              newVolumeIds.add(volumeId);
          }
      }
  
      private static SolidFireVag getVagForRandomHostInCluster(List<SolidFireUtil.SolidFireVag> sfVags, Host host, HostDao hostDao) {
          List<HostVO> hostVOs = hostDao.findByClusterId(host.getClusterId());
  
          if (hostVOs != null) {
              Collections.shuffle(hostVOs, RANDOM);
  
              for (HostVO hostVO : hostVOs) {
                  if (hostVO.getId() != host.getId() && hostSupports_iScsi(hostVO)) {
                      SolidFireVag sfVag = getVolumeAccessGroup(hostVO.getStorageUrl(), sfVags);
  
                      if (sfVag != null) {
                          return sfVag;
                      }
                  }
              }
          }
  
          return null;
      }
  
      public static void placeVolumeInVolumeAccessGroups(SolidFireConnection sfConnection, long sfVolumeId, List<HostVO> hosts, String clusterUuId) {
          if (!SolidFireUtil.hostsSupport_iScsi(hosts)) {
              String errMsg = "Not all hosts in the compute cluster support iSCSI.";
  
              LOGGER.warn(errMsg);
  
              throw new CloudRuntimeException(errMsg);
          }
          List<SolidFireUtil.SolidFireVag> sfVags = SolidFireUtil.getAllVags(sfConnection);
          Map<SolidFireUtil.SolidFireVag, List<String>> sfVagToIqnsMap = buildVagToIQNMap(hosts, sfVags);
          if (sfVagToIqnsMap.size() > MAX_NUM_VAGS_PER_VOLUME) {
              throw new CloudRuntimeException("A SolidFire volume can be in at most four volume access groups simultaneously.");
          }
          if (sfVagToIqnsMap.containsKey(null)) {
              sfVagToIqnsMap = updateNullKeyInSfVagToIqnsMap(sfVagToIqnsMap, sfVags, sfConnection, clusterUuId, sfVolumeId);
          }
          addVolumestoVagIfNotPresent(sfVagToIqnsMap.keySet(), sfVolumeId, sfConnection);
      }
  
      private static Map<SolidFireUtil.SolidFireVag, List<String>> updateNullKeyInSfVagToIqnsMap(Map<SolidFireUtil.SolidFireVag,List<String>> sfVagToIqnsMap, List <SolidFireUtil.SolidFireVag> sfVags, SolidFireConnection sfConnection, String clusterUuId, long sfVolumeId){
          SolidFireUtil.SolidFireVag sfVagMatchingClusterId = createClusterVagIfDoesntExist(sfVags, sfConnection, clusterUuId, sfVagToIqnsMap, sfVolumeId);
          sfVagToIqnsMap.put(sfVagMatchingClusterId, sfVagToIqnsMap.get(null));
          sfVagToIqnsMap.remove(null);
          return sfVagToIqnsMap;
      }
  
      private static SolidFireVag createClusterVagIfDoesntExist(List<SolidFireUtil.SolidFireVag> sfVags, SolidFireConnection sfConnection, String clusterUuId, Map<SolidFireUtil.SolidFireVag, List<String>> sfVagToIqnsMap, long sfVolumeId) {
          SolidFireVag sfVagMatchingClusterId = sfVags.stream().filter(vag -> vag.getName().equals("CloudStack-" + clusterUuId)).findFirst().orElse(null);
          if (sfVagMatchingClusterId == null) {
              LOGGER.info("Creating volume access group CloudStack-" + clusterUuId);
              SolidFireUtil.createVag(sfConnection, "CloudStack-" + clusterUuId, sfVagToIqnsMap.get(null).toArray(new String[0]), new long[]{sfVolumeId});
              sfVags = SolidFireUtil.getAllVags(sfConnection);
              return sfVags.stream().filter(vag -> vag.getName().equals("CloudStack-" + clusterUuId)).findFirst().orElse(null);
          }else{
              return sfVagMatchingClusterId;
          }
      }
  
      private static void addVolumestoVagIfNotPresent(Set<SolidFireUtil.SolidFireVag> sfVagSet, long sfVolumeId, SolidFireConnection sfConnection){
          for (SolidFireUtil.SolidFireVag sfVag : sfVagSet) {
              if (sfVag != null) {
                  if (!SolidFireUtil.isVolumeIdInSfVag(sfVolumeId, sfVag)) {
                      SolidFireUtil.addVolumeIdsToSolidFireVag(sfConnection, sfVag.getId(), new Long[] { sfVolumeId });
                  }
              }
          }
      }
  
  
      private static Map<SolidFireVag,List<String>> buildVagToIQNMap(List<HostVO> hosts, List<SolidFireVag> sfVags) {
  
          Map<SolidFireUtil.SolidFireVag, List<String>> sfVagToIqnsMap = new HashMap<>();
          for (HostVO hostVO : hosts) {
              String iqn = hostVO.getStorageUrl();
  
              SolidFireUtil.SolidFireVag sfVag = getVolumeAccessGroup(iqn, sfVags);
  
              List<String> iqnsInVag = sfVagToIqnsMap.computeIfAbsent(sfVag, k -> new ArrayList<>());
  
              iqnsInVag.add(iqn);
          }
          return sfVagToIqnsMap;
  
      }
  
      public static SolidFireUtil.SolidFireVag getVolumeAccessGroup(String hostIqn, List<SolidFireUtil.SolidFireVag> sfVags) {
          if (hostIqn == null) {
              return null;
          }
  
          hostIqn = hostIqn.toLowerCase();
  
          if (sfVags != null) {
              for (SolidFireUtil.SolidFireVag sfVag : sfVags) {
                  List<String> lstInitiators = getStringArrayAsLowerCaseStringList(sfVag.getInitiators());
  
                  // lstInitiators should not be returned from getStringArrayAsLowerCaseStringList as null
                  if (lstInitiators.contains(hostIqn)) {
                      return sfVag;
                  }
              }
          }
  
          return null;
      }
  
      public static boolean sfVagContains(SolidFireUtil.SolidFireVag sfVag, long sfVolumeId, long clusterId, HostDao hostDao) {
          if (isVolumeIdInSfVag(sfVolumeId, sfVag)) {
              String[] iqns = sfVag.getInitiators();
              List<HostVO> hosts = hostDao.findByClusterId(clusterId);
  
              for (String iqn : iqns) {
                  for (HostVO host : hosts) {
                      String hostIqn = host.getStorageUrl();
  
                      if (iqn.equalsIgnoreCase(hostIqn)) {
                          return true;
                      }
                  }
              }
          }
  
          return false;
      }
  
      private static boolean isVolumeIdInSfVag(long sfVolumeIdToCheck, SolidFireUtil.SolidFireVag sfVag) {
          long[] sfVolumeIds = sfVag.getVolumeIds();
  
          for (long sfVolumeId : sfVolumeIds) {
              if (sfVolumeId == sfVolumeIdToCheck) {
                  return true;
              }
          }
  
          return false;
      }
  
      private static boolean isInitiatorInSfVag(String initiatorName, SolidFireUtil.SolidFireVag sfVag) {
          String[] initiatorsList = sfVag.getInitiators();
  
          for (String initiator : initiatorsList) {
              if (initiatorName.equals(initiator)) {
                  return true;
              }
          }
  
          return false;
      }
  
      private static boolean hostSupports_iScsi(Host host) {
          return host != null && host.getStorageUrl() != null && host.getStorageUrl().trim().length() > 0 && host.getStorageUrl().startsWith("iqn");
      }
  
      private static boolean hostsSupport_iScsi(List<HostVO> hosts) {
          if (hosts == null || hosts.size() == 0) {
              return false;
          }
  
          for (Host host : hosts) {
              if (!hostSupports_iScsi(host)) {
                  return false;
              }
          }
  
          return true;
      }
  
      public static String getVagKey(long storagePoolId) {
          return "sfVolumeAccessGroup_" + storagePoolId;
      }
  
      public static String getAccountKey(long storagePoolId) {
          return SolidFireUtil.ACCOUNT_ID + "_" + storagePoolId;
      }
  
      public static AccountDetailVO getAccountDetail(long csAccountId, long storagePoolId, AccountDetailsDao accountDetailsDao) {
          AccountDetailVO accountDetail = accountDetailsDao.findDetail(csAccountId, SolidFireUtil.getAccountKey(storagePoolId));
  
          if (accountDetail == null || accountDetail.getValue() == null) {
              accountDetail = accountDetailsDao.findDetail(csAccountId, SolidFireUtil.ACCOUNT_ID);
          }
  
          return accountDetail;
      }
  
      public static String getSolidFireVolumeName(String strCloudStackVolumeName) {
          final String specialChar = "-";
  
          StringBuilder strSolidFireVolumeName = new StringBuilder();
  
          for (int i = 0; i < strCloudStackVolumeName.length(); i++) {
              String strChar = strCloudStackVolumeName.substring(i, i + 1);
  
              if (StringUtils.isAlphanumeric(strChar)) {
                  strSolidFireVolumeName.append(strChar);
              } else {
                  strSolidFireVolumeName.append(specialChar);
              }
          }
  
          return strSolidFireVolumeName.toString();
      }
  
      public static long createVolume(SolidFireConnection sfConnection, String volumeName, long accountId, long totalSize,
                                      boolean enable512e, Map<String, String> mapAttributes, long minIops, long maxIops, long burstIops) {
          CreateVolumeRequest request = CreateVolumeRequest.builder()
                  .name(volumeName)
                  .accountID(accountId)
                  .totalSize(totalSize)
                  .enable512e(enable512e)
                  .optionalAttributes(convertMap(mapAttributes))
                  .optionalQos(new QoS(Optional.of(minIops), Optional.of(maxIops), Optional.of(burstIops), Optional.EMPTY_LONG))
                  .build();
  
          return getSolidFireElement(sfConnection).createVolume(request).getVolumeID();
      }
  
      public static void modifyVolume(SolidFireConnection sfConnection, long volumeId, Long totalSize, Map<String, String> mapAttributes,
                                      long minIops, long maxIops, long burstIops) {
          ModifyVolumeRequest request = ModifyVolumeRequest.builder()
                  .volumeID(volumeId)
                  .optionalTotalSize(totalSize)
                  .optionalAttributes(convertMap(mapAttributes))
                  .optionalQos(new QoS(Optional.of(minIops), Optional.of(maxIops), Optional.of(burstIops), Optional.EMPTY_LONG))
                  .build();
  
          getSolidFireElement(sfConnection).modifyVolume(request);
      }
  
      public static void modifyVolumeQoS(SolidFireConnection sfConnection, long volumeId, long minIops, long maxIops, long burstIops) {
          ModifyVolumeRequest request = ModifyVolumeRequest.builder()
                  .volumeID(volumeId)
                  .optionalQos(new QoS(Optional.of(minIops), Optional.of(maxIops), Optional.of(burstIops), Optional.EMPTY_LONG))
                  .build();
  
          getSolidFireElement(sfConnection).modifyVolume(request);
      }
  
      public static SolidFireVolume getVolume(SolidFireConnection sfConnection, long volumeId) {
          ListVolumesRequest request = ListVolumesRequest.builder()
                  .optionalStartVolumeID(volumeId)
                  .optionalLimit(1L)
                  .build();
  
          Volume volume = getSolidFireElement(sfConnection).listVolumes(request).getVolumes()[0];
  
          return new SolidFireVolume(volume.getVolumeID(), volume.getName(), volume.getIqn(), volume.getAccountID(), volume.getStatus(),
                  volume.getEnable512e(), volume.getQos().getMinIOPS(), volume.getQos().getMaxIOPS(), volume.getQos().getBurstIOPS(),
                  volume.getTotalSize(), volume.getScsiNAADeviceID());
      }
  
      public static void deleteVolume(SolidFireConnection sfConnection, long volumeId) {
          DeleteVolumeRequest request = DeleteVolumeRequest.builder()
                  .volumeID(volumeId)
                  .build();
  
          getSolidFireElement(sfConnection).deleteVolume(request);
      }
  
      private static final String ACTIVE = "active";
  
      public static class SolidFireVolume {
          private final long _id;
          private final String _name;
          private final String _iqn;
          private final long _accountId;
          private final String _status;
          private final boolean _enable512e;
          private final long _minIops;
          private final long _maxIops;
          private final long _burstIops;
          private final long _totalSize;
          private final String _scsiNaaDeviceId;
  
          SolidFireVolume(long id, String name, String iqn, long accountId, String status, boolean enable512e,
                          long minIops, long maxIops, long burstIops, long totalSize, String scsiNaaDeviceId) {
              _id = id;
              _name = name;
              _iqn = "/" + iqn + "/0";
              _accountId = accountId;
              _status = status;
              _enable512e = enable512e;
              _minIops = minIops;
              _maxIops = maxIops;
              _burstIops = burstIops;
              _totalSize = totalSize;
              _scsiNaaDeviceId = scsiNaaDeviceId;
          }
  
          public long getId() {
              return _id;
          }
  
          public String getName() {
              return _name;
          }
  
          public String getIqn() {
              return _iqn;
          }
  
          public long getAccountId() {
              return _accountId;
          }
  
          public boolean isActive() {
              return ACTIVE.equalsIgnoreCase(_status);
          }
  
          public boolean isEnable512e() {
              return _enable512e;
          }
  
          public long getMinIops() {
              return _minIops;
          }
  
          public long getMaxIops() {
              return _maxIops;
          }
  
          public long getBurstIops() {
              return _burstIops;
          }
  
          public long getTotalSize() {
              return _totalSize;
          }
  
          public String getScsiNaaDeviceId() {
              return _scsiNaaDeviceId;
          }
  
          @Override
          public int hashCode() {
              return _iqn.hashCode();
          }
  
          @Override
          public String toString() {
              return _name;
          }
  
          @Override
          public boolean equals(Object obj) {
              if (obj == null) {
                  return false;
              }
  
              if (!obj.getClass().equals(SolidFireVolume.class)) {
                  return false;
              }
  
              SolidFireVolume sfv = (SolidFireVolume)obj;
  
              return _id == sfv._id && _name.equals(sfv._name) && _iqn.equals(sfv._iqn) && _accountId == sfv._accountId &&
                      isActive() == sfv.isActive() && getTotalSize() == sfv.getTotalSize();
          }
      }
  
      public static long createSnapshot(SolidFireConnection sfConnection, long volumeId, String snapshotName, Map<String, String> mapAttributes) {
          CreateSnapshotRequest request = CreateSnapshotRequest.builder()
                  .volumeID(volumeId)
                  .optionalName(snapshotName)
                  .optionalAttributes(convertMap(mapAttributes))
                  .build();
  
          return getSolidFireElement(sfConnection).createSnapshot(request).getSnapshotID();
      }
  
      public static SolidFireSnapshot getSnapshot(SolidFireConnection sfConnection, long volumeId, long snapshotId) {
          ListSnapshotsRequest request = ListSnapshotsRequest.builder()
                  .optionalVolumeID(volumeId)
                  .build();
  
          Snapshot[] snapshots = getSolidFireElement(sfConnection).listSnapshots(request).getSnapshots();
  
          String snapshotName = null;
          long totalSize = 0;
  
          if (snapshots != null) {
              for (Snapshot snapshot : snapshots) {
                  if (snapshot.getSnapshotID() == snapshotId) {
                      snapshotName = snapshot.getName();
                      totalSize = snapshot.getTotalSize();
  
                      break;
                  }
              }
          }
  
          if (snapshotName == null) {
              throw new CloudRuntimeException("Could not find SolidFire snapshot ID: " + snapshotId + " for the following SolidFire volume ID: " + volumeId);
          }
  
          return new SolidFireSnapshot(snapshotId, snapshotName, totalSize);
      }
  
      public static void deleteSnapshot(SolidFireConnection sfConnection, long snapshotId) {
          DeleteSnapshotRequest request = DeleteSnapshotRequest.builder()
                  .snapshotID(snapshotId)
                  .build();
  
          getSolidFireElement(sfConnection).deleteSnapshot(request);
      }
  
      public static void rollBackVolumeToSnapshot(SolidFireConnection sfConnection, long volumeId, long snapshotId) {
          RollbackToSnapshotRequest request = RollbackToSnapshotRequest.builder()
                  .volumeID(volumeId)
                  .snapshotID(snapshotId)
                  .build();
  
          getSolidFireElement(sfConnection).rollbackToSnapshot(request);
      }
  
      public static class SolidFireSnapshot {
          private final long _id;
          private final String _name;
          private final long _totalSize;
  
          SolidFireSnapshot(long id, String name, long totalSize) {
              _id = id;
              _name = name;
              _totalSize = totalSize;
          }
  
          public long getId() {
              return _id;
          }
  
          public String getName() {
              return _name;
          }
  
          public long getTotalSize() {
              return _totalSize;
          }
      }
  
      public static long createClone(SolidFireConnection sfConnection, long volumeId, long snapshotId, long accountId,
                                     String cloneName, Map<String, String> mapAttributes) {
          CloneVolumeRequest request = CloneVolumeRequest.builder()
                  .volumeID(volumeId)
                  .optionalSnapshotID(snapshotId < 1 ? null : snapshotId)
                  .optionalNewAccountID(accountId)
                  .name(cloneName)
                  .optionalAttributes(convertMap(mapAttributes))
                  .build();
  
          CloneVolumeResult result = getSolidFireElement(sfConnection).cloneVolume(request);
  
          // Clone is an async operation. Poll until we get data.
  
          GetAsyncResultRequest asyncResultRequest = GetAsyncResultRequest.builder()
                  .asyncHandle(result.getAsyncHandle())
                  .build();
  
          do {
              String status = getSolidFireElement(sfConnection).getAsyncResult(asyncResultRequest).getStatus();
  
              if (status.equals("complete")) {
                  break;
              }
  
              try {
                  Thread.sleep(500); // sleep for 1/2 of a second
              }
              catch (Exception ex) {
                  // ignore
              }
          }
          while (true);
  
          return result.getVolumeID();
      }
  
      public static long createAccount(SolidFireConnection sfConnection, String accountName) {
          AddAccountRequest request = AddAccountRequest.builder()
                  .username(accountName)
                  .build();
  
          return getSolidFireElement(sfConnection).addAccount(request).getAccountID();
      }
  
      public static SolidFireAccount getAccountById(SolidFireConnection sfConnection, long accountId) {
          GetAccountByIDRequest request = GetAccountByIDRequest.builder()
                  .accountID(accountId)
                  .build();
  
          Account sfAccount = getSolidFireElement(sfConnection).getAccountByID(request).getAccount();
  
          String sfAccountName = sfAccount.getUsername();
          String sfAccountInitiatorSecret = sfAccount.getInitiatorSecret().isPresent() ? sfAccount.getInitiatorSecret().get().toString() : "";
          String sfAccountTargetSecret = sfAccount.getTargetSecret().isPresent() ? sfAccount.getTargetSecret().get().toString() : "";
  
          return new SolidFireAccount(accountId, sfAccountName, sfAccountInitiatorSecret, sfAccountTargetSecret);
      }
  
      private static SolidFireAccount getAccountByName(SolidFireConnection sfConnection, String accountName) {
          GetAccountByNameRequest request = GetAccountByNameRequest.builder()
                  .username(accountName)
                  .build();
  
          Account sfAccount = getSolidFireElement(sfConnection).getAccountByName(request).getAccount();
  
          long sfAccountId = sfAccount.getAccountID();
          String sfAccountInitiatorSecret = sfAccount.getInitiatorSecret().isPresent() ? sfAccount.getInitiatorSecret().get().toString() : "";
          String sfAccountTargetSecret = sfAccount.getTargetSecret().isPresent() ? sfAccount.getTargetSecret().get().toString() : "";
  
          return new SolidFireAccount(sfAccountId, accountName, sfAccountInitiatorSecret, sfAccountTargetSecret);
      }
  
      public static class SolidFireAccount {
          private final long _id;
          private final String _name;
          private final String _initiatorSecret;
          private final String _targetSecret;
  
          SolidFireAccount(long id, String name, String initiatorSecret, String targetSecret) {
              _id = id;
              _name = name;
              _initiatorSecret = initiatorSecret;
              _targetSecret = targetSecret;
          }
  
          public long getId() {
              return _id;
          }
  
          public String getName() {
              return _name;
          }
  
          @Override
          public int hashCode() {
              return (_id + _name).hashCode();
          }
  
          @Override
          public String toString() {
              return _name;
          }
  
          @Override
          public boolean equals(Object obj) {
              if (obj == null) {
                  return false;
              }
  
              if (!obj.getClass().equals(SolidFireAccount.class)) {
                  return false;
              }
  
              SolidFireAccount sfa = (SolidFireAccount)obj;
  
              return _id == sfa._id && _name.equals(sfa._name) &&
                      _initiatorSecret.equals(sfa._initiatorSecret) &&
                      _targetSecret.equals(sfa._targetSecret);
          }
      }
  
      private static long createVag(SolidFireConnection sfConnection, String vagName, String[] iqns, long[] volumeIds) {
          CreateVolumeAccessGroupRequest request = CreateVolumeAccessGroupRequest.builder()
                  .name(vagName)
                  .optionalInitiators(iqns)
                  .optionalVolumes(Longs.asList(volumeIds).toArray(new Long[volumeIds.length]))
                  .build();
  
          return getSolidFireElement(sfConnection).createVolumeAccessGroup(request).getVolumeAccessGroupID();
      }
  
      private static void addInitiatorsToSolidFireVag(SolidFireConnection sfConnection, long vagId, String[] initiators) {
          AddInitiatorsToVolumeAccessGroupRequest request = AddInitiatorsToVolumeAccessGroupRequest.builder()
                  .volumeAccessGroupID(vagId)
                  .initiators(initiators)
                  .build();
  
          getSolidFireElement(sfConnection).addInitiatorsToVolumeAccessGroup(request);
      }
  
      private static void removeInitiatorsFromSolidFireVag(SolidFireConnection sfConnection, long vagId, String[] initiators) {
          RemoveInitiatorsFromVolumeAccessGroupRequest request = RemoveInitiatorsFromVolumeAccessGroupRequest.builder()
                  .volumeAccessGroupID(vagId)
                  .initiators(initiators)
                  .build();
  
          getSolidFireElement(sfConnection).removeInitiatorsFromVolumeAccessGroup(request);
      }
  
      private static void addVolumeIdsToSolidFireVag(SolidFireConnection sfConnection, long vagId, Long[] volumeIds) {
          AddVolumesToVolumeAccessGroupRequest request = AddVolumesToVolumeAccessGroupRequest.builder()
                  .volumeAccessGroupID(vagId)
                  .volumes(volumeIds)
                  .build();
  
          getSolidFireElement(sfConnection).addVolumesToVolumeAccessGroup(request);
      }
  
      public static void removeVolumeIdsFromSolidFireVag(SolidFireConnection sfConnection, long vagId, Long[] volumeIds) {
          RemoveVolumesFromVolumeAccessGroupRequest request = RemoveVolumesFromVolumeAccessGroupRequest.builder()
                  .volumeAccessGroupID(vagId)
                  .volumes(volumeIds)
                  .build();
  
          getSolidFireElement(sfConnection).removeVolumesFromVolumeAccessGroup(request);
      }
  
      public static List<SolidFireVag> getAllVags(SolidFireConnection sfConnection)
      {
          ListVolumeAccessGroupsRequest request = ListVolumeAccessGroupsRequest.builder().build();
  
          VolumeAccessGroup[] vags = getSolidFireElement(sfConnection).listVolumeAccessGroups(request).getVolumeAccessGroups();
  
          List<SolidFireVag> lstSolidFireVags = new ArrayList<>();
  
          if (vags != null) {
              for (VolumeAccessGroup vag : vags) {
                  SolidFireVag sfVag = new SolidFireVag(vag.getVolumeAccessGroupID(), vag.getInitiators(), toPrimitive(vag.getVolumes()), vag.getName());
  
                  lstSolidFireVags.add(sfVag);
              }
          }
  
          return lstSolidFireVags;
      }
  
      public static class SolidFireVag {
          private final long _id;
          private final String[] _initiators;
          private final long[] _volumeIds;
          private final String _vagName;
  
          SolidFireVag(long id, String[] initiators, long[] volumeIds, String name) {
              _id = id;
              _initiators = initiators;
              _volumeIds = volumeIds;
              _vagName = name;
          }
  
          public long getId() {
              return _id;
          }
  
          public String[] getInitiators() {
              return _initiators;
          }
  
          public long[] getVolumeIds() {
              return _volumeIds;
          }
  
          public String getName() { return _vagName; }
  
          @Override
          public int hashCode() {
              return String.valueOf(_id).hashCode();
          }
  
          @Override
          public String toString() {
              return String.valueOf(_id);
          }
  
          @Override
          public boolean equals(Object obj) {
              if (obj == null) {
                  return false;
              }
  
              if (!obj.getClass().equals(SolidFireVag.class)) {
                  return false;
              }
  
              SolidFireVag sfVag = (SolidFireVag)obj;
  
              return _id == sfVag._id;
          }
      }
  
      private static String getVip(String keyToMatch, String url) {
          String delimiter = ":";
  
          String storageVip = getValue(keyToMatch, url);
  
          int index = storageVip.indexOf(delimiter);
  
          if (index != -1) {
              return storageVip.substring(0, index);
          }
  
          return storageVip;
      }
  
      private static int getPort(String keyToMatch, String url, int defaultPortNumber) {
          String delimiter = ":";
  
          String storageVip = getValue(keyToMatch, url);
  
          int index = storageVip.indexOf(delimiter);
  
          int portNumber = defaultPortNumber;
  
          if (index != -1) {
              String port = storageVip.substring(index + delimiter.length());
  
              try {
                  portNumber = Integer.parseInt(port);
              } catch (NumberFormatException ex) {
                  throw new IllegalArgumentException("Invalid URL format (port is not an integer)");
              }
          }
  
          return portNumber;
      }
  
      private static List<String> getStringArrayAsLowerCaseStringList(String[] aString) {
          List<String> lstLowerCaseString = new ArrayList<>();
  
          if (aString != null) {
              for (String str : aString) {
                  if (str != null) {
                      lstLowerCaseString.add(str.toLowerCase());
                  }
              }
          }
  
          return lstLowerCaseString;
      }
  
      private static Map<String, Object> convertMap(Map<String, String> map) {
          if (map == null) {
              return null;
          }
  
          return new HashMap<>(map);
      }
  }
  