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.motion;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  
  import javax.inject.Inject;
  
  import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
  import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy;
  import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
  import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
  import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
  import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
  import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
  import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
  import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
  import org.apache.cloudstack.storage.to.VolumeObjectTO;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  import org.springframework.stereotype.Component;
  
  import com.cloud.agent.AgentManager;
  import com.cloud.agent.api.Answer;
  import com.cloud.agent.api.MigrateWithStorageAnswer;
  import com.cloud.agent.api.MigrateWithStorageCommand;
  import com.cloud.agent.api.to.StorageFilerTO;
  import com.cloud.agent.api.to.VirtualMachineTO;
  import com.cloud.agent.api.to.VolumeTO;
  import com.cloud.exception.AgentUnavailableException;
  import com.cloud.exception.OperationTimedoutException;
  import com.cloud.host.Host;
  import com.cloud.hypervisor.Hypervisor.HypervisorType;
  import com.cloud.storage.Storage.StoragePoolType;
  import com.cloud.storage.StoragePool;
  import com.cloud.storage.VolumeVO;
  import com.cloud.storage.dao.VolumeDao;
  import com.cloud.utils.Pair;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.vm.VMInstanceVO;
  import com.cloud.vm.dao.VMInstanceDao;
  
  @Component
  public class HypervStorageMotionStrategy implements DataMotionStrategy {
      protected Logger logger = LogManager.getLogger(getClass());
      @Inject AgentManager agentMgr;
      @Inject VolumeDao volDao;
      @Inject VolumeDataFactory volFactory;
      @Inject PrimaryDataStoreDao storagePoolDao;
      @Inject VMInstanceDao instanceDao;
  
      @Override
      public StrategyPriority canHandle(DataObject srcData, DataObject destData) {
          return StrategyPriority.CANT_HANDLE;
      }
  
      @Override
      public StrategyPriority canHandle(Map<VolumeInfo, DataStore> volumeMap, Host srcHost, Host destHost) {
          if (srcHost.getHypervisorType() == HypervisorType.Hyperv &&
                  destHost.getHypervisorType() == HypervisorType.Hyperv) {
              return StrategyPriority.HYPERVISOR;
          }
  
          return StrategyPriority.CANT_HANDLE;
      }
  
      @Override
      public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
          throw new UnsupportedOperationException();
      }
  
      @Override
      public void copyAsync(Map<VolumeInfo, DataStore> volumeMap, VirtualMachineTO vmTo, Host srcHost, Host destHost,
              AsyncCompletionCallback<CopyCommandResult> callback) {
          Answer answer = null;
          String errMsg = null;
          try {
              VMInstanceVO instance = instanceDao.findById(vmTo.getId());
              if (instance != null) {
                  answer = migrateVmWithVolumes(instance, vmTo, srcHost, destHost, volumeMap);
              } else {
                  throw new CloudRuntimeException("Unsupported operation requested for moving data.");
              }
          } catch (Exception e) {
              logger.error("copy failed", e);
              errMsg = e.toString();
          }
  
          CopyCommandResult result = new CopyCommandResult(null, answer);
          result.setResult(errMsg);
          callback.complete(result);
      }
  
      private Answer migrateVmWithVolumes(VMInstanceVO vm, VirtualMachineTO to, Host srcHost,
              Host destHost, Map<VolumeInfo, DataStore> volumeToPool) throws AgentUnavailableException {
  
          // Initiate migration of a virtual machine with it's volumes.
          try {
              List<Pair<VolumeTO, StorageFilerTO>> volumeToFilerto = new ArrayList<Pair<VolumeTO, StorageFilerTO>>();
              for (Map.Entry<VolumeInfo, DataStore> entry : volumeToPool.entrySet()) {
                  VolumeInfo volume = entry.getKey();
                  VolumeTO volumeTo = new VolumeTO(volume, storagePoolDao.findById(volume.getPoolId()));
                  StorageFilerTO filerTo = new StorageFilerTO((StoragePool)entry.getValue());
                  volumeToFilerto.add(new Pair<VolumeTO, StorageFilerTO>(volumeTo, filerTo));
              }
  
              MigrateWithStorageCommand command = new MigrateWithStorageCommand(to, volumeToFilerto, destHost.getPrivateIpAddress());
              MigrateWithStorageAnswer answer = (MigrateWithStorageAnswer) agentMgr.send(srcHost.getId(), command);
              if (answer == null) {
                  logger.error("Migration with storage of vm " + vm + " failed.");
                  throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost);
              } else if (!answer.getResult()) {
                  logger.error("Migration with storage of vm " + vm+ " failed. Details: " + answer.getDetails());
                  throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost +
                          ". " + answer.getDetails());
              } else {
                  // Update the volume details after migration.
                  updateVolumePathsAfterMigration(volumeToPool, answer.getVolumeTos());
              }
  
              return answer;
          } catch (OperationTimedoutException e) {
              logger.error("Error while migrating vm " + vm + " to host " + destHost, e);
              throw new AgentUnavailableException("Operation timed out on storage motion for " + vm, destHost.getId());
          }
      }
  
      private void updateVolumePathsAfterMigration(Map<VolumeInfo, DataStore> volumeToPool, List<VolumeObjectTO> volumeTos) {
          for (Map.Entry<VolumeInfo, DataStore> entry : volumeToPool.entrySet()) {
              boolean updated = false;
              VolumeInfo volume = entry.getKey();
              StoragePool pool = (StoragePool)entry.getValue();
              for (VolumeObjectTO volumeTo : volumeTos) {
                  if (volume.getId() == volumeTo.getId()) {
                      VolumeVO volumeVO = volDao.findById(volume.getId());
                      Long oldPoolId = volumeVO.getPoolId();
                      volumeVO.setPath(volumeTo.getPath());
                      volumeVO.setPodId(pool.getPodId());
                      volumeVO.setPoolId(pool.getId());
                      volumeVO.setPoolType(pool.getPoolType());
                      volumeVO.setLastPoolId(oldPoolId);
                      // For SMB, pool credentials are also stored in the uri query string.  We trim the query string
                      // part  here to make sure the credentials do not get stored in the db unencrypted.
                      String folder = pool.getPath();
                      if (pool.getPoolType() == StoragePoolType.SMB && folder != null && folder.contains("?")) {
                          folder = folder.substring(0, folder.indexOf("?"));
                      }
                      volumeVO.setFolder(folder);
  
                      volDao.update(volume.getId(), volumeVO);
                      updated = true;
                      break;
                  }
              }
  
              if (!updated) {
                  logger.error("Volume path wasn't updated for volume " + volume + " after it was migrated.");
              }
          }
      }
  }
  