This is an automated email from the ASF dual-hosted git repository. rohit pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit fcc87d9261406990fee47eb5ad59eead983d1a7d Merge: 00468cf 223a373 Author: Rohit Yadav <[email protected]> AuthorDate: Tue Sep 18 16:18:37 2018 +0530 Merge branch '4.11' Signed-off-by: Rohit Yadav <[email protected]> .../com/cloud/vm/VirtualMachinePowerStateSyncImpl.java | 8 ++++++-- .../engine/orchestration/VolumeOrchestrator.java | 2 +- .../main/java/com/cloud/vm/UserVmCloneSettingVO.java | 17 ++++++++++++++++- .../main/resources/META-INF/db/schema-41110to41120.sql | 6 ++++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --cc engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java index f4cd565,0000000..80a4225 mode 100644,000000..100644 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java @@@ -1,179 -1,0 +1,183 @@@ +// 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 com.cloud.vm; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import org.apache.log4j.Logger; +import com.cloud.configuration.ManagementServiceConfiguration; +import org.apache.cloudstack.framework.messagebus.MessageBus; +import org.apache.cloudstack.framework.messagebus.PublishScope; + +import com.cloud.agent.api.HostVmStateReportEntry; +import com.cloud.utils.DateUtil; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.dao.VMInstanceDao; + +public class VirtualMachinePowerStateSyncImpl implements VirtualMachinePowerStateSync { + private static final Logger s_logger = Logger.getLogger(VirtualMachinePowerStateSyncImpl.class); + + @Inject MessageBus _messageBus; + @Inject VMInstanceDao _instanceDao; + @Inject VirtualMachineManager _vmMgr; + @Inject ManagementServiceConfiguration mgmtServiceConf; + + public VirtualMachinePowerStateSyncImpl() { + } + + @Override + public void resetHostSyncState(long hostId) { + s_logger.info("Reset VM power state sync for host: " + hostId); + _instanceDao.resetHostPowerStateTracking(hostId); + } + + @Override + public void processHostVmStateReport(long hostId, Map<String, HostVmStateReportEntry> report) { + s_logger.debug("Process host VM state report. host: " + hostId); + + Map<Long, VirtualMachine.PowerState> translatedInfo = convertVmStateReport(report); + processReport(hostId, translatedInfo); + } + + @Override + public void processHostVmStatePingReport(long hostId, Map<String, HostVmStateReportEntry> report) { + if (s_logger.isDebugEnabled()) + s_logger.debug("Process host VM state report from ping process. host: " + hostId); + + Map<Long, VirtualMachine.PowerState> translatedInfo = convertVmStateReport(report); + processReport(hostId, translatedInfo); + } + + private void processReport(long hostId, Map<Long, VirtualMachine.PowerState> translatedInfo) { + + if (s_logger.isDebugEnabled()) + s_logger.debug("Process VM state report. host: " + hostId + ", number of records in report: " + translatedInfo.size()); + + for (Map.Entry<Long, VirtualMachine.PowerState> entry : translatedInfo.entrySet()) { + + if (s_logger.isDebugEnabled()) + s_logger.debug("VM state report. host: " + hostId + ", vm id: " + entry.getKey() + ", power state: " + entry.getValue()); + + if (_instanceDao.updatePowerState(entry.getKey(), hostId, entry.getValue())) { + if (s_logger.isDebugEnabled()) + s_logger.debug("VM state report is updated. host: " + hostId + ", vm id: " + entry.getKey() + ", power state: " + entry.getValue()); + + _messageBus.publish(null, VirtualMachineManager.Topics.VM_POWER_STATE, PublishScope.GLOBAL, entry.getKey()); + } else { + if (s_logger.isDebugEnabled()) + s_logger.debug("VM power state does not change, skip DB writing. vm id: " + entry.getKey()); + } + } + + // for all running/stopping VMs, we provide monitoring of missing report + List<VMInstanceVO> vmsThatAreMissingReport = _instanceDao.findByHostInStates(hostId, VirtualMachine.State.Running, + VirtualMachine.State.Stopping, VirtualMachine.State.Starting); + java.util.Iterator<VMInstanceVO> it = vmsThatAreMissingReport.iterator(); + while (it.hasNext()) { + VMInstanceVO instance = it.next(); + if (translatedInfo.get(instance.getId()) != null) + it.remove(); + } + + if (vmsThatAreMissingReport.size() > 0) { + Date currentTime = DateUtil.currentGMTTime(); + if (s_logger.isDebugEnabled()) + s_logger.debug("Run missing VM report. current time: " + currentTime.getTime()); + + // 2 times of sync-update interval for graceful period + long milliSecondsGracefullPeriod = mgmtServiceConf.getPingInterval() * 2000L; + + for (VMInstanceVO instance : vmsThatAreMissingReport) { + + // Make sure powerState is up to date for missing VMs + try { + if (!_instanceDao.isPowerStateUpToDate(instance.getId())) { + s_logger.warn("Detected missing VM but power state is outdated, wait for another process report run for VM id: " + instance.getId()); + _instanceDao.resetVmPowerStateTracking(instance.getId()); + continue; + } + } catch (CloudRuntimeException e) { + s_logger.warn("Checked for missing powerstate of a none existing vm", e); + continue; + } + + Date vmStateUpdateTime = instance.getPowerStateUpdateTime(); + if (vmStateUpdateTime == null) { - s_logger.warn("VM state was updated but update time is null?! vm id: " + instance.getId()); - vmStateUpdateTime = currentTime; ++ s_logger.warn("VM power state update time is null, falling back to update time for vm id: " + instance.getId()); ++ vmStateUpdateTime = instance.getUpdateTime(); ++ if (vmStateUpdateTime == null) { ++ s_logger.warn("VM update time is null, falling back to creation time for vm id: " + instance.getId()); ++ vmStateUpdateTime = instance.getCreated(); ++ } + } + + if (s_logger.isDebugEnabled()) + s_logger.debug("Detected missing VM. host: " + hostId + ", vm id: " + instance.getId() + + ", power state: PowerReportMissing, last state update: " + vmStateUpdateTime.getTime()); + + long milliSecondsSinceLastStateUpdate = currentTime.getTime() - vmStateUpdateTime.getTime(); + + if (milliSecondsSinceLastStateUpdate > milliSecondsGracefullPeriod) { + s_logger.debug("vm id: " + instance.getId() + " - time since last state update(" + milliSecondsSinceLastStateUpdate + "ms) has passed graceful period"); + + if (_instanceDao.updatePowerState(instance.getId(), hostId, VirtualMachine.PowerState.PowerReportMissing)) { + if (s_logger.isDebugEnabled()) + s_logger.debug("VM state report is updated. host: " + hostId + ", vm id: " + instance.getId() + ", power state: PowerReportMissing "); + + _messageBus.publish(null, VirtualMachineManager.Topics.VM_POWER_STATE, PublishScope.GLOBAL, instance.getId()); + } else { + if (s_logger.isDebugEnabled()) + s_logger.debug("VM power state does not change, skip DB writing. vm id: " + instance.getId()); + } + } else { + s_logger.debug("vm id: " + instance.getId() + " - time since last state update(" + milliSecondsSinceLastStateUpdate + "ms) has not passed graceful period yet"); + } + } + } + + if (s_logger.isDebugEnabled()) + s_logger.debug("Done with process of VM state report. host: " + hostId); + } + + @Override + public Map<Long, VirtualMachine.PowerState> convertVmStateReport(Map<String, HostVmStateReportEntry> states) { + final HashMap<Long, VirtualMachine.PowerState> map = new HashMap<Long, VirtualMachine.PowerState>(); + if (states == null) { + return map; + } + + for (Map.Entry<String, HostVmStateReportEntry> entry : states.entrySet()) { + VMInstanceVO vm = findVM(entry.getKey()); + if (vm != null) { + map.put(vm.getId(), entry.getValue().getState()); + } else { + s_logger.info("Unable to find matched VM in CloudStack DB. name: " + entry.getKey()); + } + } + + return map; + } + + private VMInstanceVO findVM(String vmName) { + return _instanceDao.findVMByInstanceName(vmName); + } +} diff --cc engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index 6576646,0000000..8ad7dcf mode 100644,000000..100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@@ -1,1602 -1,0 +1,1602 @@@ +/* + * 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.engine.orchestration; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; +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.DataStoreCapabilities; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation; +import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; +import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory; +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.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult; +import org.apache.cloudstack.framework.async.AsyncCallFuture; +import org.apache.cloudstack.framework.config.ConfigDepot; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.framework.jobs.AsyncJobManager; +import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; +import org.apache.cloudstack.storage.command.CommandResult; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; +import org.apache.log4j.Logger; + +import com.cloud.agent.api.to.DataTO; +import com.cloud.agent.api.to.DiskTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.manager.allocator.PodAllocator; +import com.cloud.capacity.CapacityManager; +import com.cloud.cluster.ClusterManager; +import com.cloud.configuration.Resource.ResourceType; +import com.cloud.dc.DataCenter; +import com.cloud.dc.Pod; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventUtils; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientStorageCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.StorageUnavailableException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.offering.DiskOffering; +import com.cloud.offering.ServiceOffering; +import com.cloud.org.Cluster; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.ScopeType; +import com.cloud.storage.Snapshot; +import com.cloud.storage.Storage; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePool; +import com.cloud.storage.VMTemplateStorageResourceAssoc; +import com.cloud.storage.Volume; +import com.cloud.storage.Volume.Type; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.SnapshotDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.dao.VolumeDetailsDao; +import com.cloud.template.TemplateManager; +import com.cloud.template.VirtualMachineTemplate; +import com.cloud.user.Account; +import com.cloud.user.ResourceLimitService; +import com.cloud.uservm.UserVm; +import com.cloud.utils.Pair; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.utils.fsm.StateMachine2; +import com.cloud.vm.DiskProfile; +import com.cloud.vm.UserVmCloneSettingVO; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.VirtualMachineProfileImpl; +import com.cloud.vm.VmWorkAttachVolume; +import com.cloud.vm.VmWorkMigrateVolume; +import com.cloud.vm.VmWorkSerializer; +import com.cloud.vm.VmWorkTakeVolumeSnapshot; +import com.cloud.vm.dao.UserVmCloneSettingDao; +import com.cloud.vm.dao.UserVmDao; + +public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrationService, Configurable { + + public enum UserVmCloneType { + full, linked + } + + private static final Logger s_logger = Logger.getLogger(VolumeOrchestrator.class); + + @Inject + EntityManager _entityMgr; + @Inject + protected TemplateManager _tmpltMgr; + @Inject + protected VolumeDao _volsDao; + @Inject + protected PrimaryDataStoreDao _storagePoolDao = null; + @Inject + protected TemplateDataStoreDao _vmTemplateStoreDao = null; + @Inject + protected VolumeDao _volumeDao; + @Inject + protected SnapshotDao _snapshotDao; + @Inject + protected SnapshotDataStoreDao _snapshotDataStoreDao; + @Inject + protected ResourceLimitService _resourceLimitMgr; + @Inject + VolumeDetailsDao _volDetailDao; + @Inject + DataStoreManager dataStoreMgr; + @Inject + VolumeService volService; + @Inject + VolumeDataFactory volFactory; + @Inject + TemplateDataFactory tmplFactory; + @Inject + SnapshotDataFactory snapshotFactory; + @Inject + ConfigDepot _configDepot; + @Inject + HostDao _hostDao; + @Inject + SnapshotService _snapshotSrv; + @Inject + protected UserVmDao _userVmDao; + @Inject + protected AsyncJobManager _jobMgr; + @Inject + ClusterManager clusterManager; + @Inject + StorageManager storageMgr; + @Inject + protected UserVmCloneSettingDao _vmCloneSettingDao; + @Inject + StorageStrategyFactory _storageStrategyFactory; + + private final StateMachine2<Volume.State, Volume.Event, Volume> _volStateMachine; + protected List<StoragePoolAllocator> _storagePoolAllocators; + + public List<StoragePoolAllocator> getStoragePoolAllocators() { + return _storagePoolAllocators; + } + + public void setStoragePoolAllocators(List<StoragePoolAllocator> storagePoolAllocators) { + _storagePoolAllocators = storagePoolAllocators; + } + + protected List<PodAllocator> _podAllocators; + + public List<PodAllocator> getPodAllocators() { + return _podAllocators; + } + + public void setPodAllocators(List<PodAllocator> podAllocators) { + _podAllocators = podAllocators; + } + + protected VolumeOrchestrator() { + _volStateMachine = Volume.State.getStateMachine(); + } + + @Override + public VolumeInfo moveVolume(VolumeInfo volume, long destPoolDcId, Long destPoolPodId, Long destPoolClusterId, HypervisorType dataDiskHyperType) + throws ConcurrentOperationException, StorageUnavailableException { + + // Find a destination storage pool with the specified criteria + DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, volume.getDiskOfferingId()); + DiskProfile dskCh = new DiskProfile(volume.getId(), volume.getVolumeType(), volume.getName(), diskOffering.getId(), diskOffering.getDiskSize(), diskOffering.getTagsArray(), + diskOffering.getUseLocalStorage(), diskOffering.isRecreatable(), null); + dskCh.setHyperType(dataDiskHyperType); + storageMgr.setDiskProfileThrottling(dskCh, null, diskOffering); + + DataCenter destPoolDataCenter = _entityMgr.findById(DataCenter.class, destPoolDcId); + Pod destPoolPod = _entityMgr.findById(Pod.class, destPoolPodId); + + StoragePool destPool = findStoragePool(dskCh, destPoolDataCenter, destPoolPod, destPoolClusterId, null, null, new HashSet<StoragePool>()); + + if (destPool == null) { + throw new CloudRuntimeException("Failed to find a storage pool with enough capacity to move the volume to."); + } + + Volume newVol = migrateVolume(volume, destPool); + return volFactory.getVolume(newVol.getId()); + } + + @Override + public Volume allocateDuplicateVolume(Volume oldVol, Long templateId) { + return allocateDuplicateVolumeVO(oldVol, templateId); + } + + public VolumeVO allocateDuplicateVolumeVO(Volume oldVol, Long templateId) { + VolumeVO newVol = new VolumeVO(oldVol.getVolumeType(), oldVol.getName(), oldVol.getDataCenterId(), oldVol.getDomainId(), oldVol.getAccountId(), oldVol.getDiskOfferingId(), + oldVol.getProvisioningType(), oldVol.getSize(), oldVol.getMinIops(), oldVol.getMaxIops(), oldVol.get_iScsiName()); + if (templateId != null) { + newVol.setTemplateId(templateId); + } else { + newVol.setTemplateId(oldVol.getTemplateId()); + } + newVol.setDeviceId(oldVol.getDeviceId()); + newVol.setInstanceId(oldVol.getInstanceId()); + newVol.setRecreatable(oldVol.isRecreatable()); + newVol.setFormat(oldVol.getFormat()); + return _volsDao.persist(newVol); + } + + @Override + public StoragePool findStoragePool(DiskProfile dskCh, DataCenter dc, Pod pod, Long clusterId, Long hostId, VirtualMachine vm, final Set<StoragePool> avoid) { + Long podId = null; + if (pod != null) { + podId = pod.getId(); + } else if (clusterId != null) { + Cluster cluster = _entityMgr.findById(Cluster.class, clusterId); + if (cluster != null) { + podId = cluster.getPodId(); + } + } + + VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + for (StoragePoolAllocator allocator : _storagePoolAllocators) { + + ExcludeList avoidList = new ExcludeList(); + for (StoragePool pool : avoid) { + avoidList.addPool(pool.getId()); + } + DataCenterDeployment plan = new DataCenterDeployment(dc.getId(), podId, clusterId, hostId, null, null); + + final List<StoragePool> poolList = allocator.allocateToPool(dskCh, profile, plan, avoidList, 1); + if (poolList != null && !poolList.isEmpty()) { + return (StoragePool)dataStoreMgr.getDataStore(poolList.get(0).getId(), DataStoreRole.Primary); + } + } + return null; + } + + public Pair<Pod, Long> findPod(VirtualMachineTemplate template, ServiceOffering offering, DataCenter dc, long accountId, Set<Long> avoids) { + for (PodAllocator allocator : _podAllocators) { + final Pair<Pod, Long> pod = allocator.allocateTo(template, offering, dc, accountId, avoids); + if (pod != null) { + return pod; + } + } + return null; + } + + @DB + @Override + public VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot, UserVm vm) throws StorageUnavailableException { + Account account = _entityMgr.findById(Account.class, volume.getAccountId()); + + final HashSet<StoragePool> poolsToAvoid = new HashSet<StoragePool>(); + StoragePool pool = null; + + Set<Long> podsToAvoid = new HashSet<Long>(); + Pair<Pod, Long> pod = null; + + DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, volume.getDiskOfferingId()); + DataCenter dc = _entityMgr.findById(DataCenter.class, volume.getDataCenterId()); + DiskProfile dskCh = new DiskProfile(volume, diskOffering, snapshot.getHypervisorType()); + + String msg = "There are no available storage pools to store the volume in"; + + if (vm != null) { + Pod podofVM = _entityMgr.findById(Pod.class, vm.getPodIdToDeployIn()); + if (podofVM != null) { + pod = new Pair<Pod, Long>(podofVM, podofVM.getId()); + } + } + + if (vm != null && pod != null) { + //if VM is running use the hostId to find the clusterID. If it is stopped, refer the cluster where the ROOT volume of the VM exists. + Long hostId = null; + Long clusterId = null; + if (vm.getState() == State.Running) { + hostId = vm.getHostId(); + if (hostId != null) { + Host vmHost = _entityMgr.findById(Host.class, hostId); + clusterId = vmHost.getClusterId(); + } + } else { + List<VolumeVO> rootVolumesOfVm = _volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT); + if (rootVolumesOfVm.size() != 1) { + throw new CloudRuntimeException("The VM " + vm.getHostName() + " has more than one ROOT volume and is in an invalid state. Please contact Cloud Support."); + } else { + VolumeVO rootVolumeOfVm = rootVolumesOfVm.get(0); + StoragePoolVO rootDiskPool = _storagePoolDao.findById(rootVolumeOfVm.getPoolId()); + clusterId = (rootDiskPool == null ? null : rootDiskPool.getClusterId()); + } + } + // Determine what storage pool to store the volume in + while ((pool = findStoragePool(dskCh, dc, pod.first(), clusterId, hostId, vm, poolsToAvoid)) != null) { + break; + } + + if (pool == null) { + //pool could not be found in the VM's pod/cluster. + if (s_logger.isDebugEnabled()) { + s_logger.debug("Could not find any storage pool to create Volume in the pod/cluster of the provided VM " + vm.getUuid()); + } + StringBuilder addDetails = new StringBuilder(msg); + addDetails.append(", Could not find any storage pool to create Volume in the pod/cluster of the VM "); + addDetails.append(vm.getUuid()); + msg = addDetails.toString(); + } + } else { + // Determine what pod to store the volume in + while ((pod = findPod(null, null, dc, account.getId(), podsToAvoid)) != null) { + podsToAvoid.add(pod.first().getId()); + // Determine what storage pool to store the volume in + while ((pool = findStoragePool(dskCh, dc, pod.first(), null, null, null, poolsToAvoid)) != null) { + break; + } + + if (pool != null) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Found a suitable pool for create volume: " + pool.getId()); + } + break; + } + } + } + + if (pool == null) { + s_logger.info(msg); + throw new StorageUnavailableException(msg, -1); + } + + VolumeInfo vol = volFactory.getVolume(volume.getId()); + DataStore store = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary); + DataStoreRole dataStoreRole = getDataStoreRole(snapshot); + SnapshotInfo snapInfo = snapshotFactory.getSnapshot(snapshot.getId(), dataStoreRole); + + if (snapInfo == null && dataStoreRole == DataStoreRole.Image) { + // snapshot is not backed up to secondary, let's do that now. + snapInfo = snapshotFactory.getSnapshot(snapshot.getId(), DataStoreRole.Primary); + + if (snapInfo == null) { + throw new CloudRuntimeException("Cannot find snapshot " + snapshot.getId()); + } + // We need to copy the snapshot onto secondary. + SnapshotStrategy snapshotStrategy = _storageStrategyFactory.getSnapshotStrategy(snapshot, SnapshotOperation.BACKUP); + snapshotStrategy.backupSnapshot(snapInfo); + + // Attempt to grab it again. + snapInfo = snapshotFactory.getSnapshot(snapshot.getId(), dataStoreRole); + if (snapInfo == null) { + throw new CloudRuntimeException("Cannot find snapshot " + snapshot.getId() + " on secondary and could not create backup"); + } + } + // don't try to perform a sync if the DataStoreRole of the snapshot is equal to DataStoreRole.Primary + if (!DataStoreRole.Primary.equals(dataStoreRole)) { + try { + // sync snapshot to region store if necessary + DataStore snapStore = snapInfo.getDataStore(); + long snapVolId = snapInfo.getVolumeId(); + + _snapshotSrv.syncVolumeSnapshotsToRegionStore(snapVolId, snapStore); + } catch (Exception ex) { + // log but ignore the sync error to avoid any potential S3 down issue, it should be sync next time + s_logger.warn(ex.getMessage(), ex); + } + } + + // create volume on primary from snapshot + AsyncCallFuture<VolumeApiResult> future = volService.createVolumeFromSnapshot(vol, store, snapInfo); + try { + VolumeApiResult result = future.get(); + if (result.isFailed()) { + s_logger.debug("Failed to create volume from snapshot:" + result.getResult()); + throw new CloudRuntimeException("Failed to create volume from snapshot:" + result.getResult()); + } + return result.getVolume(); + } catch (InterruptedException e) { + s_logger.debug("Failed to create volume from snapshot", e); + throw new CloudRuntimeException("Failed to create volume from snapshot", e); + } catch (ExecutionException e) { + s_logger.debug("Failed to create volume from snapshot", e); + throw new CloudRuntimeException("Failed to create volume from snapshot", e); + } + + } + + public DataStoreRole getDataStoreRole(Snapshot snapshot) { + SnapshotDataStoreVO snapshotStore = _snapshotDataStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary); + + if (snapshotStore == null) { + return DataStoreRole.Image; + } + + long storagePoolId = snapshotStore.getDataStoreId(); + DataStore dataStore = dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary); + + Map<String, String> mapCapabilities = dataStore.getDriver().getCapabilities(); + + if (mapCapabilities != null) { + String value = mapCapabilities.get(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString()); + Boolean supportsStorageSystemSnapshots = new Boolean(value); + + if (supportsStorageSystemSnapshots) { + return DataStoreRole.Primary; + } + } + + return DataStoreRole.Image; + } + + protected DiskProfile createDiskCharacteristics(VolumeInfo volume, VirtualMachineTemplate template, DataCenter dc, DiskOffering diskOffering) { + if (volume.getVolumeType() == Type.ROOT && Storage.ImageFormat.ISO != template.getFormat()) { + TemplateDataStoreVO ss = _vmTemplateStoreDao.findByTemplateZoneDownloadStatus(template.getId(), dc.getId(), VMTemplateStorageResourceAssoc.Status.DOWNLOADED); + if (ss == null) { + throw new CloudRuntimeException("Template " + template.getName() + " has not been completely downloaded to zone " + dc.getId()); + } + + return new DiskProfile(volume.getId(), volume.getVolumeType(), volume.getName(), diskOffering.getId(), ss.getSize(), diskOffering.getTagsArray(), diskOffering.getUseLocalStorage(), + diskOffering.isRecreatable(), Storage.ImageFormat.ISO != template.getFormat() ? template.getId() : null); + } else { + return new DiskProfile(volume.getId(), volume.getVolumeType(), volume.getName(), diskOffering.getId(), diskOffering.getDiskSize(), diskOffering.getTagsArray(), + diskOffering.getUseLocalStorage(), diskOffering.isRecreatable(), null); + } + } + + @DB + public VolumeInfo copyVolumeFromSecToPrimary(VolumeInfo volume, VirtualMachine vm, VirtualMachineTemplate template, DataCenter dc, Pod pod, Long clusterId, ServiceOffering offering, + DiskOffering diskOffering, List<StoragePool> avoids, long size, HypervisorType hyperType) throws NoTransitionException { + + final HashSet<StoragePool> avoidPools = new HashSet<StoragePool>(avoids); + DiskProfile dskCh = createDiskCharacteristics(volume, template, dc, diskOffering); + dskCh.setHyperType(vm.getHypervisorType()); + storageMgr.setDiskProfileThrottling(dskCh, null, diskOffering); + + // Find a suitable storage to create volume on + StoragePool destPool = findStoragePool(dskCh, dc, pod, clusterId, null, vm, avoidPools); + if (destPool == null) { + throw new CloudRuntimeException("Failed to find a suitable storage pool to create Volume in the pod/cluster of the provided VM "+ vm.getUuid()); + } + DataStore destStore = dataStoreMgr.getDataStore(destPool.getId(), DataStoreRole.Primary); + AsyncCallFuture<VolumeApiResult> future = volService.copyVolume(volume, destStore); + + try { + VolumeApiResult result = future.get(); + if (result.isFailed()) { + s_logger.debug("copy volume failed: " + result.getResult()); + throw new CloudRuntimeException("copy volume failed: " + result.getResult()); + } + return result.getVolume(); + } catch (InterruptedException e) { + s_logger.debug("Failed to copy volume: " + volume.getId(), e); + throw new CloudRuntimeException("Failed to copy volume", e); + } catch (ExecutionException e) { + s_logger.debug("Failed to copy volume: " + volume.getId(), e); + throw new CloudRuntimeException("Failed to copy volume", e); + } + } + + @DB + public VolumeInfo createVolume(VolumeInfo volume, VirtualMachine vm, VirtualMachineTemplate template, DataCenter dc, Pod pod, Long clusterId, ServiceOffering offering, DiskOffering diskOffering, + List<StoragePool> avoids, long size, HypervisorType hyperType) { + // update the volume's hv_ss_reserve (hypervisor snapshot reserve) from a disk offering (used for managed storage) + volume = volService.updateHypervisorSnapshotReserveForVolume(diskOffering, volume.getId(), hyperType); + + StoragePool pool = null; + + DiskProfile dskCh = null; + if (volume.getVolumeType() == Type.ROOT && Storage.ImageFormat.ISO != template.getFormat()) { + dskCh = createDiskCharacteristics(volume, template, dc, offering); + storageMgr.setDiskProfileThrottling(dskCh, offering, diskOffering); + } else { + dskCh = createDiskCharacteristics(volume, template, dc, diskOffering); + storageMgr.setDiskProfileThrottling(dskCh, null, diskOffering); + } + + if (diskOffering != null && diskOffering.isCustomized()) { + dskCh.setSize(size); + } + + dskCh.setHyperType(hyperType); + + final HashSet<StoragePool> avoidPools = new HashSet<StoragePool>(avoids); + + pool = findStoragePool(dskCh, dc, pod, clusterId, vm.getHostId(), vm, avoidPools); + if (pool == null) { + s_logger.warn("Unable to find suitable primary storage when creating volume " + volume.getName()); + throw new CloudRuntimeException("Unable to find suitable primary storage when creating volume " + volume.getName()); + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Trying to create " + volume + " on " + pool); + } + DataStore store = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary); + for (int i = 0; i < 2; i++) { + // retry one more time in case of template reload is required for Vmware case + AsyncCallFuture<VolumeApiResult> future = null; + boolean isNotCreatedFromTemplate = volume.getTemplateId() == null ? true : false; + if (isNotCreatedFromTemplate) { + future = volService.createVolumeAsync(volume, store); + } else { + TemplateInfo templ = tmplFactory.getTemplate(template.getId(), DataStoreRole.Image); + future = volService.createVolumeFromTemplateAsync(volume, store.getId(), templ); + } + try { + VolumeApiResult result = future.get(); + if (result.isFailed()) { + if (result.getResult().contains("request template reload") && (i == 0)) { + s_logger.debug("Retry template re-deploy for vmware"); + continue; + } else { + s_logger.debug("create volume failed: " + result.getResult()); + throw new CloudRuntimeException("create volume failed:" + result.getResult()); + } + } + + return result.getVolume(); + } catch (InterruptedException e) { + s_logger.error("create volume failed", e); + throw new CloudRuntimeException("create volume failed", e); + } catch (ExecutionException e) { + s_logger.error("create volume failed", e); + throw new CloudRuntimeException("create volume failed", e); + } + } + throw new CloudRuntimeException("create volume failed even after template re-deploy"); + } + + public String getRandomVolumeName() { + return UUID.randomUUID().toString(); + } + + @Override + public boolean volumeOnSharedStoragePool(Volume volume) { + Long poolId = volume.getPoolId(); + if (poolId == null) { + return false; + } else { + StoragePoolVO pool = _storagePoolDao.findById(poolId); + + if (pool == null) { + return false; + } else { + return (pool.getScope() == ScopeType.HOST) ? false : true; + } + } + } + + @Override + public boolean volumeInactive(Volume volume) { + Long vmId = volume.getInstanceId(); + + if (vmId == null) { + return true; + } + + UserVm vm = _entityMgr.findById(UserVm.class, vmId); + + if (vm == null) { + return true; + } + + State state = vm.getState(); + + if (state.equals(State.Stopped) || state.equals(State.Destroyed)) { + return true; + } + + return false; + } + + @Override + public String getVmNameOnVolume(Volume volume) { + Long vmId = volume.getInstanceId(); + if (vmId != null) { + VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, vmId); + + if (vm == null) { + return null; + } + return vm.getInstanceName(); + } + return null; + } + + @Override + public boolean validateVolumeSizeRange(long size) { + if (size < 0 || (size > 0 && size < (1024 * 1024 * 1024))) { + throw new InvalidParameterValueException("Please specify a size of at least 1 GB."); + } else if (size > (MaxVolumeSize.value() * 1024 * 1024 * 1024)) { + throw new InvalidParameterValueException("volume size " + size + ", but the maximum size allowed is " + MaxVolumeSize + " GB."); + } + + return true; + } + + protected DiskProfile toDiskProfile(Volume vol, DiskOffering offering) { + return new DiskProfile(vol.getId(), vol.getVolumeType(), vol.getName(), offering.getId(), vol.getSize(), offering.getTagsArray(), offering.getUseLocalStorage(), offering.isRecreatable(), + vol.getTemplateId()); + } + + @Override + public DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template, Account owner, + Long deviceId) { + if (size == null) { + size = offering.getDiskSize(); + } else { + size = (size * 1024 * 1024 * 1024); + } + + minIops = minIops != null ? minIops : offering.getMinIops(); + maxIops = maxIops != null ? maxIops : offering.getMaxIops(); + + VolumeVO vol = new VolumeVO(type, name, vm.getDataCenterId(), owner.getDomainId(), owner.getId(), offering.getId(), offering.getProvisioningType(), size, minIops, maxIops, null); + if (vm != null) { + vol.setInstanceId(vm.getId()); + } + + if (deviceId != null) { + vol.setDeviceId(deviceId); + } else if (type.equals(Type.ROOT)) { + vol.setDeviceId(0l); + } else { + vol.setDeviceId(1l); + } + if (template.getFormat() == ImageFormat.ISO) { + vol.setIsoId(template.getId()); + } else if (template.getTemplateType().equals(Storage.TemplateType.DATADISK)) { + vol.setTemplateId(template.getId()); + } + // display flag matters only for the User vms + if (vm.getType() == VirtualMachine.Type.User) { + UserVmVO userVm = _userVmDao.findById(vm.getId()); + vol.setDisplayVolume(userVm.isDisplayVm()); + } + + vol.setFormat(getSupportedImageFormatForCluster(vm.getHypervisorType())); + vol = _volsDao.persist(vol); + + // Save usage event and update resource count for user vm volumes + if (vm.getType() == VirtualMachine.Type.User) { + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, vol.getAccountId(), vol.getDataCenterId(), vol.getId(), vol.getName(), offering.getId(), null, size, + Volume.class.getName(), vol.getUuid(), vol.isDisplayVolume()); + + _resourceLimitMgr.incrementResourceCount(vm.getAccountId(), ResourceType.volume, vol.isDisplayVolume()); + _resourceLimitMgr.incrementResourceCount(vm.getAccountId(), ResourceType.primary_storage, vol.isDisplayVolume(), new Long(vol.getSize())); + } + return toDiskProfile(vol, offering); + } + + @Override + public DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm, + Account owner) { + assert (template.getFormat() != ImageFormat.ISO) : "ISO is not a template really...."; + + Long size = _tmpltMgr.getTemplateSize(template.getId(), vm.getDataCenterId()); + if (rootDisksize != null) { + rootDisksize = rootDisksize * 1024 * 1024 * 1024; + if (rootDisksize > size) { + s_logger.debug("Using root disk size of " + rootDisksize + " Bytes for volume " + name); + size = rootDisksize; + } else { + s_logger.debug("Using root disk size of " + size + " Bytes for volume " + name + "since specified root disk size of " + rootDisksize + " Bytes is smaller than template"); + } + } + + minIops = minIops != null ? minIops : offering.getMinIops(); + maxIops = maxIops != null ? maxIops : offering.getMaxIops(); + + VolumeVO vol = new VolumeVO(type, name, vm.getDataCenterId(), owner.getDomainId(), owner.getId(), offering.getId(), offering.getProvisioningType(), size, minIops, maxIops, null); + vol.setFormat(getSupportedImageFormatForCluster(template.getHypervisorType())); + if (vm != null) { + vol.setInstanceId(vm.getId()); + } + vol.setTemplateId(template.getId()); + + if (type.equals(Type.ROOT)) { + vol.setDeviceId(0l); + if (!vm.getType().equals(VirtualMachine.Type.User)) { + vol.setRecreatable(true); + } + } else { + vol.setDeviceId(1l); + } + + if (vm.getType() == VirtualMachine.Type.User) { + UserVmVO userVm = _userVmDao.findById(vm.getId()); + vol.setDisplayVolume(userVm.isDisplayVm()); + } + + vol = _volsDao.persist(vol); + + // Create event and update resource count for volumes if vm is a user vm + if (vm.getType() == VirtualMachine.Type.User) { + + Long offeringId = null; + + if (offering.getType() == DiskOffering.Type.Disk) { + offeringId = offering.getId(); + } + + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, vol.getAccountId(), vol.getDataCenterId(), vol.getId(), vol.getName(), offeringId, vol.getTemplateId(), size, + Volume.class.getName(), vol.getUuid(), vol.isDisplayVolume()); + + _resourceLimitMgr.incrementResourceCount(vm.getAccountId(), ResourceType.volume, vol.isDisplayVolume()); + _resourceLimitMgr.incrementResourceCount(vm.getAccountId(), ResourceType.primary_storage, vol.isDisplayVolume(), new Long(vol.getSize())); + } + return toDiskProfile(vol, offering); + } + + private ImageFormat getSupportedImageFormatForCluster(HypervisorType hyperType) { + if (hyperType == HypervisorType.XenServer) { + return ImageFormat.VHD; + } else if (hyperType == HypervisorType.KVM) { + return ImageFormat.QCOW2; + } else if (hyperType == HypervisorType.VMware) { + return ImageFormat.OVA; + } else if (hyperType == HypervisorType.Ovm) { + return ImageFormat.RAW; + } else if (hyperType == HypervisorType.Hyperv) { + return ImageFormat.VHDX; + } else { + return null; + } + } + + private boolean isSupportedImageFormatForCluster(VolumeInfo volume, HypervisorType rootDiskHyperType) { + ImageFormat volumeFormat = volume.getFormat(); + if (rootDiskHyperType == HypervisorType.Hyperv) { + if (volumeFormat.equals(ImageFormat.VHDX) || volumeFormat.equals(ImageFormat.VHD)) { + return true; + } else { + return false; + } + } else { + return volume.getFormat().equals(getSupportedImageFormatForCluster(rootDiskHyperType)); + } + } + + private VolumeInfo copyVolume(StoragePool rootDiskPool, VolumeInfo volume, VirtualMachine vm, VirtualMachineTemplate rootDiskTmplt, DataCenter dcVO, Pod pod, DiskOffering diskVO, + ServiceOffering svo, HypervisorType rootDiskHyperType) throws NoTransitionException { + + if (!isSupportedImageFormatForCluster(volume, rootDiskHyperType)) { + throw new InvalidParameterValueException("Failed to attach volume to VM since volumes format " + volume.getFormat().getFileExtension() + " is not compatible with the vm hypervisor type"); + } + + VolumeInfo volumeOnPrimary = copyVolumeFromSecToPrimary(volume, vm, rootDiskTmplt, dcVO, pod, rootDiskPool.getClusterId(), svo, diskVO, new ArrayList<StoragePool>(), volume.getSize(), + rootDiskHyperType); + + return volumeOnPrimary; + } + + @Override + public VolumeInfo createVolumeOnPrimaryStorage(VirtualMachine vm, VolumeInfo volume, HypervisorType rootDiskHyperType, StoragePool storagePool) throws NoTransitionException { + VirtualMachineTemplate rootDiskTmplt = _entityMgr.findById(VirtualMachineTemplate.class, vm.getTemplateId()); + DataCenter dcVO = _entityMgr.findById(DataCenter.class, vm.getDataCenterId()); + Pod pod = _entityMgr.findById(Pod.class, storagePool.getPodId()); + + ServiceOffering svo = _entityMgr.findById(ServiceOffering.class, vm.getServiceOfferingId()); + DiskOffering diskVO = _entityMgr.findById(DiskOffering.class, volume.getDiskOfferingId()); + Long clusterId = storagePool.getClusterId(); + + VolumeInfo vol = null; + if (volume.getState() == Volume.State.Allocated) { + vol = createVolume(volume, vm, rootDiskTmplt, dcVO, pod, clusterId, svo, diskVO, new ArrayList<StoragePool>(), volume.getSize(), rootDiskHyperType); + } else if (volume.getState() == Volume.State.Uploaded) { + vol = copyVolume(storagePool, volume, vm, rootDiskTmplt, dcVO, pod, diskVO, svo, rootDiskHyperType); + if (vol != null) { + // Moving of Volume is successful, decrement the volume resource count from secondary for an account and increment it into primary storage under same account. + _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.secondary_storage, volume.getSize()); + _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, volume.getSize()); + } + } + + if (vol == null) { + throw new CloudRuntimeException("Volume shouldn't be null " + volume.getId()); + } + VolumeVO volVO = _volsDao.findById(vol.getId()); + if (volVO.getFormat() == null) { + volVO.setFormat(getSupportedImageFormatForCluster(rootDiskHyperType)); + } + _volsDao.update(volVO.getId(), volVO); + return volFactory.getVolume(volVO.getId()); + } + + @DB + protected VolumeVO switchVolume(final VolumeVO existingVolume, final VirtualMachineProfile vm) throws StorageUnavailableException { + Long templateIdToUse = null; + Long volTemplateId = existingVolume.getTemplateId(); + long vmTemplateId = vm.getTemplateId(); + if (volTemplateId != null && volTemplateId.longValue() != vmTemplateId) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("switchVolume: Old Volume's templateId: " + volTemplateId + " does not match the VM's templateId: " + vmTemplateId + ", updating templateId in the new Volume"); + } + templateIdToUse = vmTemplateId; + } + + final Long templateIdToUseFinal = templateIdToUse; + return Transaction.execute(new TransactionCallback<VolumeVO>() { + @Override + public VolumeVO doInTransaction(TransactionStatus status) { + VolumeVO newVolume = allocateDuplicateVolumeVO(existingVolume, templateIdToUseFinal); + try { + stateTransitTo(existingVolume, Volume.Event.DestroyRequested); + } catch (NoTransitionException e) { + s_logger.debug("Unable to destroy existing volume: " + e.toString()); + } + // In case of VMware VM will continue to use the old root disk until expunged, so force expunge old root disk + if (vm.getHypervisorType() == HypervisorType.VMware) { + s_logger.info("Expunging volume " + existingVolume.getId() + " from primary data store"); + AsyncCallFuture<VolumeApiResult> future = volService.expungeVolumeAsync(volFactory.getVolume(existingVolume.getId())); + try { + future.get(); + } catch (Exception e) { + s_logger.debug("Failed to expunge volume:" + existingVolume.getId(), e); + } + } + + return newVolume; + } + }); + } + + @Override + public void release(VirtualMachineProfile profile) { + // add code here + } + + @Override + @DB + public void cleanupVolumes(long vmId) throws ConcurrentOperationException { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Cleaning storage for vm: " + vmId); + } + final List<VolumeVO> volumesForVm = _volsDao.findByInstance(vmId); + final List<VolumeVO> toBeExpunged = new ArrayList<VolumeVO>(); + + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + for (VolumeVO vol : volumesForVm) { + if (vol.getVolumeType().equals(Type.ROOT)) { + // Destroy volume if not already destroyed + boolean volumeAlreadyDestroyed = (vol.getState() == Volume.State.Destroy || vol.getState() == Volume.State.Expunged || vol.getState() == Volume.State.Expunging); + if (!volumeAlreadyDestroyed) { + volService.destroyVolume(vol.getId()); + } else { + s_logger.debug("Skipping destroy for the volume " + vol + " as its in state " + vol.getState().toString()); + } + toBeExpunged.add(vol); + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Detaching " + vol); + } + _volsDao.detachVolume(vol.getId()); + } + } + } + }); + + AsyncCallFuture<VolumeApiResult> future = null; + for (VolumeVO expunge : toBeExpunged) { + future = volService.expungeVolumeAsync(volFactory.getVolume(expunge.getId())); + try { + future.get(); + } catch (InterruptedException e) { + s_logger.debug("failed expunge volume" + expunge.getId(), e); + } catch (ExecutionException e) { + s_logger.debug("failed expunge volume" + expunge.getId(), e); + } + } + } + + @Override + public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) { + DataStoreDriver dataStoreDriver = dataStore != null ? dataStore.getDriver() : null; + + if (dataStoreDriver instanceof PrimaryDataStoreDriver) { + ((PrimaryDataStoreDriver)dataStoreDriver).revokeAccess(dataObject, host, dataStore); + } + } + + @Override + public void revokeAccess(long vmId, long hostId) { + HostVO host = _hostDao.findById(hostId); + + List<VolumeVO> volumesForVm = _volsDao.findByInstance(vmId); + + if (volumesForVm != null) { + for (VolumeVO volumeForVm : volumesForVm) { + VolumeInfo volumeInfo = volFactory.getVolume(volumeForVm.getId()); + + // pool id can be null for the VM's volumes in Allocated state + if (volumeForVm.getPoolId() != null) { + DataStore dataStore = dataStoreMgr.getDataStore(volumeForVm.getPoolId(), DataStoreRole.Primary); + + volService.revokeAccess(volumeInfo, host, dataStore); + } + } + } + } + + @Override + @DB + public Volume migrateVolume(Volume volume, StoragePool destPool) throws StorageUnavailableException { + VolumeInfo vol = volFactory.getVolume(volume.getId()); + + DataStore dataStoreTarget = dataStoreMgr.getDataStore(destPool.getId(), DataStoreRole.Primary); + AsyncCallFuture<VolumeApiResult> future = volService.copyVolume(vol, dataStoreTarget); + try { + VolumeApiResult result = future.get(); + if (result.isFailed()) { + s_logger.error("Migrate volume failed:" + result.getResult()); + throw new StorageUnavailableException("Migrate volume failed: " + result.getResult(), destPool.getId()); + } else { + // update the volumeId for snapshots on secondary + if (!_snapshotDao.listByVolumeId(vol.getId()).isEmpty()) { + _snapshotDao.updateVolumeIds(vol.getId(), result.getVolume().getId()); + _snapshotDataStoreDao.updateVolumeIds(vol.getId(), result.getVolume().getId()); + } + } + return result.getVolume(); + } catch (InterruptedException e) { + s_logger.debug("migrate volume failed", e); + throw new CloudRuntimeException(e.getMessage()); + } catch (ExecutionException e) { + s_logger.debug("migrate volume failed", e); + throw new CloudRuntimeException(e.getMessage()); + } + } + + @DB + protected Volume liveMigrateVolume(Volume volume, StoragePool destPool) { + VolumeInfo vol = volFactory.getVolume(volume.getId()); + AsyncCallFuture<VolumeApiResult> future = volService.migrateVolume(vol, (DataStore)destPool); + try { + VolumeApiResult result = future.get(); + if (result.isFailed()) { + s_logger.debug("migrate volume failed:" + result.getResult()); + return null; + } + return result.getVolume(); + } catch (InterruptedException e) { + s_logger.debug("migrate volume failed", e); + return null; + } catch (ExecutionException e) { + s_logger.debug("migrate volume failed", e); + return null; + } + } + + @Override + public void migrateVolumes(VirtualMachine vm, VirtualMachineTO vmTo, Host srcHost, Host destHost, Map<Volume, StoragePool> volumeToPool) { + // Check if all the vms being migrated belong to the vm. + // Check if the storage pool is of the right type. + // Create a VolumeInfo to DataStore map too. + Map<VolumeInfo, DataStore> volumeMap = new HashMap<VolumeInfo, DataStore>(); + for (Map.Entry<Volume, StoragePool> entry : volumeToPool.entrySet()) { + Volume volume = entry.getKey(); + StoragePool storagePool = entry.getValue(); + StoragePool destPool = (StoragePool)dataStoreMgr.getDataStore(storagePool.getId(), DataStoreRole.Primary); + + if (volume.getInstanceId() != vm.getId()) { + throw new CloudRuntimeException("Volume " + volume + " that has to be migrated doesn't belong to the" + " instance " + vm); + } + + if (destPool == null) { + throw new CloudRuntimeException("Failed to find the destination storage pool " + storagePool.getId()); + } + + volumeMap.put(volFactory.getVolume(volume.getId()), (DataStore)destPool); + } + + AsyncCallFuture<CommandResult> future = volService.migrateVolumes(volumeMap, vmTo, srcHost, destHost); + try { + CommandResult result = future.get(); + if (result.isFailed()) { + s_logger.debug("Failed to migrated vm " + vm + " along with its volumes. " + result.getResult()); + throw new CloudRuntimeException("Failed to migrated vm " + vm + " along with its volumes. "); + } + } catch (InterruptedException e) { + s_logger.debug("Failed to migrated vm " + vm + " along with its volumes.", e); + } catch (ExecutionException e) { + s_logger.debug("Failed to migrated vm " + vm + " along with its volumes.", e); + } + } + + @Override + public boolean storageMigration(VirtualMachineProfile vm, StoragePool destPool) throws StorageUnavailableException { + List<VolumeVO> vols = _volsDao.findUsableVolumesForInstance(vm.getId()); + List<Volume> volumesNeedToMigrate = new ArrayList<Volume>(); + + for (VolumeVO volume : vols) { + if (volume.getState() != Volume.State.Ready) { + s_logger.debug("volume: " + volume.getId() + " is in " + volume.getState() + " state"); + throw new CloudRuntimeException("volume: " + volume.getId() + " is in " + volume.getState() + " state"); + } + + if (volume.getPoolId() == destPool.getId()) { + s_logger.debug("volume: " + volume.getId() + " is on the same storage pool: " + destPool.getId()); + continue; + } + + volumesNeedToMigrate.add(volume); + } + + if (volumesNeedToMigrate.isEmpty()) { + s_logger.debug("No volume need to be migrated"); + return true; + } + + for (Volume vol : volumesNeedToMigrate) { + Volume result = migrateVolume(vol, destPool); + if (result == null) { + return false; + } + } + return true; + } + + @Override + public void prepareForMigration(VirtualMachineProfile vm, DeployDestination dest) { + List<VolumeVO> vols = _volsDao.findUsableVolumesForInstance(vm.getId()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Preparing " + vols.size() + " volumes for " + vm); + } + + for (VolumeVO vol : vols) { + VolumeInfo volumeInfo = volFactory.getVolume(vol.getId()); + DataTO volTO = volumeInfo.getTO(); + DiskTO disk = storageMgr.getDiskWithThrottling(volTO, vol.getVolumeType(), vol.getDeviceId(), vol.getPath(), vm.getServiceOfferingId(), vol.getDiskOfferingId()); + DataStore dataStore = dataStoreMgr.getDataStore(vol.getPoolId(), DataStoreRole.Primary); + + disk.setDetails(getDetails(volumeInfo, dataStore)); + + vm.addDisk(disk); + } + + //if (vm.getType() == VirtualMachine.Type.User && vm.getTemplate().getFormat() == ImageFormat.ISO) { + if (vm.getType() == VirtualMachine.Type.User) { + _tmpltMgr.prepareIsoForVmProfile(vm, dest); + //DataTO dataTO = tmplFactory.getTemplate(vm.getTemplate().getId(), DataStoreRole.Image, vm.getVirtualMachine().getDataCenterId()).getTO(); + //DiskTO iso = new DiskTO(dataTO, 3L, null, Volume.Type.ISO); + //vm.addDisk(iso); + } + } + + private Map<String, String> getDetails(VolumeInfo volumeInfo, DataStore dataStore) { + Map<String, String> details = new HashMap<String, String>(); + + StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId()); + + details.put(DiskTO.MANAGED, String.valueOf(storagePool.isManaged())); + details.put(DiskTO.STORAGE_HOST, storagePool.getHostAddress()); + details.put(DiskTO.STORAGE_PORT, String.valueOf(storagePool.getPort())); + details.put(DiskTO.VOLUME_SIZE, String.valueOf(volumeInfo.getSize())); + details.put(DiskTO.IQN, volumeInfo.get_iScsiName()); + details.put(DiskTO.MOUNT_POINT, volumeInfo.get_iScsiName()); + + VolumeVO volume = _volumeDao.findById(volumeInfo.getId()); + + details.put(DiskTO.PROTOCOL_TYPE, (volume.getPoolType() != null) ? volume.getPoolType().toString() : null); + + ChapInfo chapInfo = volService.getChapInfo(volumeInfo, dataStore); + + if (chapInfo != null) { + details.put(DiskTO.CHAP_INITIATOR_USERNAME, chapInfo.getInitiatorUsername()); + details.put(DiskTO.CHAP_INITIATOR_SECRET, chapInfo.getInitiatorSecret()); + details.put(DiskTO.CHAP_TARGET_USERNAME, chapInfo.getTargetUsername()); + details.put(DiskTO.CHAP_TARGET_SECRET, chapInfo.getTargetSecret()); + } + + return details; + } + + private static enum VolumeTaskType { + RECREATE, NOP, MIGRATE + } + + private static class VolumeTask { + final VolumeTaskType type; + final StoragePoolVO pool; + final VolumeVO volume; + + VolumeTask(VolumeTaskType type, VolumeVO volume, StoragePoolVO pool) { + this.type = type; + this.pool = pool; + this.volume = volume; + } + } + + private List<VolumeTask> getTasks(List<VolumeVO> vols, Map<Volume, StoragePool> destVols, VirtualMachineProfile vm) throws StorageUnavailableException { + boolean recreate = RecreatableSystemVmEnabled.value(); + List<VolumeTask> tasks = new ArrayList<VolumeTask>(); + for (VolumeVO vol : vols) { + StoragePoolVO assignedPool = null; + if (destVols != null) { + StoragePool pool = destVols.get(vol); + if (pool != null) { + assignedPool = _storagePoolDao.findById(pool.getId()); + } + } + if (assignedPool == null && recreate) { + assignedPool = _storagePoolDao.findById(vol.getPoolId()); + } + if (assignedPool != null) { + Volume.State state = vol.getState(); + if (state == Volume.State.Allocated || state == Volume.State.Creating) { + VolumeTask task = new VolumeTask(VolumeTaskType.RECREATE, vol, null); + tasks.add(task); + } else { + if (vol.isRecreatable()) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Volume " + vol + " will be recreated on storage pool " + assignedPool + " assigned by deploymentPlanner"); + } + VolumeTask task = new VolumeTask(VolumeTaskType.RECREATE, vol, null); + tasks.add(task); + } else { + if (assignedPool.getId() != vol.getPoolId()) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Mismatch in storage pool " + assignedPool + " assigned by deploymentPlanner and the one associated with volume " + vol); + } + DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, vol.getDiskOfferingId()); + if (diskOffering.getUseLocalStorage()) { + // Currently migration of local volume is not supported so bail out + if (s_logger.isDebugEnabled()) { + s_logger.debug("Local volume " + vol + " cannot be recreated on storagepool " + assignedPool + " assigned by deploymentPlanner"); + } + throw new CloudRuntimeException("Local volume " + vol + " cannot be recreated on storagepool " + assignedPool + " assigned by deploymentPlanner"); + } else { + //Check if storage migration is enabled in config + Boolean isHAOperation = (Boolean)vm.getParameter(VirtualMachineProfile.Param.HaOperation); + Boolean storageMigrationEnabled = true; + if (isHAOperation != null && isHAOperation) { + storageMigrationEnabled = StorageHAMigrationEnabled.value(); + } else { + storageMigrationEnabled = StorageMigrationEnabled.value(); + } + if (storageMigrationEnabled) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Shared volume " + vol + " will be migrated on storage pool " + assignedPool + " assigned by deploymentPlanner"); + } + VolumeTask task = new VolumeTask(VolumeTaskType.MIGRATE, vol, assignedPool); + tasks.add(task); + } else { + throw new CloudRuntimeException("Cannot migrate volumes. Volume Migration is disabled"); + } + } + } else { + StoragePoolVO pool = _storagePoolDao.findById(vol.getPoolId()); + VolumeTask task = new VolumeTask(VolumeTaskType.NOP, vol, pool); + tasks.add(task); + } + + } + } + } else { + if (vol.getPoolId() == null) { + throw new StorageUnavailableException("Volume has no pool associate and also no storage pool assigned in DeployDestination, Unable to create " + vol, Volume.class, vol.getId()); + } + if (s_logger.isDebugEnabled()) { + s_logger.debug("No need to recreate the volume: " + vol + ", since it already has a pool assigned: " + vol.getPoolId() + ", adding disk to VM"); + } + StoragePoolVO pool = _storagePoolDao.findById(vol.getPoolId()); + VolumeTask task = new VolumeTask(VolumeTaskType.NOP, vol, pool); + tasks.add(task); + } + } + + return tasks; + } + + private Pair<VolumeVO, DataStore> recreateVolume(VolumeVO vol, VirtualMachineProfile vm, DeployDestination dest) throws StorageUnavailableException { + VolumeVO newVol; + boolean recreate = RecreatableSystemVmEnabled.value(); + DataStore destPool = null; + if (recreate && (dest.getStorageForDisks() == null || dest.getStorageForDisks().get(vol) == null)) { + destPool = dataStoreMgr.getDataStore(vol.getPoolId(), DataStoreRole.Primary); + s_logger.debug("existing pool: " + destPool.getId()); + } else { + StoragePool pool = dest.getStorageForDisks().get(vol); + destPool = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary); + } + if (vol.getState() == Volume.State.Allocated || vol.getState() == Volume.State.Creating) { + newVol = vol; + } else { + newVol = switchVolume(vol, vm); + // update the volume->PrimaryDataStoreVO map since volumeId has + // changed + if (dest.getStorageForDisks() != null && dest.getStorageForDisks().containsKey(vol)) { + StoragePool poolWithOldVol = dest.getStorageForDisks().get(vol); + dest.getStorageForDisks().put(newVol, poolWithOldVol); + dest.getStorageForDisks().remove(vol); + } + if (s_logger.isDebugEnabled()) { + s_logger.debug("Created new volume " + newVol + " for old volume " + vol); + } + } + VolumeInfo volume = volFactory.getVolume(newVol.getId(), destPool); + Long templateId = newVol.getTemplateId(); + for (int i = 0; i < 2; i++) { + // retry one more time in case of template reload is required for VMware case + AsyncCallFuture<VolumeApiResult> future; + + if (templateId == null) { + DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, volume.getDiskOfferingId()); + HypervisorType hyperType = vm.getVirtualMachine().getHypervisorType(); + + // update the volume's hv_ss_reserve (hypervisor snapshot reserve) from a disk offering (used for managed storage) + volService.updateHypervisorSnapshotReserveForVolume(diskOffering, volume.getId(), hyperType); + + volume = volFactory.getVolume(newVol.getId(), destPool); + + future = volService.createVolumeAsync(volume, destPool); + } else { + TemplateInfo templ = tmplFactory.getReadyTemplateOnImageStore(templateId, dest.getDataCenter().getId()); + + if (templ == null) { + if (tmplFactory.isTemplateMarkedForDirectDownload(templateId)) { + // Template is marked for direct download bypassing Secondary Storage + templ = tmplFactory.getReadyBypassedTemplateOnPrimaryStore(templateId, destPool.getId(), dest.getHost().getId()); + } else { + s_logger.debug("can't find ready template: " + templateId + " for data center " + dest.getDataCenter().getId()); + throw new CloudRuntimeException("can't find ready template: " + templateId + " for data center " + dest.getDataCenter().getId()); + } + } + + PrimaryDataStore primaryDataStore = (PrimaryDataStore)destPool; + + if (primaryDataStore.isManaged()) { + DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, volume.getDiskOfferingId()); + HypervisorType hyperType = vm.getVirtualMachine().getHypervisorType(); + + // update the volume's hv_ss_reserve (hypervisor snapshot reserve) from a disk offering (used for managed storage) + volService.updateHypervisorSnapshotReserveForVolume(diskOffering, volume.getId(), hyperType); + + long hostId = vm.getVirtualMachine().getHostId(); + + future = volService.createManagedStorageVolumeFromTemplateAsync(volume, destPool.getId(), templ, hostId); + } else { + future = volService.createVolumeFromTemplateAsync(volume, destPool.getId(), templ); + } + } + VolumeApiResult result; + try { + result = future.get(); + if (result.isFailed()) { + if (result.getResult().contains("request template reload") && (i == 0)) { + s_logger.debug("Retry template re-deploy for vmware"); + continue; + } else { + s_logger.debug("Unable to create " + newVol + ":" + result.getResult()); + throw new StorageUnavailableException("Unable to create " + newVol + ":" + result.getResult(), destPool.getId()); + } + } + + StoragePoolVO storagePool = _storagePoolDao.findById(destPool.getId()); + + if (storagePool.isManaged()) { + long hostId = vm.getVirtualMachine().getHostId(); + Host host = _hostDao.findById(hostId); + + volService.grantAccess(volFactory.getVolume(newVol.getId()), host, destPool); + } + + newVol = _volsDao.findById(newVol.getId()); + break; //break out of template-redeploy retry loop + } catch (InterruptedException | ExecutionException e) { + s_logger.error("Unable to create " + newVol, e); + throw new StorageUnavailableException("Unable to create " + newVol + ":" + e.toString(), destPool.getId()); + } + } + + return new Pair<VolumeVO, DataStore>(newVol, destPool); + } + + @Override + public void prepare(VirtualMachineProfile vm, DeployDestination dest) throws StorageUnavailableException, InsufficientStorageCapacityException, ConcurrentOperationException { + + if (dest == null) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("DeployDestination cannot be null, cannot prepare Volumes for the vm: " + vm); + } + throw new CloudRuntimeException("Unable to prepare Volume for vm because DeployDestination is null, vm:" + vm); + } + + // don't allow to start vm that doesn't have a root volume + if (_volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT).isEmpty()) { + throw new CloudRuntimeException("Unable to prepare volumes for vm as ROOT volume is missing"); + } + + List<VolumeVO> vols = _volsDao.findUsableVolumesForInstance(vm.getId()); + + List<VolumeTask> tasks = getTasks(vols, dest.getStorageForDisks(), vm); + Volume vol = null; + StoragePool pool; + for (VolumeTask task : tasks) { + if (task.type == VolumeTaskType.NOP) { + vol = task.volume; + + pool = (StoragePool)dataStoreMgr.getDataStore(task.pool.getId(), DataStoreRole.Primary); + + // For zone-wide managed storage, it is possible that the VM can be started in another + // cluster. In that case, make sure that the volume is in the right access group. + if (pool.isManaged()) { + Host lastHost = _hostDao.findById(vm.getVirtualMachine().getLastHostId()); + Host host = _hostDao.findById(vm.getVirtualMachine().getHostId()); + + long lastClusterId = lastHost == null || lastHost.getClusterId() == null ? -1 : lastHost.getClusterId(); + long clusterId = host == null || host.getClusterId() == null ? -1 : host.getClusterId(); + + if (lastClusterId != clusterId) { + if (lastHost != null) { + storageMgr.removeStoragePoolFromCluster(lastHost.getId(), vol.get_iScsiName(), pool); + + DataStore storagePool = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary); + + volService.revokeAccess(volFactory.getVolume(vol.getId()), lastHost, storagePool); + } + + volService.grantAccess(volFactory.getVolume(vol.getId()), host, (DataStore)pool); + } + } + } else if (task.type == VolumeTaskType.MIGRATE) { + pool = (StoragePool)dataStoreMgr.getDataStore(task.pool.getId(), DataStoreRole.Primary); + vol = migrateVolume(task.volume, pool); + } else if (task.type == VolumeTaskType.RECREATE) { + Pair<VolumeVO, DataStore> result = recreateVolume(task.volume, vm, dest); + pool = (StoragePool)dataStoreMgr.getDataStore(result.second().getId(), DataStoreRole.Primary); + vol = result.first(); + } + + VolumeInfo volumeInfo = volFactory.getVolume(vol.getId()); + DataTO volTO = volumeInfo.getTO(); + DiskTO disk = storageMgr.getDiskWithThrottling(volTO, vol.getVolumeType(), vol.getDeviceId(), vol.getPath(), vm.getServiceOfferingId(), vol.getDiskOfferingId()); + DataStore dataStore = dataStoreMgr.getDataStore(vol.getPoolId(), DataStoreRole.Primary); + + disk.setDetails(getDetails(volumeInfo, dataStore)); + + vm.addDisk(disk); + + // If hypervisor is vSphere, check for clone type setting. + if (vm.getHypervisorType().equals(HypervisorType.VMware)) { + // retrieve clone flag. + UserVmCloneType cloneType = UserVmCloneType.linked; + Boolean value = CapacityManager.VmwareCreateCloneFull.valueIn(vol.getPoolId()); + if (value != null && value) { + cloneType = UserVmCloneType.full; + } + UserVmCloneSettingVO cloneSettingVO = _vmCloneSettingDao.findByVmId(vm.getId()); + if (cloneSettingVO != null) { + if (!cloneSettingVO.getCloneType().equals(cloneType.toString())) { + cloneSettingVO.setCloneType(cloneType.toString()); - _vmCloneSettingDao.update(cloneSettingVO.getVmId(), cloneSettingVO); ++ _vmCloneSettingDao.update(cloneSettingVO.getId(), cloneSettingVO); + } + } else { + UserVmCloneSettingVO vmCloneSettingVO = new UserVmCloneSettingVO(vm.getId(), cloneType.toString()); + _vmCloneSettingDao.persist(vmCloneSettingVO); + } + } + + } + } + + private boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException { + return _volStateMachine.transitTo(vol, event, null, _volsDao); + } + + @Override + public boolean canVmRestartOnAnotherServer(long vmId) { + List<VolumeVO> vols = _volsDao.findCreatedByInstance(vmId); + for (VolumeVO vol : vols) { + StoragePoolVO storagePoolVO = _storagePoolDao.findById(vol.getPoolId()); + if (!vol.isRecreatable() && storagePoolVO != null && storagePoolVO.getPoolType() != null && !(storagePoolVO.getPoolType().isShared())) { + return false; + } + } + return true; + } + + public static final ConfigKey<Long> MaxVolumeSize = new ConfigKey<Long>(Long.class, "storage.max.volume.size", "Storage", "2000", "The maximum size for a volume (in GB).", true); + + public static final ConfigKey<Boolean> RecreatableSystemVmEnabled = new ConfigKey<Boolean>(Boolean.class, "recreate.systemvm.enabled", "Advanced", "false", + "If true, will recreate system vm root disk whenever starting system vm", true); + + public static final ConfigKey<Boolean> StorageHAMigrationEnabled = new ConfigKey<Boolean>(Boolean.class, "enable.ha.storage.migration", "Storage", "true", + "Enable/disable storage migration across primary storage during HA", true); + + public static final ConfigKey<Boolean> StorageMigrationEnabled = new ConfigKey<Boolean>(Boolean.class, "enable.storage.migration", "Storage", "true", + "Enable/disable storage migration across primary storage", true); + + static final ConfigKey<Boolean> VolumeUrlCheck = new ConfigKey<Boolean>("Advanced", Boolean.class, "volume.url.check", "true", + "Check the url for a volume before downloading it from the management server. Set to flase when you managment has no internet access.", true); + + @Override + public ConfigKey<?>[] getConfigKeys() { + return new ConfigKey<?>[] {RecreatableSystemVmEnabled, MaxVolumeSize, StorageHAMigrationEnabled, StorageMigrationEnabled, CustomDiskOfferingMaxSize, CustomDiskOfferingMinSize, VolumeUrlCheck}; + } + + @Override + public String getConfigComponentName() { + return VolumeOrchestrationService.class.getSimpleName(); + } + + @Override + public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { + return true; + } + + private void cleanupVolumeDuringAttachFailure(Long volumeId, Long vmId) { + VolumeVO volume = _volsDao.findById(volumeId); + if (volume == null) { + return; + } + + if (volume.getState().equals(Volume.State.Creating)) { + s_logger.debug("Remove volume: " + volume.getId() + ", as it's leftover from last mgt server stop"); + _volsDao.remove(volume.getId()); + } + + if (volume.getState().equals(Volume.State.Attaching)) { + s_logger.warn("Vol: " + volume.getName() + " failed to attach to VM: " + _userVmDao.findById(vmId).getHostName() + " on last mgt server stop, changing state back to Ready"); + volume.setState(Volume.State.Ready); + _volsDao.update(volumeId, volume); + } + } + + private void cleanupVolumeDuringMigrationFailure(Long volumeId, Long destPoolId) { + StoragePool destPool = (StoragePool)dataStoreMgr.getDataStore(destPoolId, DataStoreRole.Primary); + if (destPool == null) { + return; + } + + VolumeVO volume = _volsDao.findById(volumeId); + if (volume.getState() == Volume.State.Migrating) { + VolumeVO duplicateVol = _volsDao.findByPoolIdName(destPoolId, volume.getName()); + if (duplicateVol != null) { + s_logger.debug("Remove volume " + duplicateVol.getId() + " on storage pool " + destPoolId); + _volsDao.remove(duplicateVol.getId()); + } + + s_logger.debug("change volume state to ready from migrating in case migration failure for vol: " + volumeId); + volume.setState(Volume.State.Ready); + _volsDao.update(volumeId, volume); + } + + } + + private void cleanupVolumeDuringSnapshotFailure(Long volumeId, Long snapshotId) { + _snapshotSrv.cleanupVolumeDuringSnapshotFailure(volumeId, snapshotId); + VolumeVO volume = _volsDao.findById(volumeId); + if (volume.getState() == Volume.State.Snapshotting) { + s_logger.debug("change volume state back to Ready: " + volume.getId()); + volume.setState(Volume.State.Ready); + _volsDao.update(volume.getId(), volume); + } + } + + @Override + public void cleanupStorageJobs() { + //clean up failure jobs related to volume + List<AsyncJobVO> jobs = _jobMgr.findFailureAsyncJobs(VmWorkAttachVolume.class.getName(), VmWorkMigrateVolume.class.getName(), VmWorkTakeVolumeSnapshot.class.getName()); + + for (AsyncJobVO job : jobs) { + try { + if (job.getCmd().equalsIgnoreCase(VmWorkAttachVolume.class.getName())) { + VmWorkAttachVolume work = VmWorkSerializer.deserialize(VmWorkAttachVolume.class, job.getCmdInfo()); + cleanupVolumeDuringAttachFailure(work.getVolumeId(), work.getVmId()); + } else if (job.getCmd().equalsIgnoreCase(VmWorkMigrateVolume.class.getName())) { + VmWorkMigrateVolume work = VmWorkSerializer.deserialize(VmWorkMigrateVolume.class, job.getCmdInfo()); + cleanupVolumeDuringMigrationFailure(work.getVolumeId(), work.getDestPoolId()); + } else if (job.getCmd().equalsIgnoreCase(VmWorkTakeVolumeSnapshot.class.getName())) { + VmWorkTakeVolumeSnapshot work = VmWorkSerializer.deserialize(VmWorkTakeVolumeSnapshot.class, job.getCmdInfo()); + cleanupVolumeDuringSnapshotFailure(work.getVolumeId(), work.getSnapshotId()); + } + } catch (Exception e) { + s_logger.debug("clean up job failure, will continue", e); + } + } + } + + @Override + public boolean stop() { + return true; + } + + @Override + public String getName() { + return "Volume Manager"; + } + + @Override + public void destroyVolume(Volume volume) { + try { + // Mark volume as removed if volume has not been created on primary + if (volume.getState() == Volume.State.Allocated) { + _volsDao.remove(volume.getId()); + stateTransitTo(volume, Volume.Event.DestroyRequested); + } else { + volService.destroyVolume(volume.getId()); + } + // FIXME - All this is boiler plate code and should be done as part of state transition. This shouldn't be part of orchestrator. + // publish usage event for the volume + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), Volume.class.getName(), + volume.getUuid(), volume.isDisplayVolume()); + _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.volume, volume.isDisplay()); + _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, volume.isDisplay(), new Long(volume.getSize())); + } catch (Exception e) { + s_logger.debug("Failed to destroy volume" + volume.getId(), e); + throw new CloudRuntimeException("Failed to destroy volume" + volume.getId(), e); + } + } + + @Override + public String getVmNameFromVolumeId(long volumeId) { + VolumeVO volume = _volsDao.findById(volumeId); + return getVmNameOnVolume(volume); + } + + @Override + public String getStoragePoolOfVolume(long volumeId) { + VolumeVO vol = _volsDao.findById(volumeId); + return dataStoreMgr.getPrimaryDataStore(vol.getPoolId()).getUuid(); + } + + @Override + public void updateVolumeDiskChain(long volumeId, String path, String chainInfo) { + VolumeVO vol = _volsDao.findById(volumeId); + boolean needUpdate = false; + // Volume path is not getting updated in the DB, need to find reason and fix the issue. + if (vol.getPath() == null) { + return; + } + if (!vol.getPath().equalsIgnoreCase(path)) { + needUpdate = true; + } + + if (chainInfo != null && (vol.getChainInfo() == null || !chainInfo.equalsIgnoreCase(vol.getChainInfo()))) { + needUpdate = true; + } + + if (needUpdate) { + s_logger.info("Update volume disk chain info. vol: " + vol.getId() + ", " + vol.getPath() + " -> " + path + ", " + vol.getChainInfo() + " -> " + chainInfo); + vol.setPath(path); + vol.setChainInfo(chainInfo); + _volsDao.update(volumeId, vol); + } + } +}
