// 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 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.api.ApiDBUtils; import com.cloud.configuration.Config; import com.cloud.configuration.ConfigurationManager; import com.cloud.configuration.Resource.ResourceType; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; import com.cloud.domain.Domain; import com.cloud.event.ActionEvent; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; 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.gpu.GPU; 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.dao.HypervisorCapabilitiesDao; import com.cloud.org.Grouping; import com.cloud.serializer.GsonHelper; import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.SnapshotDao; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.dao.VolumeDetailsDao; import com.cloud.storage.snapshot.SnapshotApiService; import com.cloud.storage.snapshot.SnapshotManager; import com.cloud.template.TemplateManager; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.ResourceLimitService; import com.cloud.user.User; import com.cloud.user.VmDiskStatisticsVO; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.VmDiskStatisticsDao; import com.cloud.utils.DateUtil; import com.cloud.utils.EncryptionUtil; import com.cloud.utils.EnumUtils; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; import com.cloud.utils.Predicate; import com.cloud.utils.ReflectionUse; import com.cloud.utils.StringUtils; 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.db.TransactionCallback; import com.cloud.utils.db.TransactionCallbackWithException; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.db.UUIDManager; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.fsm.NoTransitionException; import com.cloud.utils.fsm.StateMachine2; 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.VmDetailConstants; import com.cloud.vm.VmWork; import com.cloud.vm.VmWorkAttachVolume; import com.cloud.vm.VmWorkConstants; import com.cloud.vm.VmWorkDetachVolume; import com.cloud.vm.VmWorkExtractVolume; import com.cloud.vm.VmWorkJobHandler; import com.cloud.vm.VmWorkJobHandlerProxy; import com.cloud.vm.VmWorkMigrateVolume; import com.cloud.vm.VmWorkResizeVolume; import com.cloud.vm.VmWorkSerializer; import com.cloud.vm.VmWorkTakeVolumeSnapshot; 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; import com.google.common.base.Strings; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; 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.GetUploadParamsForVolumeCmd; 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.UploadVolumeCmd; import org.apache.cloudstack.api.response.GetUploadParamsResponse; 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.DataObject; 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.EndPoint; 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.StoragePoolAllocator; 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.ConfigKey; 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.Outcome; import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.apache.cloudstack.framework.jobs.impl.OutcomeImpl; import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; import org.apache.cloudstack.jobs.JobInfo; 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.command.TemplateOrVolumePostUploadCommand; 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.VolumeDataStoreDao; import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO; import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.cloudstack.utils.imagestore.ImageStoreUtil; import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo; import org.apache.log4j.Logger; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import javax.inject.Inject; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutionException; public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiService, VmWorkJobHandler { private final static Logger s_logger = Logger.getLogger(VolumeApiServiceImpl.class); public static final String VM_WORK_JOB_HANDLER = VolumeApiServiceImpl.class.getSimpleName(); @Inject VolumeOrchestrationService _volumeMgr; @Inject EntityManager _entityMgr; @Inject AgentManager _agentMgr; @Inject TemplateManager _tmpltMgr; @Inject SnapshotManager _snapshotMgr; @Inject AccountManager _accountMgr; @Inject ConfigurationManager _configMgr; @Inject VolumeDao _volsDao; @Inject VolumeDetailsDao _volDetailDao; @Inject HostDao _hostDao; @Inject SnapshotDao _snapshotDao; @Inject ServiceOfferingDetailsDao _serviceOfferingDetailsDao; @Inject StoragePoolDetailsDao storagePoolDetailsDao; @Inject UserVmDao _userVmDao; @Inject VolumeDataStoreDao _volumeStoreDao; @Inject VMInstanceDao _vmInstanceDao; @Inject PrimaryDataStoreDao _storagePoolDao; @Inject DiskOfferingDao _diskOfferingDao; @Inject AccountDao _accountDao; @Inject DataCenterDao _dcDao = null; @Inject VMTemplateDao _templateDao; @Inject ResourceLimitService _resourceLimitMgr; @Inject VmDiskStatisticsDao _vmDiskStatsDao; @Inject VMSnapshotDao _vmSnapshotDao; @Inject ConfigurationDao _configDao; @Inject DataStoreManager dataStoreMgr; @Inject VolumeService volService; @Inject VolumeDataFactory volFactory; @Inject SnapshotApiService snapshotMgr; @Inject UUIDManager _uuidMgr; @Inject HypervisorCapabilitiesDao _hypervisorCapabilitiesDao; @Inject AsyncJobManager _jobMgr; @Inject VmWorkJobDao _workJobDao; @Inject ClusterDetailsDao _clusterDetailsDao; @Inject UserVmManager _userVmMgr; protected Gson _gson; @Inject StorageManager storageMgr; private List<StoragePoolAllocator> _storagePoolAllocators; VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this); static final ConfigKey<Long> VmJobCheckInterval = new ConfigKey<Long>("Advanced", Long.class, "vm.job.check.interval", "3000", "Interval in milliseconds to check if the job is complete", false); private long _maxVolumeSizeInGb; private final StateMachine2<Volume.State, Volume.Event, Volume> _volStateMachine; protected VolumeApiServiceImpl() { _volStateMachine = Volume.State.getStateMachine(); _gson = GsonHelper.getGsonLogger(); } /* * 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(); Long diskOfferingId = cmd.getDiskOfferingId(); String imageStoreUuid = cmd.getImageStoreUuid(); DataStore store = _tmpltMgr.getImageStore(imageStoreUuid, zoneId); validateVolume(caller, ownerId, zoneId, volumeName, url, format, diskOfferingId); VolumeVO volume = persistVolume(owner, zoneId, volumeName, url, cmd.getFormat(), diskOfferingId, Volume.State.Allocated); 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; } @Override @ActionEvent(eventType = EventTypes.EVENT_VOLUME_UPLOAD, eventDescription = "uploading volume for post upload", async = true) public GetUploadParamsResponse uploadVolume(final GetUploadParamsForVolumeCmd cmd) throws ResourceAllocationException, MalformedURLException { Account caller = CallContext.current().getCallingAccount(); long ownerId = cmd.getEntityOwnerId(); final Account owner = _entityMgr.findById(Account.class, ownerId); final Long zoneId = cmd.getZoneId(); final String volumeName = cmd.getName(); String format = cmd.getFormat(); final Long diskOfferingId = cmd.getDiskOfferingId(); String imageStoreUuid = cmd.getImageStoreUuid(); final DataStore store = _tmpltMgr.getImageStore(imageStoreUuid, zoneId); validateVolume(caller, ownerId, zoneId, volumeName, null, format, diskOfferingId); return Transaction.execute(new TransactionCallbackWithException<GetUploadParamsResponse, MalformedURLException>() { @Override public GetUploadParamsResponse doInTransaction(TransactionStatus status) throws MalformedURLException { VolumeVO volume = persistVolume(owner, zoneId, volumeName, null, cmd.getFormat(), diskOfferingId, Volume.State.NotUploaded); VolumeInfo vol = volFactory.getVolume(volume.getId()); RegisterVolumePayload payload = new RegisterVolumePayload(null, cmd.getChecksum(), cmd.getFormat()); vol.addPayload(payload); Pair<EndPoint, DataObject> pair = volService.registerVolumeForPostUpload(vol, store); EndPoint ep = pair.first(); DataObject dataObject = pair.second(); GetUploadParamsResponse response = new GetUploadParamsResponse(); String ssvmUrlDomain = _configDao.getValue(Config.SecStorageSecureCopyCert.key()); String url = ImageStoreUtil.generatePostUploadUrl(ssvmUrlDomain, ep.getPublicAddr(), vol.getUuid()); response.setPostURL(new URL(url)); // set the post url, this is used in the monitoring thread to determine the SSVM VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(vol.getId()); assert (volumeStore != null) : "sincle volume is registered, volumestore cannot be null at this stage"; volumeStore.setExtractUrl(url); _volumeStoreDao.persist(volumeStore); response.setId(UUID.fromString(vol.getUuid())); int timeout = ImageStoreUploadMonitorImpl.getUploadOperationTimeout(); DateTime currentDateTime = new DateTime(DateTimeZone.UTC); String expires = currentDateTime.plusMinutes(timeout).toString(); response.setTimeout(expires); String key = _configDao.getValue(Config.SSVMPSK.key()); /* * encoded metadata using the post upload config key */ TemplateOrVolumePostUploadCommand command = new TemplateOrVolumePostUploadCommand(vol.getId(), vol.getUuid(), volumeStore.getInstallPath(), cmd.getChecksum(), vol.getType().toString(), vol.getName(), vol.getFormat().toString(), dataObject.getDataStore().getUri(), dataObject.getDataStore().getRole().toString()); command.setLocalPath(volumeStore.getLocalDownloadPath()); //using the existing max upload size configuration command.setMaxUploadSize(_configDao.getValue(Config.MaxUploadVolumeSize.key())); command.setDefaultMaxAccountSecondaryStorage(_configDao.getValue(Config.DefaultMaxAccountSecondaryStorage.key())); command.setAccountId(vol.getAccountId()); Gson gson = new GsonBuilder().create(); String metadata = EncryptionUtil.encodeData(gson.toJson(command), key); response.setMetadata(metadata); /* * signature calculated on the url, expiry, metadata. */ response.setSignature(EncryptionUtil.generateSignature(metadata + url + expires, key)); return response; } }); } private boolean validateVolume(Account caller, long ownerId, Long zoneId, String volumeName, String url, String format, Long diskOfferingId) throws ResourceAllocationException { // permission check Account volumeOwner = _accountMgr.getActiveAccountById(ownerId); _accountMgr.checkAccess(caller, null, true, volumeOwner); // Check that the resource limit for volumes won't be exceeded _resourceLimitMgr.checkResourceLimit(volumeOwner, 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.getId())) { throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zoneId); } //validating the url only when url is not null. url can be null incase of form based post upload if (url != null ) { if( url.toLowerCase().contains("file://")) { throw new InvalidParameterValueException("File:// type urls are currently unsupported"); } UriUtils.validateUrl(format, url); // check URL existence UriUtils.checkUrlExistence(url); // Check that the resource limit for secondary storage won't be exceeded _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(ownerId), ResourceType.secondary_storage, UriUtils.getRemoteSize(url)); } else { _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(ownerId), ResourceType.secondary_storage); } try { ImageFormat.valueOf(format.toUpperCase()); } catch (IllegalArgumentException e) { s_logger.debug("ImageFormat IllegalArgumentException: " + e.getMessage()); throw new IllegalArgumentException("Image format: " + format + " is incorrect. Supported formats are " + EnumUtils.listValues(ImageFormat.values())); } // Check that the the disk offering specified is valid if (diskOfferingId != null) { DiskOfferingVO 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()) { throw new InvalidParameterValueException("Please specify a custom sized disk offering."); } if (diskOffering.getDomainId() == null) { // do nothing as offering is public } else { _configMgr.checkDiskOfferingAccess(volumeOwner, diskOffering); } } return false; } public String getRandomVolumeName() { return UUID.randomUUID().toString(); } @DB protected VolumeVO persistVolume(final Account owner, final Long zoneId, final String volumeName, final String url, final String format, final Long diskOfferingId, final Volume.State state) { return Transaction.execute(new TransactionCallback<VolumeVO>() { @Override public VolumeVO doInTransaction(TransactionStatus status) { VolumeVO volume = new VolumeVO(volumeName, zoneId, -1, -1, -1, new Long(-1), null, null, Storage.ProvisioningType.THIN, 0, Volume.Type.DATADISK); volume.setPoolId(null); volume.setDataCenterId(zoneId); volume.setPodId(null); volume.setState(state); // initialize the state // to prevent a null pointer deref I put the system account id here when no owner is given. // TODO Decide if this is valid or whether throwing a CloudRuntimeException is more appropriate volume.setAccountId((owner == null) ? Account.ACCOUNT_ID_SYSTEM : owner.getAccountId()); volume.setDomainId((owner == null) ? Domain.ROOT_DOMAIN : owner.getDomainId()); if (diskOfferingId == null) { DiskOfferingVO diskOfferingVO = _diskOfferingDao.findByUniqueName("Cloud.com-Custom"); if (diskOfferingVO != null) { long defaultDiskOfferingId = diskOfferingVO.getId(); volume.setDiskOfferingId(defaultDiskOfferingId); } } else { 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); //url can be null incase of postupload if (url != null) { _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.secondary_storage, UriUtils.getRemoteSize(url)); } return volume; } }); } /** * Retrieves the volume name from CreateVolumeCmd object. * * If the retrieved volume name is null, empty or blank, then A random name * will be generated using getRandomVolumeName method. * * @param cmd * @return Either the retrieved name or a random name. */ public String getVolumeNameFromCommand(CreateVolumeCmd cmd) { String userSpecifiedName = cmd.getVolumeName(); if (org.apache.commons.lang.StringUtils.isBlank(userSpecifiedName)) { userSpecifiedName = getRandomVolumeName(); } return userSpecifiedName; } /* * 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(); Account owner = _accountMgr.getActiveAccountById(ownerId); Boolean displayVolume = cmd.getDisplayVolume(); // permission check _accountMgr.checkAccess(caller, null, true, _accountMgr.getActiveAccountById(ownerId)); if (displayVolume == null) { displayVolume = true; } else { if (!_accountMgr.isRootAdmin(caller.getId())) { throw new PermissionDeniedException("Cannot update parameter displayvolume, only admin permitted "); } } // Check that the resource limit for volumes won't be exceeded _resourceLimitMgr.checkResourceLimit(owner, ResourceType.volume, displayVolume); Long zoneId = cmd.getZoneId(); Long diskOfferingId = null; DiskOfferingVO diskOffering = null; Storage.ProvisioningType provisioningType; 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"); } Long customDiskOfferingMaxSize = _volumeMgr.CustomDiskOfferingMaxSize.value(); Long customDiskOfferingMinSize = _volumeMgr.CustomDiskOfferingMinSize.value(); 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(); } } provisioningType = diskOffering.getProvisioningType(); 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); if (zoneId == null) { // if zoneId is not provided, we default to create volume in the same zone as the snapshot zone. zoneId = snapshotCheck.getDataCenterId(); } size = snapshotCheck.getSize(); // ; disk offering is used for tags // purposes minIops = snapshotCheck.getMinIops(); maxIops = snapshotCheck.getMaxIops(); provisioningType = diskOffering.getProvisioningType(); // check snapshot permissions _accountMgr.checkAccess(caller, null, true, snapshotCheck); // one step operation - create volume in VM's cluster and attach it // to the VM Long vmId = cmd.getVirtualMachineId(); if (vmId != null) { // 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."); } // permission check _accountMgr.checkAccess(caller, null, false, vm); } } // Check that the resource limit for primary storage won't be exceeded _resourceLimitMgr.checkResourceLimit(owner, ResourceType.primary_storage, displayVolume, 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.getId())) { 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 = getVolumeNameFromCommand(cmd); VolumeVO volume = commitVolume(cmd, caller, owner, displayVolume, zoneId, diskOfferingId, provisioningType, size, minIops, maxIops, parentVolume, userSpecifiedName, _uuidMgr.generateUuid(Volume.class, cmd.getCustomId())); return volume; } private VolumeVO commitVolume(final CreateVolumeCmd cmd, final Account caller, final Account owner, final Boolean displayVolume, final Long zoneId, final Long diskOfferingId, final Storage.ProvisioningType provisioningType, final Long size, final Long minIops, final Long maxIops, final VolumeVO parentVolume, final String userSpecifiedName, final String uuid) { return Transaction.execute(new TransactionCallback<VolumeVO>() { @Override public VolumeVO doInTransaction(TransactionStatus status) { VolumeVO volume = new VolumeVO(userSpecifiedName, -1, -1, -1, -1, new Long(-1), null, null, provisioningType, 0, Volume.Type.DATADISK); volume.setPoolId(null); volume.setUuid(uuid); volume.setDataCenterId(zoneId); volume.setPodId(null); volume.setAccountId(owner.getId()); volume.setDomainId(owner.getDomainId()); volume.setDiskOfferingId(diskOfferingId); volume.setSize(size); volume.setMinIops(minIops); volume.setMaxIops(maxIops); volume.setInstanceId(null); volume.setUpdated(new Date()); volume.setDisplayVolume(displayVolume); if (parentVolume != null) { volume.setTemplateId(parentVolume.getTemplateId()); volume.setFormat(parentVolume.getFormat()); } else { volume.setTemplateId(null); } volume = _volsDao.persist(volume); if (cmd.getSnapshotId() == null && displayVolume) { // 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(), displayVolume); } CallContext.current().setEventDetails("Volume Id: " + volume.getId()); // Increment resource count during allocation; if actual creation fails, // decrement it _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.volume, displayVolume); _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, displayVolume, new Long(volume.getSize())); 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("Requested volume size is " + 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(), cmd.getVirtualMachineId()); if (volume.getState() != Volume.State.Ready) { created = false; } // if VM Id is provided, attach the volume to the VM if (cmd.getVirtualMachineId() != null) { try { attachVolumeToVM(cmd.getVirtualMachineId(), volume.getId(), volume.getDeviceId()); } catch (Exception ex) { StringBuilder message = new StringBuilder("Volume: "); message.append(volume.getUuid()); message.append(" created successfully, but failed to attach the newly created volume to VM: "); message.append(cmd.getVirtualMachineId()); message.append(" due to error: "); message.append(ex.getMessage()); if (s_logger.isDebugEnabled()) { s_logger.debug(message, ex); } throw new CloudRuntimeException(message.toString()); } } } return volume; } catch (Exception e) { created = false; VolumeInfo vol = volFactory.getVolume(cmd.getEntityId()); vol.stateTransit(Volume.Event.DestroyRequested); throw new CloudRuntimeException("Failed to create volume: " + volume.getId(), e); } 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, cmd.getDisplayVolume()); _resourceLimitMgr.recalculateResourceCount(volume.getAccountId(), volume.getDomainId(), ResourceType.primary_storage.getOrdinal()); } } } protected VolumeVO createVolumeFromSnapshot(VolumeVO volume, long snapshotId, Long vmId) throws StorageUnavailableException { VolumeInfo createdVolume = null; SnapshotVO snapshot = _snapshotDao.findById(snapshotId); snapshot.getVolumeId(); UserVmVO vm = null; if (vmId != null) { vm = _userVmDao.findById(vmId); } // sync old snapshots to region store if necessary createdVolume = _volumeMgr.createVolumeFromSnapshot(volume, snapshot, vm); VolumeVO volumeVo = _volsDao.findById(createdVolume.getId()); UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, createdVolume.getAccountId(), createdVolume.getDataCenterId(), createdVolume.getId(), createdVolume.getName(), createdVolume.getDiskOfferingId(), null, createdVolume.getSize(), Volume.class.getName(), createdVolume.getUuid(), volumeVo.isDisplayVolume()); return volumeVo; } @Override @DB @ActionEvent(eventType = EventTypes.EVENT_VOLUME_RESIZE, eventDescription = "resizing volume", async = true) public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationException { Long newSize = null; Long newMinIops = null; Long newMaxIops = null; Integer newHypervisorSnapshotReserve = null; boolean shrinkOk = cmd.getShrinkOk(); VolumeVO volume = _volsDao.findById(cmd.getEntityId()); if (volume == null) { throw new InvalidParameterValueException("No such volume"); } /* Does the caller have authority to act on this volume? */ _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, volume); if(volume.getInstanceId() != null) { // Check that Vm to which this volume is attached does not have VM Snapshots if (_vmSnapshotDao.findByVm(volume.getInstanceId()).size() > 0) { throw new InvalidParameterValueException("Volume cannot be resized which is attached to VM with VM Snapshots"); } } DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); DiskOfferingVO newDiskOffering = null; if (cmd.getNewDiskOfferingId() != null && volume.getDiskOfferingId() != cmd.getNewDiskOfferingId()) { newDiskOffering = _diskOfferingDao.findById(cmd.getNewDiskOfferingId()); } /* Only works for KVM/XenServer/VMware (or "Any") for now, and volumes with 'None' since they're just allocated in DB */ HypervisorType hypervisorType = _volsDao.getHypervisorType(volume.getId()); if (hypervisorType != HypervisorType.KVM && hypervisorType != HypervisorType.XenServer && hypervisorType != HypervisorType.VMware && hypervisorType != HypervisorType.Any && hypervisorType != HypervisorType.None ) { throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support rootdisksize override"); } if (volume.getState() != Volume.State.Ready && volume.getState() != Volume.State.Allocated) { throw new InvalidParameterValueException("Volume should be in ready or allocated state before attempting a resize. Volume " + volume.getUuid() + " is in state " + volume.getState() + "."); } // if we are to use the existing disk offering if (newDiskOffering == null) { newSize = cmd.getSize(); newHypervisorSnapshotReserve = volume.getHypervisorSnapshotReserve(); // if the caller is looking to change the size of the volume if (newSize != null) { if (!diskOffering.isCustomized() && !volume.getVolumeType().equals(Volume.Type.ROOT)) { throw new InvalidParameterValueException("To change a volume's size without providing a new disk offering, its current disk offering must be " + "customizable or it must be a root volume (if providing a disk offering, make sure it is different from the current disk offering)."); } // convert from bytes to GiB newSize = newSize << 30; } else { // no parameter provided; just use the original size of the volume newSize = volume.getSize(); } newMinIops = cmd.getMinIops(); if (newMinIops != null) { if (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops()) { throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Min IOPS' parameter."); } } else { // no parameter provided; just use the original min IOPS of the volume newMinIops = volume.getMinIops(); } newMaxIops = cmd.getMaxIops(); if (newMaxIops != null) { if (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops()) { throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Max IOPS' parameter."); } } else { // no parameter provided; just use the original max IOPS of the volume newMaxIops = volume.getMaxIops(); } validateIops(newMinIops, newMaxIops); } else { if (newDiskOffering.getRemoved() != null) { throw new InvalidParameterValueException("Requested disk offering has been removed."); } if (!DiskOfferingVO.Type.Disk.equals(newDiskOffering.getType())) { throw new InvalidParameterValueException("Requested disk offering type is invalid."); } if (diskOffering.getTags() != null) { if (!StringUtils.areTagsEqual(diskOffering.getTags(), newDiskOffering.getTags())) { throw new InvalidParameterValueException("The tags on the new and old disk offerings must match."); } } else if (newDiskOffering.getTags() != null) { throw new InvalidParameterValueException("There are no tags on the current disk offering. The new disk offering needs to have no tags, as well."); } if (newDiskOffering.getDomainId() != null) { // not a public offering; check access _configMgr.checkDiskOfferingAccess(CallContext.current().getCallingAccount(), newDiskOffering); } if (newDiskOffering.isCustomized()) { newSize = cmd.getSize(); if (newSize == null) { throw new InvalidParameterValueException("The new disk offering requires that a size be specified."); } // convert from bytes to GiB newSize = newSize << 30; } else { newSize = newDiskOffering.getDiskSize(); } if (!volume.getSize().equals(newSize) && !volume.getVolumeType().equals(Volume.Type.DATADISK)) { throw new InvalidParameterValueException("Only data volumes can be resized via a new disk offering."); } if (newDiskOffering.isCustomizedIops() != null && newDiskOffering.isCustomizedIops()) { newMinIops = cmd.getMinIops() != null ? cmd.getMinIops() : volume.getMinIops(); newMaxIops = cmd.getMaxIops() != null ? cmd.getMaxIops() : volume.getMaxIops(); validateIops(newMinIops, newMaxIops); } else { newMinIops = newDiskOffering.getMinIops(); newMaxIops = newDiskOffering.getMaxIops(); } // if the hypervisor snapshot reserve value is null, it must remain null (currently only KVM uses null and null is all KVM uses for a value here) newHypervisorSnapshotReserve = volume.getHypervisorSnapshotReserve() != null ? newDiskOffering.getHypervisorSnapshotReserve() : null; } long currentSize = volume.getSize(); // if the caller is looking to change the size of the volume if (currentSize != newSize) { if (!validateVolumeSizeRange(newSize)) { throw new InvalidParameterValueException("Requested size out of range"); } /* * Let's 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 (newSize > currentSize) { /* Check resource limit for this account on primary storage resource */ _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(volume.getAccountId()), ResourceType.primary_storage, volume.isDisplayVolume(), new Long(newSize - currentSize).longValue()); } } // Note: The storage plug-in in question should perform validation on the IOPS to check if a sufficient number of IOPS is available to perform // the requested change /* If this volume has never been beyond allocated state, short circuit everything and simply update the database. */ if (volume.getState() == Volume.State.Allocated) { s_logger.debug("Volume is in the allocated state, but has never been created. Simply updating database with new size and IOPS."); volume.setSize(newSize); volume.setMinIops(newMinIops); volume.setMaxIops(newMaxIops); volume.setHypervisorSnapshotReserve(newHypervisorSnapshotReserve); if (newDiskOffering != null) { volume.setDiskOfferingId(cmd.getNewDiskOfferingId()); } _volsDao.update(volume.getId(), volume); return volume; } UserVmVO userVm = _userVmDao.findById(volume.getInstanceId()); if (userVm != null) { if (volume.getVolumeType().equals(Volume.Type.ROOT) && userVm.getPowerState()!= VirtualMachine.PowerState.PowerOff && hypervisorType == HypervisorType.VMware){ s_logger.error(" For ROOT volume resize VM should be in Power Off state."); throw new InvalidParameterValueException("VM current state is : "+userVm.getPowerState()+ ". But VM should be in "+VirtualMachine.PowerState.PowerOff+" state."); } // serialize VM operation AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); if ( jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { // avoid re-entrance VmWorkJobVO placeHolder = null; placeHolder = createPlaceHolderWork(userVm.getId()); try { return orchestrateResizeVolume(volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, shrinkOk); } finally { _workJobDao.expunge(placeHolder.getId()); } } else { Outcome<Volume> outcome = resizeVolumeThroughJobQueue(userVm.getId(), volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, shrinkOk); try { outcome.get(); } catch (InterruptedException e) { throw new RuntimeException("Operation was interrupted", e); } catch (java.util.concurrent.ExecutionException e) { throw new RuntimeException("Execution exception", e); } Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob()); if (jobResult != null) { if (jobResult instanceof ConcurrentOperationException) { throw (ConcurrentOperationException)jobResult; } else if (jobResult instanceof ResourceAllocationException) { throw (ResourceAllocationException)jobResult; } else if (jobResult instanceof RuntimeException) { throw (RuntimeException)jobResult; } else if (jobResult instanceof Throwable) { throw new RuntimeException("Unexpected exception", (Throwable)jobResult); } else if (jobResult instanceof Long) { return _volsDao.findById((Long)jobResult); } } return volume; } } return orchestrateResizeVolume(volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, shrinkOk); } private void validateIops(Long minIops, Long maxIops) { if ((minIops == null && maxIops != null) || (minIops != null && maxIops == null)) { throw new InvalidParameterValueException("Either 'miniops' and 'maxiops' must both be provided or neither must be provided."); } if (minIops != null && maxIops != null) { if (minIops > maxIops) { throw new InvalidParameterValueException("The 'miniops' parameter must be less than or equal to the 'maxiops' parameter."); } } } private VolumeVO orchestrateResizeVolume(long volumeId, long currentSize, long newSize, Long newMinIops, Long newMaxIops, Integer newHypervisorSnapshotReserve, Long newDiskOfferingId, boolean shrinkOk) { VolumeVO volume = _volsDao.findById(volumeId); UserVmVO userVm = _userVmDao.findById(volume.getInstanceId()); StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId()); boolean isManaged = storagePool.isManaged(); /* * 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()}; } final String errorMsg = "The VM must be stopped or the disk detached in order to resize with the XenServer Hypervisor."; if (storagePool.isManaged() && storagePool.getHypervisor() == HypervisorType.Any && hosts != null && hosts.length > 0) { HostVO host = _hostDao.findById(hosts[0]); if (currentSize != newSize && host.getHypervisorType() == HypervisorType.XenServer && !userVm.getState().equals(State.Stopped)) { throw new InvalidParameterValueException(errorMsg); } } /* Xen only works offline, SR does not support VDI.resizeOnline */ if (currentSize != newSize && _volsDao.getHypervisorType(volume.getId()) == HypervisorType.XenServer && !userVm.getState().equals(State.Stopped)) { throw new InvalidParameterValueException(errorMsg); } } ResizeVolumePayload payload = new ResizeVolumePayload(newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, shrinkOk, instanceName, hosts, isManaged); try { VolumeInfo vol = volFactory.getVolume(volume.getId()); vol.addPayload(payload); // this call to resize has a different impact depending on whether the // underlying primary storage is managed or not // if managed, this is the chance for the plug-in to change IOPS value, if applicable // if not managed, this is the chance for the plug-in to talk to the hypervisor layer // to change the size of the disk AsyncCallFuture<VolumeApiResult> future = volService.resize(vol); VolumeApiResult result = future.get(); // managed storage is designed in such a way that the storage plug-in does not // talk to the hypervisor layer; as such, if the storage is managed and the // current and new sizes are different, then CloudStack (i.e. not a storage plug-in) // needs to tell the hypervisor to resize the disk if (storagePool.isManaged() && currentSize != newSize) { if (hosts != null && hosts.length > 0) { volService.resizeVolumeOnHypervisor(volumeId, newSize, hosts[0], instanceName); } volume.setSize(newSize); _volsDao.update(volume.getId(), volume); } if (result.isFailed()) { s_logger.warn("Failed to resize the volume " + volume); String details = ""; if (result.getResult() != null && !result.getResult().isEmpty()) { details = result.getResult(); } throw new CloudRuntimeException(details); } volume = _volsDao.findById(volume.getId()); if (newDiskOfferingId != null) { volume.setDiskOfferingId(newDiskOfferingId); } if (currentSize != newSize) { volume.setSize(newSize); } _volsDao.update(volume.getId(), volume); /* Update resource count for the account on primary storage resource */ if (!shrinkOk) { _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, volume.isDisplayVolume(), new Long(newSize - currentSize)); } else { _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, volume.isDisplayVolume(), new Long(currentSize - newSize)); } return volume; } catch (InterruptedException e) { s_logger.warn("failed get resize volume result", e); throw new CloudRuntimeException(e.getMessage()); } catch (ExecutionException e) { s_logger.warn("failed get resize volume result", e); throw new CloudRuntimeException(e.getMessage()); } catch (Exception e) { s_logger.warn("failed get resize volume result", e); throw new CloudRuntimeException(e.getMessage()); } } @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 find volume with ID: " + volumeId); } if (!_snapshotMgr.canOperateOnVolume(volume)) { throw new InvalidParameterValueException("There are snapshot operations in progress on the volume, unable to delete it"); } _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"); } } if (volume.getState() == Volume.State.NotUploaded || volume.getState() == Volume.State.UploadInProgress) { throw new InvalidParameterValueException("The volume is either getting uploaded or it may be initiated shortly, please wait for it to be completed"); } try { if (volume.getState() != Volume.State.Destroy && volume.getState() != Volume.State.Expunging && volume.getState() != Volume.State.Expunged) { 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, volume.isDisplayVolume()); } } // 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<VolumeApiResult> future = volService.expungeVolumeAsync(volOnPrimary); future.get(); //decrement primary storage count _resourceLimitMgr.recalculateResourceCount(volume.getAccountId(), volume.getDomainId(), ResourceType.primary_storage.getOrdinal()); } // 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<VolumeApiResult> future2 = volService.expungeVolumeAsync(volOnSecondary); future2.get(); //decrement secondary storage count _resourceLimitMgr.recalculateResourceCount(volume.getAccountId(), volume.getDomainId(), ResourceType.secondary_storage.getOrdinal()); } // delete all cache entries for this volume List<VolumeInfo> cacheVols = volFactory.listVolumeOnCache(volume.getId()); for (VolumeInfo volOnCache : cacheVols) { s_logger.info("Delete volume from image cache store: " + volOnCache.getDataStore().getName()); volOnCache.delete(); } } catch (InterruptedException | ExecutionException | NoTransitionException 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) { return attachVolumeToVM(command.getVirtualMachineId(), command.getId(), command.getDeviceId()); } private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long deviceId) { VolumeInfo volumeToAttach = volFactory.getVolume(volumeId); if (volumeToAttach.isAttachedVM()) { throw new CloudRuntimeException("This volume is already attached to a VM."); } UserVmVO vm = _userVmDao.findById(vmId); VolumeVO exstingVolumeOfVm = null; List<VolumeVO> 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 { if (!rootVolumesOfVm.isEmpty()) { exstingVolumeOfVm = rootVolumesOfVm.get(0); } else { // locate data volume of the vm List<VolumeVO> diskVolumesOfVm = _volsDao.findByInstanceAndType(vmId, Volume.Type.DATADISK); for (VolumeVO diskVolume : diskVolumesOfVm) { if (diskVolume.getState() != Volume.State.Allocated) { exstingVolumeOfVm = diskVolume; break; } } } } HypervisorType rootDiskHyperType = vm.getHypervisorType(); HypervisorType volumeToAttachHyperType = _volsDao.getHypervisorType(volumeToAttach.getId()); VolumeInfo newVolumeOnPrimaryStorage = volumeToAttach; //don't create volume on primary storage if its being attached to the vm which Root's volume hasn't been created yet StoragePoolVO destPrimaryStorage = null; if (exstingVolumeOfVm != null && !exstingVolumeOfVm.getState().equals(Volume.State.Allocated)) { destPrimaryStorage = _storagePoolDao.findById(exstingVolumeOfVm.getPoolId()); } boolean volumeOnSecondary = volumeToAttach.getState() == Volume.State.Uploaded; if (destPrimaryStorage != null && (volumeToAttach.getState() == Volume.State.Allocated || volumeOnSecondary)) { try { newVolumeOnPrimaryStorage = _volumeMgr.createVolumeOnPrimaryStorage(vm, volumeToAttach, rootDiskHyperType, destPrimaryStorage); } 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 newVolumeOnPrimaryStorage = volFactory.getVolume(newVolumeOnPrimaryStorage.getId()); boolean moveVolumeNeeded = needMoveVolume(exstingVolumeOfVm, newVolumeOnPrimaryStorage); if (moveVolumeNeeded) { PrimaryDataStoreInfo primaryStore = (PrimaryDataStoreInfo)newVolumeOnPrimaryStorage.getDataStore(); if (primaryStore.isLocal()) { throw new CloudRuntimeException("Failed to attach local data volume " + volumeToAttach.getName() + " to VM " + vm.getDisplayName() + " as migration of local data volume is not allowed"); } StoragePoolVO vmRootVolumePool = _storagePoolDao.findById(exstingVolumeOfVm.getPoolId()); try { newVolumeOnPrimaryStorage = _volumeMgr.moveVolume(newVolumeOnPrimaryStorage, vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(), volumeToAttachHyperType); } catch (ConcurrentOperationException e) { s_logger.debug("move volume failed", e); throw new CloudRuntimeException("move volume failed", e); } catch (StorageUnavailableException e) { s_logger.debug("move volume failed", e); throw new CloudRuntimeException("move volume failed", e); } } VolumeVO newVol = _volsDao.findById(newVolumeOnPrimaryStorage.getId()); // Getting the fresh vm object in case of volume migration to check the current state of VM if (moveVolumeNeeded || volumeOnSecondary) { vm = _userVmDao.findById(vmId); if (vm == null) { throw new InvalidParameterValueException("VM not found."); } } newVol = sendAttachVolumeCommand(vm, newVol, deviceId); return newVol; } public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId) { Account caller = CallContext.current().getCallingAccount(); // Check that the volume ID is valid VolumeInfo volumeToAttach = volFactory.getVolume(volumeId); // Check that the volume is a data volume if (volumeToAttach == null || !(volumeToAttach.getVolumeType() == Volume.Type.DATADISK || volumeToAttach.getVolumeType() == Volume.Type.ROOT)) { throw new InvalidParameterValueException("Please specify a volume with the valid type: " + Volume.Type.ROOT.toString() + " or " + Volume.Type.DATADISK.toString()); } // Check that the volume is not currently attached to any VM if (volumeToAttach.getInstanceId() != null) { throw new InvalidParameterValueException("Please specify a volume that is not attached to any VM."); } // Check that the volume is not destroyed if (volumeToAttach.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 VM and the volume are in the same zone if (vm.getDataCenterId() != volumeToAttach.getDataCenterId()) { throw new InvalidParameterValueException("Please specify a VM that is in the same zone as the volume."); } // Check that the device ID is valid if (deviceId != null) { // validate ROOT volume type if (deviceId.longValue() == 0) { validateRootVolumeDetachAttach(_volsDao.findById(volumeToAttach.getId()), vm); // vm shouldn't have any volume with deviceId 0 if (!_volsDao.findByInstanceAndDeviceId(vm.getId(), 0).isEmpty()) { throw new InvalidParameterValueException("Vm already has root volume attached to it"); } // volume can't be in Uploaded state if (volumeToAttach.getState() == Volume.State.Uploaded) { throw new InvalidParameterValueException("No support for Root volume attach in state " + Volume.State.Uploaded); } } } // Check that the number of data volumes attached to VM is less than // that supported by hypervisor if (deviceId == null || deviceId.longValue() != 0) { List<VolumeVO> existingDataVolumes = _volsDao.findByInstanceAndType(vmId, Volume.Type.DATADISK); int maxAttachableDataVolumesSupported = getMaxDataVolumesSupported(vm); if (existingDataVolumes.size() >= maxAttachableDataVolumesSupported) { throw new InvalidParameterValueException("The specified VM already has the maximum number of data disks (" + maxAttachableDataVolumesSupported + ") attached. Please specify another VM."); } } // If local storage is disabled then attaching a volume with local disk // offering not allowed DataCenterVO dataCenter = _dcDao.findById(volumeToAttach.getDataCenterId()); if (!dataCenter.isLocalStorageEnabled()) { DiskOfferingVO diskOffering = _diskOfferingDao.findById(volumeToAttach.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<VMSnapshotVO> 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, volumeToAttach, vm); if (!(Volume.State.Allocated.equals(volumeToAttach.getState()) || Volume.State.Ready.equals(volumeToAttach.getState()) || Volume.State.Uploaded.equals(volumeToAttach .getState()))) { throw new InvalidParameterValueException("Volume state must be in Allocated, Ready or in Uploaded state"); } Account owner = _accountDao.findById(volumeToAttach.getAccountId()); try { _resourceLimitMgr.checkResourceLimit(owner, ResourceType.primary_storage, volumeToAttach.getSize()); } catch (ResourceAllocationException e) { s_logger.error("primary storage resource limit check failed", e); throw new InvalidParameterValueException(e.getMessage()); } HypervisorType rootDiskHyperType = vm.getHypervisorType(); HypervisorType volumeToAttachHyperType = _volsDao.getHypervisorType(volumeToAttach.getId()); StoragePoolVO volumeToAttachStoragePool = _storagePoolDao.findById(volumeToAttach.getPoolId()); // managed storage can be used for different types of hypervisors // only perform this check if the volume's storage pool is not null and not managed if (volumeToAttachStoragePool != null && !volumeToAttachStoragePool.isManaged()) { if (volumeToAttachHyperType != HypervisorType.None && rootDiskHyperType != volumeToAttachHyperType) { throw new InvalidParameterValueException("Can't attach a volume created by: " + volumeToAttachHyperType + " to a " + rootDiskHyperType + " vm"); } } 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"); } _jobMgr.updateAsyncJobAttachment(job.getId(), "Volume", volumeId); } AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { // avoid re-entrance VmWorkJobVO placeHolder = null; placeHolder = createPlaceHolderWork(vmId); try { return orchestrateAttachVolumeToVM(vmId, volumeId, deviceId); } finally { _workJobDao.expunge(placeHolder.getId()); } } else { Outcome<Volume> outcome = attachVolumeToVmThroughJobQueue(vmId, volumeId, deviceId); Volume vol = null; try { outcome.get(); } catch (InterruptedException e) { throw new RuntimeException("Operation is interrupted", e); } catch (java.util.concurrent.ExecutionException e) { throw new RuntimeException("Execution excetion", e); } Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob()); if (jobResult != null) { if (jobResult instanceof ConcurrentOperationException) throw (ConcurrentOperationException)jobResult; else if (jobResult instanceof InvalidParameterValueException) throw (InvalidParameterValueException)jobResult; else if (jobResult instanceof RuntimeException) throw (RuntimeException)jobResult; else if (jobResult instanceof Throwable) throw new RuntimeException("Unexpected exception", (Throwable)jobResult); else if (jobResult instanceof Long) { vol = _volsDao.findById((Long)jobResult); } } return vol; } } @Override @ActionEvent(eventType = EventTypes.EVENT_VOLUME_UPDATE, eventDescription = "updating volume", async = true) public Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, String customId, long entityOwnerId, String chainInfo) { VolumeVO volume = _volsDao.findById(volumeId); if(volume == null) throw new InvalidParameterValueException("The volume id doesn't exist"); if (path != null) { volume.setPath(path); } if(chainInfo != null){ volume.setChainInfo(chainInfo); } if (state != null) { try { Volume.State volumeState = Volume.State.valueOf(state); volume.setState(volumeState); } catch (IllegalArgumentException ex) { throw new InvalidParameterValueException("Invalid volume state specified"); } } if (storageId != null) { StoragePool pool = _storagePoolDao.findById(storageId); if (pool.getDataCenterId() != volume.getDataCenterId()) { throw new InvalidParameterValueException("Invalid storageId specified; refers to the pool outside of the volume's zone"); } volume.setPoolId(pool.getId()); } if (customId != null) { volume.setUuid(customId); } updateDisplay(volume, displayVolume); _volsDao.update(volumeId, volume); return volume; } @Override public void updateDisplay(Volume volume, Boolean displayVolume){ // 1. Resource limit changes updateResourceCount(volume, displayVolume); // 2. generate usage event if not in destroyed state saveUsageEvent(volume, displayVolume); // 3. Set the flag if (displayVolume != null && displayVolume != volume.isDisplayVolume()){ // FIXME - Confused - typecast for now. ((VolumeVO)volume).setDisplayVolume(displayVolume); _volsDao.update(volume.getId(), (VolumeVO) volume); } } private void updateResourceCount(Volume volume, Boolean displayVolume){ // Update only when the flag has changed. if (displayVolume != null && displayVolume != volume.isDisplayVolume()){ _resourceLimitMgr.changeResourceCount(volume.getAccountId(), ResourceType.volume, displayVolume); _resourceLimitMgr.changeResourceCount(volume.getAccountId(), ResourceType.primary_storage, displayVolume, new Long(volume.getSize())); } } private void saveUsageEvent(Volume volume, Boolean displayVolume){ // Update only when the flag has changed && only when volume in a non-destroyed state. if ((displayVolume != null && displayVolume != volume.isDisplayVolume()) && !isVolumeDestroyed(volume)){ if (displayVolume){ // flag turned 1 equivalent to freshly created volume UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), volume.getDiskOfferingId(), volume.getTemplateId(), volume.getSize(), Volume.class.getName(), volume.getUuid()); }else { // flag turned 0 equivalent to deleting a volume UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), Volume.class.getName(), volume.getUuid()); } } } private boolean isVolumeDestroyed(Volume volume){ if(volume.getState() == Volume.State.Destroy || volume.getState() == Volume.State.Expunging && volume.getState() == Volume.State.Expunged) return true; return false; } @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); } // Check that the volume ID is valid if (volume == null) { throw new InvalidParameterValueException("Unable to find volume with ID: " + volumeId); } Long vmId = null; if (cmmd.getVirtualMachineId() == null) { vmId = volume.getInstanceId(); } else { vmId = cmmd.getVirtualMachineId(); } // Permissions check _accountMgr.checkAccess(caller, null, true, 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."); } // Check that the volume is a data/root volume if (!(volume.getVolumeType() == Volume.Type.ROOT || volume.getVolumeType() == Volume.Type.DATADISK)) { throw new InvalidParameterValueException("Please specify volume of type " + Volume.Type.DATADISK.toString() + " or " + Volume.Type.ROOT.toString()); } // Root volume detach is allowed for following hypervisors: Xen/KVM/VmWare if (volume.getVolumeType() == Volume.Type.ROOT) { validateRootVolumeDetachAttach(volume, vm); } // Don't allow detach if target VM has associated VM snapshots List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(vmId); if (vmSnapshots.size() > 0) { throw new InvalidParameterValueException("Unable to detach volume, please specify a VM that does not have VM snapshots"); } 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"); } _jobMgr.updateAsyncJobAttachment(job.getId(), "Volume", volumeId); } AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { // avoid re-entrance VmWorkJobVO placeHolder = null; placeHolder = createPlaceHolderWork(vmId); try { return orchestrateDetachVolumeFromVM(vmId, volumeId); } finally { _workJobDao.expunge(placeHolder.getId()); } } else { Outcome<Volume> outcome = detachVolumeFromVmThroughJobQueue(vmId, volumeId); Volume vol = null; try { outcome.get(); } catch (InterruptedException e) { throw new RuntimeException("Operation is interrupted", e); } catch (java.util.concurrent.ExecutionException e) { throw new RuntimeException("Execution excetion", e); } Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob()); if (jobResult != null) { if (jobResult instanceof ConcurrentOperationException) throw (ConcurrentOperationException)jobResult; else if (jobResult instanceof RuntimeException) throw (RuntimeException)jobResult; else if (jobResult instanceof Throwable) throw new RuntimeException("Unexpected exception", (Throwable)jobResult); else if (jobResult instanceof Long) { vol = _volsDao.findById((Long) jobResult); } } return vol; } } private void validateRootVolumeDetachAttach(VolumeVO volume, UserVmVO vm) { if (!(vm.getHypervisorType() == HypervisorType.XenServer || vm.getHypervisorType() == HypervisorType.VMware || vm.getHypervisorType() == HypervisorType.KVM || vm.getHypervisorType() == HypervisorType.Simulator)) { throw new InvalidParameterValueException("Root volume detach is not supported for hypervisor type " + vm.getHypervisorType() ); } if (!(vm.getState() == State.Stopped) || (vm.getState() == State.Destroyed)) { throw new InvalidParameterValueException("Root volume detach can happen only when vm is in states: " + State.Stopped.toString() + " or " + State.Destroyed.toString()); } if (volume.getPoolId() != null) { StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId()); if (pool.isManaged()) { throw new InvalidParameterValueException("Root volume detach is not supported for Managed DataStores"); } } } private Volume orchestrateDetachVolumeFromVM(long vmId, long volumeId) { Volume volume = _volsDao.findById(volumeId); VMInstanceVO vm = _vmInstanceDao.findById(vmId); String errorMsg = "Failed to detach volume " + volume.getName() + " from VM " + vm.getHostName(); boolean sendCommand = vm.getState() == State.Running; Long hostId = vm.getHostId(); if (hostId == null) { hostId = vm.getLastHostId(); HostVO host = _hostDao.findById(hostId); if (host != null && host.getHypervisorType() == HypervisorType.VMware) { sendCommand = true; } } HostVO host = null; StoragePoolVO volumePool = _storagePoolDao.findByIdIncludingRemoved(volume.getPoolId()); if (hostId != null) { host = _hostDao.findById(hostId); if (host != null && host.getHypervisorType() == HypervisorType.XenServer && volumePool != null && volumePool.isManaged()) { sendCommand = true; } } Answer answer = null; if (sendCommand) { DataTO volTO = volFactory.getVolume(volume.getId()).getTO(); DiskTO disk = new DiskTO(volTO, volume.getDeviceId(), volume.getPath(), 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(hostId, 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()); // volume.getPoolId() should be null if the VM we are detaching the disk from has never been started before DataStore dataStore = volume.getPoolId() != null ? dataStoreMgr.getDataStore(volume.getPoolId(), DataStoreRole.Primary) : null; volService.revokeAccess(volFactory.getVolume(volume.getId()), host, dataStore); return _volsDao.findById(volumeId); } else { if (answer != null) { String details = answer.getDetails(); if (details != null && !details.isEmpty()) { errorMsg += "; " + details; } } throw new CloudRuntimeException(errorMsg); } } public void updateMissingRootDiskController(final VMInstanceVO vm, final String rootVolChainInfo) { if (vm == null || !VirtualMachine.Type.User.equals(vm.getType()) || Strings.isNullOrEmpty(rootVolChainInfo)) { return; } String rootDiskController = null; try { final VirtualMachineDiskInfo infoInChain = _gson.fromJson(rootVolChainInfo, VirtualMachineDiskInfo.class); if (infoInChain != null) { rootDiskController = infoInChain.getControllerFromDeviceBusName(); } final UserVmVO userVmVo = _userVmDao.findById(vm.getId()); if ((rootDiskController != null) && (!rootDiskController.isEmpty())) { _userVmDao.loadDetails(userVmVo); _userVmMgr.persistDeviceBusInfo(userVmVo, rootDiskController); } } catch (JsonParseException e) { s_logger.debug("Error parsing chain info json: " + e.getMessage()); } } @DB @Override @ActionEvent(eventType = EventTypes.EVENT_VOLUME_MIGRATE, eventDescription = "migrating volume", async = true) 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(); Long srcClusterId = null; VMInstanceVO vm = null; if (instanceId != null) { vm = _vmInstanceDao.findById(instanceId); } // Check that Vm to which this volume is attached does not have VM Snapshots if (vm != null && _vmSnapshotDao.findByVm(vm.getId()).size() > 0) { throw new InvalidParameterValueException("Volume cannot be migrated, please remove all VM snapshots for VM to which this volume is attached"); } if (vm != null && vm.getState() == State.Running) { // Check if the VM is GPU enabled. if(_serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) { throw new InvalidParameterValueException("Live Migration of GPU enabled VM is not supported"); } // 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()); srcClusterId = host.getClusterId(); } if (capabilities != null) { liveMigrateVolume = capabilities.isStorageMotionSupported(); } } // If vm is running, and hypervisor doesn't support live migration, then return error if (!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); } else if (destPool.isInMaintenance()) { throw new InvalidParameterValueException("Cannot migrate volume " + vol + "to the destination storage pool " + destPool.getName() + " as the storage pool is in maintenance mode."); } if (_volumeMgr.volumeOnSharedStoragePool(vol)) { if (destPool.isLocal()) { throw new InvalidParameterValueException("Migration of volume from shared to local storage pool is not supported"); } else { // If the volume is attached to a running vm and the volume is on a shared storage pool, check // to make sure that the destination storage pool is in the same cluster as the vm. if (liveMigrateVolume && destPool.getClusterId() != null && srcClusterId != null) { if (!srcClusterId.equals(destPool.getClusterId())) { throw new InvalidParameterValueException("Cannot migrate a volume of a virtual machine to a storage pool in a different cluster"); } } // In case of VMware, if ROOT volume is being cold-migrated, then ensure destination storage pool is in the same Datacenter as the VM. if (vm != null && vm.getHypervisorType().equals(HypervisorType.VMware)) { if (!liveMigrateVolume && vol.volumeType.equals(Volume.Type.ROOT)) { Long hostId = vm.getHostId() != null ? vm.getHostId() : vm.getLastHostId(); HostVO host = _hostDao.findById(hostId); if (host != null) srcClusterId = host.getClusterId(); if (srcClusterId != null && destPool.getClusterId() != null && !srcClusterId.equals(destPool.getClusterId())) { String srcDcName = _clusterDetailsDao.getVmwareDcName(srcClusterId); String destDcName = _clusterDetailsDao.getVmwareDcName(destPool.getClusterId()); if (srcDcName != null && destDcName != null && !srcDcName.equals(destDcName)) { throw new InvalidParameterValueException("Cannot migrate ROOT volume of a stopped VM to a storage pool in a different VMware datacenter"); } } updateMissingRootDiskController(vm, vol.getChainInfo()); } } } } else { throw new InvalidParameterValueException("Migration of volume from local storage pool is not supported"); } if (vm != null) { // serialize VM operation AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { // avoid re-entrance VmWorkJobVO placeHolder = null; placeHolder = createPlaceHolderWork(vm.getId()); try { return orchestrateMigrateVolume(vol.getId(), destPool.getId(), liveMigrateVolume); } finally { _workJobDao.expunge(placeHolder.getId()); } } else { Outcome<Volume> outcome = migrateVolumeThroughJobQueue(vm.getId(), vol.getId(), destPool.getId(), liveMigrateVolume); try { outcome.get(); } catch (InterruptedException e) { throw new RuntimeException("Operation is interrupted", e); } catch (java.util.concurrent.ExecutionException e) { throw new RuntimeException("Execution excetion", e); } Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob()); if (jobResult != null) { if (jobResult instanceof ConcurrentOperationException) throw (ConcurrentOperationException)jobResult; else if (jobResult instanceof RuntimeException) throw (RuntimeException)jobResult; else if (jobResult instanceof Throwable) throw new RuntimeException("Unexpected exception", (Throwable)jobResult); } // retrieve the migrated new volume from job result if (jobResult != null && jobResult instanceof Long) { return _entityMgr.findById(VolumeVO.class, ((Long)jobResult)); } return null; } } return orchestrateMigrateVolume(vol.getId(), destPool.getId(), liveMigrateVolume); } private Volume orchestrateMigrateVolume(long volumeId, long destPoolId, boolean liveMigrateVolume) { VolumeVO vol = _volsDao.findById(volumeId); assert (vol != null); StoragePool destPool = (StoragePool)dataStoreMgr.getDataStore(destPoolId, DataStoreRole.Primary); assert (destPool != null); Volume newVol = null; try { if (liveMigrateVolume) { newVol = liveMigrateVolume(vol, destPool); } else { newVol = _volumeMgr.migrateVolume(vol, destPool); } } catch (StorageUnavailableException e) { s_logger.debug("Failed to migrate volume", e); throw new CloudRuntimeException(e.getMessage()); } catch (Exception e) { s_logger.debug("Failed to migrate volume", e); throw new CloudRuntimeException(e.getMessage()); } return newVol; } @DB protected Volume liveMigrateVolume(Volume volume, StoragePool destPool) throws StorageUnavailableException { VolumeInfo vol = volFactory.getVolume(volume.getId()); AsyncCallFuture<VolumeApiResult> future = volService.migrateVolume(vol, (DataStore)destPool); try { VolumeApiResult result = future.get(); if (result.isFailed()) { s_logger.debug("migrate volume failed:" + result.getResult()); throw new StorageUnavailableException("Migrate volume failed: " + result.getResult(), destPool.getId()); } return result.getVolume(); } catch (InterruptedException e) { s_logger.debug("migrate volume failed", e); throw new CloudRuntimeException(e.getMessage()); } catch (ExecutionException e) { s_logger.debug("migrate volume failed", e); throw new CloudRuntimeException(e.getMessage()); } } @Override @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_CREATE, eventDescription = "taking snapshot", async = true) public Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType) 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."); } StoragePoolVO storagePoolVO = _storagePoolDao.findById(volume.getPoolId()); if (storagePoolVO.isManaged() && locationType == null) { locationType = Snapshot.LocationType.PRIMARY; } VMInstanceVO vm = null; if (volume.getInstanceId() != null) vm = _vmInstanceDao.findById(volume.getInstanceId()); if (vm != null) { // serialize VM operation AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { // avoid re-entrance VmWorkJobVO placeHolder = null; placeHolder = createPlaceHolderWork(vm.getId()); try { return orchestrateTakeVolumeSnapshot(volumeId, policyId, snapshotId, account, quiescevm, locationType); } finally { _workJobDao.expunge(placeHolder.getId()); } } else { Outcome<Snapshot> outcome = takeVolumeSnapshotThroughJobQueue(vm.getId(), volumeId, policyId, snapshotId, account.getId(), quiescevm, locationType); try { outcome.get(); } catch (InterruptedException e) { throw new RuntimeException("Operation is interrupted", e); } catch (java.util.concurrent.ExecutionException e) { throw new RuntimeException("Execution excetion", e); } Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob()); if (jobResult != null) { if (jobResult instanceof ConcurrentOperationException) throw (ConcurrentOperationException)jobResult; else if (jobResult instanceof ResourceAllocationException) throw (ResourceAllocationException)jobResult; else if (jobResult instanceof Throwable) throw new RuntimeException("Unexpected exception", (Throwable)jobResult); } return _snapshotDao.findById(snapshotId); } } else { CreateSnapshotPayload payload = new CreateSnapshotPayload(); payload.setSnapshotId(snapshotId); payload.setSnapshotPolicyId(policyId); payload.setAccount(account); payload.setQuiescevm(quiescevm); volume.addPayload(payload); return volService.takeSnapshot(volume); } } private Snapshot orchestrateTakeVolumeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType) 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); payload.setQuiescevm(quiescevm); payload.setLocationType(locationType); volume.addPayload(payload); return volService.takeSnapshot(volume); } @Override @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_CREATE, eventDescription = "allocating snapshot", create = true) public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType) 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.getId())) { 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 (ImageFormat.DIR.equals(volume.getFormat())){ throw new InvalidParameterValueException("Snapshot not supported for volume:" + volumeId); } 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"); } } StoragePoolVO storagePoolVO = _storagePoolDao.findById(volume.getPoolId()); if (!storagePoolVO.isManaged() && locationType != null) { throw new InvalidParameterValueException("VolumeId: " + volumeId + " LocationType is supported only for managed storage"); } if (storagePoolVO.isManaged() && locationType == null) { locationType = Snapshot.LocationType.PRIMARY; } 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, snapshotName, locationType); } @Override public Snapshot allocSnapshotForVm(Long vmId, Long volumeId, String snapshotName) throws ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); VMInstanceVO vm = _vmInstanceDao.findById(vmId); if (vm == null) { throw new InvalidParameterValueException("Creating snapshot failed due to vm:" + vmId + " doesn't exist"); } _accountMgr.checkAccess(caller, null, true, vm); VolumeInfo volume = volFactory.getVolume(volumeId); if (volume == null) { throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist"); } _accountMgr.checkAccess(caller, null, true, volume); VirtualMachine attachVM = volume.getAttachedVM(); if (attachVM == null || attachVM.getId() != vm.getId()) { throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't attach to vm :" + vm); } 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.getId())) { 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, Snapshot.MANUAL_POLICY_ID, snapshotName, null); } @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.getId()) && ApiDBUtils.isExtractionDisabled()) { throw new PermissionDeniedException("Extraction has been disabled by admin"); } VolumeVO volume = _volsDao.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 && !_accountMgr.isRootAdmin(account.getId())) { // 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; } } } 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 "); } // Check if the url already exists VolumeDataStoreVO volumeStoreRef = _volumeStoreDao.findByVolume(volumeId); if (volumeStoreRef != null && volumeStoreRef.getExtractUrl() != null) { return volumeStoreRef.getExtractUrl(); } VMInstanceVO vm = null; if (volume.getInstanceId() != null) { vm = _vmInstanceDao.findById(volume.getInstanceId()); } if (vm != null) { // serialize VM operation AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { // avoid re-entrance VmWorkJobVO placeHolder = null; placeHolder = createPlaceHolderWork(vm.getId()); try { return orchestrateExtractVolume(volume.getId(), zoneId); } finally { _workJobDao.expunge(placeHolder.getId()); } } else { Outcome<String> outcome = extractVolumeThroughJobQueue(vm.getId(), volume.getId(), zoneId); try { outcome.get(); } catch (InterruptedException e) { throw new RuntimeException("Operation is interrupted", e); } catch (java.util.concurrent.ExecutionException e) { throw new RuntimeException("Execution excetion", e); } Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob()); if (jobResult != null) { if (jobResult instanceof ConcurrentOperationException) throw (ConcurrentOperationException)jobResult; else if (jobResult instanceof RuntimeException) throw (RuntimeException)jobResult; else if (jobResult instanceof Throwable) throw new RuntimeException("Unexpected exception", (Throwable)jobResult); } // retrieve the entity url from job result if (jobResult != null && jobResult instanceof String) { return (String)jobResult; } return null; } } return orchestrateExtractVolume(volume.getId(), zoneId); } private String orchestrateExtractVolume(long volumeId, long zoneId) { // get latest volume state to make sure that it is not updated by other parallel operations VolumeVO volume = _volsDao.findById(volumeId); if (volume == null || volume.getState() != Volume.State.Ready) { throw new InvalidParameterValueException("Volume to be extracted has been removed or not in right state!"); } // perform extraction ImageStoreEntity secStore = (ImageStoreEntity)dataStoreMgr.getImageStore(zoneId); String value = _configDao.getValue(Config.CopyVolumeWait.toString()); NumbersUtil.parseInt(value, Integer.parseInt(Config.CopyVolumeWait.getDefaultValue())); // Copy volume from primary to secondary storage VolumeInfo srcVol = volFactory.getVolume(volumeId); AsyncCallFuture<VolumeApiResult> 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); VolumeDataStoreVO volumeStoreRef = _volumeStoreDao.findByVolume(volumeId); volumeStoreRef.setExtractUrl(extractUrl); volumeStoreRef.setExtractUrlCreated(DateUtil.now()); _volumeStoreDao.update(volumeStoreRef.getId(), volumeStoreRef); return extractUrl; } @Override public boolean isDisplayResourceEnabled(Long id) { Volume volume = _volsDao.findById(id); if (volume == null) { return true; // bad id given, default to true } return volume.isDisplayVolume(); } 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.Hyperv) { return "vhdx"; } else if (cluster.getHypervisorType() == HypervisorType.VMware) { return "ova"; } else if (cluster.getHypervisorType() == HypervisorType.Ovm) { return "raw"; } else { return null; } } private boolean needMoveVolume(VolumeVO existingVolume, VolumeInfo newVolume) { if (existingVolume == null || existingVolume.getPoolId() == null || newVolume.getPoolId() == null) { return false; } DataStore storeForExistingVol = dataStoreMgr.getPrimaryDataStore(existingVolume.getPoolId()); DataStore storeForNewVol = dataStoreMgr.getPrimaryDataStore(newVolume.getPoolId()); Scope storeForExistingStoreScope = storeForExistingVol.getScope(); if (storeForExistingStoreScope == null) { throw new CloudRuntimeException("Can't get scope of data store: " + storeForExistingVol.getId()); } Scope storeForNewStoreScope = storeForNewVol.getScope(); if (storeForNewStoreScope == null) { throw new CloudRuntimeException("Can't get scope of data store: " + storeForNewVol.getId()); } if (storeForNewStoreScope.getScopeType() == ScopeType.ZONE) { return false; } if (storeForExistingStoreScope.getScopeType() != storeForNewStoreScope.getScopeType()) { if (storeForNewStoreScope.getScopeType() == ScopeType.CLUSTER) { Long vmClusterId = null; if (storeForExistingStoreScope.getScopeType() == ScopeType.HOST) { HostScope hs = (HostScope)storeForExistingStoreScope; vmClusterId = hs.getClusterId(); } else if (storeForExistingStoreScope.getScopeType() == ScopeType.ZONE) { Long hostId = _vmInstanceDao.findById(existingVolume.getInstanceId()).getHostId(); if (hostId != null) { HostVO host = _hostDao.findById(hostId); vmClusterId = host.getClusterId(); } } if (storeForNewStoreScope.getScopeId().equals(vmClusterId)) { return false; } else { return true; } } else if (storeForNewStoreScope.getScopeType() == ScopeType.HOST && (storeForExistingStoreScope.getScopeType() == ScopeType.CLUSTER || storeForExistingStoreScope.getScopeType() == ScopeType.ZONE)) { Long hostId = _vmInstanceDao.findById(existingVolume.getInstanceId()).getHostId(); if (storeForNewStoreScope.getScopeId().equals(hostId)) { return false; } } throw new InvalidParameterValueException("Can't move volume between scope: " + storeForNewStoreScope.getScopeType() + " and " + storeForExistingStoreScope.getScopeType()); } return !storeForExistingStoreScope.isSameScope(storeForNewStoreScope); } 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; } } HostVO host = null; StoragePoolVO volumeToAttachStoragePool = _storagePoolDao.findById(volumeToAttach.getPoolId()); if (hostId != null) { host = _hostDao.findById(hostId); if (host != null && host.getHypervisorType() == HypervisorType.XenServer && volumeToAttachStoragePool != null && volumeToAttachStoragePool.isManaged()) { sendCommand = true; } } // volumeToAttachStoragePool should be null if the VM we are attaching the disk to has never been started before DataStore dataStore = volumeToAttachStoragePool != null ? dataStoreMgr.getDataStore(volumeToAttachStoragePool.getId(), DataStoreRole.Primary) : null; // if we don't have a host, the VM we are attaching the disk to has never been started before if (host != null) { try { volService.grantAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore); } catch (Exception e) { volService.revokeAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore); throw new CloudRuntimeException(e.getMessage()); } } if (sendCommand) { if (host != null && host.getHypervisorType() == HypervisorType.KVM && volumeToAttachStoragePool.isManaged() && volumeToAttach.getPath() == null) { volumeToAttach.setPath(volumeToAttach.get_iScsiName()); _volsDao.update(volumeToAttach.getId(), volumeToAttach); } DataTO volTO = volFactory.getVolume(volumeToAttach.getId()).getTO(); deviceId = getDeviceId(vm, deviceId); DiskTO disk = storageMgr.getDiskWithThrottling(volTO, volumeToAttach.getVolumeType(), deviceId, volumeToAttach.getPath(), vm.getServiceOfferingId(), volumeToAttach.getDiskOfferingId()); AttachCommand cmd = new AttachCommand(disk, vm.getInstanceName()); ChapInfo chapInfo = volService.getChapInfo(volFactory.getVolume(volumeToAttach.getId()), dataStore); Map<String, String> details = new HashMap<String, String>(); disk.setDetails(details); details.put(DiskTO.MANAGED, String.valueOf(volumeToAttachStoragePool.isManaged())); details.put(DiskTO.STORAGE_HOST, volumeToAttachStoragePool.getHostAddress()); details.put(DiskTO.STORAGE_PORT, String.valueOf(volumeToAttachStoragePool.getPort())); details.put(DiskTO.VOLUME_SIZE, String.valueOf(volumeToAttach.getSize())); details.put(DiskTO.IQN, volumeToAttach.get_iScsiName()); details.put(DiskTO.MOUNT_POINT, volumeToAttach.get_iScsiName()); details.put(DiskTO.PROTOCOL_TYPE, (volumeToAttach.getPoolType() != null) ? volumeToAttach.getPoolType().toString() : null); if (chapInfo != null) { details.put(DiskTO.CHAP_INITIATOR_USERNAME, chapInfo.getInitiatorUsername()); details.put(DiskTO.CHAP_INITIATOR_SECRET, chapInfo.getInitiatorSecret()); details.put(DiskTO.CHAP_TARGET_USERNAME, chapInfo.getTargetUsername()); details.put(DiskTO.CHAP_TARGET_SECRET, chapInfo.getTargetSecret()); } _userVmDao.loadDetails(vm); Map<String, String> controllerInfo = new HashMap<String, String>(); controllerInfo.put(VmDetailConstants.ROOT_DISK_CONTROLLER, vm.getDetail(VmDetailConstants.ROOT_DISK_CONTROLLER)); controllerInfo.put(VmDetailConstants.DATA_DISK_CONTROLLER, vm.getDetail(VmDetailConstants.DATA_DISK_CONTROLLER)); cmd.setControllerInfo(controllerInfo); s_logger.debug("Attach volume id:" + volumeToAttach.getId() + " on VM id:" + vm.getId() + " has controller info:" + controllerInfo); try { answer = (AttachAnswer)_agentMgr.send(hostId, cmd); } catch (Exception e) { if(host!=null) { volService.revokeAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore); } 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().getPath()); _volsDao.update(volumeToAttach.getId(), volumeToAttach); } } else { deviceId = getDeviceId(vm, deviceId); _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; } } if(host!= null) { volService.revokeAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore); } 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.intValue() <= 0) { maxDataVolumesSupported = 6; // 6 data disks by default if nothing // is specified in // 'hypervisor_capabilities' table } return maxDataVolumesSupported.intValue(); } private Long getDeviceId(UserVmVO vm, Long deviceId) { // allocate deviceId int maxDevices = getMaxDataVolumesSupported(vm) + 2; // add 2 to consider devices root volume and cdrom int maxDeviceId = maxDevices - 1; List<VolumeVO> vols = _volsDao.findByInstance(vm.getId()); if (deviceId != null) { if (deviceId.longValue() < 0 || deviceId.longValue() > maxDeviceId || deviceId.longValue() == 3) { throw new RuntimeException("deviceId should be 0,1,2,4-" + maxDeviceId); } for (VolumeVO vol : vols) { if (vol.getDeviceId().equals(deviceId)) { throw new RuntimeException("deviceId " + deviceId + " is used by vm " + vm.getId()); } } } else { // allocate deviceId here List<String> devIds = new ArrayList<String>(); for (int i = 1; i <= maxDeviceId; i++) { devIds.add(String.valueOf(i)); } devIds.remove("3"); for (VolumeVO vol : vols) { devIds.remove(vol.getDeviceId().toString().trim()); } if (devIds.isEmpty()) { throw new RuntimeException("All device Ids are used by vm " + vm.getId()); } deviceId = Long.parseLong(devIds.iterator().next()); } return deviceId; } @Override public boolean configure(String name, Map<String, Object> params) { String maxVolumeSizeInGbString = _configDao.getValue(Config.MaxVolumeSize.toString()); _maxVolumeSizeInGb = NumbersUtil.parseLong(maxVolumeSizeInGbString, 2000); return true; } public List<StoragePoolAllocator> getStoragePoolAllocators() { return _storagePoolAllocators; } @Inject public void setStoragePoolAllocators(List<StoragePoolAllocator> storagePoolAllocators) { _storagePoolAllocators = storagePoolAllocators; } public class VmJobVolumeUrlOutcome extends OutcomeImpl<String> { public VmJobVolumeUrlOutcome(final AsyncJob job) { super(String.class, job, VmJobCheckInterval.value(), new Predicate() { @Override public boolean checkCondition() { AsyncJobVO jobVo = _entityMgr.findById(AsyncJobVO.class, job.getId()); assert (jobVo != null); if (jobVo == null || jobVo.getStatus() != JobInfo.Status.IN_PROGRESS) return true; return false; } }, AsyncJob.Topics.JOB_STATE); } } public class VmJobVolumeOutcome extends OutcomeImpl<Volume> { private long _volumeId; public VmJobVolumeOutcome(final AsyncJob job, final long volumeId) { super(Volume.class, job, VmJobCheckInterval.value(), new Predicate() { @Override public boolean checkCondition() { AsyncJobVO jobVo = _entityMgr.findById(AsyncJobVO.class, job.getId()); assert (jobVo != null); if (jobVo == null || jobVo.getStatus() != JobInfo.Status.IN_PROGRESS) return true; return false; } }, AsyncJob.Topics.JOB_STATE); _volumeId = volumeId; } @Override protected Volume retrieve() { return _volsDao.findById(_volumeId); } } public class VmJobSnapshotOutcome extends OutcomeImpl<Snapshot> { private long _snapshotId; public VmJobSnapshotOutcome(final AsyncJob job, final long snapshotId) { super(Snapshot.class, job, VmJobCheckInterval.value(), new Predicate() { @Override public boolean checkCondition() { AsyncJobVO jobVo = _entityMgr.findById(AsyncJobVO.class, job.getId()); assert (jobVo != null); if (jobVo == null || jobVo.getStatus() != JobInfo.Status.IN_PROGRESS) return true; return false; } }, AsyncJob.Topics.JOB_STATE); _snapshotId = snapshotId; } @Override protected Snapshot retrieve() { return _snapshotDao.findById(_snapshotId); } } public Outcome<Volume> attachVolumeToVmThroughJobQueue(final Long vmId, final Long volumeId, final Long deviceId) { final CallContext context = CallContext.current(); final User callingUser = context.getCallingUser(); final Account callingAccount = context.getCallingAccount(); final VMInstanceVO vm = _vmInstanceDao.findById(vmId); VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId()); workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER); workJob.setCmd(VmWorkAttachVolume.class.getName()); workJob.setAccountId(callingAccount.getId()); workJob.setUserId(callingUser.getId()); workJob.setStep(VmWorkJobVO.Step.Starting); workJob.setVmType(VirtualMachine.Type.Instance); workJob.setVmInstanceId(vm.getId()); workJob.setRelated(AsyncJobExecutionContext.getOriginJobId()); // save work context info (there are some duplications) VmWorkAttachVolume workInfo = new VmWorkAttachVolume(callingUser.getId(), callingAccount.getId(), vm.getId(), VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, deviceId); workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo)); _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId()); AsyncJobVO jobVo = _jobMgr.getAsyncJob(workJob.getId()); s_logger.debug("New job " + workJob.getId() + ", result field: " + jobVo.getResult()); AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); return new VmJobVolumeOutcome(workJob, volumeId); } public Outcome<Volume> detachVolumeFromVmThroughJobQueue(final Long vmId, final Long volumeId) { final CallContext context = CallContext.current(); final User callingUser = context.getCallingUser(); final Account callingAccount = context.getCallingAccount(); final VMInstanceVO vm = _vmInstanceDao.findById(vmId); VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId()); workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER); workJob.setCmd(VmWorkDetachVolume.class.getName()); workJob.setAccountId(callingAccount.getId()); workJob.setUserId(callingUser.getId()); workJob.setStep(VmWorkJobVO.Step.Starting); workJob.setVmType(VirtualMachine.Type.Instance); workJob.setVmInstanceId(vm.getId()); workJob.setRelated(AsyncJobExecutionContext.getOriginJobId()); // save work context info (there are some duplications) VmWorkDetachVolume workInfo = new VmWorkDetachVolume(callingUser.getId(), callingAccount.getId(), vm.getId(), VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId); workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo)); _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId()); AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); return new VmJobVolumeOutcome(workJob, volumeId); } public Outcome<Volume> resizeVolumeThroughJobQueue(final Long vmId, final long volumeId, final long currentSize, final long newSize, final Long newMinIops, final Long newMaxIops, final Integer newHypervisorSnapshotReserve, final Long newServiceOfferingId, final boolean shrinkOk) { final CallContext context = CallContext.current(); final User callingUser = context.getCallingUser(); final Account callingAccount = context.getCallingAccount(); final VMInstanceVO vm = _vmInstanceDao.findById(vmId); VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId()); workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER); workJob.setCmd(VmWorkResizeVolume.class.getName()); workJob.setAccountId(callingAccount.getId()); workJob.setUserId(callingUser.getId()); workJob.setStep(VmWorkJobVO.Step.Starting); workJob.setVmType(VirtualMachine.Type.Instance); workJob.setVmInstanceId(vm.getId()); workJob.setRelated(AsyncJobExecutionContext.getOriginJobId()); // save work context info (there are some duplications) VmWorkResizeVolume workInfo = new VmWorkResizeVolume(callingUser.getId(), callingAccount.getId(), vm.getId(), VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, newServiceOfferingId, shrinkOk); workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo)); _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId()); AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); return new VmJobVolumeOutcome(workJob,volumeId); } public Outcome<String> extractVolumeThroughJobQueue(final Long vmId, final long volumeId, final long zoneId) { final CallContext context = CallContext.current(); final User callingUser = context.getCallingUser(); final Account callingAccount = context.getCallingAccount(); final VMInstanceVO vm = _vmInstanceDao.findById(vmId); VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId()); workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER); workJob.setCmd(VmWorkExtractVolume.class.getName()); workJob.setAccountId(callingAccount.getId()); workJob.setUserId(callingUser.getId()); workJob.setStep(VmWorkJobVO.Step.Starting); workJob.setVmType(VirtualMachine.Type.Instance); workJob.setVmInstanceId(vm.getId()); workJob.setRelated(AsyncJobExecutionContext.getOriginJobId()); // save work context info (there are some duplications) VmWorkExtractVolume workInfo = new VmWorkExtractVolume(callingUser.getId(), callingAccount.getId(), vm.getId(), VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, zoneId); workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo)); _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId()); AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); return new VmJobVolumeUrlOutcome(workJob); } public Outcome<Volume> migrateVolumeThroughJobQueue(final Long vmId, final long volumeId, final long destPoolId, final boolean liveMigrate) { final CallContext context = CallContext.current(); final User callingUser = context.getCallingUser(); final Account callingAccount = context.getCallingAccount(); final VMInstanceVO vm = _vmInstanceDao.findById(vmId); VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId()); workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER); workJob.setCmd(VmWorkMigrateVolume.class.getName()); workJob.setAccountId(callingAccount.getId()); workJob.setUserId(callingUser.getId()); workJob.setStep(VmWorkJobVO.Step.Starting); workJob.setVmType(VirtualMachine.Type.Instance); workJob.setVmInstanceId(vm.getId()); workJob.setRelated(AsyncJobExecutionContext.getOriginJobId()); // save work context info (there are some duplications) VmWorkMigrateVolume workInfo = new VmWorkMigrateVolume(callingUser.getId(), callingAccount.getId(), vm.getId(), VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, destPoolId, liveMigrate); workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo)); _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId()); AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); return new VmJobVolumeOutcome(workJob,volumeId); } public Outcome<Snapshot> takeVolumeSnapshotThroughJobQueue(final Long vmId, final Long volumeId, final Long policyId, final Long snapshotId, final Long accountId, final boolean quiesceVm, final Snapshot.LocationType locationType) { final CallContext context = CallContext.current(); final User callingUser = context.getCallingUser(); final Account callingAccount = context.getCallingAccount(); final VMInstanceVO vm = _vmInstanceDao.findById(vmId); VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId()); workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER); workJob.setCmd(VmWorkTakeVolumeSnapshot.class.getName()); workJob.setAccountId(callingAccount.getId()); workJob.setUserId(callingUser.getId()); workJob.setStep(VmWorkJobVO.Step.Starting); workJob.setVmType(VirtualMachine.Type.Instance); workJob.setVmInstanceId(vm.getId()); workJob.setRelated(AsyncJobExecutionContext.getOriginJobId()); // save work context info (there are some duplications) VmWorkTakeVolumeSnapshot workInfo = new VmWorkTakeVolumeSnapshot( callingUser.getId(), accountId != null ? accountId : callingAccount.getId(), vm.getId(), VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, policyId, snapshotId, quiesceVm, locationType); workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo)); _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId()); AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); return new VmJobSnapshotOutcome(workJob,snapshotId); } @ReflectionUse private Pair<JobInfo.Status, String> orchestrateExtractVolume(VmWorkExtractVolume work) throws Exception { String volUrl = orchestrateExtractVolume(work.getVolumeId(), work.getZoneId()); return new Pair<JobInfo.Status, String>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(volUrl)); } @ReflectionUse private Pair<JobInfo.Status, String> orchestrateAttachVolumeToVM(VmWorkAttachVolume work) throws Exception { Volume vol = orchestrateAttachVolumeToVM(work.getVmId(), work.getVolumeId(), work.getDeviceId()); return new Pair<JobInfo.Status, String>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(new Long(vol.getId()))); } @ReflectionUse private Pair<JobInfo.Status, String> orchestrateDetachVolumeFromVM(VmWorkDetachVolume work) throws Exception { Volume vol = orchestrateDetachVolumeFromVM(work.getVmId(), work.getVolumeId()); return new Pair<JobInfo.Status, String>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(new Long(vol.getId()))); } @ReflectionUse private Pair<JobInfo.Status, String> orchestrateResizeVolume(VmWorkResizeVolume work) throws Exception { Volume vol = orchestrateResizeVolume(work.getVolumeId(), work.getCurrentSize(), work.getNewSize(), work.getNewMinIops(), work.getNewMaxIops(), work.getNewHypervisorSnapshotReserve(), work.getNewServiceOfferingId(), work.isShrinkOk()); return new Pair<JobInfo.Status, String>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(new Long(vol.getId()))); } @ReflectionUse private Pair<JobInfo.Status, String> orchestrateMigrateVolume(VmWorkMigrateVolume work) throws Exception { Volume newVol = orchestrateMigrateVolume(work.getVolumeId(), work.getDestPoolId(), work.isLiveMigrate()); return new Pair<JobInfo.Status, String>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(new Long(newVol.getId()))); } @ReflectionUse private Pair<JobInfo.Status, String> orchestrateTakeVolumeSnapshot(VmWorkTakeVolumeSnapshot work) throws Exception { Account account = _accountDao.findById(work.getAccountId()); orchestrateTakeVolumeSnapshot(work.getVolumeId(), work.getPolicyId(), work.getSnapshotId(), account, work.isQuiesceVm(), work.getLocationType()); return new Pair<JobInfo.Status, String>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(work.getSnapshotId())); } @Override public Pair<JobInfo.Status, String> handleVmWorkJob(VmWork work) throws Exception { return _jobHandlerProxy.handleVmWorkJob(work); } private VmWorkJobVO createPlaceHolderWork(long instanceId) { VmWorkJobVO workJob = new VmWorkJobVO(""); workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_PLACEHOLDER); workJob.setCmd(""); workJob.setCmdInfo(""); workJob.setAccountId(0); workJob.setUserId(0); workJob.setStep(VmWorkJobVO.Step.Starting); workJob.setVmType(VirtualMachine.Type.Instance); workJob.setVmInstanceId(instanceId); workJob.setInitMsid(ManagementServerNode.getManagementServerId()); _workJobDao.persist(workJob); return workJob; } }