Return-Path: X-Original-To: apmail-cloudstack-commits-archive@www.apache.org Delivered-To: apmail-cloudstack-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id BB5F010CAA for ; Tue, 13 Aug 2013 04:09:45 +0000 (UTC) Received: (qmail 68578 invoked by uid 500); 13 Aug 2013 04:09:43 -0000 Delivered-To: apmail-cloudstack-commits-archive@cloudstack.apache.org Received: (qmail 68503 invoked by uid 500); 13 Aug 2013 04:09:43 -0000 Mailing-List: contact commits-help@cloudstack.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cloudstack.apache.org Delivered-To: mailing list commits@cloudstack.apache.org Received: (qmail 67826 invoked by uid 99); 13 Aug 2013 04:09:36 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 13 Aug 2013 04:09:36 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 0CF928BD3FB; Tue, 13 Aug 2013 04:09:34 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: ahuang@apache.org To: commits@cloudstack.apache.org Date: Tue, 13 Aug 2013 04:09:38 -0000 Message-Id: <604aaa0b54a44529b887258826d708c3@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [06/11] Moved volume related code into cloud-engine-orchestration http://git-wip-us.apache.org/repos/asf/cloudstack/blob/de2bd96d/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java ---------------------------------------------------------------------- diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java new file mode 100644 index 0000000..5c0ec66 --- /dev/null +++ b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -0,0 +1,2861 @@ +/* + * 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.storage; + +import java.util.ArrayList; +import java.util.Date; +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 com.cloud.utils.DateUtil; +import com.cloud.utils.EnumUtils; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.UriUtils; + +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.ExtractVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.UpdateVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager; +import org.apache.cloudstack.engine.subsystem.api.storage.HostScope; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.Scope; +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.StoragePoolAllocator; +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.dao.ConfigurationDao; +import org.apache.cloudstack.framework.jobs.AsyncJob; +import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; +import org.apache.cloudstack.framework.jobs.AsyncJobManager; +import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; +import org.apache.cloudstack.storage.command.AttachAnswer; +import org.apache.cloudstack.storage.command.AttachCommand; +import org.apache.cloudstack.storage.command.CommandResult; +import org.apache.cloudstack.storage.command.DettachCommand; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO; +import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.to.DataTO; +import com.cloud.agent.api.to.DiskTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.alert.AlertManager; +import com.cloud.api.ApiDBUtils; +import com.cloud.capacity.CapacityManager; +import com.cloud.capacity.dao.CapacityDao; +import com.cloud.configuration.Config; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.configuration.Resource.ResourceType; +import com.cloud.consoleproxy.ConsoleProxyManager; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.HostPodVO; +import com.cloud.dc.Pod; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.HostPodDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.domain.Domain; +import com.cloud.domain.dao.DomainDao; +import com.cloud.event.ActionEvent; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventUtils; +import com.cloud.event.dao.EventDao; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientStorageCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.exception.ResourceAllocationException; +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.hypervisor.HypervisorCapabilitiesVO; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao; +import com.cloud.network.NetworkModel; +import com.cloud.org.Grouping; +import com.cloud.resource.ResourceManager; +import com.cloud.server.ManagementServer; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.Volume.Type; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.SnapshotDao; +import com.cloud.storage.dao.SnapshotPolicyDao; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.storage.dao.StoragePoolWorkDao; +import com.cloud.storage.dao.UploadDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VMTemplatePoolDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.dao.VolumeDetailsDao; +import com.cloud.storage.download.DownloadMonitor; +import com.cloud.storage.secondary.SecondaryStorageVmManager; +import com.cloud.storage.snapshot.SnapshotApiService; +import com.cloud.storage.snapshot.SnapshotManager; +import com.cloud.storage.snapshot.SnapshotScheduler; +import com.cloud.storage.upload.UploadMonitor; +import com.cloud.tags.dao.ResourceTagDao; +import com.cloud.template.TemplateManager; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.ResourceLimitService; +import com.cloud.user.VmDiskStatisticsVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserDao; +import com.cloud.user.dao.VmDiskStatisticsDao; +import com.cloud.uservm.UserVm; +import com.cloud.utils.EnumUtils; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.UriUtils; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.Transaction; +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.UserVmManager; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.ConsoleProxyDao; +import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.SecondaryStorageVmDao; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; + +@Component +public class VolumeManagerImpl extends ManagerBase implements VolumeManager { + private static final Logger s_logger = Logger + .getLogger(VolumeManagerImpl.class); + @Inject + protected UserVmManager _userVmMgr; + @Inject + protected AgentManager _agentMgr; + @Inject + protected TemplateManager _tmpltMgr; + @Inject + protected AsyncJobManager _asyncMgr; + @Inject + protected SnapshotManager _snapshotMgr; + @Inject + protected SnapshotScheduler _snapshotScheduler; + @Inject + protected AccountManager _accountMgr; + @Inject + protected ConfigurationManager _configMgr; + @Inject + protected ConsoleProxyManager _consoleProxyMgr; + @Inject + protected SecondaryStorageVmManager _secStorageMgr; + @Inject + protected NetworkModel _networkMgr; + @Inject + protected ServiceOfferingDao _serviceOfferingDao; + @Inject + protected VolumeDao _volsDao; + @Inject + protected HostDao _hostDao; + @Inject + protected ConsoleProxyDao _consoleProxyDao; + @Inject + protected SnapshotDao _snapshotDao; + @Inject + protected SnapshotManager _snapMgr; + @Inject + protected SnapshotPolicyDao _snapshotPolicyDao; + @Inject + protected StoragePoolHostDao _storagePoolHostDao; + @Inject + StoragePoolDetailsDao storagePoolDetailsDao; + @Inject + protected AlertManager _alertMgr; + @Inject + protected TemplateDataStoreDao _vmTemplateStoreDao = null; + @Inject + protected VMTemplatePoolDao _vmTemplatePoolDao = null; + @Inject + protected VMTemplateDao _vmTemplateDao = null; + @Inject + protected StoragePoolHostDao _poolHostDao = null; + @Inject + protected UserVmDao _userVmDao; + @Inject + VolumeDataStoreDao _volumeStoreDao; + @Inject + protected VMInstanceDao _vmInstanceDao; + @Inject + protected PrimaryDataStoreDao _storagePoolDao = null; + @Inject + protected CapacityDao _capacityDao; + @Inject + protected CapacityManager _capacityMgr; + @Inject + protected DiskOfferingDao _diskOfferingDao; + @Inject + protected AccountDao _accountDao; + @Inject + protected EventDao _eventDao = null; + @Inject + protected DataCenterDao _dcDao = null; + @Inject + protected HostPodDao _podDao = null; + @Inject + protected VMTemplateDao _templateDao; + @Inject + protected ServiceOfferingDao _offeringDao; + @Inject + protected DomainDao _domainDao; + @Inject + protected UserDao _userDao; + @Inject + protected ClusterDao _clusterDao; + @Inject + protected VirtualMachineManager _vmMgr; + @Inject + protected DomainRouterDao _domrDao; + @Inject + protected SecondaryStorageVmDao _secStrgDao; + @Inject + protected StoragePoolWorkDao _storagePoolWorkDao; + @Inject + protected HypervisorGuruManager _hvGuruMgr; + @Inject + protected VolumeDao _volumeDao; + @Inject + protected OCFS2Manager _ocfs2Mgr; + @Inject + protected ResourceLimitService _resourceLimitMgr; + @Inject + protected SecondaryStorageVmManager _ssvmMgr; + @Inject + protected ResourceManager _resourceMgr; + @Inject + protected DownloadMonitor _downloadMonitor; + @Inject + protected ResourceTagDao _resourceTagDao; + @Inject + protected VmDiskStatisticsDao _vmDiskStatsDao; + @Inject + protected VMSnapshotDao _vmSnapshotDao; + @Inject + protected List _storagePoolAllocators; + @Inject + ConfigurationDao _configDao; + @Inject + VolumeDetailsDao _volDetailDao; + @Inject + ManagementServer _msServer; + @Inject + DataStoreManager dataStoreMgr; + @Inject + DataStoreProviderManager dataStoreProviderMgr; + @Inject + VolumeService volService; + @Inject + VolumeDataFactory volFactory; + @Inject + TemplateDataFactory tmplFactory; + @Inject + SnapshotDataFactory snapshotFactory; + @Inject + SnapshotApiService snapshotMgr; + @Inject + UploadMonitor _uploadMonitor; + @Inject + UploadDao _uploadDao; + + private int _copyvolumewait; + @Inject + protected HypervisorCapabilitiesDao _hypervisorCapabilitiesDao; + private final StateMachine2 _volStateMachine; + @Inject + StorageManager storageMgr; + private int _customDiskOfferingMinSize = 1; + private final int _customDiskOfferingMaxSize = 1024; + private long _maxVolumeSizeInGb; + private boolean _recreateSystemVmEnabled; + + public VolumeManagerImpl() { + _volStateMachine = Volume.State.getStateMachine(); + } + + @Override + public VolumeInfo moveVolume(VolumeInfo volume, long destPoolDcId, + Long destPoolPodId, Long destPoolClusterId, + HypervisorType dataDiskHyperType) + throws ConcurrentOperationException { + + // Find a destination storage pool with the specified criteria + DiskOfferingVO diskOffering = _diskOfferingDao.findById(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); + DataCenterVO destPoolDataCenter = _dcDao.findById(destPoolDcId); + HostPodVO destPoolPod = _podDao.findById(destPoolPodId); + + StoragePool destPool = storageMgr.findStoragePool(dskCh, + destPoolDataCenter, destPoolPod, destPoolClusterId, null, null, + new HashSet()); + + 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()); + } + + /* + * Upload the volume to secondary storage. + */ + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_VOLUME_UPLOAD, eventDescription = "uploading volume", async = true) + public VolumeVO uploadVolume(UploadVolumeCmd cmd) + throws ResourceAllocationException { + Account caller = CallContext.current().getCallingAccount(); + long ownerId = cmd.getEntityOwnerId(); + Account owner = _accountDao.findById(ownerId); + Long zoneId = cmd.getZoneId(); + String volumeName = cmd.getVolumeName(); + String url = cmd.getUrl(); + String format = cmd.getFormat(); + String imageStoreUuid = cmd.getImageStoreUuid(); + DataStore store = _tmpltMgr.getImageStore(imageStoreUuid, zoneId); + + validateVolume(caller, ownerId, zoneId, volumeName, url, format); + + VolumeVO volume = persistVolume(owner, zoneId, volumeName, + url, cmd.getFormat()); + + VolumeInfo vol = volFactory.getVolume(volume.getId()); + + RegisterVolumePayload payload = new RegisterVolumePayload(cmd.getUrl(), cmd.getChecksum(), + cmd.getFormat()); + vol.addPayload(payload); + + volService.registerVolume(vol, store); + return volume; + } + + private boolean validateVolume(Account caller, long ownerId, Long zoneId, + String volumeName, String url, String format) + throws ResourceAllocationException { + + // permission check + _accountMgr.checkAccess(caller, null, true, + _accountMgr.getActiveAccountById(ownerId)); + + // Check that the resource limit for volumes won't be exceeded + _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(ownerId), + ResourceType.volume); + + // Verify that zone exists + DataCenterVO zone = _dcDao.findById(zoneId); + if (zone == null) { + throw new InvalidParameterValueException( + "Unable to find zone by id " + zoneId); + } + + // Check if zone is disabled + if (Grouping.AllocationState.Disabled == zone.getAllocationState() + && !_accountMgr.isRootAdmin(caller.getType())) { + throw new PermissionDeniedException( + "Cannot perform this operation, Zone is currently disabled: " + + zoneId); + } + + if (url.toLowerCase().contains("file://")) { + throw new InvalidParameterValueException( + "File:// type urls are currently unsupported"); + } + + ImageFormat imgfmt = ImageFormat.valueOf(format.toUpperCase()); + if (imgfmt == null) { + throw new IllegalArgumentException("Image format is incorrect " + + format + ". Supported formats are " + + EnumUtils.listValues(ImageFormat.values())); + } + + String userSpecifiedName = volumeName; + if (userSpecifiedName == null) { + userSpecifiedName = getRandomVolumeName(); + } + if ((!url.toLowerCase().endsWith("vhd")) + && (!url.toLowerCase().endsWith("vhd.zip")) + && (!url.toLowerCase().endsWith("vhd.bz2")) + && (!url.toLowerCase().endsWith("vhd.gz")) + && (!url.toLowerCase().endsWith("qcow2")) + && (!url.toLowerCase().endsWith("qcow2.zip")) + && (!url.toLowerCase().endsWith("qcow2.bz2")) + && (!url.toLowerCase().endsWith("qcow2.gz")) + && (!url.toLowerCase().endsWith("ova")) + && (!url.toLowerCase().endsWith("ova.zip")) + && (!url.toLowerCase().endsWith("ova.bz2")) + && (!url.toLowerCase().endsWith("ova.gz")) + && (!url.toLowerCase().endsWith("img")) + && (!url.toLowerCase().endsWith("raw"))) { + throw new InvalidParameterValueException("Please specify a valid " + + format.toLowerCase()); + } + + if ((format.equalsIgnoreCase("vhd") && (!url.toLowerCase().endsWith( + ".vhd") + && !url.toLowerCase().endsWith("vhd.zip") + && !url.toLowerCase().endsWith("vhd.bz2") && !url.toLowerCase() + .endsWith("vhd.gz"))) + || (format.equalsIgnoreCase("qcow2") && (!url.toLowerCase() + .endsWith(".qcow2") + && !url.toLowerCase().endsWith("qcow2.zip") + && !url.toLowerCase().endsWith("qcow2.bz2") && !url + .toLowerCase().endsWith("qcow2.gz"))) + || (format.equalsIgnoreCase("ova") && (!url.toLowerCase() + .endsWith(".ova") + && !url.toLowerCase().endsWith("ova.zip") + && !url.toLowerCase().endsWith("ova.bz2") && !url + .toLowerCase().endsWith("ova.gz"))) + || (format.equalsIgnoreCase("raw") && (!url.toLowerCase() + .endsWith(".img") && !url.toLowerCase().endsWith("raw")))) { + throw new InvalidParameterValueException( + "Please specify a valid URL. URL:" + url + + " is an invalid for the format " + + format.toLowerCase()); + } + UriUtils.validateUrl(url); + + // Check that the resource limit for secondary storage won't be exceeded + _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(ownerId), ResourceType.secondary_storage, + UriUtils.getRemoteSize(url)); + + return false; + } + + @Override + public VolumeVO allocateDuplicateVolume(VolumeVO oldVol, Long templateId) { + VolumeVO newVol = new VolumeVO(oldVol.getVolumeType(), + oldVol.getName(), oldVol.getDataCenterId(), + oldVol.getDomainId(), oldVol.getAccountId(), + oldVol.getDiskOfferingId(), 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); + } + + @DB + protected VolumeInfo createVolumeFromSnapshot(VolumeVO volume, + SnapshotVO snapshot) throws StorageUnavailableException { + Account account = _accountDao.findById(volume.getAccountId()); + + final HashSet poolsToAvoid = new HashSet(); + StoragePool pool = null; + + Set podsToAvoid = new HashSet(); + Pair pod = null; + + DiskOfferingVO diskOffering = _diskOfferingDao + .findByIdIncludingRemoved(volume.getDiskOfferingId()); + DataCenterVO dc = _dcDao.findById(volume.getDataCenterId()); + DiskProfile dskCh = new DiskProfile(volume, diskOffering, + snapshot.getHypervisorType()); + + // Determine what pod to store the volume in + while ((pod = _resourceMgr.findPod(null, null, dc, account.getId(), podsToAvoid)) != null) { + podsToAvoid.add(pod.first().getId()); + // Determine what storage pool to store the volume in + while ((pool = storageMgr.findStoragePool(dskCh, dc, pod.first(), null, null, + null, poolsToAvoid)) != null) { + break; + } + } + + if (pool == null) { + String msg = "There are no available storage pools to store the volume in"; + s_logger.info(msg); + throw new StorageUnavailableException(msg, -1); + } + + VolumeInfo vol = volFactory.getVolume(volume.getId()); + DataStore store = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary); + SnapshotInfo snapInfo = snapshotFactory.getSnapshot(snapshot.getId(), DataStoreRole.Image); + AsyncCallFuture 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); + } + + } + + protected DiskProfile createDiskCharacteristics(VolumeInfo volume, + VMTemplateVO template, DataCenterVO dc, DiskOfferingVO 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); + } + } + + protected VolumeVO createVolumeFromSnapshot(VolumeVO volume, long snapshotId) throws StorageUnavailableException { + VolumeInfo createdVolume = null; + SnapshotVO snapshot = _snapshotDao.findById(snapshotId); + createdVolume = createVolumeFromSnapshot(volume, + snapshot); + + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, createdVolume.getAccountId(), createdVolume.getDataCenterId(), createdVolume.getId(), + createdVolume.getName(), createdVolume.getDiskOfferingId(), null, createdVolume.getSize(), Volume.class.getName(), createdVolume.getUuid()); + + return _volsDao.findById(createdVolume.getId()); + } + + @DB + public VolumeInfo copyVolumeFromSecToPrimary(VolumeInfo volume, + VMInstanceVO vm, VMTemplateVO template, DataCenterVO dc, + HostPodVO pod, Long clusterId, ServiceOfferingVO offering, + DiskOfferingVO diskOffering, List avoids, + long size, HypervisorType hyperType) throws NoTransitionException { + + final HashSet avoidPools = new HashSet( + avoids); + DiskProfile dskCh = createDiskCharacteristics(volume, template, dc, + diskOffering); + dskCh.setHyperType(vm.getHypervisorType()); + // Find a suitable storage to create volume on + StoragePool destPool = storageMgr.findStoragePool(dskCh, dc, pod, + clusterId, null, vm, avoidPools); + DataStore destStore = dataStoreMgr.getDataStore(destPool.getId(), DataStoreRole.Primary); + AsyncCallFuture 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, VMInstanceVO vm, + VMTemplateVO template, DataCenterVO dc, HostPodVO pod, + Long clusterId, ServiceOfferingVO offering, + DiskOfferingVO diskOffering, List avoids, + long size, HypervisorType hyperType) { + StoragePool pool = null; + + if (diskOffering != null && diskOffering.isCustomized()) { + diskOffering.setDiskSize(size); + } + + DiskProfile dskCh = null; + if (volume.getVolumeType() == Type.ROOT + && Storage.ImageFormat.ISO != template.getFormat()) { + dskCh = createDiskCharacteristics(volume, template, dc, offering); + } else { + dskCh = createDiskCharacteristics(volume, template, dc, + diskOffering); + } + + dskCh.setHyperType(hyperType); + + final HashSet avoidPools = new HashSet( + avoids); + + pool = storageMgr.findStoragePool(dskCh, dc, pod, clusterId, vm.getHostId(), + vm, avoidPools); + if (pool == null) { + s_logger.warn("Unable to find storage pool when create volume " + + volume.getName()); + throw new CloudRuntimeException("Unable to find storage pool when create volume" + volume.getName()); + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Trying to create " + volume + " on " + pool); + } + DataStore store = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary); + AsyncCallFuture 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()) { + 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); + } + + } + + public String getRandomVolumeName() { + return UUID.randomUUID().toString(); + } + + private VolumeVO persistVolume(Account owner, Long zoneId, + String volumeName, String url, String format) { + + Transaction txn = Transaction.currentTxn(); + txn.start(); + + VolumeVO volume = new VolumeVO(volumeName, zoneId, -1, -1, -1, + new Long(-1), null, null, 0, Volume.Type.DATADISK); + volume.setPoolId(null); + volume.setDataCenterId(zoneId); + volume.setPodId(null); + volume.setAccountId(owner.getAccountId()); + volume.setDomainId(owner.getDomainId()); + long diskOfferingId = _diskOfferingDao.findByUniqueName( + "Cloud.com-Custom").getId(); + volume.setDiskOfferingId(diskOfferingId); + // volume.setSize(size); + volume.setInstanceId(null); + volume.setUpdated(new Date()); + volume.setDomainId((owner == null) ? Domain.ROOT_DOMAIN : owner + .getDomainId()); + volume.setFormat(ImageFormat.valueOf(format)); + volume = _volsDao.persist(volume); + CallContext.current().setEventDetails("Volume Id: " + volume.getId()); + + // Increment resource count during allocation; if actual creation fails, + // decrement it + _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), + ResourceType.volume); + _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.secondary_storage, + UriUtils.getRemoteSize(url)); + + txn.commit(); + return volume; + } + + @Override + public boolean volumeOnSharedStoragePool(VolumeVO 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) { + UserVm vm = _userVmDao.findById(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) { + VMInstanceVO vm = _vmInstanceDao.findById(vmId); + + if (vm == null) { + return null; + } + return vm.getInstanceName(); + } + return null; + } + + /* + * Just allocate a volume in the database, don't send the createvolume cmd + * to hypervisor. The volume will be finally created only when it's attached + * to a VM. + */ + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", create = true) + public VolumeVO allocVolume(CreateVolumeCmd cmd) + throws ResourceAllocationException { + // FIXME: some of the scheduled event stuff might be missing here... + Account caller = CallContext.current().getCallingAccount(); + + long ownerId = cmd.getEntityOwnerId(); + Boolean displayVolumeEnabled = cmd.getDisplayVolume(); + + // permission check + _accountMgr.checkAccess(caller, null, true, + _accountMgr.getActiveAccountById(ownerId)); + + // Check that the resource limit for volumes won't be exceeded + _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(ownerId), + ResourceType.volume); + + Long zoneId = cmd.getZoneId(); + Long diskOfferingId = null; + DiskOfferingVO diskOffering = null; + Long size = null; + Long minIops = null; + Long maxIops = null; + // Volume VO used for extracting the source template id + VolumeVO parentVolume = null; + + // validate input parameters before creating the volume + if ((cmd.getSnapshotId() == null && cmd.getDiskOfferingId() == null) + || (cmd.getSnapshotId() != null && cmd.getDiskOfferingId() != null)) { + throw new InvalidParameterValueException( + "Either disk Offering Id or snapshot Id must be passed whilst creating volume"); + } + + if (cmd.getSnapshotId() == null) {// create a new volume + + diskOfferingId = cmd.getDiskOfferingId(); + size = cmd.getSize(); + Long sizeInGB = size; + if (size != null) { + if (size > 0) { + size = size * 1024 * 1024 * 1024; // user specify size in GB + } else { + throw new InvalidParameterValueException( + "Disk size must be larger than 0"); + } + } + + // Check that the the disk offering is specified + diskOffering = _diskOfferingDao.findById(diskOfferingId); + if ((diskOffering == null) || diskOffering.getRemoved() != null + || !DiskOfferingVO.Type.Disk.equals(diskOffering.getType())) { + throw new InvalidParameterValueException( + "Please specify a valid disk offering."); + } + + if (diskOffering.isCustomized()) { + if (size == null) { + throw new InvalidParameterValueException( + "This disk offering requires a custom size specified"); + } + if ((sizeInGB < _customDiskOfferingMinSize) + || (sizeInGB > _customDiskOfferingMaxSize)) { + throw new InvalidParameterValueException("Volume size: " + + sizeInGB + "GB is out of allowed range. Max: " + + _customDiskOfferingMaxSize + " Min:" + + _customDiskOfferingMinSize); + } + } + + if (!diskOffering.isCustomized() && size != null) { + throw new InvalidParameterValueException( + "This disk offering does not allow custom size"); + } + + if (diskOffering.getDomainId() == null) { + // do nothing as offering is public + } else { + _configMgr.checkDiskOfferingAccess(caller, diskOffering); + } + + if (diskOffering.getDiskSize() > 0) { + size = diskOffering.getDiskSize(); + } + + Boolean isCustomizedIops = diskOffering.isCustomizedIops(); + + if (isCustomizedIops != null) { + if (isCustomizedIops) { + minIops = cmd.getMinIops(); + maxIops = cmd.getMaxIops(); + + if (minIops == null && maxIops == null) { + minIops = 0L; + maxIops = 0L; + } + else { + if (minIops == null || minIops <= 0) { + throw new InvalidParameterValueException("The min IOPS must be greater than 0."); + } + + if (maxIops == null) { + maxIops = 0L; + } + + if (minIops > maxIops) { + throw new InvalidParameterValueException("The min IOPS must be less than or equal to the max IOPS."); + } + } + } + else { + minIops = diskOffering.getMinIops(); + maxIops = diskOffering.getMaxIops(); + } + } + + if (!validateVolumeSizeRange(size)) {// convert size from mb to gb + // for validation + throw new InvalidParameterValueException( + "Invalid size for custom volume creation: " + size + + " ,max volume size is:" + _maxVolumeSizeInGb); + } + } else { // create volume from snapshot + Long snapshotId = cmd.getSnapshotId(); + SnapshotVO snapshotCheck = _snapshotDao.findById(snapshotId); + if (snapshotCheck == null) { + throw new InvalidParameterValueException( + "unable to find a snapshot with id " + snapshotId); + } + + if (snapshotCheck.getState() != Snapshot.State.BackedUp) { + throw new InvalidParameterValueException("Snapshot id=" + + snapshotId + " is not in " + Snapshot.State.BackedUp + + " state yet and can't be used for volume creation"); + } + parentVolume = _volsDao.findByIdIncludingRemoved(snapshotCheck.getVolumeId()); + + diskOfferingId = snapshotCheck.getDiskOfferingId(); + diskOffering = _diskOfferingDao.findById(diskOfferingId); + zoneId = snapshotCheck.getDataCenterId(); + size = snapshotCheck.getSize(); // ; disk offering is used for tags + // purposes + + // check snapshot permissions + _accountMgr.checkAccess(caller, null, true, snapshotCheck); + } + + if(displayVolumeEnabled == null){ + displayVolumeEnabled = true; + } else{ + if(!_accountMgr.isRootAdmin(caller.getType())){ + throw new PermissionDeniedException( "Cannot update parameter displayvolume, only admin permitted "); + } + } + + // Check that the resource limit for primary storage won't be exceeded + _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(ownerId), ResourceType.primary_storage, + new Long(size)); + + // Verify that zone exists + DataCenterVO zone = _dcDao.findById(zoneId); + if (zone == null) { + throw new InvalidParameterValueException( + "Unable to find zone by id " + zoneId); + } + + // Check if zone is disabled + if (Grouping.AllocationState.Disabled == zone.getAllocationState() + && !_accountMgr.isRootAdmin(caller.getType())) { + throw new PermissionDeniedException( + "Cannot perform this operation, Zone is currently disabled: " + + zoneId); + } + + // If local storage is disabled then creation of volume with local disk + // offering not allowed + if (!zone.isLocalStorageEnabled() && diskOffering.getUseLocalStorage()) { + throw new InvalidParameterValueException( + "Zone is not configured to use local storage but volume's disk offering " + + diskOffering.getName() + " uses it"); + } + + String userSpecifiedName = cmd.getVolumeName(); + if (userSpecifiedName == null) { + userSpecifiedName = getRandomVolumeName(); + } + + Transaction txn = Transaction.currentTxn(); + txn.start(); + + VolumeVO volume = new VolumeVO(userSpecifiedName, -1, -1, -1, -1, + new Long(-1), null, null, 0, Volume.Type.DATADISK); + volume.setPoolId(null); + volume.setDataCenterId(zoneId); + volume.setPodId(null); + volume.setAccountId(ownerId); + volume.setDomainId(((caller == null) ? Domain.ROOT_DOMAIN : caller + .getDomainId())); + volume.setDiskOfferingId(diskOfferingId); + volume.setSize(size); + volume.setMinIops(minIops); + volume.setMaxIops(maxIops); + volume.setInstanceId(null); + volume.setUpdated(new Date()); + volume.setDomainId((caller == null) ? Domain.ROOT_DOMAIN : caller + .getDomainId()); + volume.setDisplayVolume(displayVolumeEnabled); + if (parentVolume != null) { + volume.setTemplateId(parentVolume.getTemplateId()); + volume.setFormat(parentVolume.getFormat()); + } else { + volume.setTemplateId(null); + } + + volume = _volsDao.persist(volume); + if (cmd.getSnapshotId() == null) { + // for volume created from snapshot, create usage event after volume creation + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), diskOfferingId, + null, size, Volume.class.getName(), volume.getUuid()); + } + + CallContext.current().setEventDetails("Volume Id: " + volume.getId()); + + // Increment resource count during allocation; if actual creation fails, + // decrement it + _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), + ResourceType.volume); + _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, + new Long(volume.getSize())); + + txn.commit(); + + return volume; + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", async = true) + public VolumeVO createVolume(CreateVolumeCmd cmd) { + VolumeVO volume = _volsDao.findById(cmd.getEntityId()); + boolean created = true; + + try { + if (cmd.getSnapshotId() != null) { + volume = createVolumeFromSnapshot(volume, cmd.getSnapshotId()); + if (volume.getState() != Volume.State.Ready) { + created = false; + } + } + return volume; + } catch(Exception e) { + created = false; + s_logger.debug("Failed to create volume: " + volume.getId(), e); + return null; + } finally { + if (!created) { + s_logger.trace("Decrementing volume resource count for account id=" + + volume.getAccountId() + + " as volume failed to create on the backend"); + _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), + ResourceType.volume); + _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, + new Long(volume.getSize())); + } + } + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_VOLUME_RESIZE, eventDescription = "resizing volume", async = true) + public VolumeVO resizeVolume(ResizeVolumeCmd cmd) + throws ResourceAllocationException { + Long newSize = null; + boolean shrinkOk = cmd.getShrinkOk(); + + VolumeVO volume = _volsDao.findById(cmd.getEntityId()); + if (volume == null) { + throw new InvalidParameterValueException("No such volume"); + } + + DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume + .getDiskOfferingId()); + DiskOfferingVO newDiskOffering = null; + + newDiskOffering = _diskOfferingDao.findById(cmd.getNewDiskOfferingId()); + + /* + * Volumes with no hypervisor have never been assigned, and can be + * resized by recreating. perhaps in the future we can just update the + * db entry for the volume + */ + if (_volsDao.getHypervisorType(volume.getId()) == HypervisorType.None) { + throw new InvalidParameterValueException( + "Can't resize a volume that has never been attached, not sure which hypervisor type. Recreate volume to resize."); + } + + /* Only works for KVM/Xen for now */ + if (_volsDao.getHypervisorType(volume.getId()) != HypervisorType.KVM + && _volsDao.getHypervisorType(volume.getId()) != HypervisorType.XenServer + && _volsDao.getHypervisorType(volume.getId()) != HypervisorType.VMware) { + throw new InvalidParameterValueException( + "Cloudstack currently only supports volumes marked as KVM or XenServer hypervisor for resize"); + } + + + if (volume.getState() != Volume.State.Ready) { + throw new InvalidParameterValueException( + "Volume should be in ready state before attempting a resize"); + } + + if (!volume.getVolumeType().equals(Volume.Type.DATADISK)) { + throw new InvalidParameterValueException( + "Can only resize DATA volumes"); + } + + /* + * figure out whether or not a new disk offering or size parameter is + * required, get the correct size value + */ + if (newDiskOffering == null) { + if (diskOffering.isCustomized()) { + newSize = cmd.getSize(); + + if (newSize == null) { + throw new InvalidParameterValueException( + "new offering is of custom size, need to specify a size"); + } + + newSize = (newSize << 30); + } else { + throw new InvalidParameterValueException("current offering" + + volume.getDiskOfferingId() + + " cannot be resized, need to specify a disk offering"); + } + } else { + + if (newDiskOffering.getRemoved() != null + || !DiskOfferingVO.Type.Disk.equals(newDiskOffering + .getType())) { + throw new InvalidParameterValueException( + "Disk offering ID is missing or invalid"); + } + + if (diskOffering.getTags() != null) { + if (!newDiskOffering.getTags().equals(diskOffering.getTags())) { + throw new InvalidParameterValueException( + "Tags on new and old disk offerings must match"); + } + } else if (newDiskOffering.getTags() != null) { + throw new InvalidParameterValueException( + "There are no tags on current disk offering, new disk offering needs to have no tags"); + } + + if (newDiskOffering.getDomainId() == null) { + // do nothing as offering is public + } else { + _configMgr.checkDiskOfferingAccess(CallContext.current() + .getCallingAccount(), newDiskOffering); + } + + if (newDiskOffering.isCustomized()) { + newSize = cmd.getSize(); + + if (newSize == null) { + throw new InvalidParameterValueException( + "new offering is of custom size, need to specify a size"); + } + + newSize = (newSize << 30); + } else { + newSize = newDiskOffering.getDiskSize(); + } + } + + if (newSize == null) { + throw new InvalidParameterValueException( + "could not detect a size parameter or fetch one from the diskofferingid parameter"); + } + + if (!validateVolumeSizeRange(newSize)) { + throw new InvalidParameterValueException( + "Requested size out of range"); + } + + /* does the caller have the authority to act on this volume? */ + _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, + volume); + + UserVmVO userVm = _userVmDao.findById(volume.getInstanceId()); + + long currentSize = volume.getSize(); + + /* + * lets make certain they (think they) know what they're doing if they + * want to shrink, by forcing them to provide the shrinkok parameter. + * This will be checked again at the hypervisor level where we can see + * the actual disk size + */ + if (currentSize > newSize && !shrinkOk) { + throw new InvalidParameterValueException( + "Going from existing size of " + + currentSize + + " to size of " + + newSize + + " would shrink the volume, need to sign off by supplying the shrinkok parameter with value of true"); + } + + if (!shrinkOk) { + /* Check resource limit for this account on primary storage resource */ + _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(volume.getAccountId()), + ResourceType.primary_storage, new Long(newSize - currentSize)); + } + + /* + * get a list of hosts to send the commands to, try the system the + * associated vm is running on first, then the last known place it ran. + * If not attached to a userVm, we pass 'none' and resizevolume.sh is ok + * with that since it only needs the vm name to live resize + */ + long[] hosts = null; + String instanceName = "none"; + if (userVm != null) { + instanceName = userVm.getInstanceName(); + if (userVm.getHostId() != null) { + hosts = new long[] { userVm.getHostId() }; + } else if (userVm.getLastHostId() != null) { + hosts = new long[] { userVm.getLastHostId() }; + } + + /* Xen only works offline, SR does not support VDI.resizeOnline */ + if (_volsDao.getHypervisorType(volume.getId()) == HypervisorType.XenServer + && !userVm.getState().equals(State.Stopped)) { + throw new InvalidParameterValueException( + "VM must be stopped or disk detached in order to resize with the Xen HV"); + } + } + + ResizeVolumePayload payload = new ResizeVolumePayload(newSize, shrinkOk, instanceName, hosts); + + try { + VolumeInfo vol = volFactory.getVolume(volume.getId()); + vol.addPayload(payload); + + AsyncCallFuture future = volService.resize(vol); + VolumeApiResult result = future.get(); + if (result.isFailed()) { + s_logger.warn("Failed to resize the volume " + volume); + return null; + } + + volume = _volsDao.findById(volume.getId()); + + if (newDiskOffering != null) { + volume.setDiskOfferingId(cmd.getNewDiskOfferingId()); + } + _volsDao.update(volume.getId(), volume); + // Log usage event for volumes belonging user VM's only + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_RESIZE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), + volume.getDiskOfferingId(), volume.getTemplateId(), volume.getSize(), Volume.class.getName(), volume.getUuid()); + + /* Update resource count for the account on primary storage resource */ + if (!shrinkOk) { + _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, + new Long(newSize - currentSize)); + } else { + _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, + new Long(currentSize - newSize)); + } + return volume; + } catch (InterruptedException e) { + s_logger.warn("failed get resize volume result", e); + } catch (ExecutionException e) { + s_logger.warn("failed get resize volume result", e); + } catch (Exception e) { + s_logger.warn("failed get resize volume result", e); + } + + return null; + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DELETE, eventDescription = "deleting volume") + public boolean deleteVolume(long volumeId, Account caller) + throws ConcurrentOperationException { + + VolumeVO volume = _volsDao.findById(volumeId); + if (volume == null) { + throw new InvalidParameterValueException( + "Unable to aquire volume with ID: " + volumeId); + } + + if (!_snapshotMgr.canOperateOnVolume(volume)) { + throw new InvalidParameterValueException( + "There are snapshot creating on it, Unable to delete the volume"); + } + + _accountMgr.checkAccess(caller, null, true, volume); + + if (volume.getInstanceId() != null) { + throw new InvalidParameterValueException( + "Please specify a volume that is not attached to any VM."); + } + + if (volume.getState() == Volume.State.UploadOp) { + VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(volume + .getId()); + if (volumeStore.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS) { + throw new InvalidParameterValueException( + "Please specify a volume that is not uploading"); + } + } + + try { + if (volume.getState() != Volume.State.Destroy && volume.getState() != Volume.State.Expunging && volume.getState() != Volume.State.Expunging) { + Long instanceId = volume.getInstanceId(); + if (!volService.destroyVolume(volume.getId())) { + return false; + } + + VMInstanceVO vmInstance = _vmInstanceDao.findById(instanceId); + if (instanceId == null + || (vmInstance.getType().equals(VirtualMachine.Type.User))) { + // Decrement the resource count for volumes and primary storage belonging user VM's only + _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), + ResourceType.volume); + /* If volume is in primary storage, decrement primary storage count else decrement secondary + storage count (in case of upload volume). */ + if (volume.getFolder() != null || volume.getPath() != null || volume.getState() == Volume.State.Allocated) { + _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, + new Long(volume.getSize())); + } else { + _resourceLimitMgr.recalculateResourceCount(volume.getAccountId(), volume.getDomainId(), + ResourceType.secondary_storage.getOrdinal()); + } + + // Log usage event for volumes belonging user VM's only + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), + Volume.class.getName(), volume.getUuid()); + } + } + // Mark volume as removed if volume has not been created on primary or secondary + if (volume.getState() == Volume.State.Allocated) { + _volsDao.remove(volumeId); + stateTransitTo(volume, Volume.Event.DestroyRequested); + return true; + } + // expunge volume from primary if volume is on primary + VolumeInfo volOnPrimary = volFactory.getVolume(volume.getId(), DataStoreRole.Primary); + if (volOnPrimary != null) { + s_logger.info("Expunging volume " + volume.getId() + " from primary data store"); + AsyncCallFuture future = volService.expungeVolumeAsync(volOnPrimary); + future.get(); + } + // expunge volume from secondary if volume is on image store + VolumeInfo volOnSecondary = volFactory.getVolume(volume.getId(), DataStoreRole.Image); + if (volOnSecondary != null) { + s_logger.info("Expunging volume " + volume.getId() + " from secondary data store"); + AsyncCallFuture future2 = volService.expungeVolumeAsync(volOnSecondary); + future2.get(); + } + } catch (Exception e) { + s_logger.warn("Failed to expunge volume:", e); + return false; + } + + return true; + } + + @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 > (_maxVolumeSizeInGb * 1024 * 1024 * 1024)) { + throw new InvalidParameterValueException("volume size " + size + + ", but the maximum size allowed is " + _maxVolumeSizeInGb + + " Gb."); + } + + return true; + } + + protected DiskProfile toDiskProfile(VolumeVO vol, DiskOfferingVO 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, DiskOfferingVO offering, Long size, VMInstanceVO vm, VMTemplateVO template, Account owner) { + Long isoId=null; + if (size == null) { + size = offering.getDiskSize(); + } else { + size = (size * 1024 * 1024 * 1024); + } + VolumeVO vol = new VolumeVO(type, name, vm.getDataCenterId(), + owner.getDomainId(), owner.getId(), offering.getId(), size, + offering.getMinIops(), offering.getMaxIops(), null); + if (vm != null) { + vol.setInstanceId(vm.getId()); + } + + if (type.equals(Type.ROOT)) { + vol.setDeviceId(0l); + } else { + vol.setDeviceId(1l); + } + if (template.getFormat() == ImageFormat.ISO) { + vol.setIsoId(template.getId()); + } + + vol.setFormat(getSupportedImageFormatForCluster(vm.getHypervisorType())); + vol = _volsDao.persist(vol); + + // Save usage event and update resource count for user vm volumes + if (vm instanceof UserVm) { + + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, vol.getAccountId(), vol.getDataCenterId(), vol.getId(), vol.getName(), offering.getId(), null, size, + Volume.class.getName(), vol.getUuid()); + + _resourceLimitMgr.incrementResourceCount(vm.getAccountId(), + ResourceType.volume); + _resourceLimitMgr.incrementResourceCount(vm.getAccountId(), ResourceType.primary_storage, + new Long(vol.getSize())); + } + return toDiskProfile(vol, offering); + } + + @Override + public DiskProfile allocateTemplatedVolume( + Type type, String name, DiskOfferingVO offering, + VMTemplateVO template, VMInstanceVO vm, Account owner) { + assert (template.getFormat() != ImageFormat.ISO) : "ISO is not a template really...."; + + Long size = _tmpltMgr.getTemplateSize(template.getId(), vm.getDataCenterId()); + + VolumeVO vol = new VolumeVO(type, name, vm.getDataCenterId(), + owner.getDomainId(), owner.getId(), offering.getId(), size, + offering.getMinIops(), offering.getMaxIops(), 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); + } + + vol = _volsDao.persist(vol); + + // Create event and update resource count for volumes if vm is a user vm + if (vm instanceof UserVm) { + + Long offeringId = null; + + if (offering.getType() == DiskOfferingVO.Type.Disk) { + offeringId = offering.getId(); + } + + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, vol.getAccountId(), vol.getDataCenterId(), vol.getId(), vol.getName(), offeringId, null, size, + Volume.class.getName(), vol.getUuid()); + + _resourceLimitMgr.incrementResourceCount(vm.getAccountId(), + ResourceType.volume); + _resourceLimitMgr.incrementResourceCount(vm.getAccountId(), ResourceType.primary_storage, + 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 { + return null; + } + } + + private VolumeInfo copyVolume(StoragePoolVO rootDiskPool + , VolumeInfo volume, VMInstanceVO vm, VMTemplateVO rootDiskTmplt, DataCenterVO dcVO, + HostPodVO pod, DiskOfferingVO diskVO, ServiceOfferingVO svo, HypervisorType rootDiskHyperType) throws NoTransitionException { + + if (!volume + .getFormat() + .equals( + getSupportedImageFormatForCluster(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(), + volume.getSize(), rootDiskHyperType); + + return volumeOnPrimary; + } + + private VolumeInfo createVolumeOnPrimaryStorage(VMInstanceVO vm, VolumeVO rootVolumeOfVm, VolumeInfo volume, HypervisorType rootDiskHyperType) throws NoTransitionException { + VMTemplateVO rootDiskTmplt = _templateDao.findById(vm + .getTemplateId()); + DataCenterVO dcVO = _dcDao.findById(vm + .getDataCenterId()); + HostPodVO pod = _podDao.findById(vm.getPodIdToDeployIn()); + StoragePoolVO rootDiskPool = _storagePoolDao + .findById(rootVolumeOfVm.getPoolId()); + ServiceOfferingVO svo = _serviceOfferingDao.findById(vm + .getServiceOfferingId()); + DiskOfferingVO diskVO = _diskOfferingDao.findById(volume + .getDiskOfferingId()); + Long clusterId = (rootDiskPool == null ? null : rootDiskPool + .getClusterId()); + + VolumeInfo vol = null; + if (volume.getState() == Volume.State.Allocated) { + vol = createVolume(volume, vm, + rootDiskTmplt, dcVO, pod, clusterId, svo, diskVO, + new ArrayList(), volume.getSize(), + rootDiskHyperType); + } else if (volume.getState() == Volume.State.Uploaded) { + vol = copyVolume(rootDiskPool + , 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, new Long(volume.getSize())); + _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), + ResourceType.primary_storage, new Long(volume.getSize())); + } + } + + VolumeVO volVO = _volsDao.findById(vol.getId()); + volVO.setFormat(getSupportedImageFormatForCluster(rootDiskHyperType)); + _volsDao.update(volVO.getId(), volVO); + return volFactory.getVolume(volVO.getId()); + } + + private boolean needMoveVolume(VolumeVO rootVolumeOfVm, VolumeInfo volume) { + if (rootVolumeOfVm.getPoolId() == null || volume.getPoolId() == null) { + return false; + } + + DataStore storeForRootVol = dataStoreMgr.getPrimaryDataStore(rootVolumeOfVm.getPoolId()); + DataStore storeForDataVol = dataStoreMgr.getPrimaryDataStore(volume.getPoolId()); + + Scope storeForRootStoreScope = storeForRootVol.getScope(); + if (storeForRootStoreScope == null) { + throw new CloudRuntimeException("Can't get scope of data store: " + storeForRootVol.getId()); + } + + Scope storeForDataStoreScope = storeForDataVol.getScope(); + if (storeForDataStoreScope == null) { + throw new CloudRuntimeException("Can't get scope of data store: " + storeForDataVol.getId()); + } + + if (storeForDataStoreScope.getScopeType() == ScopeType.ZONE) { + return false; + } + + if (storeForRootStoreScope.getScopeType() != storeForDataStoreScope.getScopeType()) { + if (storeForDataStoreScope.getScopeType() == ScopeType.CLUSTER && storeForRootStoreScope.getScopeType() == ScopeType.HOST) { + HostScope hs = (HostScope)storeForRootStoreScope; + if (storeForDataStoreScope.getScopeId().equals(hs.getClusterId())) { + return false; + } + } + if (storeForRootStoreScope.getScopeType() == ScopeType.CLUSTER && storeForDataStoreScope.getScopeType() == ScopeType.HOST) { + HostScope hs = (HostScope)storeForDataStoreScope; + if (storeForRootStoreScope.getScopeId().equals(hs.getClusterId())) { + return false; + } + } + throw new CloudRuntimeException("Can't move volume between scope: " + storeForDataStoreScope.getScopeType() + " and " + storeForRootStoreScope.getScopeType()); + } + + return !storeForRootStoreScope.isSameScope(storeForDataStoreScope); + } + + private VolumeVO sendAttachVolumeCommand(UserVmVO vm, VolumeVO volumeToAttach, Long deviceId) { + String errorMsg = "Failed to attach volume: " + volumeToAttach.getName() + + " to VM: " + vm.getHostName(); + boolean sendCommand = (vm.getState() == State.Running); + AttachAnswer answer = null; + Long hostId = vm.getHostId(); + if (hostId == null) { + hostId = vm.getLastHostId(); + HostVO host = _hostDao.findById(hostId); + if (host != null + && host.getHypervisorType() == HypervisorType.VMware) { + sendCommand = true; + } + } + + StoragePoolVO volumeToAttachStoragePool = null; + + if (sendCommand) { + volumeToAttachStoragePool = _storagePoolDao.findById(volumeToAttach.getPoolId()); + long storagePoolId = volumeToAttachStoragePool.getId(); + + DataTO volTO = volFactory.getVolume(volumeToAttach.getId()).getTO(); + DiskTO disk = new DiskTO(volTO, deviceId, null, volumeToAttach.getVolumeType()); + + AttachCommand cmd = new AttachCommand(disk, vm.getInstanceName()); + + cmd.setManaged(volumeToAttachStoragePool.isManaged()); + + cmd.setStorageHost(volumeToAttachStoragePool.getHostAddress()); + cmd.setStoragePort(volumeToAttachStoragePool.getPort()); + + cmd.set_iScsiName(volumeToAttach.get_iScsiName()); + + VolumeInfo volumeInfo = volFactory.getVolume(volumeToAttach.getId()); + DataStore dataStore = dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary); + ChapInfo chapInfo = volService.getChapInfo(volumeInfo, dataStore); + + if (chapInfo != null) { + cmd.setChapInitiatorUsername(chapInfo.getInitiatorUsername()); + cmd.setChapInitiatorPassword(chapInfo.getInitiatorSecret()); + cmd.setChapTargetUsername(chapInfo.getTargetUsername()); + cmd.setChapTargetPassword(chapInfo.getTargetSecret()); + } + + try { + answer = (AttachAnswer)_agentMgr.send(hostId, cmd); + } catch (Exception e) { + throw new CloudRuntimeException(errorMsg + " due to: " + + e.getMessage()); + } + } + + if (!sendCommand || (answer != null && answer.getResult())) { + // Mark the volume as attached + if (sendCommand) { + DiskTO disk = answer.getDisk(); + _volsDao.attachVolume(volumeToAttach.getId(), vm.getId(), + disk.getDiskSeq()); + + volumeToAttach = _volsDao.findById(volumeToAttach.getId()); + + if (volumeToAttachStoragePool.isManaged() && + volumeToAttach.getPath() == null) { + volumeToAttach.setPath(answer.getDisk().getVdiUuid()); + + _volsDao.update(volumeToAttach.getId(), volumeToAttach); + } + } else { + _volsDao.attachVolume(volumeToAttach.getId(), vm.getId(), deviceId); + } + + // insert record for disk I/O statistics + VmDiskStatisticsVO diskstats = _vmDiskStatsDao.findBy(vm.getAccountId(), vm.getDataCenterId(),vm.getId(), volumeToAttach.getId()); + if (diskstats == null) { + diskstats = new VmDiskStatisticsVO(vm.getAccountId(), vm.getDataCenterId(),vm.getId(), volumeToAttach.getId()); + _vmDiskStatsDao.persist(diskstats); + } + + return _volsDao.findById(volumeToAttach.getId()); + } else { + if (answer != null) { + String details = answer.getDetails(); + if (details != null && !details.isEmpty()) { + errorMsg += "; " + details; + } + } + throw new CloudRuntimeException(errorMsg); + } + } + + private int getMaxDataVolumesSupported(UserVmVO vm) { + Long hostId = vm.getHostId(); + if (hostId == null) { + hostId = vm.getLastHostId(); + } + HostVO host = _hostDao.findById(hostId); + Integer maxDataVolumesSupported = null; + if (host != null) { + _hostDao.loadDetails(host); + maxDataVolumesSupported = _hypervisorCapabilitiesDao + .getMaxDataVolumesLimit(host.getHypervisorType(), + host.getDetail("product_version")); + } + if (maxDataVolumesSupported == null) { + maxDataVolumesSupported = 6; // 6 data disks by default if nothing + // is specified in + // 'hypervisor_capabilities' table + } + + return maxDataVolumesSupported.intValue(); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VOLUME_ATTACH, eventDescription = "attaching volume", async = true) + public Volume attachVolumeToVM(AttachVolumeCmd command) { + Long vmId = command.getVirtualMachineId(); + Long volumeId = command.getId(); + Long deviceId = command.getDeviceId(); + Account caller = CallContext.current().getCallingAccount(); + + // Check that the volume ID is valid + VolumeInfo volume = volFactory.getVolume(volumeId); + // Check that the volume is a data volume + if (volume == null || volume.getVolumeType() != Volume.Type.DATADISK) { + throw new InvalidParameterValueException( + "Please specify a valid data volume."); + } + + // Check that the volume is not currently attached to any VM + if (volume.getInstanceId() != null) { + throw new InvalidParameterValueException( + "Please specify a volume that is not attached to any VM."); + } + + // Check that the volume is not destroyed + if (volume.getState() == Volume.State.Destroy) { + throw new InvalidParameterValueException( + "Please specify a volume that is not destroyed."); + } + + // Check that the virtual machine ID is valid and it's a user vm + UserVmVO vm = _userVmDao.findById(vmId); + if (vm == null || vm.getType() != VirtualMachine.Type.User) { + throw new InvalidParameterValueException( + "Please specify a valid User VM."); + } + + // Check that the VM is in the correct state + if (vm.getState() != State.Running && vm.getState() != State.Stopped) { + throw new InvalidParameterValueException( + "Please specify a VM that is either running or stopped."); + } + + // Check that the device ID is valid + if (deviceId != null) { + if (deviceId.longValue() == 0) { + throw new InvalidParameterValueException( + "deviceId can't be 0, which is used by Root device"); + } + } + + // Check that the number of data volumes attached to VM is less than + // that supported by hypervisor + List existingDataVolumes = _volsDao.findByInstanceAndType( + vmId, Volume.Type.DATADISK); + int maxDataVolumesSupported = getMaxDataVolumesSupported(vm); + if (existingDataVolumes.size() >= maxDataVolumesSupported) { + throw new InvalidParameterValueException( + "The specified VM already has the maximum number of data disks (" + + maxDataVolumesSupported + + "). Please specify another VM."); + } + + // Check that the VM and the volume are in the same zone + if (vm.getDataCenterId() != volume.getDataCenterId()) { + throw new InvalidParameterValueException( + "Please specify a VM that is in the same zone as the volume."); + } + + // If local storage is disabled then attaching a volume with local disk + // offering not allowed + DataCenterVO dataCenter = _dcDao.findById(volume.getDataCenterId()); + if (!dataCenter.isLocalStorageEnabled()) { + DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume + .getDiskOfferingId()); + if (diskOffering.getUseLocalStorage()) { + throw new InvalidParameterValueException( + "Zone is not configured to use local storage but volume's disk offering " + + diskOffering.getName() + " uses it"); + } + } + + // if target VM has associated VM snapshots + List vmSnapshots = _vmSnapshotDao.findByVm(vmId); + if(vmSnapshots.size() > 0){ + throw new InvalidParameterValueException( + "Unable to attach volume, please specify a VM that does not have VM snapshots"); + } + + // permission check + _accountMgr.checkAccess(caller, null, true, volume, vm); + + if (!(Volume.State.Allocated.equals(volume.getState()) + || Volume.State.Ready.equals(volume.getState()) || Volume.State.Uploaded + .equals(volume.getState()))) { + throw new InvalidParameterValueException( + "Volume state must be in Allocated, Ready or in Uploaded state"); + } + + VolumeVO rootVolumeOfVm = null; + List rootVolumesOfVm = _volsDao.findByInstanceAndType(vmId, + 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."); + } else { + rootVolumeOfVm = rootVolumesOfVm.get(0); + } + + HypervisorType rootDiskHyperType = vm.getHypervisorType(); + + HypervisorType dataDiskHyperType = _volsDao.getHypervisorType(volume + .getId()); + if (dataDiskHyperType != HypervisorType.None + && rootDiskHyperType != dataDiskHyperType) { + throw new InvalidParameterValueException( + "Can't attach a volume created by: " + dataDiskHyperType + + " to a " + rootDiskHyperType + " vm"); + } + + + deviceId = getDeviceId(vmId, deviceId); + VolumeInfo volumeOnPrimaryStorage = volume; + + // Check if volume is stored on secondary storage + boolean isVolumeOnSec = false; + VolumeInfo volOnSecondary = volFactory.getVolume(volume.getId(), DataStoreRole.Image); + if (volOnSecondary != null) { + isVolumeOnSec = true; + if(volOnSecondary.getState() != Volume.State.Uploaded) { + throw new InvalidParameterValueException("Volume is not uploaded yet. Please try this operation once the volume is uploaded"); + } + } + + boolean createVolumeOnBackend = true; + if (rootVolumeOfVm.getState() == Volume.State.Allocated) { + createVolumeOnBackend = false; + if(isVolumeOnSec) { + throw new CloudRuntimeException("Cant attach uploaded volume to the vm which is not created. Please start it and then retry"); + } + } + + // Create volume on the backend only when VM's root volume is allocated + if (createVolumeOnBackend) { + if (volume.getState().equals(Volume.State.Allocated) + || volume.getState() == Volume.State.Uploaded) { + try { + volumeOnPrimaryStorage = createVolumeOnPrimaryStorage(vm, rootVolumeOfVm, volume, rootDiskHyperType); + } catch (NoTransitionException e) { + s_logger.debug("Failed to create volume on primary storage", e); + throw new CloudRuntimeException("Failed to create volume on primary storage", e); + } + } + + // reload the volume from db + volumeOnPrimaryStorage = volFactory.getVolume(volumeOnPrimaryStorage.getId()); + boolean moveVolumeNeeded = needMoveVolume(rootVolumeOfVm, volumeOnPrimaryStorage); + + if (moveVolumeNeeded) { + PrimaryDataStoreInfo primaryStore = (PrimaryDataStoreInfo)volumeOnPrimaryStorage.getDataStore(); + if (primaryStore.isLocal()) { + throw new CloudRuntimeException( + "Failed to attach local data volume " + + volume.getName() + + " to VM " + + vm.getDisplayName() + + " as migration of local data volume is not allowed"); + } + StoragePoolVO vmRootVolumePool = _storagePoolDao + .findById(rootVolumeOfVm.getPoolId()); + + try { + volumeOnPrimaryStorage = moveVolume(volumeOnPrimaryStorage, + vmRootVolumePool.getDataCenterId(), + vmRootVolumePool.getPodId(), + vmRootVolumePool.getClusterId(), + dataDiskHyperType); + } catch (ConcurrentOperationException e) { + s_logger.debug("move volume failed", e); + throw new CloudRuntimeException("move volume failed", e); + } + } + } + + + AsyncJobExecutionContext asyncExecutionContext = AsyncJobExecutionContext.getCurrentExecutionContext(); + + if (asyncExecutionContext != null) { + AsyncJob job = asyncExecutionContext.getJob(); + + if (s_logger.isInfoEnabled()) { + s_logger.info("Trying to attaching volume " + volumeId + + " to vm instance:" + vm.getId() + + ", update async job-" + job.getId() + + " progress status"); + } + + _asyncMgr.updateAsyncJobAttachment(job.getId(), "volume", volumeId); + _asyncMgr.updateAsyncJobStatus(job.getId(), BaseCmd.PROGRESS_INSTANCE_CREATED, Long.toString(volumeId)); + } + + VolumeVO newVol = _volumeDao.findById(volumeOnPrimaryStorage.getId()); + newVol = sendAttachVolumeCommand(vm, newVol, deviceId); + return newVol; + } + + @Override + public Volume updateVolume(UpdateVolumeCmd cmd){ + Long volumeId = cmd.getId(); + String path = cmd.getPath(); + + if(path == null){ + throw new InvalidParameterValueException("Failed to update the volume as path was null"); + } + + VolumeVO volume = ApiDBUtils.findVolumeById(volumeId); + volume.setPath(path); + _volumeDao.update(volumeId, volume); + + return volume; + } + + + @Override + @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DETACH, eventDescription = "detaching volume", async = true) + public Volume detachVolumeFromVM(DetachVolumeCmd cmmd) { + Account caller = CallContext.current().getCallingAccount(); + if ((cmmd.getId() == null && cmmd.getDeviceId() == null && cmmd + .getVirtualMachineId() == null) + || (cmmd.getId() != null && (cmmd.getDeviceId() != null || cmmd + .getVirtualMachineId() != null)) + || (cmmd.getId() == null && (cmmd.getDeviceId() == null || cmmd + .getVirtualMachineId() == null))) { + throw new InvalidParameterValueException( + "Please provide either a volume id, or a tuple(device id, instance id)"); + } + + Long volumeId = cmmd.getId(); + VolumeVO volume = null; + + if (volumeId != null) { + volume = _volsDao.findById(volumeId); + } else { + volume = _volsDao.findByInstanceAndDeviceId( + cmmd.getVirtualMachineId(), cmmd.getDeviceId()).get(0); + } + + Long vmId = null; + + if (cmmd.getVirtualMachineId() == null) { + vmId = volume.getInstanceId(); + } else { + vmId = cmmd.getVirtualMachineId(); + } + + // Check that the volume ID is valid + if (volume == null) { + throw new InvalidParameterValueException( + "Unable to find volume with ID: " + volumeId); + } + + // Permissions check + _accountMgr.checkAccess(caller, null, true, volume); + + // Check that the volume is a data volume + if (volume.getVolumeType() != Volume.Type.DATADISK) { + throw new InvalidParameterValueException( + "Please specify a data volume."); + } + + // Check that the volume is currently attached to a VM + if (vmId == null) { + throw new InvalidParameterValueException( + "The specified volume is not attached to a VM."); + } + + // Check that the VM is in the correct state + UserVmVO vm = _userVmDao.findById(vmId); + if (vm.getState() != State.Running && vm.getState() != State.Stopped + && vm.getState() != State.Destroyed) { + throw new InvalidParameterValueException( + "Please specify a VM that is either running or stopped."); + } + + AsyncJobExecutionContext asyncExecutionContext = AsyncJobExecutionContext.getCurrentExecutionContext(); + if (asyncExecutionContext != null) { + AsyncJob job = asyncExecutionContext.getJob(); + + if (s_logger.isInfoEnabled()) { + s_logger.info("Trying to attaching volume " + volumeId + + "to vm instance:" + vm.getId() + + ", update async job-" + job.getId() + + " progress status"); + } + + _asyncMgr.updateAsyncJobAttachment(job.getId(), "volume", volumeId); + _asyncMgr.updateAsyncJobStatus(job.getId(), BaseCmd.PROGRESS_INSTANCE_CREATED, volumeId.toString()); + } + +