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.lifecycle;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  import java.util.StringTokenizer;
  
  import javax.inject.Inject;
  
  import com.cloud.host.Host;
  import com.cloud.storage.dao.StoragePoolAndAccessGroupMapDao;
  import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
  import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
  import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
  import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
  import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle;
  import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters;
  import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
  import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
  import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
  import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
  import org.apache.cloudstack.storage.datastore.util.ElastistorUtil;
  import org.apache.cloudstack.storage.datastore.util.ElastistorUtil.FileSystem;
  import org.apache.cloudstack.storage.datastore.util.ElastistorUtil.Tsm;
  import org.apache.cloudstack.storage.datastore.util.ElastistorUtil.UpdateTsmCmdResponse;
  import org.apache.cloudstack.storage.datastore.util.ElastistorUtil.UpdateTsmStorageCmdResponse;
  import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
  
  import com.cloud.agent.AgentManager;
  import com.cloud.agent.api.Answer;
  import com.cloud.agent.api.CreateStoragePoolCommand;
  import com.cloud.agent.api.DeleteStoragePoolCommand;
  import com.cloud.agent.api.StoragePoolInfo;
  import com.cloud.capacity.CapacityManager;
  import com.cloud.dc.dao.DataCenterDao;
  import com.cloud.host.HostVO;
  import com.cloud.host.dao.HostDao;
  import com.cloud.hypervisor.Hypervisor.HypervisorType;
  import com.cloud.resource.ResourceManager;
  import com.cloud.storage.Storage.StoragePoolType;
  import com.cloud.storage.StorageManager;
  import com.cloud.storage.StoragePool;
  import com.cloud.storage.StoragePoolAutomation;
  import com.cloud.storage.StoragePoolHostVO;
  import com.cloud.storage.dao.StoragePoolHostDao;
  import com.cloud.utils.exception.CloudRuntimeException;
  
  public class ElastistorPrimaryDataStoreLifeCycle extends BasePrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle {
      @Inject
      HostDao _hostDao;
      @Inject
      StoragePoolHostDao _storagePoolHostDao;
      @Inject
      protected ResourceManager _resourceMgr;
      @Inject
      PrimaryDataStoreDao primaryDataStoreDao;
      @Inject
      AgentManager agentMgr;
      @Inject
      StorageManager storageMgr;
      @Inject
      PrimaryDataStoreHelper dataStoreHelper;
      @Inject
      PrimaryDataStoreDao _storagePoolDao;
      @Inject
      PrimaryDataStoreHelper _dataStoreHelper;
      @Inject
      StoragePoolAutomation _storagePoolAutomation;
      @Inject
      StoragePoolDetailsDao _storagePoolDetailsDao;
      @Inject
      DataCenterDao _zoneDao;
      @Inject
      CapacityManager _capacityMgr;
      @Inject
      private StoragePoolAndAccessGroupMapDao storagePoolAndAccessGroupMapDao;
  
      @Override
      public DataStore initialize(Map<String, Object> dsInfos) {
  
          String url = (String) dsInfos.get("url");
          Long zoneId = (Long) dsInfos.get("zoneId");
          Long podId = (Long) dsInfos.get("podId");
          Long clusterId = (Long) dsInfos.get("clusterId");
          String storagePoolName = (String) dsInfos.get("name");
          String providerName = (String) dsInfos.get("providerName");
          Long capacityBytes = (Long) dsInfos.get("capacityBytes");
          Long capacityIops = (Long) dsInfos.get("capacityIops");
          String tags = (String) dsInfos.get("tags");
          Boolean isTagARule = (Boolean) dsInfos.get("isTagARule");
          boolean managed = (Boolean) dsInfos.get("managed");
          Map<String, String> details = (Map<String, String>) dsInfos.get("details");
          String domainName = details.get("domainname");
  
          String storageIp;
          int storagePort = 0;
          StoragePoolType storagetype = null;
          String accesspath = null;
          String protocoltype = null;
          String mountpoint = null;
  
          if (!managed) {
              storageIp = getStorageIp(url);
              storagePort = getDefaultStoragePort(url);
              storagetype = getStorageType(url);
              accesspath = getAccessPath(url);
              protocoltype = getProtocolType(url);
              String[] mp = accesspath.split("/");
              mountpoint = mp[1];
  
          } else if (details.get("hypervisortype") == "KVM") {
              storageIp = url;
              storagePort = 3260;
              storagetype = StoragePoolType.Iscsi;
              accesspath = storageIp + ":/" + storagePoolName;
          }else{
              storageIp = url;
              storagePort = 2049;
              storagetype = StoragePoolType.NetworkFilesystem;
              accesspath = storageIp + ":/" + storagePoolName;
          }
          /**
           * if the elastistor params which are required for plugin configuration
           * are not injected through spring-storage-volume-cloudbyte-context.xml,
           * it can be set from details map.
           */
          if (details.get("esaccountid") != null)
              ElastistorUtil.setElastistorAccountId(details.get("esaccountid"));
          if (details.get("esdefaultgateway") != null)
              ElastistorUtil.setElastistorGateway(details.get("esdefaultgateway"));
          if (details.get("estntinterface") != null)
              ElastistorUtil.setElastistorInterface(details.get("estntinterface"));
          if (details.get("espoolid") != null)
              ElastistorUtil.setElastistorPoolId(details.get("espoolid"));
          if (details.get("essubnet") != null)
              ElastistorUtil.setElastistorSubnet(details.get("essubnet"));
  
          logger.info("Elastistor details was set successfully.");
  
          if (capacityBytes == null || capacityBytes <= 0) {
              throw new IllegalArgumentException("'capacityBytes' must be present and greater than 0.");
          }
  
          if (capacityIops == null || capacityIops <= 0) {
              throw new IllegalArgumentException("'capacityIops' must be present and greater than 0.");
          }
  
          if (domainName == null) {
              domainName = "ROOT";
              logger.debug("setting the domain to ROOT");
          }
  
          // elastistor does not allow same name and ip pools.
          List<StoragePoolVO> storagePoolVO = _storagePoolDao.listAll();
          for (StoragePoolVO poolVO : storagePoolVO) {
              if (storagePoolName.equals(poolVO.getName())) {
                  throw new IllegalArgumentException("Storage pool with this name already exists in elastistor, please specify a unique name. [name:" + storagePoolName + "]");
              }
              if (storageIp.equals(poolVO.getHostAddress())) {
                  throw new IllegalArgumentException("Storage pool with this ip already exists in elastistor, please specify a unique ip. [ip:" + storageIp + "]");
              }
          }
  
          PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters();
  
          parameters.setHost(storageIp);
          parameters.setPort(storagePort);
          parameters.setPath(accesspath);
          parameters.setType(storagetype);
          parameters.setZoneId(zoneId);
          parameters.setPodId(podId);
          parameters.setName(storagePoolName);
          parameters.setProviderName(providerName);
          parameters.setManaged(managed);
          parameters.setCapacityBytes(capacityBytes);
          parameters.setUsedBytes(0);
          parameters.setCapacityIops(capacityIops);
          parameters.setHypervisorType(HypervisorType.Any);
          parameters.setTags(tags);
          parameters.setIsTagARule(isTagARule);
          parameters.setDetails(details);
          parameters.setClusterId(clusterId);
  
          Tsm tsm = null;
          if (managed) {
              // creates the TSM in elastistor
              tsm = createElastistorTSM(storagePoolName, storageIp, capacityBytes, capacityIops, domainName);
          } else {
              // creates the TSM & Volume in elastistor
              tsm = createElastistorTSM(storagePoolName, storageIp, capacityBytes, capacityIops, domainName);
  
              parameters = createElastistorVolume(parameters, tsm, storagePoolName, capacityBytes, capacityIops, protocoltype, mountpoint);
          }
  
          // setting tsm's uuid as storagepool's uuid
          parameters.setUuid(tsm.getUuid());
  
          return _dataStoreHelper.createPrimaryDataStore(parameters);
      }
  
      private Tsm createElastistorTSM(String storagePoolName, String storageIp, Long capacityBytes, Long capacityIops, String domainName) {
  
          logger.info("Creation of elastistor TSM started.");
  
          Tsm tsm;
          String elastistorAccountId;
          try {
              // to create a tsm , account id is required, so getting the account id for the given cloudstack domain
              elastistorAccountId = ElastistorUtil.getElastistorAccountId(domainName);
  
              // create the tsm for the given account id
              tsm = ElastistorUtil.createElastistorTsm(storagePoolName, storageIp, capacityBytes, capacityIops, elastistorAccountId);
          } catch (Throwable e) {
              logger.error("Failed to create TSM in elastistor.", e);
              throw new CloudRuntimeException("Failed to create TSM in elastistor. " + e.getMessage());
          }
  
          logger.info("Creation of elastistor TSM completed successfully.");
  
          return tsm;
      }
  
      private PrimaryDataStoreParameters createElastistorVolume(PrimaryDataStoreParameters parameters, Tsm tsm, String storagePoolName, Long capacityBytes, Long capacityIops, String protocoltype,
              String mountpoint) {
  
          try {
  
              logger.info("Creation of elastistor volume started.");
  
              FileSystem volume = ElastistorUtil.createElastistorVolume(storagePoolName, tsm.getUuid(), capacityBytes, capacityIops, protocoltype, mountpoint);
  
              if (protocoltype.contentEquals("iscsi")) {
                  String accesspath = "/" + volume.getIqn() + "/0";
                  parameters.setPath(accesspath);
              }
              logger.info("Creation of elastistor volume completed successfully.");
  
              return parameters;
          } catch (Throwable e) {
              logger.error("Failed to create volume in elastistor.", e);
              throw new CloudRuntimeException("Failed to create volume in elastistor. " + e.getMessage());
          }
  
      }
  
      private String getAccessPath(String url) {
          StringTokenizer st = new StringTokenizer(url, "/");
          int count = 0;
          while (st.hasMoreElements()) {
              if (count == 2) {
                  String s = "/";
                  return s.concat(st.nextElement().toString());
              }
              st.nextElement();
              count++;
          }
          return null;
      }
  
      private StoragePoolType getStorageType(String url) {
  
          StringTokenizer st = new StringTokenizer(url, ":");
  
          while (st.hasMoreElements()) {
              String accessprotocol = st.nextElement().toString();
  
              if (accessprotocol.contentEquals("nfs")) {
                  return StoragePoolType.NetworkFilesystem;
              } else if (accessprotocol.contentEquals("iscsi")) {
                  return StoragePoolType.IscsiLUN;
              }
  
              else
  
                  break;
  
          }
          return null;
      }
  
      private String getProtocolType(String url) {
          StringTokenizer st = new StringTokenizer(url, ":");
  
          while (st.hasMoreElements()) {
              String accessprotocol = st.nextElement().toString();
  
              if (accessprotocol.contentEquals("nfs")) {
                  return "nfs";
              } else if (accessprotocol.contentEquals("iscsi")) {
                  return "iscsi";
              } else
                  break;
          }
          return null;
      }
  
      // this method parses the url and gets the default storage port based on
      // access protocol
      private int getDefaultStoragePort(String url) {
  
          StringTokenizer st = new StringTokenizer(url, ":");
  
          while (st.hasMoreElements()) {
  
              String accessprotocol = st.nextElement().toString();
  
              if (accessprotocol.contentEquals("nfs")) {
                  return 2049;
              } else if (accessprotocol.contentEquals("iscsi")) {
                  return 3260;
              } else
                  break;
  
          }
          return -1;
  
      }
  
      // parses the url and returns the storage volume ip
      private String getStorageIp(String url) {
  
          StringTokenizer st = new StringTokenizer(url, "/");
          int count = 0;
  
          while (st.hasMoreElements()) {
              if (count == 1)
                  return st.nextElement().toString();
  
              st.nextElement();
              count++;
          }
          return null;
      }
  
      @Override
      public boolean attachCluster(DataStore store, ClusterScope scope) {
  
          dataStoreHelper.attachCluster(store);
  
          StoragePoolVO dataStoreVO = _storagePoolDao.findById(store.getId());
  
          PrimaryDataStoreInfo primarystore = (PrimaryDataStoreInfo) store;
          List<HostVO> hostsToConnect = _resourceMgr.getEligibleUpHostsInClusterForStorageConnection(primarystore);
          logger.debug(String.format("Attaching the pool to each of the hosts %s in the cluster: %s", hostsToConnect, primarystore.getClusterId()));
  
          if (!dataStoreVO.isManaged()) {
              boolean success = false;
              for (HostVO h : hostsToConnect) {
                  success = createStoragePool(h, primarystore);
                  if (success) {
                      break;
                  }
              }
          }
  
          logger.debug("In createPool Adding the pool to each of the hosts");
          List<HostVO> poolHosts = new ArrayList<HostVO>();
          for (HostVO h : hostsToConnect) {
              try {
                  storageMgr.connectHostToSharedPool(h, primarystore.getId());
                  poolHosts.add(h);
              } catch (Exception e) {
                  logger.warn("Unable to establish a connection between " + h + " and " + primarystore, e);
              }
  
              if (poolHosts.isEmpty()) {
                  logger.warn("No host can access storage pool " + primarystore + " on cluster " + primarystore.getClusterId());
                  primaryDataStoreDao.expunge(primarystore.getId());
                  throw new CloudRuntimeException("Failed to access storage pool");
              }
          }
  
          return true;
      }
  
      private boolean createStoragePool(Host host, StoragePool pool) {
          logger.debug(String.format("creating pool %s on host %s", pool, host));
          if (pool.getPoolType() != StoragePoolType.NetworkFilesystem && pool.getPoolType() != StoragePoolType.Filesystem && pool.getPoolType() != StoragePoolType.IscsiLUN
                  && pool.getPoolType() != StoragePoolType.Iscsi && pool.getPoolType() != StoragePoolType.VMFS && pool.getPoolType() != StoragePoolType.SharedMountPoint
                  && pool.getPoolType() != StoragePoolType.PreSetup && pool.getPoolType() != StoragePoolType.OCFS2 && pool.getPoolType() != StoragePoolType.RBD
                  && pool.getPoolType() != StoragePoolType.CLVM) {
              logger.warn(" Doesn't support storage pool type " + pool.getPoolType());
              return false;
          }
          CreateStoragePoolCommand cmd = new CreateStoragePoolCommand(true, pool);
          final Answer answer = agentMgr.easySend(host.getId(), cmd);
          if (answer != null && answer.getResult()) {
              return true;
          } else {
              primaryDataStoreDao.expunge(pool.getId());
              String msg = "";
              if (answer != null) {
                  msg = String.format("Can not create storage pool through host %s due to %s", host, answer.getDetails());
                  logger.warn(msg);
              } else {
                  msg = String.format("Can not create storage pool through host %s due to CreateStoragePoolCommand returns null", host);
                  logger.warn(msg);
              }
              throw new CloudRuntimeException(msg);
          }
      }
  
      @Override
      public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) {
          _dataStoreHelper.attachHost(store, scope, existingInfo);
          return true;
      }
  
      @Override
      public boolean attachZone(DataStore dataStore, ZoneScope scope, HypervisorType hypervisorType) {
          List<HostVO> hostsToConnect = _resourceMgr.getEligibleUpAndEnabledHostsInZoneForStorageConnection(dataStore, scope.getScopeId(), hypervisorType);
  
          logger.debug(String.format("In createPool. Attaching the pool to each of the hosts in %s.", hostsToConnect));
          List<HostVO> poolHosts = new ArrayList<HostVO>();
          for (HostVO host : hostsToConnect) {
              try {
                  storageMgr.connectHostToSharedPool(host, dataStore.getId());
                  poolHosts.add(host);
              } catch (Exception e) {
                  logger.warn("Unable to establish a connection between " + host + " and " + dataStore, e);
              }
          }
          if (poolHosts.isEmpty()) {
              logger.warn("No host can access storage pool " + dataStore + " in this zone.");
              primaryDataStoreDao.expunge(dataStore.getId());
              throw new CloudRuntimeException("Failed to create storage pool as it is not accessible to hosts.");
          }
          dataStoreHelper.attachZone(dataStore, hypervisorType);
          return true;
      }
  
      @Override
      public boolean maintain(DataStore store) {
          _storagePoolAutomation.maintain(store);
          _dataStoreHelper.maintain(store);
          return true;
      }
  
      @Override
      public boolean cancelMaintain(DataStore store) {
          _dataStoreHelper.cancelMaintain(store);
          _storagePoolAutomation.cancelMaintain(store);
          return true;
      }
  
      @Override
      public void enableStoragePool(DataStore dataStore) {
          _dataStoreHelper.enable(dataStore);
      }
  
      @Override
      public void disableStoragePool(DataStore dataStore) {
          _dataStoreHelper.disable(dataStore);
      }
  
      @SuppressWarnings("finally")
      @Override
      public boolean deleteDataStore(DataStore store) {
          List<StoragePoolHostVO> hostPoolRecords = _storagePoolHostDao.listByPoolId(store.getId());
          StoragePool pool = (StoragePool) store;
  
          // find the hypervisor where the storage is attached to.
          HypervisorType hType = null;
          if (hostPoolRecords.size() > 0) {
              hType = getHypervisorType(hostPoolRecords.get(0).getHostId());
          }
  
          StoragePoolVO storagePoolVO = _storagePoolDao.findById(store.getId());
  
          if (!(storagePoolVO.isManaged())) {
              // Remove the SR associated with the Xenserver
              for (StoragePoolHostVO host : hostPoolRecords) {
                  DeleteStoragePoolCommand deleteCmd = new DeleteStoragePoolCommand(pool);
                  final Answer answer = agentMgr.easySend(host.getHostId(), deleteCmd);
  
                  if (answer != null && answer.getResult()) {
                      // if host is KVM hypervisor then send deleteStoragepoolcmd
                      // to all the kvm hosts.
                      if (HypervisorType.KVM != hType) {
                          break;
                      }
                  } else {
                      if (answer != null) {
                          logger.error("Failed to delete storage pool: " + answer.getResult());
                      }
                  }
              }
          }
          // delete the Elastistor volume at backend
          deleteElastistorVolume(pool, storagePoolVO.isManaged());
  
          return _dataStoreHelper.deletePrimaryDataStore(store);
      }
  
      private void deleteElastistorVolume(StoragePool pool, boolean managed) {
  
          String poolid = pool.getUuid();
          boolean status;
  
          try {
              status = ElastistorUtil.deleteElastistorTsm(poolid, managed);
          } catch (Throwable e) {
              throw new CloudRuntimeException("Failed to delete primary storage on elastistor" + e);
          }
  
          if (status == true) {
              logger.info("deletion of elastistor primary storage complete");
          } else {
              logger.error("deletion of elastistor volume failed");
          }
  
      }
  
      private HypervisorType getHypervisorType(long hostId) {
          HostVO host = _hostDao.findById(hostId);
          if (host != null)
              return host.getHypervisorType();
          return HypervisorType.None;
      }
  
      @Override
      public boolean migrateToObjectStore(DataStore store) {
          return false;
      }
  
      @Override
      public void updateStoragePool(StoragePool storagePool, Map<String, String> details) {
          String capacityBytes = details.get(PrimaryDataStoreLifeCycle.CAPACITY_BYTES);
          String capacityIops = details.get(PrimaryDataStoreLifeCycle.CAPACITY_IOPS);
  
          StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePool.getId());
  
             try {
                  if(capacityBytes != null){
                      long usedBytes = _capacityMgr.getUsedBytes(storagePoolVO);
  
                      if (Long.parseLong(capacityBytes) < usedBytes) {
                          throw new CloudRuntimeException("Cannot reduce the number of bytes for this storage pool as it would lead to an insufficient number of bytes");
                      }
  
                      UpdateTsmStorageCmdResponse updateTsmStorageCmdResponse  = ElastistorUtil.updateElastistorTsmStorage(capacityBytes,storagePool.getUuid());
  
                     if(updateTsmStorageCmdResponse.getStorage().getId() != null){
                      // update the cloudstack db
                      _storagePoolDao.updateCapacityBytes(storagePool.getId(), Long.parseLong(capacityBytes));
  
                      logger.info("elastistor TSM storage successfully updated");
                     }else{
                         throw new CloudRuntimeException("Failed to update the storage of Elastistor TSM" + updateTsmStorageCmdResponse.toString());
                     }
                  }
  
                  if(capacityIops != null){
  
                      long usedIops = _capacityMgr.getUsedIops(storagePoolVO);
                  long capacity = Long.parseLong(capacityIops);
  
                  if (capacity < usedIops) {
                          throw new CloudRuntimeException("Cannot reduce the number of IOPS for this storage pool as it would lead to an insufficient number of IOPS");
                      }
  
                      UpdateTsmCmdResponse updateTsmCmdResponse   = ElastistorUtil.updateElastistorTsmIOPS(capacityIops,storagePool.getUuid());
  
                     if(updateTsmCmdResponse.getTsm(0).getUuid() != null){
                     // update the cloudstack db
                      _storagePoolDao.updateCapacityIops(storagePool.getId(), capacity);
  
                      logger.info("elastistor TSM IOPS successfully updated");
  
                     }else{
                         throw new CloudRuntimeException("Failed to update the IOPS of Elastistor TSM" + updateTsmCmdResponse.toString());
                     }
                  }
  
              } catch (Throwable e) {
                  throw new CloudRuntimeException("Failed to update the storage pool" + e);
              }
  
  
      }
  
  }
  