// 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.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 org.apache.log4j.Logger; 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.orchestration.service.VolumeOrchestrationService; 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.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.storage.command.AttachAnswer; import org.apache.cloudstack.storage.command.AttachCommand; 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.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.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.Pod; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.HostPodDao; 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.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.StorageUnavailableException; 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.dao.ServiceOfferingDao; import com.cloud.storage.Storage.ImageFormat; 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.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.EntityManager; 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.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; public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiService { private final static Logger s_logger = Logger.getLogger(VolumeApiServiceImpl.class); @Inject VolumeOrchestrationService _volumeMgr; @Inject EntityManager _entityMgr; @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; @Inject protected HypervisorCapabilitiesDao _hypervisorCapabilitiesDao; @Inject StorageManager storageMgr; private int _customDiskOfferingMinSize = 1; private final int _customDiskOfferingMaxSize = 1024; private long _maxVolumeSizeInGb; private final StateMachine2 _volStateMachine; protected VolumeApiServiceImpl() { _volStateMachine = Volume.State.getStateMachine(); } /* * 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 = _entityMgr.findById(Account.class, 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; } public String getRandomVolumeName() { return UUID.randomUUID().toString(); } @DB protected 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; } /* * 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; } 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; } @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())); } } } 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 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 = _volumeMgr.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); } } @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; } private boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException { return _volStateMachine.transitTo(vol, event, null, _volsDao); } @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; if (volume.getState().equals(Volume.State.Allocated) || volume.getState() == Volume.State.Uploaded) { try { volumeOnPrimaryStorage = _volumeMgr.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 = _volumeMgr.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()); } String errorMsg = "Failed to detach volume: " + volume.getName() + " from VM: " + vm.getHostName(); boolean sendCommand = (vm.getState() == State.Running); Answer answer = null; if (sendCommand) { StoragePoolVO volumePool = _storagePoolDao.findById(volume.getPoolId()); DataTO volTO = volFactory.getVolume(volume.getId()).getTO(); DiskTO disk = new DiskTO(volTO, volume.getDeviceId(), null, volume.getVolumeType()); DettachCommand cmd = new DettachCommand(disk, vm.getInstanceName()); cmd.setManaged(volumePool.isManaged()); cmd.setStorageHost(volumePool.getHostAddress()); cmd.setStoragePort(volumePool.getPort()); cmd.set_iScsiName(volume.get_iScsiName()); try { answer = _agentMgr.send(vm.getHostId(), cmd); } catch (Exception e) { throw new CloudRuntimeException(errorMsg + " due to: " + e.getMessage()); } } if (!sendCommand || (answer != null && answer.getResult())) { // Mark the volume as detached _volsDao.detachVolume(volume.getId()); return _volsDao.findById(volumeId); } else { if (answer != null) { String details = answer.getDetails(); if (details != null && !details.isEmpty()) { errorMsg += "; " + details; } } throw new CloudRuntimeException(errorMsg); } } @DB @Override public Volume migrateVolume(MigrateVolumeCmd cmd) { Long volumeId = cmd.getVolumeId(); Long storagePoolId = cmd.getStoragePoolId(); VolumeVO vol = _volsDao.findById(volumeId); if (vol == null) { throw new InvalidParameterValueException("Failed to find the volume id: " + volumeId); } if (vol.getState() != Volume.State.Ready) { throw new InvalidParameterValueException("Volume must be in ready state"); } boolean liveMigrateVolume = false; Long instanceId = vol.getInstanceId(); VMInstanceVO vm = null; if (instanceId != null) { vm = _vmInstanceDao.findById(instanceId); } if (vm != null && vm.getState() == State.Running) { // Check if the underlying hypervisor supports storage motion. Long hostId = vm.getHostId(); if (hostId != null) { HostVO host = _hostDao.findById(hostId); HypervisorCapabilitiesVO capabilities = null; if (host != null) { capabilities = _hypervisorCapabilitiesDao.findByHypervisorTypeAndVersion(host.getHypervisorType(), host.getHypervisorVersion()); } if (capabilities != null) { liveMigrateVolume = capabilities.isStorageMotionSupported(); } } } // If the disk is not attached to any VM then it can be moved. Otherwise, it needs to be attached to a vm // running on a hypervisor that supports storage motion so that it be be migrated. if (instanceId != null && !liveMigrateVolume) { throw new InvalidParameterValueException("Volume needs to be detached from VM"); } if (liveMigrateVolume && !cmd.isLiveMigrate()) { throw new InvalidParameterValueException("The volume " + vol + "is attached to a vm and for migrating it " + "the parameter livemigrate should be specified"); } StoragePool destPool = (StoragePool)dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary); if (destPool == null) { throw new InvalidParameterValueException("Failed to find the destination storage pool: " + storagePoolId); } if (!_volumeMgr.volumeOnSharedStoragePool(vol)) { throw new InvalidParameterValueException("Migration of volume from local storage pool is not supported"); } Volume newVol = null; if (liveMigrateVolume) { newVol = liveMigrateVolume(vol, destPool); } else { newVol = _volumeMgr.migrateVolume(vol, destPool); } return newVol; } @DB protected Volume liveMigrateVolume(Volume volume, StoragePool destPool) { VolumeInfo vol = volFactory.getVolume(volume.getId()); AsyncCallFuture 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 Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account) throws ResourceAllocationException { VolumeInfo volume = volFactory.getVolume(volumeId); if (volume == null) { throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist"); } if (volume.getState() != Volume.State.Ready) { throw new InvalidParameterValueException("VolumeId: " + volumeId + " is not in " + Volume.State.Ready + " state but " + volume.getState() + ". Cannot take snapshot."); } CreateSnapshotPayload payload = new CreateSnapshotPayload(); payload.setSnapshotId(snapshotId); payload.setSnapshotPolicyId(policyId); payload.setAccount(account); volume.addPayload(payload); return volService.takeSnapshot(volume); } @Override public Snapshot allocSnapshot(Long volumeId, Long policyId) throws ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); VolumeInfo volume = volFactory.getVolume(volumeId); if (volume == null) { throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist"); } DataCenter zone = _dcDao.findById(volume.getDataCenterId()); if (zone == null) { throw new InvalidParameterValueException("Can't find zone by id " + volume.getDataCenterId()); } if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getType())) { throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zone.getName()); } if (volume.getState() != Volume.State.Ready) { throw new InvalidParameterValueException("VolumeId: " + volumeId + " is not in " + Volume.State.Ready + " state but " + volume.getState() + ". Cannot take snapshot."); } if (volume.getTemplateId() != null) { VMTemplateVO template = _templateDao.findById(volume.getTemplateId()); if (template != null && template.getTemplateType() == Storage.TemplateType.SYSTEM) { throw new InvalidParameterValueException("VolumeId: " + volumeId + " is for System VM , Creating snapshot against System VM volumes is not supported"); } } StoragePool storagePool = (StoragePool)volume.getDataStore(); if (storagePool == null) { throw new InvalidParameterValueException("VolumeId: " + volumeId + " please attach this volume to a VM before create snapshot for it"); } return snapshotMgr.allocSnapshot(volumeId, policyId); } @Override @ActionEvent(eventType = EventTypes.EVENT_VOLUME_EXTRACT, eventDescription = "extracting volume", async = true) public String extractVolume(ExtractVolumeCmd cmd) { Long volumeId = cmd.getId(); Long zoneId = cmd.getZoneId(); String mode = cmd.getMode(); Account account = CallContext.current().getCallingAccount(); if (!_accountMgr.isRootAdmin(account.getType()) && ApiDBUtils.isExtractionDisabled()) { throw new PermissionDeniedException("Extraction has been disabled by admin"); } VolumeVO volume = _volumeDao.findById(volumeId); if (volume == null) { InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find volume with specified volumeId"); ex.addProxyObject(volumeId.toString(), "volumeId"); throw ex; } // perform permission check _accountMgr.checkAccess(account, null, true, volume); if (_dcDao.findById(zoneId) == null) { throw new InvalidParameterValueException("Please specify a valid zone."); } if (volume.getPoolId() == null) { throw new InvalidParameterValueException("The volume doesnt belong to a storage pool so cant extract it"); } // Extract activity only for detached volumes or for volumes whose // instance is stopped if (volume.getInstanceId() != null && ApiDBUtils.findVMInstanceById(volume.getInstanceId()).getState() != State.Stopped) { s_logger.debug("Invalid state of the volume with ID: " + volumeId + ". It should be either detached or the VM should be in stopped state."); PermissionDeniedException ex = new PermissionDeniedException( "Invalid state of the volume with specified ID. It should be either detached or the VM should be in stopped state."); ex.addProxyObject(volume.getUuid(), "volumeId"); throw ex; } if (volume.getVolumeType() != Volume.Type.DATADISK) { // Datadisk dont have any template dependence. VMTemplateVO template = ApiDBUtils.findTemplateById(volume.getTemplateId()); if (template != null) { // For ISO based volumes template = null and // we allow extraction of all ISO based // volumes boolean isExtractable = template.isExtractable() && template.getTemplateType() != Storage.TemplateType.SYSTEM; if (!isExtractable && account != null && account.getType() != Account.ACCOUNT_TYPE_ADMIN) { // Global admins are always allowed to extract PermissionDeniedException ex = new PermissionDeniedException("The volume with specified volumeId is not allowed to be extracted"); ex.addProxyObject(volume.getUuid(), "volumeId"); throw ex; } } } Upload.Mode extractMode; if (mode == null || (!mode.equals(Upload.Mode.FTP_UPLOAD.toString()) && !mode.equals(Upload.Mode.HTTP_DOWNLOAD.toString()))) { throw new InvalidParameterValueException("Please specify a valid extract Mode "); } else { extractMode = mode.equals(Upload.Mode.FTP_UPLOAD.toString()) ? Upload.Mode.FTP_UPLOAD : Upload.Mode.HTTP_DOWNLOAD; } // Check if the url already exists VolumeDataStoreVO volumeStoreRef = _volumeStoreDao.findByVolume(volumeId); if (volumeStoreRef != null && volumeStoreRef.getExtractUrl() != null) { return volumeStoreRef.getExtractUrl(); } // Clean up code to remove all those previous uploadVO and uploadMonitor code. Previous code is trying to fake an async operation purely in // db table with uploadVO and async_job entry, but internal implementation is actually synchronous. StoragePool srcPool = (StoragePool)dataStoreMgr.getPrimaryDataStore(volume.getPoolId()); ImageStoreEntity secStore = (ImageStoreEntity)dataStoreMgr.getImageStore(zoneId); String secondaryStorageURL = secStore.getUri(); String value = _configDao.getValue(Config.CopyVolumeWait.toString()); int copyvolumewait = NumbersUtil.parseInt(value, Integer.parseInt(Config.CopyVolumeWait.getDefaultValue())); // Copy volume from primary to secondary storage VolumeInfo srcVol = volFactory.getVolume(volume.getId()); AsyncCallFuture cvAnswer = volService.copyVolume(srcVol, secStore); // Check if you got a valid answer. VolumeApiResult cvResult = null; try { cvResult = cvAnswer.get(); } catch (InterruptedException e1) { s_logger.debug("failed copy volume", e1); throw new CloudRuntimeException("Failed to copy volume", e1); } catch (ExecutionException e1) { s_logger.debug("failed copy volume", e1); throw new CloudRuntimeException("Failed to copy volume", e1); } if (cvResult == null || cvResult.isFailed()) { String errorString = "Failed to copy the volume from the source primary storage pool to secondary storage."; throw new CloudRuntimeException(errorString); } VolumeInfo vol = cvResult.getVolume(); String extractUrl = secStore.createEntityExtractUrl(vol.getPath(), vol.getFormat(), vol); volumeStoreRef = _volumeStoreDao.findByVolume(volumeId); volumeStoreRef.setExtractUrl(extractUrl); _volumeStoreDao.update(volumeStoreRef.getId(), volumeStoreRef); return extractUrl; } private String getFormatForPool(StoragePool pool) { ClusterVO cluster = ApiDBUtils.findClusterById(pool.getClusterId()); if (cluster.getHypervisorType() == HypervisorType.XenServer) { return "vhd"; } else if (cluster.getHypervisorType() == HypervisorType.KVM) { return "qcow2"; } else if (cluster.getHypervisorType() == HypervisorType.VMware) { return "ova"; } else if (cluster.getHypervisorType() == HypervisorType.Ovm) { return "raw"; } else { return null; } } 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(); } private Long getDeviceId(long vmId, Long deviceId) { // allocate deviceId List vols = _volsDao.findByInstance(vmId); if (deviceId != null) { if (deviceId.longValue() > 15 || deviceId.longValue() == 0 || deviceId.longValue() == 3) { throw new RuntimeException("deviceId should be 1,2,4-15"); } for (VolumeVO vol : vols) { if (vol.getDeviceId().equals(deviceId)) { throw new RuntimeException("deviceId " + deviceId + " is used by vm" + vmId); } } } else { // allocate deviceId here List devIds = new ArrayList(); for (int i = 1; i < 15; i++) { devIds.add(String.valueOf(i)); } devIds.remove("3"); for (VolumeVO vol : vols) { devIds.remove(vol.getDeviceId().toString().trim()); } deviceId = Long.parseLong(devIds.iterator().next()); } return deviceId; } @Override public boolean configure(String name, Map params) { String _customDiskOfferingMinSizeStr = _configDao.getValue(Config.CustomDiskOfferingMinSize.toString()); _customDiskOfferingMinSize = NumbersUtil.parseInt(_customDiskOfferingMinSizeStr, Integer.parseInt(Config.CustomDiskOfferingMinSize.getDefaultValue())); return true; } }