blob: bf8fa43fe6c2e57e7a25a12791254a1b26f86b02 [file] [log] [blame]
/*
* 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.io.File;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.storage.command.CopyCommand;
import org.apache.cloudstack.storage.datastore.DataStoreManagerImpl;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.MigrateCommand;
import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo;
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.DataStoreRole;
import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.VMTemplateStoragePoolVO;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VirtualMachineManager;
/**
* Extends {@link StorageSystemDataMotionStrategy}, allowing KVM hosts to migrate VMs with the ROOT volume on a non managed local storage pool.
* As {@link StorageSystemDataMotionStrategy} is considering KVM, this implementation also migrates only from/to KVM hosts.
*/
public class KvmNonManagedStorageDataMotionStrategy extends StorageSystemDataMotionStrategy {
@Inject
private TemplateDataFactory templateDataFactory;
@Inject
private VMTemplatePoolDao vmTemplatePoolDao;
@Inject
private DataStoreManagerImpl dataStoreManagerImpl;
@Inject
private VirtualMachineManager virtualMachineManager;
/**
* Uses the canHandle from the Super class {@link StorageSystemDataMotionStrategy}. If the storage pool is of file and the internalCanHandle from {@link StorageSystemDataMotionStrategy} CANT_HANDLE, returns the StrategyPriority.HYPERVISOR strategy priority. otherwise returns CANT_HANDLE.
* Note that the super implementation (override) is called by {@link #canHandle(Map, Host, Host)} which ensures that {@link #internalCanHandle(Map)} will be executed only if the source host is KVM.
*/
@Override
protected StrategyPriority internalCanHandle(Map<VolumeInfo, DataStore> volumeMap, Host srcHost, Host destHost) {
if (super.internalCanHandle(volumeMap, srcHost, destHost) != StrategyPriority.CANT_HANDLE) {
return StrategyPriority.CANT_HANDLE;
}
if (canHandleKVMNonManagedLiveNFSStorageMigration(volumeMap, srcHost, destHost) != StrategyPriority.CANT_HANDLE) {
return StrategyPriority.HYPERVISOR;
}
Set<VolumeInfo> volumeInfoSet = volumeMap.keySet();
for (VolumeInfo volumeInfo : volumeInfoSet) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(volumeInfo.getPoolId());
if (!supportStoragePoolType(storagePoolVO.getPoolType())) {
return StrategyPriority.CANT_HANDLE;
}
}
return StrategyPriority.HYPERVISOR;
}
/**
* Allow KVM live storage migration for non managed storage when:
* - Source host and destination host are different, and are on the same cluster
* - Source and destination storage are NFS
* - Destination storage is cluster-wide
*/
protected StrategyPriority canHandleKVMNonManagedLiveNFSStorageMigration(Map<VolumeInfo, DataStore> volumeMap,
Host srcHost, Host destHost) {
if (srcHost.getId() != destHost.getId() &&
srcHost.getClusterId().equals(destHost.getClusterId()) &&
isSourceNfsPrimaryStorage(volumeMap) &&
isDestinationNfsPrimaryStorageClusterWide(volumeMap)) {
return StrategyPriority.HYPERVISOR;
}
return StrategyPriority.CANT_HANDLE;
}
/**
* True if volumes source storage are NFS
*/
protected boolean isSourceNfsPrimaryStorage(Map<VolumeInfo, DataStore> volumeMap) {
if (MapUtils.isNotEmpty(volumeMap)) {
for (VolumeInfo volumeInfo : volumeMap.keySet()) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(volumeInfo.getPoolId());
return storagePoolVO != null &&
storagePoolVO.getPoolType() == Storage.StoragePoolType.NetworkFilesystem;
}
}
return false;
}
/**
* True if destination storage is cluster-wide NFS
*/
protected boolean isDestinationNfsPrimaryStorageClusterWide(Map<VolumeInfo, DataStore> volumeMap) {
if (MapUtils.isNotEmpty(volumeMap)) {
for (DataStore dataStore : volumeMap.values()) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(dataStore.getId());
return storagePoolVO != null &&
storagePoolVO.getPoolType() == Storage.StoragePoolType.NetworkFilesystem &&
storagePoolVO.getScope() == ScopeType.CLUSTER;
}
}
return false;
}
/**
* Configures a {@link MigrateDiskInfo} object configured for migrating a File System volume and calls rootImageProvisioning.
*/
@Override
protected MigrateCommand.MigrateDiskInfo configureMigrateDiskInfo(VolumeInfo srcVolumeInfo, String destPath, String backingPath) {
return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(), MigrateCommand.MigrateDiskInfo.DiskType.FILE, MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
MigrateCommand.MigrateDiskInfo.Source.FILE, destPath, backingPath);
}
/**
* Generates the volume path by appending the Volume UUID to the Libvirt destiny images path.</br>
* Example: /var/lib/libvirt/images/f3d49ecc-870c-475a-89fa-fd0124420a9b
*/
@Override
protected String generateDestPath(Host destHost, StoragePoolVO destStoragePool, VolumeInfo destVolumeInfo) {
return new File(destStoragePool.getPath(), destVolumeInfo.getUuid()).getAbsolutePath();
}
@Override
protected String generateBackingPath(StoragePoolVO destStoragePool, VolumeInfo destVolumeInfo) {
String templateInstallPath = getVolumeBackingFile(destVolumeInfo);
if (templateInstallPath == null) {
return null;
}
return new File(destStoragePool.getPath(), templateInstallPath).getAbsolutePath();
}
/**
* Returns the template UUID with the given id. If the template ID is null, it returns null.
*/
protected String getTemplateUuid(Long templateId) {
if (templateId == null) {
return null;
}
TemplateInfo templateImage = templateDataFactory.getTemplate(templateId, DataStoreRole.Image);
return templateImage.getUuid();
}
/**
* Sets the volume path as the volume UUID.
*/
@Override
protected void setVolumePath(VolumeVO volume) {
volume.setPath(volume.getUuid());
}
/**
* Return true if the volume should be migrated. Currently only supports migrating volumes on storage pool of the type StoragePoolType.Filesystem.
* This ensures that volumes on shared storage are not migrated and those on local storage pools are migrated.
*/
@Override
protected boolean shouldMigrateVolume(StoragePoolVO sourceStoragePool, Host destHost, StoragePoolVO destStoragePool) {
return supportStoragePoolType(sourceStoragePool.getPoolType());
}
/**
* If the template is not on the target primary storage then it copies the template.
*/
@Override
protected void copyTemplateToTargetFilesystemStorageIfNeeded(VolumeInfo srcVolumeInfo, StoragePool srcStoragePool, DataStore destDataStore, StoragePool destStoragePool,
Host destHost) {
if (srcVolumeInfo.getVolumeType() != Volume.Type.ROOT || srcVolumeInfo.getTemplateId() == null) {
return;
}
TemplateInfo directDownloadTemplateInfo = templateDataFactory.getReadyBypassedTemplateOnPrimaryStore(srcVolumeInfo.getTemplateId(), destDataStore.getId(), destHost.getId());
if (directDownloadTemplateInfo != null) {
logger.debug(String.format("Template %s was of direct download type and successfully staged to primary store %s", directDownloadTemplateInfo.getId(), directDownloadTemplateInfo.getDataStore().getId()));
return;
}
VMTemplateStoragePoolVO sourceVolumeTemplateStoragePoolVO = vmTemplatePoolDao.findByPoolTemplate(destStoragePool.getId(), srcVolumeInfo.getTemplateId(), null);
if (sourceVolumeTemplateStoragePoolVO == null && (isStoragePoolTypeInList(destStoragePool.getPoolType(), StoragePoolType.Filesystem, StoragePoolType.SharedMountPoint))) {
DataStore sourceTemplateDataStore = dataStoreManagerImpl.getRandomImageStore(srcVolumeInfo.getDataCenterId());
if (sourceTemplateDataStore != null) {
TemplateInfo sourceTemplateInfo = templateDataFactory.getTemplate(srcVolumeInfo.getTemplateId(), sourceTemplateDataStore);
TemplateObjectTO sourceTemplate = new TemplateObjectTO(sourceTemplateInfo);
logger.debug(String.format("Could not find template [id=%s, name=%s] on the storage pool [id=%s]; copying the template to the target storage pool.",
srcVolumeInfo.getTemplateId(), sourceTemplateInfo.getName(), destDataStore.getId()));
TemplateInfo destTemplateInfo = templateDataFactory.getTemplate(srcVolumeInfo.getTemplateId(), destDataStore);
final TemplateObjectTO destTemplate = new TemplateObjectTO(destTemplateInfo);
Answer copyCommandAnswer = sendCopyCommand(destHost, sourceTemplate, destTemplate, destDataStore);
if (copyCommandAnswer != null && copyCommandAnswer.getResult()) {
updateTemplateReferenceIfSuccessfulCopy(srcVolumeInfo.getTemplateId(), destTemplateInfo.getUuid(), destDataStore.getId(), destTemplate.getSize());
}
return;
}
}
logger.debug(String.format("Skipping 'copy template to target filesystem storage before migration' due to the template [%s] already exist on the storage pool [%s].", srcVolumeInfo.getTemplateId(), destStoragePool.getId()));
}
/**
* Update the template reference on table "template_spool_ref" (VMTemplateStoragePoolVO).
*/
protected void updateTemplateReferenceIfSuccessfulCopy(long templateId, String destTemplateInfoUuid, long destDataStoreId, long templateSize) {
VMTemplateStoragePoolVO destVolumeTemplateStoragePoolVO = new VMTemplateStoragePoolVO(destDataStoreId, templateId, null);
destVolumeTemplateStoragePoolVO.setDownloadPercent(100);
destVolumeTemplateStoragePoolVO.setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOADED);
destVolumeTemplateStoragePoolVO.setState(ObjectInDataStoreStateMachine.State.Ready);
destVolumeTemplateStoragePoolVO.setTemplateSize(templateSize);
destVolumeTemplateStoragePoolVO.setLocalDownloadPath(destTemplateInfoUuid);
destVolumeTemplateStoragePoolVO.setInstallPath(destTemplateInfoUuid);
vmTemplatePoolDao.persist(destVolumeTemplateStoragePoolVO);
}
/**
* Sends the CopyCommand to migrate the template to the dest host.
*/
protected Answer sendCopyCommand(Host destHost, TemplateObjectTO sourceTemplate, TemplateObjectTO destTemplate, DataStore destDataStore) {
boolean executeInSequence = virtualMachineManager.getExecuteInSequence(HypervisorType.KVM);
CopyCommand copyCommand = new CopyCommand(sourceTemplate, destTemplate, StorageManager.PRIMARY_STORAGE_DOWNLOAD_WAIT.value(), executeInSequence);
try {
Answer copyCommandAnswer = agentManager.send(destHost.getId(), copyCommand);
logInCaseOfTemplateCopyFailure(copyCommandAnswer, sourceTemplate, destDataStore);
return copyCommandAnswer;
} catch (AgentUnavailableException | OperationTimedoutException e) {
throw new CloudRuntimeException(generateFailToCopyTemplateMessage(sourceTemplate, destDataStore), e);
}
}
private String generateFailToCopyTemplateMessage(TemplateObjectTO sourceTemplate, DataStore destDataStore) {
return String.format("Failed to copy template [id=%s, name=%s] to the primary storage pool [id=%s].", sourceTemplate.getId(),
sourceTemplate.getName(), destDataStore.getId());
}
/**
* Logs in debug mode the copy command failure if the CopyCommand Answer has result as false.
*/
protected void logInCaseOfTemplateCopyFailure(Answer copyCommandAnswer, TemplateObjectTO sourceTemplate, DataStore destDataStore) {
if (copyCommandAnswer != null && !copyCommandAnswer.getResult()) {
String failureDetails = StringUtils.EMPTY;
if (copyCommandAnswer.getDetails() != null) {
failureDetails = " Details: " + copyCommandAnswer.getDetails();
}
logger.error(generateFailToCopyTemplateMessage(sourceTemplate, destDataStore) + failureDetails);
}
}
protected Boolean supportStoragePoolType(StoragePoolType storagePoolType) {
return super.supportStoragePoolType(storagePoolType, StoragePoolType.Filesystem);
}
}