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.provider;
  
  import java.util.HashMap;
  import java.util.Map;
  
  import javax.inject.Inject;
  
  import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
  import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
  import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
  import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
  import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
  import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
  import org.apache.cloudstack.storage.datastore.manager.ScaleIOSDCManager;
  import org.apache.cloudstack.storage.datastore.manager.ScaleIOSDCManagerImpl;
  import org.apache.commons.collections.MapUtils;
  import org.apache.commons.lang3.StringUtils;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  
  import com.cloud.agent.AgentManager;
  import com.cloud.agent.api.Answer;
  import com.cloud.agent.api.ModifyStoragePoolAnswer;
  import com.cloud.agent.api.ModifyStoragePoolCommand;
  import com.cloud.alert.AlertManager;
  import com.cloud.host.HostVO;
  import com.cloud.host.dao.HostDao;
  import com.cloud.storage.DataStoreRole;
  import com.cloud.storage.StoragePool;
  import com.cloud.storage.StoragePoolHostVO;
  import com.cloud.storage.dao.StoragePoolHostDao;
  import com.cloud.utils.component.ComponentContext;
  import com.cloud.utils.exception.CloudRuntimeException;
  
  public class ScaleIOHostListener implements HypervisorHostListener {
      protected Logger logger = LogManager.getLogger(getClass());
  
      @Inject private AgentManager _agentMgr;
      @Inject private AlertManager _alertMgr;
      @Inject private DataStoreManager _dataStoreMgr;
      @Inject private HostDao _hostDao;
      @Inject private StoragePoolHostDao _storagePoolHostDao;
      @Inject private StoragePoolDetailsDao _storagePoolDetailsDao;
      private ScaleIOSDCManager _sdcManager = new ScaleIOSDCManagerImpl();
  
      @Override
      public boolean hostAdded(long hostId) {
          return true;
      }
  
      @Override
      public boolean hostConnect(long hostId, long poolId) {
          HostVO host = _hostDao.findById(hostId);
          if (host == null) {
              logger.error("Failed to connect host by HostListener as host was not found with id : " + hostId);
              return false;
          }
  
          DataStore dataStore = _dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary);
          StoragePool storagePool = (StoragePool) dataStore;
          StoragePoolHostVO storagePoolHost = _storagePoolHostDao.findByPoolHost(poolId, hostId);
          String sdcId = getSdcIdOfHost(host, dataStore);
          if (StringUtils.isBlank(sdcId)) {
              if (storagePoolHost != null) {
                  _storagePoolHostDao.deleteStoragePoolHostDetails(hostId, poolId);
              }
          } else {
              if (storagePoolHost == null) {
                  storagePoolHost = new StoragePoolHostVO(poolId, hostId, sdcId);
                  _storagePoolHostDao.persist(storagePoolHost);
              } else {
                  storagePoolHost.setLocalPath(sdcId);
                  _storagePoolHostDao.update(storagePoolHost.getId(), storagePoolHost);
              }
              logger.info("Connection established between storage pool: {} and host: {}", storagePool, host);
          }
          return true;
      }
  
      private String getSdcIdOfHost(HostVO host, DataStore dataStore) {
          StoragePool storagePool = (StoragePool) dataStore;
          long hostId = host.getId();
          long poolId = storagePool.getId();
          String systemId = null;
          StoragePoolDetailVO systemIdDetail = _storagePoolDetailsDao.findDetail(poolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
          if (systemIdDetail != null) {
              systemId = systemIdDetail.getValue();
          }
          if (systemId == null) {
              throw new CloudRuntimeException("Failed to get the system id for PowerFlex storage pool " + storagePool.getName());
          }
          Map<String, String> details = new HashMap<>();
          details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
          _sdcManager = ComponentContext.inject(_sdcManager);
          _sdcManager.populateSdcSettings(details, host.getDataCenterId());
          if (_sdcManager.areSDCConnectionsWithinLimit(poolId)) {
              details.put(ScaleIOSDCManager.ConnectOnDemand.key(), String.valueOf(ScaleIOSDCManager.ConnectOnDemand.valueIn(host.getDataCenterId())));
              String mdms = _sdcManager.getMdms(poolId);
              details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, mdms);
          }
  
          ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(true, storagePool, storagePool.getPath(), details);
          ModifyStoragePoolAnswer answer  = sendModifyStoragePoolCommand(cmd, storagePool, host);
          Map<String, String> poolDetails = answer.getPoolInfo().getDetails();
          if (MapUtils.isEmpty(poolDetails)) {
              String msg = String.format("PowerFlex storage SDC details not found on the host: %s, (re)install SDC and restart agent", host);
              logger.warn(msg);
              _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), host.getPodId(), "SDC details not found on host: " + host.getUuid(), msg);
              return null;
          }
  
          String sdcId = null;
          if (poolDetails.containsKey(ScaleIOGatewayClient.SDC_ID)) {
              sdcId = poolDetails.get(ScaleIOGatewayClient.SDC_ID);
          } else if (poolDetails.containsKey(ScaleIOGatewayClient.SDC_GUID)) {
              String sdcGuid = poolDetails.get(ScaleIOGatewayClient.SDC_GUID);
              sdcId = _sdcManager.getHostSdcId(sdcGuid, dataStore);
          }
  
          if (StringUtils.isBlank(sdcId)) {
              String msg = String.format("Couldn't retrieve PowerFlex storage SDC details from the host: %s, add MDMs if On-demand connect disabled or try (re)install SDC & restart agent", host);
              logger.warn(msg);
              _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), host.getPodId(), "SDC details not found on host: " + host.getUuid(), msg);
              return null;
          }
  
          if (details.containsKey(ScaleIOSDCManager.ConnectOnDemand.key())) {
              String connectOnDemand = details.get(ScaleIOSDCManager.ConnectOnDemand.key());
              if (connectOnDemand != null && !Boolean.parseBoolean(connectOnDemand) && !_sdcManager.isHostSdcConnected(sdcId, dataStore, 15)) {
                  logger.warn("SDC not connected on the host: " + hostId);
                  String msg = "SDC not connected on the host: " + hostId + ", reconnect the SDC to MDM and restart agent";
                  _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), host.getPodId(), "SDC not connected on host: " + host.getUuid(), msg);
                  return null;
              }
          }
  
          return sdcId;
      }
  
      private ModifyStoragePoolAnswer sendModifyStoragePoolCommand(ModifyStoragePoolCommand cmd, StoragePool storagePool, HostVO host) {
          Answer answer = _agentMgr.easySend(host.getId(), cmd);
  
          if (answer == null) {
              throw new CloudRuntimeException(String.format("Unable to get an answer to the modify storage pool command (add: %s) for PowerFlex storage pool %s, sent to host %s",
                      cmd.getAdd(), getStoragePoolDetails(storagePool), host));
          }
  
          if (!answer.getResult()) {
              if (cmd.getAdd()) {
                  String msg = "Unable to attach PowerFlex storage pool " + getStoragePoolDetails(storagePool) + " to the host " + host;
  
                  _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, storagePool.getDataCenterId(), storagePool.getPodId(), msg, msg);
  
                  throw new CloudRuntimeException("Unable to connect to PowerFlex storage pool " + getStoragePoolDetails(storagePool) + " due to " + answer.getDetails());
              } else {
                  String msg = "Unable to detach PowerFlex storage pool " + getStoragePoolDetails(storagePool) + " from the host " + host;
  
                  _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, storagePool.getDataCenterId(), storagePool.getPodId(), msg, msg);
              }
          }
  
          assert (answer instanceof ModifyStoragePoolAnswer) : "ModifyStoragePoolAnswer expected ; PowerFlex Storage Pool = " + storagePool + " Host = " + host;
  
          return (ModifyStoragePoolAnswer) answer;
      }
  
      @Override
      public boolean hostDisconnected(long hostId, long poolId) {
          HostVO host = _hostDao.findById(hostId);
          if (host == null) {
              logger.error("Failed to disconnect host by HostListener as host was not found with id : " + hostId);
              return false;
          }
  
          DataStore dataStore = _dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary);
          StoragePool storagePool = (StoragePool) dataStore;
          String systemId = null;
          StoragePoolDetailVO systemIdDetail = _storagePoolDetailsDao.findDetail(poolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
          if (systemIdDetail != null) {
              systemId = systemIdDetail.getValue();
          }
          if (systemId == null) {
              throw new CloudRuntimeException("Failed to get the system id for PowerFlex storage pool " + storagePool.getName());
          }
          Map<String, String> details = new HashMap<>();
          details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
          _sdcManager = ComponentContext.inject(_sdcManager);
          _sdcManager.populateSdcSettings(details, host.getDataCenterId());
          if (_sdcManager.canUnprepareSDC(host, dataStore)) {
              details.put(ScaleIOSDCManager.ConnectOnDemand.key(), String.valueOf(ScaleIOSDCManager.ConnectOnDemand.valueIn(host.getDataCenterId())));
              String mdms = _sdcManager.getMdms(poolId);
              details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, mdms);
          }
  
          ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(false, storagePool, storagePool.getPath(), details);
          ModifyStoragePoolAnswer answer  = sendModifyStoragePoolCommand(cmd, storagePool, host);
          if (!answer.getResult()) {
              logger.error("Failed to disconnect storage pool: " + storagePool + " and host: " + hostId);
              return false;
          }
  
          StoragePoolHostVO storagePoolHost = _storagePoolHostDao.findByPoolHost(poolId, hostId);
          if (storagePoolHost != null) {
              _storagePoolHostDao.deleteStoragePoolHostDetails(hostId, poolId);
          }
          logger.info("Connection removed between storage pool: " + storagePool + " and host: " + hostId);
          return true;
      }
  
      @Override
      public boolean hostAboutToBeRemoved(long hostId) {
          return true;
      }
  
      @Override
      public boolean hostRemoved(long hostId, long clusterId) {
          return true;
      }
  
      @Override
      public boolean hostEnabled(long hostId) {
          return true;
      }
  
      private String getStoragePoolDetails(StoragePool storagePool) {
          String poolDetails = "";
          if (storagePool != null) {
              poolDetails = String.format("%s (id: %d, uuid: %s)", storagePool.getName(), storagePool.getId(), storagePool.getUuid());
          }
          return poolDetails;
      }
  }
  