| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| package org.apache.cloudstack.storage.datastore.driver; |
| |
| import java.text.NumberFormat; |
| import java.util.Arrays; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.inject.Inject; |
| |
| import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; |
| import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; |
| import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; |
| import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; |
| import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; |
| import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; |
| import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; |
| import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; |
| import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; |
| import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; |
| import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; |
| import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; |
| import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; |
| import org.apache.cloudstack.framework.async.AsyncCompletionCallback; |
| import org.apache.cloudstack.storage.command.CommandResult; |
| import org.apache.cloudstack.storage.command.CreateObjectAnswer; |
| import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; |
| import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; |
| import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; |
| import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; |
| import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; |
| import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; |
| import org.apache.cloudstack.storage.datastore.util.SolidFireUtil; |
| import org.apache.cloudstack.storage.to.SnapshotObjectTO; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.logging.log4j.LogManager; |
| |
| import com.cloud.agent.api.Answer; |
| import com.cloud.agent.api.to.DataObjectType; |
| import com.cloud.agent.api.to.DataStoreTO; |
| import com.cloud.agent.api.to.DataTO; |
| import com.cloud.agent.api.to.DiskTO; |
| import com.cloud.dc.ClusterVO; |
| import com.cloud.dc.dao.ClusterDao; |
| import com.cloud.host.Host; |
| import com.cloud.host.HostVO; |
| import com.cloud.host.dao.HostDao; |
| import com.cloud.hypervisor.Hypervisor; |
| import com.cloud.storage.DataStoreRole; |
| import com.cloud.storage.ResizeVolumePayload; |
| import com.cloud.storage.Snapshot.State; |
| import com.cloud.storage.SnapshotVO; |
| import com.cloud.storage.Storage.StoragePoolType; |
| import com.cloud.storage.StoragePool; |
| import com.cloud.storage.VMTemplateStoragePoolVO; |
| import com.cloud.storage.Volume; |
| import com.cloud.storage.VolumeDetailVO; |
| import com.cloud.storage.VolumeVO; |
| import com.cloud.storage.dao.SnapshotDao; |
| import com.cloud.storage.dao.SnapshotDetailsDao; |
| import com.cloud.storage.dao.SnapshotDetailsVO; |
| import com.cloud.storage.dao.VMTemplatePoolDao; |
| import com.cloud.storage.dao.VolumeDao; |
| import com.cloud.storage.dao.VolumeDetailsDao; |
| import com.cloud.user.AccountDetailVO; |
| import com.cloud.user.AccountDetailsDao; |
| import com.cloud.user.AccountVO; |
| import com.cloud.user.dao.AccountDao; |
| import com.cloud.utils.Pair; |
| import com.cloud.utils.db.GlobalLock; |
| import com.cloud.utils.exception.CloudRuntimeException; |
| import com.cloud.vm.dao.VMInstanceDao; |
| import com.cloud.vm.VirtualMachine; |
| import com.google.common.base.Preconditions; |
| |
| public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { |
| protected Logger logger = LogManager.getLogger(getClass()); |
| private static final int LOWEST_HYPERVISOR_SNAPSHOT_RESERVE = 10; |
| private static final long MIN_IOPS_FOR_TEMPLATE_VOLUME = 100L; |
| private static final long MAX_IOPS_FOR_TEMPLATE_VOLUME = 20000L; |
| private static final long MIN_IOPS_FOR_TEMP_VOLUME = 100L; |
| private static final long MAX_IOPS_FOR_TEMP_VOLUME = 20000L; |
| private static final long MAX_IOPS_FOR_MIGRATING_VOLUME = 20000L; |
| private static final long MIN_IOPS_FOR_SNAPSHOT_VOLUME = 100L; |
| private static final long MAX_IOPS_FOR_SNAPSHOT_VOLUME = 20000L; |
| |
| private static final String BASIC_SF_ID = "basicSfId"; |
| |
| @Inject private AccountDao accountDao; |
| @Inject private AccountDetailsDao accountDetailsDao; |
| @Inject private ClusterDao clusterDao; |
| @Inject private DataStoreManager dataStoreMgr; |
| @Inject private HostDao hostDao; |
| @Inject private SnapshotDao snapshotDao; |
| @Inject private SnapshotDetailsDao snapshotDetailsDao; |
| @Inject private SnapshotDataStoreDao snapshotDataStoreDao; |
| @Inject private PrimaryDataStoreDao storagePoolDao; |
| @Inject private StoragePoolDetailsDao storagePoolDetailsDao; |
| @Inject private VMTemplatePoolDao vmTemplatePoolDao; |
| @Inject private VMInstanceDao vmDao; |
| @Inject private VolumeDao volumeDao; |
| @Inject private VolumeDetailsDao volumeDetailsDao; |
| @Inject private VolumeDataFactory volumeFactory; |
| |
| @Override |
| public Map<String, String> getCapabilities() { |
| Map<String, String> mapCapabilities = new HashMap<>(); |
| |
| mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString()); |
| mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString()); |
| mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString()); |
| mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.TRUE.toString()); |
| |
| return mapCapabilities; |
| } |
| |
| @Override |
| public DataTO getTO(DataObject data) { |
| return null; |
| } |
| |
| @Override |
| public DataStoreTO getStoreTO(DataStore store) { |
| return null; |
| } |
| |
| private SolidFireUtil.SolidFireAccount createSolidFireAccount(SolidFireUtil.SolidFireConnection sfConnection, String sfAccountName) { |
| long accountNumber = SolidFireUtil.createAccount(sfConnection, sfAccountName); |
| |
| return SolidFireUtil.getAccountById(sfConnection, accountNumber); |
| } |
| |
| @Override |
| public ChapInfo getChapInfo(DataObject dataObject) { |
| return null; |
| } |
| |
| @Override |
| public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore) { |
| Preconditions.checkArgument(dataObject != null, "'dataObject' should not be 'null'"); |
| Preconditions.checkArgument(host != null, "'host' should not be 'null'"); |
| Preconditions.checkArgument(dataStore != null, "'dataStore' should not be 'null'"); |
| |
| long sfVolumeId = getSolidFireVolumeId(dataObject, true); |
| long clusterId = host.getClusterId(); |
| long storagePoolId = dataStore.getId(); |
| |
| ClusterVO cluster = clusterDao.findById(clusterId); |
| |
| GlobalLock lock = GlobalLock.getInternLock(cluster.getUuid()); |
| |
| if (!lock.lock(SolidFireUtil.LOCK_TIME_IN_SECONDS)) { |
| String errMsg = "Couldn't lock the DB (in grantAccess) on the following string: " + cluster.getUuid(); |
| |
| logger.warn(errMsg); |
| |
| throw new CloudRuntimeException(errMsg); |
| } |
| |
| try { |
| List<HostVO> hosts = hostDao.findByClusterId(clusterId); |
| |
| String clusterUuId = clusterDao.findById(clusterId).getUuid(); |
| |
| SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao); |
| |
| SolidFireUtil.placeVolumeInVolumeAccessGroups(sfConnection, sfVolumeId, hosts, clusterUuId); |
| |
| return true; |
| } |
| finally { |
| lock.unlock(); |
| lock.releaseRef(); |
| } |
| } |
| |
| private boolean isRevokeAccessNotNeeded(DataObject dataObject) { |
| // Workaround: don't unplug iscsi lun when volume is attached to a VM |
| // This is regression workaround from upper layers which are calling |
| // a releaseVmResources() method that calls the revoke on an attached disk |
| if (dataObject.getType() == DataObjectType.VOLUME) { |
| Volume volume = volumeDao.findById(dataObject.getId()); |
| if (volume.getInstanceId() != null) { |
| VirtualMachine vm = vmDao.findById(volume.getInstanceId()); |
| if (vm != null && !Arrays.asList(VirtualMachine.State.Destroyed, VirtualMachine.State.Expunging, VirtualMachine.State.Error).contains(vm.getState())) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) { |
| if (dataObject == null || host == null || dataStore == null) { |
| return; |
| } |
| |
| if (isRevokeAccessNotNeeded(dataObject)) { |
| logger.debug("Skipping revoke access for Solidfire data object type:" + dataObject.getType() + " id:" + dataObject.getId()); |
| return; |
| } |
| |
| long sfVolumeId = getSolidFireVolumeId(dataObject, false); |
| long clusterId = host.getClusterId(); |
| long storagePoolId = dataStore.getId(); |
| |
| ClusterVO cluster = clusterDao.findById(clusterId); |
| |
| GlobalLock lock = GlobalLock.getInternLock(cluster.getUuid()); |
| |
| if (!lock.lock(SolidFireUtil.LOCK_TIME_IN_SECONDS)) { |
| String errMsg = "Couldn't lock the DB (in revokeAccess) on the following string: " + cluster.getUuid(); |
| |
| logger.warn(errMsg); |
| |
| throw new CloudRuntimeException(errMsg); |
| } |
| |
| logger.debug("Revoking access for Solidfire data object type:" + dataObject.getType() + " id:" + dataObject.getId()); |
| |
| try { |
| SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao); |
| |
| List<SolidFireUtil.SolidFireVag> sfVags = SolidFireUtil.getAllVags(sfConnection); |
| |
| for (SolidFireUtil.SolidFireVag sfVag : sfVags) { |
| if (SolidFireUtil.sfVagContains(sfVag, sfVolumeId, clusterId, hostDao)) { |
| SolidFireUtil.removeVolumeIdsFromSolidFireVag(sfConnection, sfVag.getId(), new Long[] { sfVolumeId }); |
| } |
| } |
| } |
| finally { |
| lock.unlock(); |
| lock.releaseRef(); |
| } |
| } |
| |
| private long getSolidFireVolumeId(DataObject dataObject, boolean grantAccess) { |
| if (dataObject.getType() == DataObjectType.VOLUME) { |
| final VolumeInfo volumeInfo = (VolumeInfo)dataObject; |
| final long volumeId = volumeInfo.getId(); |
| |
| if (grantAccess && isBasicGrantAccess(volumeId)) { |
| volumeDetailsDao.removeDetail(volumeInfo.getId(), BASIC_GRANT_ACCESS); |
| |
| final Long sfVolumeId = getBasicSfVolumeId(volumeId); |
| |
| Preconditions.checkNotNull(sfVolumeId, "'sfVolumeId' should not be 'null' (basic grant access)."); |
| |
| return sfVolumeId; |
| } |
| else if (!grantAccess && isBasicRevokeAccess(volumeId)) { |
| volumeDetailsDao.removeDetail(volumeInfo.getId(), BASIC_REVOKE_ACCESS); |
| |
| final Long sfVolumeId = getBasicSfVolumeId(volumeId); |
| |
| Preconditions.checkNotNull(sfVolumeId, "'sfVolumeId' should not be 'null' (basic revoke access)."); |
| |
| return sfVolumeId; |
| } |
| |
| return Long.parseLong(volumeInfo.getFolder()); |
| } |
| |
| if (dataObject.getType() == DataObjectType.SNAPSHOT) { |
| SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(dataObject.getId(), SolidFireUtil.VOLUME_ID); |
| |
| if (snapshotDetails == null || snapshotDetails.getValue() == null) { |
| throw new CloudRuntimeException("Unable to locate the volume ID associated with the following snapshot ID: " + dataObject.getId()); |
| } |
| |
| return Long.parseLong(snapshotDetails.getValue()); |
| } |
| |
| if (dataObject.getType() == DataObjectType.TEMPLATE) { |
| return getVolumeIdFrom_iScsiPath(((TemplateInfo)dataObject).getInstallPath()); |
| } |
| |
| throw new CloudRuntimeException("Invalid DataObjectType (" + dataObject.getType() + ") passed to getSolidFireVolumeId(DataObject, boolean)"); |
| } |
| |
| private long getVolumeIdFrom_iScsiPath(String iScsiPath) { |
| String[] splits = iScsiPath.split("/"); |
| String iqn = splits[1]; |
| |
| String sequenceToSearchFor = "."; |
| int lastIndexOf = iqn.lastIndexOf(sequenceToSearchFor); |
| String volumeIdAsString = iqn.substring(lastIndexOf + sequenceToSearchFor.length()); |
| |
| return Long.parseLong(volumeIdAsString); |
| } |
| |
| private long getDefaultMinIops(long storagePoolId) { |
| StoragePoolDetailVO storagePoolDetail = storagePoolDetailsDao.findDetail(storagePoolId, SolidFireUtil.CLUSTER_DEFAULT_MIN_IOPS); |
| |
| String clusterDefaultMinIops = storagePoolDetail.getValue(); |
| |
| return Long.parseLong(clusterDefaultMinIops); |
| } |
| |
| private long getDefaultMaxIops(long storagePoolId) { |
| StoragePoolDetailVO storagePoolDetail = storagePoolDetailsDao.findDetail(storagePoolId, SolidFireUtil.CLUSTER_DEFAULT_MAX_IOPS); |
| |
| String clusterDefaultMaxIops = storagePoolDetail.getValue(); |
| |
| return Long.parseLong(clusterDefaultMaxIops); |
| } |
| |
| private long getDefaultBurstIops(long storagePoolId, long maxIops) { |
| StoragePoolDetailVO storagePoolDetail = storagePoolDetailsDao.findDetail(storagePoolId, SolidFireUtil.CLUSTER_DEFAULT_BURST_IOPS_PERCENT_OF_MAX_IOPS); |
| |
| String clusterDefaultBurstIopsPercentOfMaxIops = storagePoolDetail.getValue(); |
| |
| float fClusterDefaultBurstIopsPercentOfMaxIops = Float.parseFloat(clusterDefaultBurstIopsPercentOfMaxIops); |
| |
| return Math.min((long)(maxIops * fClusterDefaultBurstIopsPercentOfMaxIops), SolidFireUtil.MAX_IOPS_PER_VOLUME); |
| } |
| |
| private SolidFireUtil.SolidFireVolume createSolidFireVolume(SolidFireUtil.SolidFireConnection sfConnection, |
| DataObject dataObject, long storagePoolId, long sfAccountId) { |
| final Long minIops; |
| final Long maxIops; |
| final Long volumeSize; |
| final String volumeName; |
| |
| final Map<String, String> mapAttributes; |
| |
| if (dataObject.getType() == DataObjectType.VOLUME) { |
| VolumeInfo volumeInfo = (VolumeInfo)dataObject; |
| |
| minIops = volumeInfo.getMinIops(); |
| maxIops = volumeInfo.getMaxIops(); |
| volumeSize = getDataObjectSizeIncludingHypervisorSnapshotReserve(volumeInfo, storagePoolDao.findById(storagePoolId)); |
| volumeName = volumeInfo.getName(); |
| |
| mapAttributes = getVolumeAttributes(volumeInfo); |
| } else if (dataObject.getType() == DataObjectType.TEMPLATE) { |
| TemplateInfo templateInfo = (TemplateInfo)dataObject; |
| |
| minIops = MIN_IOPS_FOR_TEMPLATE_VOLUME; |
| maxIops = MAX_IOPS_FOR_TEMPLATE_VOLUME; |
| volumeSize = getDataObjectSizeIncludingHypervisorSnapshotReserve(templateInfo, storagePoolDao.findById(storagePoolId)); |
| volumeName = templateInfo.getUniqueName(); |
| |
| mapAttributes = getTemplateAttributes(templateInfo); |
| } |
| else { |
| throw new CloudRuntimeException("Invalid type passed to createSolidFireVolume: " + dataObject.getType()); |
| } |
| |
| final Iops iops = getIops(minIops, maxIops, storagePoolId); |
| |
| long sfVolumeId = SolidFireUtil.createVolume(sfConnection, SolidFireUtil.getSolidFireVolumeName(volumeName), sfAccountId, |
| volumeSize, true, mapAttributes, iops.getMinIops(), iops.getMaxIops(), iops.getBurstIops()); |
| |
| return SolidFireUtil.getVolume(sfConnection, sfVolumeId); |
| } |
| |
| private Iops getIops(Long minIops, Long maxIops, long storagePoolId) { |
| if (minIops == null || minIops <= 0 || maxIops == null || maxIops <= 0) { |
| long defaultMaxIops = getDefaultMaxIops(storagePoolId); |
| |
| return new Iops(getDefaultMinIops(storagePoolId), defaultMaxIops, getDefaultBurstIops(storagePoolId, defaultMaxIops)); |
| } |
| |
| return new Iops(minIops, maxIops, getDefaultBurstIops(storagePoolId, maxIops)); |
| } |
| |
| @Override |
| public long getUsedBytes(StoragePool storagePool) { |
| return getUsedBytes(storagePool, Long.MIN_VALUE); |
| } |
| |
| private long getUsedBytes(StoragePool storagePool, long volumeIdToIgnore) { |
| long usedSpace = 0; |
| |
| List<VolumeVO> lstVolumes = volumeDao.findByPoolIdAndState(storagePool.getId(), Volume.State.Ready); |
| |
| if (lstVolumes != null) { |
| for (VolumeVO volume : lstVolumes) { |
| if (volume.getId() == volumeIdToIgnore) { |
| continue; |
| } |
| |
| usedSpace += getVolumeSizeIncludingHypervisorSnapshotReserve(volume.getSize(),volume.getHypervisorSnapshotReserve()); |
| |
| } |
| } |
| |
| List<SnapshotDataStoreVO> snapshotDataStoreVOList = snapshotDataStoreDao.listByStoreIdAndState(storagePool.getId(), ObjectInDataStoreStateMachine.State.Ready); |
| |
| if (snapshotDataStoreVOList != null) { |
| for (SnapshotDataStoreVO snapshot : snapshotDataStoreVOList) { |
| |
| usedSpace += snapshot.getPhysicalSize(); |
| |
| } |
| } |
| |
| List<VMTemplateStoragePoolVO> vmTemplateStoragePoolVOList = vmTemplatePoolDao.listByPoolIdAndState(storagePool.getId(), ObjectInDataStoreStateMachine.State.Ready); |
| |
| if (vmTemplateStoragePoolVOList != null) { |
| for (VMTemplateStoragePoolVO template : vmTemplateStoragePoolVOList) { |
| usedSpace += template.getTemplateSize(); |
| } |
| } |
| |
| return usedSpace; |
| } |
| |
| @Override |
| public long getUsedIops(StoragePool storagePool) { |
| long usedIops = 0; |
| |
| List<VolumeVO> volumes = volumeDao.findByPoolId(storagePool.getId(), null); |
| |
| if (volumes != null) { |
| for (VolumeVO volume : volumes) { |
| if (!Volume.State.Creating.equals(volume.getState())) { |
| usedIops += volume.getMinIops() != null ? volume.getMinIops() : 0; |
| } |
| } |
| } |
| |
| return usedIops; |
| } |
| |
| @Override |
| public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool) { |
| long volumeSize = 0; |
| |
| if (dataObject.getType() == DataObjectType.VOLUME) { |
| VolumeInfo volume = (VolumeInfo)dataObject; |
| |
| volumeSize = getVolumeSizeIncludingHypervisorSnapshotReserve(volume.getSize(), volume.getHypervisorSnapshotReserve()); |
| } else if (dataObject.getType() == DataObjectType.TEMPLATE) { |
| TemplateInfo templateInfo = (TemplateInfo)dataObject; |
| |
| // TemplateInfo sometimes has a size equal to null. |
| long templateSize = templateInfo.getSize() != null ? templateInfo.getSize() : 0; |
| |
| if (templateInfo.getHypervisorType() == Hypervisor.HypervisorType.KVM) { |
| volumeSize = templateSize; |
| } |
| else { |
| volumeSize = (long)(templateSize + templateSize * (LOWEST_HYPERVISOR_SNAPSHOT_RESERVE / 100f)); |
| } |
| } |
| |
| return Math.max(volumeSize, SolidFireUtil.MIN_VOLUME_SIZE); |
| } |
| |
| private long getVolumeSizeIncludingHypervisorSnapshotReserve(long volumeSize, Integer hypervisorSnapshotReserve) { |
| if (hypervisorSnapshotReserve != null) { |
| hypervisorSnapshotReserve = Math.max(hypervisorSnapshotReserve, LOWEST_HYPERVISOR_SNAPSHOT_RESERVE); |
| |
| volumeSize += volumeSize * (hypervisorSnapshotReserve / 100f); |
| } |
| |
| return Math.max(volumeSize, SolidFireUtil.MIN_VOLUME_SIZE); |
| } |
| |
| /** |
| * This method is only relevant when storagePool is being used with a compute cluster that supports UUID resigning. |
| */ |
| @Override |
| public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool) { |
| List<VMTemplateStoragePoolVO> lstTemplatePoolRefs = vmTemplatePoolDao.listByPoolId(storagePool.getId()); |
| |
| if (lstTemplatePoolRefs != null) { |
| for (VMTemplateStoragePoolVO templatePoolRef : lstTemplatePoolRefs) { |
| if (templatePoolRef.getTemplateId() == templateInfo.getId()) { |
| // This indicates that we already have this template stored on this primary storage, so |
| // we do not require additional space. |
| return 0; |
| } |
| } |
| } |
| |
| // This indicates that we do not have a copy of this template on this primary storage, so |
| // we need to take it into consideration from a space standpoint (ex. when a new VM is spun |
| // up and wants to use this particular template for its root disk). |
| return getDataObjectSizeIncludingHypervisorSnapshotReserve(templateInfo, storagePool); |
| } |
| |
| private static class Iops { |
| private final long _minIops; |
| private final long _maxIops; |
| private final long _burstIops; |
| |
| Iops(long minIops, long maxIops, long burstIops) throws IllegalArgumentException { |
| if (minIops <= 0 || maxIops <= 0) { |
| throw new IllegalArgumentException("The 'Min IOPS' and 'Max IOPS' values must be greater than 0."); |
| } |
| |
| if (minIops > maxIops) { |
| throw new IllegalArgumentException("The 'Min IOPS' value cannot exceed the 'Max IOPS' value."); |
| } |
| |
| if (maxIops > burstIops) { |
| throw new IllegalArgumentException("The 'Max IOPS' value cannot exceed the 'Burst IOPS' value."); |
| } |
| |
| _minIops = minIops; |
| _maxIops = maxIops; |
| _burstIops = burstIops; |
| } |
| |
| long getMinIops() { |
| return _minIops; |
| } |
| |
| long getMaxIops() { |
| return _maxIops; |
| } |
| |
| long getBurstIops() { |
| return _burstIops; |
| } |
| } |
| |
| private void deleteSolidFireVolume(SolidFireUtil.SolidFireConnection sfConnection, VolumeInfo volumeInfo) |
| { |
| Long storagePoolId = volumeInfo.getPoolId(); |
| |
| if (storagePoolId == null) { |
| return; // this volume was never assigned to a storage pool, so no SAN volume should exist for it |
| } |
| |
| long sfVolumeId = Long.parseLong(volumeInfo.getFolder()); |
| |
| deleteSolidFireVolume(sfConnection, volumeInfo.getId(), sfVolumeId); |
| } |
| |
| @Override |
| public void createAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CreateCmdResult> callback) { |
| String iqn = null; |
| String errMsg = null; |
| |
| try { |
| if (dataObject.getType() == DataObjectType.VOLUME) { |
| iqn = createVolume((VolumeInfo)dataObject, dataStore.getId()); |
| } else if (dataObject.getType() == DataObjectType.SNAPSHOT) { |
| createTempVolume((SnapshotInfo)dataObject, dataStore.getId()); |
| } else if (dataObject.getType() == DataObjectType.TEMPLATE) { |
| iqn = createTemplateVolume((TemplateInfo)dataObject, dataStore.getId()); |
| } else { |
| errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to createAsync"; |
| |
| logger.error(errMsg); |
| } |
| } |
| catch (Exception ex) { |
| errMsg = ex.getMessage(); |
| |
| logger.error(errMsg); |
| |
| if (callback == null) { |
| throw ex; |
| } |
| } |
| |
| if (callback != null) { |
| // path = iqn |
| // size is pulled from DataObject instance, if errMsg is null |
| CreateCmdResult result = new CreateCmdResult(iqn, new Answer(null, errMsg == null, errMsg)); |
| |
| result.setResult(errMsg); |
| |
| callback.complete(result); |
| } |
| else { |
| if (errMsg != null) { |
| throw new CloudRuntimeException(errMsg); |
| } |
| } |
| } |
| |
| private long getCreateSolidFireAccountId(SolidFireUtil.SolidFireConnection sfConnection, long csAccountId, long storagePoolId) { |
| AccountDetailVO accountDetail = SolidFireUtil.getAccountDetail(csAccountId, storagePoolId, accountDetailsDao); |
| |
| if (accountDetail == null || accountDetail.getValue() == null) { |
| AccountVO account = accountDao.findById(csAccountId); |
| String sfAccountName = SolidFireUtil.getSolidFireAccountName(account.getUuid(), account.getAccountId()); |
| SolidFireUtil.SolidFireAccount sfAccount = SolidFireUtil.getAccount(sfConnection, sfAccountName); |
| |
| if (sfAccount == null) { |
| sfAccount = createSolidFireAccount(sfConnection, sfAccountName); |
| } |
| |
| SolidFireUtil.updateCsDbWithSolidFireAccountInfo(account.getId(), sfAccount, storagePoolId, accountDetailsDao); |
| |
| accountDetail = SolidFireUtil.getAccountDetail(csAccountId, storagePoolId, accountDetailsDao); |
| } |
| |
| return Long.parseLong(accountDetail.getValue()); |
| } |
| |
| private void handleSnapshotDetails(long csSnapshotId, String name, String value) { |
| snapshotDetailsDao.removeDetail(csSnapshotId, name); |
| |
| SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false); |
| |
| snapshotDetailsDao.persist(snapshotDetails); |
| } |
| |
| private void addTempVolumeId(long csSnapshotId, String tempVolumeId) { |
| SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, SolidFireUtil.VOLUME_ID); |
| |
| if (snapshotDetails == null || snapshotDetails.getValue() == null) { |
| throw new CloudRuntimeException("'addTempVolumeId' should not be invoked unless " + SolidFireUtil.VOLUME_ID + " exists."); |
| } |
| |
| String originalVolumeId = snapshotDetails.getValue(); |
| |
| handleSnapshotDetails(csSnapshotId, SolidFireUtil.TEMP_VOLUME_ID, originalVolumeId); |
| handleSnapshotDetails(csSnapshotId, SolidFireUtil.VOLUME_ID, tempVolumeId); |
| } |
| |
| private void removeTempVolumeId(long csSnapshotId) { |
| SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, SolidFireUtil.TEMP_VOLUME_ID); |
| |
| if (snapshotDetails == null || snapshotDetails.getValue() == null) { |
| throw new CloudRuntimeException("'removeTempVolumeId' should not be invoked unless " + SolidFireUtil.TEMP_VOLUME_ID + " exists."); |
| } |
| |
| String originalVolumeId = snapshotDetails.getValue(); |
| |
| handleSnapshotDetails(csSnapshotId, SolidFireUtil.VOLUME_ID, originalVolumeId); |
| |
| snapshotDetailsDao.remove(snapshotDetails.getId()); |
| } |
| |
| private Long getBasicSfVolumeId(long volumeId) { |
| VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, BASIC_SF_ID); |
| |
| if (volumeDetail != null && volumeDetail.getValue() != null) { |
| return new Long(volumeDetail.getValue()); |
| } |
| |
| return null; |
| } |
| |
| private String getBasicIqn(long volumeId) { |
| VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, BASIC_IQN); |
| |
| if (volumeDetail != null && volumeDetail.getValue() != null) { |
| return volumeDetail.getValue(); |
| } |
| |
| return null; |
| } |
| |
| // If isBasicCreate returns true, this means the calling code simply wants us to create a SolidFire volume with specified |
| // characteristics. We do not update the cloud.volumes table with this info. |
| private boolean isBasicCreate(long volumeId) { |
| return getBooleanValueFromVolumeDetails(volumeId, BASIC_CREATE); |
| } |
| |
| private boolean isBasicDelete(long volumeId) { |
| return getBooleanValueFromVolumeDetails(volumeId, BASIC_DELETE); |
| } |
| |
| private boolean isBasicDeleteFailure(long volumeId) { |
| return getBooleanValueFromVolumeDetails(volumeId, BASIC_DELETE_FAILURE); |
| } |
| |
| private boolean isBasicDeleteByFolder(long volumeId) { |
| return getBooleanValueFromVolumeDetails(volumeId, PrimaryDataStoreDriver.BASIC_DELETE_BY_FOLDER); |
| } |
| |
| private boolean isBasicGrantAccess(long volumeId) { |
| return getBooleanValueFromVolumeDetails(volumeId, BASIC_GRANT_ACCESS); |
| } |
| |
| private boolean isBasicRevokeAccess(long volumeId) { |
| return getBooleanValueFromVolumeDetails(volumeId, BASIC_REVOKE_ACCESS); |
| } |
| |
| private boolean getBooleanValueFromVolumeDetails(long volumeId, String name) { |
| VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, name); |
| |
| return volumeDetail != null && volumeDetail.getValue() != null && Boolean.parseBoolean(volumeDetail.getValue()); |
| } |
| |
| private long getCsIdForCloning(long volumeId, String cloneOf) { |
| VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, cloneOf); |
| |
| if (volumeDetail != null && volumeDetail.getValue() != null) { |
| return new Long(volumeDetail.getValue()); |
| } |
| |
| return Long.MIN_VALUE; |
| } |
| |
| private boolean shouldTakeSnapshot(long snapshotId) { |
| SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(snapshotId, "takeSnapshot"); |
| |
| return snapshotDetails != null && snapshotDetails.getValue() != null && Boolean.parseBoolean(snapshotDetails.getValue()); |
| } |
| |
| private SolidFireUtil.SolidFireVolume createClone(SolidFireUtil.SolidFireConnection sfConnection, long dataObjectId, VolumeInfo volumeInfo, long sfAccountId, |
| long storagePoolId, DataObjectType dataObjectType) { |
| String sfNewVolumeName = volumeInfo.getName(); |
| |
| long sfVolumeId = Long.MIN_VALUE; |
| long sfSnapshotId = Long.MIN_VALUE; |
| |
| if (dataObjectType == DataObjectType.SNAPSHOT) { |
| SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(dataObjectId, SolidFireUtil.SNAPSHOT_ID); |
| |
| if (snapshotDetails != null && snapshotDetails.getValue() != null) { |
| sfSnapshotId = Long.parseLong(snapshotDetails.getValue()); |
| } |
| |
| snapshotDetails = snapshotDetailsDao.findDetail(dataObjectId, SolidFireUtil.VOLUME_ID); |
| |
| sfVolumeId = Long.parseLong(snapshotDetails.getValue()); |
| } else if (dataObjectType == DataObjectType.TEMPLATE) { |
| // get the cached template on this storage |
| VMTemplateStoragePoolVO templatePoolRef = vmTemplatePoolDao.findByPoolTemplate(storagePoolId, dataObjectId, null); |
| |
| if (templatePoolRef != null) { |
| sfVolumeId = Long.parseLong(templatePoolRef.getLocalDownloadPath()); |
| } |
| } |
| |
| if (sfVolumeId <= 0) { |
| throw new CloudRuntimeException("Unable to find SolidFire volume for the following data-object ID: " + dataObjectId + |
| " and data-object type: " + dataObjectType); |
| } |
| |
| final long newSfVolumeId = SolidFireUtil.createClone(sfConnection, sfVolumeId, sfSnapshotId, sfAccountId, |
| SolidFireUtil.getSolidFireVolumeName(sfNewVolumeName), getVolumeAttributes(volumeInfo)); |
| |
| final Iops iops = getIops(volumeInfo.getMinIops(), volumeInfo.getMaxIops(), storagePoolId); |
| |
| SolidFireUtil.modifyVolume(sfConnection, newSfVolumeId, null, null, iops.getMinIops(), iops.getMaxIops(), iops.getBurstIops()); |
| |
| return SolidFireUtil.getVolume(sfConnection, newSfVolumeId); |
| } |
| |
| private Map<String, String> getVolumeAttributes(VolumeInfo volumeInfo) { |
| Map<String, String> mapAttributes = new HashMap<>(); |
| |
| mapAttributes.put(SolidFireUtil.CloudStackVolumeId, String.valueOf(volumeInfo.getId())); |
| mapAttributes.put(SolidFireUtil.CloudStackVolumeSize, NumberFormat.getInstance().format(volumeInfo.getSize())); |
| |
| return mapAttributes; |
| } |
| |
| private Map<String, String> getSnapshotAttributes(SnapshotInfo snapshotInfo) { |
| Map<String, String> mapAttributes = new HashMap<>(); |
| |
| mapAttributes.put(SolidFireUtil.CloudStackSnapshotId, String.valueOf(snapshotInfo.getId())); |
| mapAttributes.put(SolidFireUtil.CloudStackSnapshotSize, NumberFormat.getInstance().format(snapshotInfo.getSize())); |
| |
| return mapAttributes; |
| } |
| |
| private Map<String, String> getTemplateAttributes(TemplateInfo templateInfo) { |
| Map<String, String> mapAttributes = new HashMap<>(); |
| |
| mapAttributes.put(SolidFireUtil.CloudStackTemplateId, String.valueOf(templateInfo.getId())); |
| mapAttributes.put(SolidFireUtil.CloudStackTemplateSize, NumberFormat.getInstance().format(templateInfo.getSize())); |
| |
| return mapAttributes; |
| } |
| |
| private SolidFireUtil.SolidFireVolume createCloneFromSnapshot(SolidFireUtil.SolidFireConnection sfConnection, long csSnapshotId, long sfAccountId) { |
| SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, SolidFireUtil.VOLUME_ID); |
| |
| long sfVolumeId = Long.parseLong(snapshotDetails.getValue()); |
| |
| snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, SolidFireUtil.SNAPSHOT_ID); |
| |
| long sfSnapshotId = Long.parseLong(snapshotDetails.getValue()); |
| |
| SolidFireUtil.SolidFireSnapshot sfSnapshot = SolidFireUtil.getSnapshot(sfConnection, sfVolumeId, sfSnapshotId); |
| |
| long newSfVolumeId = SolidFireUtil.createClone(sfConnection, sfVolumeId, sfSnapshotId, sfAccountId, SolidFireUtil.getSolidFireVolumeName(sfSnapshot.getName()), null); |
| |
| snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, SolidFireUtil.STORAGE_POOL_ID); |
| |
| long storagePoolId = Long.parseLong(snapshotDetails.getValue()); |
| |
| final Iops iops = getIops(MIN_IOPS_FOR_TEMP_VOLUME, MAX_IOPS_FOR_TEMP_VOLUME, storagePoolId); |
| |
| SolidFireUtil.modifyVolume(sfConnection, newSfVolumeId, null, null, iops.getMinIops(), iops.getMaxIops(), iops.getBurstIops()); |
| |
| return SolidFireUtil.getVolume(sfConnection, newSfVolumeId); |
| } |
| |
| private void updateVolumeDetails(long volumeId, long sfVolumeSize, String scsiNaaDeviceId) { |
| volumeDetailsDao.removeDetail(volumeId, SolidFireUtil.VOLUME_SIZE); |
| volumeDetailsDao.removeDetail(volumeId, DiskTO.SCSI_NAA_DEVICE_ID); |
| |
| VolumeDetailVO volumeDetailVo = new VolumeDetailVO(volumeId, SolidFireUtil.VOLUME_SIZE, String.valueOf(sfVolumeSize), false); |
| |
| volumeDetailsDao.persist(volumeDetailVo); |
| |
| volumeDetailVo = new VolumeDetailVO(volumeId, DiskTO.SCSI_NAA_DEVICE_ID, scsiNaaDeviceId, false); |
| |
| volumeDetailsDao.persist(volumeDetailVo); |
| } |
| |
| @Override |
| public void deleteAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CommandResult> callback) { |
| String errMsg = null; |
| |
| try { |
| if (dataObject.getType() == DataObjectType.VOLUME) { |
| deleteVolume((VolumeInfo)dataObject, dataStore.getId()); |
| } else if (dataObject.getType() == DataObjectType.SNAPSHOT) { |
| deleteSnapshot((SnapshotInfo)dataObject, dataStore.getId()); |
| } else if (dataObject.getType() == DataObjectType.TEMPLATE) { |
| deleteTemplate((TemplateInfo)dataObject, dataStore.getId()); |
| } else { |
| errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync"; |
| } |
| } |
| catch (Exception ex) { |
| errMsg = ex.getMessage(); |
| |
| logger.error(errMsg); |
| } |
| |
| if (callback != null) { |
| CommandResult result = new CommandResult(); |
| |
| result.setResult(errMsg); |
| |
| callback.complete(result); |
| } |
| } |
| |
| @Override |
| public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean canCopy(DataObject srcData, DataObject destData) { |
| return false; |
| } |
| |
| @Override |
| public void takeSnapshot(SnapshotInfo snapshotInfo, AsyncCompletionCallback<CreateCmdResult> callback) { |
| CreateCmdResult result; |
| |
| try { |
| VolumeInfo volumeInfo = snapshotInfo.getBaseVolume(); |
| VolumeVO volumeVO = volumeDao.findById(volumeInfo.getId()); |
| |
| long sfVolumeId = Long.parseLong(volumeVO.getFolder()); |
| long storagePoolId = volumeVO.getPoolId(); |
| |
| SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao); |
| |
| SolidFireUtil.SolidFireVolume sfVolume = SolidFireUtil.getVolume(sfConnection, sfVolumeId); |
| |
| StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); |
| |
| long capacityBytes = storagePool.getCapacityBytes(); |
| // getUsedBytes(StoragePool) will not include the bytes of the proposed new volume or snapshot because |
| // updateSnapshotDetails has not yet been called for this new volume or snapshot |
| long usedBytes = getUsedBytes(storagePool); |
| long sfVolumeSize = sfVolume.getTotalSize(); |
| |
| usedBytes += sfVolumeSize; |
| |
| // For creating a volume or a snapshot, we need to check to make sure a sufficient amount of space remains in the primary storage. |
| // For the purpose of "charging" these bytes against storage_pool.capacity_bytes, we take the full size of the SolidFire volume |
| // that is serving as the volume the snapshot is of (either a new SolidFire volume or a SolidFire snapshot). |
| if (usedBytes > capacityBytes) { |
| throw new CloudRuntimeException("Insufficient amount of space remains in this primary storage to take a snapshot"); |
| } |
| |
| storagePool.setUsedBytes(usedBytes); |
| |
| SnapshotObjectTO snapshotObjectTo = (SnapshotObjectTO)snapshotInfo.getTO(); |
| |
| if (shouldTakeSnapshot(snapshotInfo.getId())) { |
| // We are supposed to take a SolidFire snapshot to serve as the back-end for our CloudStack volume snapshot. |
| |
| String sfNewSnapshotName = volumeInfo.getName() + "-" + snapshotInfo.getUuid(); |
| |
| int maxSnapshotNameLength = 64; |
| int trimRequired = sfNewSnapshotName.length() - maxSnapshotNameLength; |
| |
| if (trimRequired > 0) { |
| sfNewSnapshotName = StringUtils.left(volumeInfo.getName(), (volumeInfo.getName().length() - trimRequired)) + "-" + snapshotInfo.getUuid(); |
| } |
| |
| long sfNewSnapshotId = SolidFireUtil.createSnapshot(sfConnection, sfVolumeId, SolidFireUtil.getSolidFireVolumeName(sfNewSnapshotName), |
| getSnapshotAttributes(snapshotInfo)); |
| |
| updateSnapshotDetails(snapshotInfo.getId(), volumeInfo.getId(), sfVolumeId, sfNewSnapshotId, storagePoolId, sfVolumeSize); |
| |
| snapshotObjectTo.setPath("SfSnapshotId=" + sfNewSnapshotId); |
| } |
| else { |
| // We are supposed to create a new SolidFire volume to serve as the back-end for our CloudStack volume snapshot. |
| |
| String sfNewVolumeName = volumeInfo.getName() + "-" + snapshotInfo.getUuid(); |
| |
| final Iops iops = getIops(MIN_IOPS_FOR_SNAPSHOT_VOLUME, MAX_IOPS_FOR_SNAPSHOT_VOLUME, storagePoolId); |
| |
| long sfNewVolumeId = SolidFireUtil.createVolume(sfConnection, SolidFireUtil.getSolidFireVolumeName(sfNewVolumeName), sfVolume.getAccountId(), |
| sfVolumeSize, sfVolume.isEnable512e(), getSnapshotAttributes(snapshotInfo), iops.getMinIops(), iops.getMaxIops(), iops.getBurstIops()); |
| |
| SolidFireUtil.SolidFireVolume sfNewVolume = SolidFireUtil.getVolume(sfConnection, sfNewVolumeId); |
| |
| updateSnapshotDetails(snapshotInfo.getId(), sfNewVolumeId, storagePoolId, sfVolumeSize, sfNewVolume.getIqn()); |
| |
| snapshotObjectTo.setPath("SfVolumeId=" + sfNewVolumeId); |
| } |
| |
| // Now that we have successfully created a volume or a snapshot, update the space usage in the cloud.storage_pool table |
| // (even though cloud.storage_pool.used_bytes is likely no longer in use). |
| storagePoolDao.update(storagePoolId, storagePool); |
| |
| CreateObjectAnswer createObjectAnswer = new CreateObjectAnswer(snapshotObjectTo); |
| |
| result = new CreateCmdResult(null, createObjectAnswer); |
| |
| result.setResult(null); |
| } |
| catch (Exception ex) { |
| logger.debug(SolidFireUtil.LOGGER_PREFIX + "Failed to take CloudStack snapshot: " + snapshotInfo.getId(), ex); |
| |
| result = new CreateCmdResult(null, new CreateObjectAnswer(ex.toString())); |
| |
| result.setResult(ex.toString()); |
| } |
| |
| callback.complete(result); |
| } |
| |
| private void updateSnapshotDetails(long csSnapshotId, long csVolumeId, long sfVolumeId, long sfNewSnapshotId, long storagePoolId, long sfNewVolumeSize) { |
| SnapshotDetailsVO snapshotDetail = new SnapshotDetailsVO(csSnapshotId, |
| SolidFireUtil.ORIG_CS_VOLUME_ID, |
| String.valueOf(csVolumeId), |
| false); |
| |
| snapshotDetailsDao.persist(snapshotDetail); |
| |
| snapshotDetail = new SnapshotDetailsVO(csSnapshotId, |
| SolidFireUtil.VOLUME_ID, |
| String.valueOf(sfVolumeId), |
| false); |
| |
| snapshotDetailsDao.persist(snapshotDetail); |
| |
| snapshotDetail = new SnapshotDetailsVO(csSnapshotId, |
| SolidFireUtil.SNAPSHOT_ID, |
| String.valueOf(sfNewSnapshotId), |
| false); |
| |
| snapshotDetailsDao.persist(snapshotDetail); |
| |
| snapshotDetail = new SnapshotDetailsVO(csSnapshotId, |
| SolidFireUtil.STORAGE_POOL_ID, |
| String.valueOf(storagePoolId), |
| false); |
| |
| snapshotDetailsDao.persist(snapshotDetail); |
| |
| snapshotDetail = new SnapshotDetailsVO(csSnapshotId, |
| SolidFireUtil.VOLUME_SIZE, |
| String.valueOf(sfNewVolumeSize), |
| false); |
| |
| snapshotDetailsDao.persist(snapshotDetail); |
| } |
| |
| private void updateSnapshotDetails(long csSnapshotId, long sfNewVolumeId, long storagePoolId, long sfNewVolumeSize, String sfNewVolumeIqn) { |
| SnapshotDetailsVO snapshotDetail = new SnapshotDetailsVO(csSnapshotId, |
| SolidFireUtil.VOLUME_ID, |
| String.valueOf(sfNewVolumeId), |
| false); |
| |
| snapshotDetailsDao.persist(snapshotDetail); |
| |
| snapshotDetail = new SnapshotDetailsVO(csSnapshotId, |
| SolidFireUtil.STORAGE_POOL_ID, |
| String.valueOf(storagePoolId), |
| false); |
| |
| snapshotDetailsDao.persist(snapshotDetail); |
| |
| snapshotDetail = new SnapshotDetailsVO(csSnapshotId, |
| SolidFireUtil.VOLUME_SIZE, |
| String.valueOf(sfNewVolumeSize), |
| false); |
| |
| snapshotDetailsDao.persist(snapshotDetail); |
| |
| snapshotDetail = new SnapshotDetailsVO(csSnapshotId, |
| DiskTO.IQN, |
| sfNewVolumeIqn, |
| false); |
| |
| snapshotDetailsDao.persist(snapshotDetail); |
| } |
| |
| private void addBasicCreateInfoToVolumeDetails(long volumeId, SolidFireUtil.SolidFireVolume sfVolume) { |
| VolumeDetailVO volumeDetailVo = new VolumeDetailVO(volumeId, BASIC_SF_ID, String.valueOf(sfVolume.getId()), false); |
| |
| volumeDetailsDao.persist(volumeDetailVo); |
| |
| volumeDetailVo = new VolumeDetailVO(volumeId, BASIC_IQN, sfVolume.getIqn(), false); |
| |
| volumeDetailsDao.persist(volumeDetailVo); |
| } |
| |
| private String createVolume(VolumeInfo volumeInfo, long storagePoolId) { |
| boolean isBasicCreate = isBasicCreate(volumeInfo.getId()); |
| |
| if (!isBasicCreate) { |
| verifySufficientBytesForStoragePool(volumeInfo, storagePoolId); |
| verifySufficientIopsForStoragePool(volumeInfo.getMinIops() != null ? volumeInfo.getMinIops() : getDefaultMinIops(storagePoolId), storagePoolId); |
| } |
| |
| SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao); |
| |
| long sfAccountId = getCreateSolidFireAccountId(sfConnection, volumeInfo.getAccountId(), storagePoolId); |
| |
| SolidFireUtil.SolidFireVolume sfVolume; |
| |
| if (isBasicCreate) { |
| sfVolume = createSolidFireVolume(sfConnection, volumeInfo, storagePoolId, sfAccountId); |
| |
| volumeDetailsDao.removeDetail(volumeInfo.getId(), BASIC_CREATE); |
| |
| addBasicCreateInfoToVolumeDetails(volumeInfo.getId(), sfVolume); |
| |
| return sfVolume.getIqn(); |
| } |
| |
| long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot"); |
| long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate"); |
| |
| if (csSnapshotId > 0) { |
| // We are supposed to create a clone of the underlying volume or snapshot that supports the CloudStack snapshot. |
| sfVolume = createClone(sfConnection, csSnapshotId, volumeInfo, sfAccountId, storagePoolId, DataObjectType.SNAPSHOT); |
| } else if (csTemplateId > 0) { |
| // Clone from template. |
| sfVolume = createClone(sfConnection, csTemplateId, volumeInfo, sfAccountId, storagePoolId, DataObjectType.TEMPLATE); |
| |
| long volumeSize = getDataObjectSizeIncludingHypervisorSnapshotReserve(volumeInfo, storagePoolDao.findById(storagePoolId)); |
| |
| if (volumeSize > sfVolume.getTotalSize()) { |
| // Expand the volume to include HSR. |
| SolidFireUtil.modifyVolume(sfConnection, sfVolume.getId(), volumeSize, getVolumeAttributes(volumeInfo), |
| sfVolume.getMinIops(), sfVolume.getMaxIops(), sfVolume.getBurstIops()); |
| |
| // Get the SolidFire volume from the SAN again because we just updated its size. |
| sfVolume = SolidFireUtil.getVolume(sfConnection, sfVolume.getId()); |
| } |
| } |
| else { |
| sfVolume = createSolidFireVolume(sfConnection, volumeInfo, storagePoolId, sfAccountId); |
| } |
| |
| String iqn = sfVolume.getIqn(); |
| |
| VolumeVO volume = volumeDao.findById(volumeInfo.getId()); |
| |
| volume.set_iScsiName(iqn); |
| volume.setFolder(String.valueOf(sfVolume.getId())); |
| volume.setPoolType(StoragePoolType.IscsiLUN); |
| volume.setPoolId(storagePoolId); |
| |
| volumeDao.update(volume.getId(), volume); |
| |
| updateVolumeDetails(volume.getId(), sfVolume.getTotalSize(), sfVolume.getScsiNaaDeviceId()); |
| |
| StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); |
| |
| long capacityBytes = storagePool.getCapacityBytes(); |
| // getUsedBytes(StoragePool) will include the bytes of the newly created volume because |
| // updateVolumeDetails(long, long) has already been called for this volume |
| long usedBytes = getUsedBytes(storagePool); |
| |
| storagePool.setUsedBytes(usedBytes > capacityBytes ? capacityBytes : usedBytes); |
| |
| storagePoolDao.update(storagePoolId, storagePool); |
| |
| return iqn; |
| } |
| |
| private void createTempVolume(SnapshotInfo snapshotInfo, long storagePoolId) { |
| long csSnapshotId = snapshotInfo.getId(); |
| |
| SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, SolidFireUtil.SNAPSHOT_ID); |
| |
| if (snapshotDetails == null || snapshotDetails.getValue() == null) { |
| throw new CloudRuntimeException("'createTempVolume(SnapshotInfo, long)' should not be invoked unless " + SolidFireUtil.SNAPSHOT_ID + " exists."); |
| } |
| |
| SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao); |
| |
| snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, "tempVolume"); |
| |
| if (snapshotDetails != null && snapshotDetails.getValue() != null && snapshotDetails.getValue().equalsIgnoreCase("create")) { |
| long sfAccountId = getCreateSolidFireAccountId(sfConnection, snapshotInfo.getAccountId(), storagePoolId); |
| |
| SolidFireUtil.SolidFireVolume sfVolume = createCloneFromSnapshot(sfConnection, csSnapshotId, sfAccountId); |
| |
| addTempVolumeId(csSnapshotId, String.valueOf(sfVolume.getId())); |
| |
| handleSnapshotDetails(csSnapshotId, DiskTO.IQN, sfVolume.getIqn()); |
| handleSnapshotDetails(csSnapshotId, DiskTO.SCSI_NAA_DEVICE_ID, sfVolume.getScsiNaaDeviceId()); |
| } |
| else if (snapshotDetails != null && snapshotDetails.getValue() != null && snapshotDetails.getValue().equalsIgnoreCase("delete")) { |
| snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, SolidFireUtil.VOLUME_ID); |
| |
| SolidFireUtil.deleteVolume(sfConnection, Long.parseLong(snapshotDetails.getValue())); |
| |
| removeTempVolumeId(csSnapshotId); |
| |
| snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, DiskTO.IQN); |
| |
| snapshotDetailsDao.remove(snapshotDetails.getId()); |
| |
| snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, DiskTO.SCSI_NAA_DEVICE_ID); |
| |
| snapshotDetailsDao.remove(snapshotDetails.getId()); |
| } |
| else { |
| throw new CloudRuntimeException("Invalid state in 'createTempVolume(SnapshotInfo, long)'"); |
| } |
| } |
| |
| private String createTemplateVolume(TemplateInfo templateInfo, long storagePoolId) { |
| verifySufficientBytesForStoragePool(templateInfo, storagePoolId); |
| |
| SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao); |
| |
| long sfAccountId = getCreateSolidFireAccountId(sfConnection, templateInfo.getAccountId(), storagePoolId); |
| |
| SolidFireUtil.SolidFireVolume sfVolume = createSolidFireVolume(sfConnection, templateInfo, storagePoolId, sfAccountId); |
| |
| String iqn = sfVolume.getIqn(); |
| |
| VMTemplateStoragePoolVO templatePoolRef = vmTemplatePoolDao.findByPoolTemplate(storagePoolId, templateInfo.getId(), null); |
| |
| templatePoolRef.setInstallPath(iqn); |
| templatePoolRef.setLocalDownloadPath(Long.toString(sfVolume.getId())); |
| templatePoolRef.setTemplateSize(sfVolume.getTotalSize()); |
| |
| vmTemplatePoolDao.update(templatePoolRef.getId(), templatePoolRef); |
| |
| StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); |
| |
| long capacityBytes = storagePool.getCapacityBytes(); |
| // getUsedBytes(StoragePool) will include the bytes of the newly created template volume because |
| // _tmpltPoolDao.update(Long, VMTemplateStoragePoolVO) has already been invoked |
| long usedBytes = getUsedBytes(storagePool); |
| |
| storagePool.setUsedBytes(usedBytes > capacityBytes ? capacityBytes : usedBytes); |
| |
| storagePoolDao.update(storagePoolId, storagePool); |
| |
| return iqn; |
| } |
| |
| private void performBasicDelete(SolidFireUtil.SolidFireConnection sfConnection, long volumeId) { |
| Long sfVolumeId = getBasicSfVolumeId(volumeId); |
| |
| Preconditions.checkNotNull(sfVolumeId, "'sfVolumeId' should not be 'null'."); |
| |
| String iqn = getBasicIqn(volumeId); |
| |
| Preconditions.checkNotNull(iqn, "'iqn' should not be 'null'."); |
| |
| VolumeVO volumeVO = volumeDao.findById(volumeId); |
| |
| deleteSolidFireVolume(sfConnection, volumeId, Long.parseLong(volumeVO.getFolder())); |
| |
| volumeVO.setFolder(String.valueOf(sfVolumeId)); |
| volumeVO.set_iScsiName(iqn); |
| |
| volumeDao.update(volumeId, volumeVO); |
| |
| volumeDetailsDao.removeDetail(volumeId, BASIC_SF_ID); |
| volumeDetailsDao.removeDetail(volumeId, BASIC_IQN); |
| volumeDetailsDao.removeDetail(volumeId, BASIC_DELETE); |
| } |
| |
| private void performBasicDeleteFailure(SolidFireUtil.SolidFireConnection sfConnection, long volumeId) { |
| Long sfVolumeId = getBasicSfVolumeId(volumeId); |
| |
| Preconditions.checkNotNull(sfVolumeId, "'sfVolumeId' should not be 'null'."); |
| |
| SolidFireUtil.deleteVolume(sfConnection, sfVolumeId); |
| |
| volumeDetailsDao.removeDetail(volumeId, BASIC_SF_ID); |
| volumeDetailsDao.removeDetail(volumeId, BASIC_IQN); |
| volumeDetailsDao.removeDetail(volumeId, BASIC_DELETE_FAILURE); |
| } |
| |
| private void performBasicDeleteByFolder(SolidFireUtil.SolidFireConnection sfConnection, long volumeId) { |
| VolumeVO volumeVO = volumeDao.findById(volumeId); |
| |
| Preconditions.checkNotNull(volumeVO, "'volumeVO' should not be 'null'."); |
| |
| String folder = volumeVO.getFolder(); |
| |
| Preconditions.checkNotNull(folder, "'folder' should not be 'null'."); |
| |
| long sfVolumeId = Long.parseLong(folder); |
| |
| SolidFireUtil.deleteVolume(sfConnection, sfVolumeId); |
| } |
| |
| private void deleteVolume(VolumeInfo volumeInfo, long storagePoolId) { |
| try { |
| long volumeId = volumeInfo.getId(); |
| |
| SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao); |
| |
| if (isBasicDeleteByFolder(volumeId)) { |
| performBasicDeleteByFolder(sfConnection, volumeId); |
| } |
| else if (isBasicDelete(volumeId)) { |
| performBasicDelete(sfConnection, volumeId); |
| } |
| else if (isBasicDeleteFailure(volumeId)) { |
| performBasicDeleteFailure(sfConnection, volumeId); |
| } |
| else { |
| deleteSolidFireVolume(sfConnection, volumeInfo); |
| |
| volumeDetailsDao.removeDetails(volumeId); |
| |
| StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); |
| |
| long usedBytes = getUsedBytes(storagePool, volumeId); |
| |
| storagePool.setUsedBytes(usedBytes < 0 ? 0 : usedBytes); |
| |
| storagePoolDao.update(storagePoolId, storagePool); |
| } |
| } |
| catch (Exception ex) { |
| logger.debug(SolidFireUtil.LOGGER_PREFIX + "Failed to delete SolidFire volume. CloudStack volume ID: " + volumeInfo.getId(), ex); |
| |
| throw ex; |
| } |
| } |
| |
| private void deleteSnapshot(SnapshotInfo snapshotInfo, long storagePoolId) { |
| long csSnapshotId = snapshotInfo.getId(); |
| |
| try { |
| SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao); |
| |
| SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, SolidFireUtil.SNAPSHOT_ID); |
| |
| if (snapshotDetails != null && snapshotDetails.getValue() != null) { |
| // A SolidFire snapshot is being used to support the CloudStack volume snapshot. |
| |
| long sfSnapshotId = Long.parseLong(snapshotDetails.getValue()); |
| |
| deleteSolidFireSnapshot(sfConnection, csSnapshotId, sfSnapshotId); |
| } |
| else { |
| // A SolidFire volume is being used to support the CloudStack volume snapshot. |
| |
| snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, SolidFireUtil.VOLUME_ID); |
| |
| long sfVolumeId = Long.parseLong(snapshotDetails.getValue()); |
| |
| SolidFireUtil.deleteVolume(sfConnection, sfVolumeId); |
| } |
| |
| snapshotDetailsDao.removeDetails(csSnapshotId); |
| |
| StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); |
| |
| // getUsedBytes(StoragePool) will not include the snapshot to delete because it has already been deleted by this point |
| long usedBytes = getUsedBytes(storagePool); |
| |
| storagePool.setUsedBytes(usedBytes < 0 ? 0 : usedBytes); |
| |
| storagePoolDao.update(storagePoolId, storagePool); |
| } |
| catch (Exception ex) { |
| logger.debug(SolidFireUtil.LOGGER_PREFIX + "Issue in 'deleteSnapshot(SnapshotInfo, long)'. CloudStack snapshot ID: " + csSnapshotId, ex); |
| |
| throw ex; |
| } |
| } |
| |
| private void deleteTemplate(TemplateInfo template, long storagePoolId) { |
| try { |
| SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao); |
| |
| long sfTemplateVolumeId = getVolumeIdFrom_iScsiPath(template.getInstallPath()); |
| |
| SolidFireUtil.deleteVolume(sfConnection, sfTemplateVolumeId); |
| |
| StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); |
| |
| // getUsedBytes(StoragePool) will not include the template to delete because the "template_spool_ref" table has already been updated by this point |
| long usedBytes = getUsedBytes(storagePool); |
| |
| storagePool.setUsedBytes(usedBytes < 0 ? 0 : usedBytes); |
| |
| storagePoolDao.update(storagePoolId, storagePool); |
| } |
| catch (Exception ex) { |
| logger.debug(SolidFireUtil.LOGGER_PREFIX + "Failed to delete SolidFire template volume. CloudStack template ID: " + template.getId(), ex); |
| |
| throw ex; |
| } |
| } |
| |
| @Override |
| public void revertSnapshot(SnapshotInfo snapshot, SnapshotInfo snapshot2, AsyncCompletionCallback<CommandResult> callback) { |
| VolumeInfo volumeInfo = snapshot.getBaseVolume(); |
| |
| VolumeVO volumeVO = volumeDao.findById(volumeInfo.getId()); |
| |
| if (volumeVO == null || volumeVO.getRemoved() != null) { |
| String errMsg = "The volume that the snapshot belongs to no longer exists."; |
| |
| CommandResult commandResult = new CommandResult(); |
| |
| commandResult.setResult(errMsg); |
| |
| callback.complete(commandResult); |
| |
| return; |
| } |
| |
| SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(volumeVO.getPoolId(), storagePoolDetailsDao); |
| |
| long sfVolumeId = Long.parseLong(volumeInfo.getFolder()); |
| |
| SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(snapshot.getId(), SolidFireUtil.SNAPSHOT_ID); |
| |
| long sfSnapshotId = Long.parseLong(snapshotDetails.getValue()); |
| |
| SolidFireUtil.rollBackVolumeToSnapshot(sfConnection, sfVolumeId, sfSnapshotId); |
| |
| SolidFireUtil.SolidFireVolume sfVolume = SolidFireUtil.getVolume(sfConnection, sfVolumeId); |
| |
| updateVolumeDetails(volumeVO.getId(), sfVolume.getTotalSize(), sfVolume.getScsiNaaDeviceId()); |
| |
| CommandResult commandResult = new CommandResult(); |
| |
| callback.complete(commandResult); |
| } |
| |
| @Override |
| public void resize(DataObject dataObject, AsyncCompletionCallback<CreateCmdResult> callback) { |
| String iqn = null; |
| String errMsg = null; |
| |
| try { |
| if (dataObject.getType() == DataObjectType.VOLUME) { |
| VolumeInfo volumeInfo = (VolumeInfo)dataObject; |
| |
| iqn = volumeInfo.get_iScsiName(); |
| |
| long storagePoolId = volumeInfo.getPoolId(); |
| long sfVolumeId = Long.parseLong(volumeInfo.getFolder()); |
| |
| ResizeVolumePayload payload = (ResizeVolumePayload)volumeInfo.getpayload(); |
| |
| SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao); |
| SolidFireUtil.SolidFireVolume sfVolume = SolidFireUtil.getVolume(sfConnection, sfVolumeId); |
| |
| long newMinIops = payload.newMinIops != null ? payload.newMinIops : sfVolume.getMinIops(); |
| |
| verifySufficientIopsForStoragePool(storagePoolId, sfVolume, newMinIops); |
| |
| long newSize = payload.newSize != null ? payload.newSize : volumeInfo.getSize(); |
| |
| verifySufficientBytesForStoragePool(storagePoolId, volumeInfo.getId(), newSize, payload.newHypervisorSnapshotReserve); |
| |
| long sfNewVolumeSize = sfVolume.getTotalSize(); |
| |
| Integer hsr = volumeInfo.getHypervisorSnapshotReserve(); |
| |
| if (payload.newSize != null || payload.newHypervisorSnapshotReserve != null) { |
| if (payload.newHypervisorSnapshotReserve != null) { |
| if (hsr != null) { |
| if (payload.newHypervisorSnapshotReserve > hsr) { |
| hsr = payload.newHypervisorSnapshotReserve; |
| } |
| } else { |
| hsr = payload.newHypervisorSnapshotReserve; |
| } |
| } |
| |
| sfNewVolumeSize = getVolumeSizeIncludingHypervisorSnapshotReserve(newSize, hsr); |
| } |
| |
| Map<String, String> mapAttributes = new HashMap<>(); |
| |
| mapAttributes.put(SolidFireUtil.CloudStackVolumeId, String.valueOf(volumeInfo.getId())); |
| mapAttributes.put(SolidFireUtil.CloudStackVolumeSize, NumberFormat.getInstance().format(payload.newSize)); |
| |
| long newMaxIops = payload.newMaxIops != null ? payload.newMaxIops : sfVolume.getMaxIops(); |
| |
| SolidFireUtil.modifyVolume(sfConnection, sfVolumeId, sfNewVolumeSize, mapAttributes, |
| newMinIops, newMaxIops, getDefaultBurstIops(storagePoolId, newMaxIops)); |
| |
| VolumeVO volume = volumeDao.findById(volumeInfo.getId()); |
| |
| volume.setMinIops(newMinIops); |
| volume.setMaxIops(newMaxIops); |
| volume.setHypervisorSnapshotReserve(hsr); |
| |
| volumeDao.update(volume.getId(), volume); |
| |
| // SolidFireUtil.VOLUME_SIZE was introduced in 4.5. |
| updateVolumeDetails(volume.getId(), sfNewVolumeSize, sfVolume.getScsiNaaDeviceId()); |
| } else { |
| errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to resize"; |
| } |
| } |
| catch (Exception ex) { |
| errMsg = ex.getMessage(); |
| } |
| finally { |
| CreateCmdResult result = new CreateCmdResult(iqn, new Answer(null, errMsg == null, errMsg)); |
| |
| result.setResult(errMsg); |
| |
| callback.complete(result); |
| } |
| } |
| |
| @Override |
| public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) { |
| SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(volumeInfo.getPoolId(), storagePoolDetailsDao); |
| |
| Iops iops; |
| |
| if (QualityOfServiceState.MIGRATION.equals(qualityOfServiceState)) { |
| iops = getIops(volumeInfo.getMinIops(), MAX_IOPS_FOR_MIGRATING_VOLUME, volumeInfo.getPoolId()); |
| } else { |
| iops = getIops(volumeInfo.getMinIops(), volumeInfo.getMaxIops(), volumeInfo.getPoolId()); |
| } |
| |
| SolidFireUtil.modifyVolumeQoS(sfConnection, Long.parseLong(volumeInfo.getFolder()), iops.getMinIops(), iops.getMaxIops(), iops.getBurstIops()); |
| } |
| |
| private void verifySufficientBytesForStoragePool(long requestedBytes, long storagePoolId) { |
| StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); |
| |
| long capacityBytes = storagePool.getCapacityBytes(); |
| long usedBytes = getUsedBytes(storagePool); |
| |
| usedBytes += requestedBytes; |
| |
| if (usedBytes > capacityBytes) { |
| throw new CloudRuntimeException("Insufficient amount of space remains in this primary storage"); |
| } |
| } |
| |
| private void verifySufficientBytesForStoragePool(DataObject dataObject, long storagePoolId) { |
| StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); |
| |
| long requestedBytes = getDataObjectSizeIncludingHypervisorSnapshotReserve(dataObject, storagePool); |
| |
| verifySufficientBytesForStoragePool(requestedBytes, storagePoolId); |
| } |
| |
| private void verifySufficientBytesForStoragePool(long storagePoolId, long volumeId, long newSize, Integer newHypervisorSnapshotReserve) { |
| DataStore primaryDataStore = dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary); |
| VolumeInfo volumeInfo = volumeFactory.getVolume(volumeId, primaryDataStore); |
| StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); |
| long currentSizeWithHsr = getDataObjectSizeIncludingHypervisorSnapshotReserve(volumeInfo, storagePool); |
| |
| newHypervisorSnapshotReserve = newHypervisorSnapshotReserve == null ? LOWEST_HYPERVISOR_SNAPSHOT_RESERVE : |
| Math.max(newHypervisorSnapshotReserve, LOWEST_HYPERVISOR_SNAPSHOT_RESERVE); |
| |
| long newSizeWithHsr = (long)(newSize + newSize * (newHypervisorSnapshotReserve / 100f)); |
| |
| if (newSizeWithHsr < currentSizeWithHsr) { |
| throw new CloudRuntimeException("Storage pool " + storagePoolId + " does not support shrinking a volume."); |
| } |
| |
| long availableBytes = storagePool.getCapacityBytes() - getUsedBytes(storagePool); |
| |
| if ((newSizeWithHsr - currentSizeWithHsr) > availableBytes) { |
| throw new CloudRuntimeException("Storage pool " + storagePoolId + " does not have enough space to expand the volume."); |
| } |
| } |
| |
| private void verifySufficientIopsForStoragePool(long requestedIops, long storagePoolId) { |
| StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); |
| |
| long usedIops = getUsedIops(storagePool); |
| long capacityIops = storagePool.getCapacityIops(); |
| |
| if (usedIops + requestedIops > capacityIops) { |
| throw new CloudRuntimeException("Insufficient number of IOPS available in this storage pool"); |
| } |
| } |
| |
| private void verifySufficientIopsForStoragePool(long storagePoolId, SolidFireUtil.SolidFireVolume sfVolume, long newMinIops) { |
| long currentMinIops = sfVolume.getMinIops(); |
| long diffInMinIops = newMinIops - currentMinIops; |
| |
| // if the desire is for more IOPS |
| if (diffInMinIops > 0) { |
| verifySufficientIopsForStoragePool(diffInMinIops, storagePoolId); |
| } |
| } |
| |
| private void deleteSolidFireVolume(SolidFireUtil.SolidFireConnection sfConnection, long csVolumeId, long sfVolumeId) { |
| List<SnapshotVO> lstSnapshots = getNonDestroyedSnapshots(csVolumeId); |
| |
| boolean deleteVolume = true; |
| |
| for (SnapshotVO snapshot : lstSnapshots) { |
| SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(snapshot.getId(), SolidFireUtil.SNAPSHOT_ID); |
| |
| if (snapshotDetails != null && snapshotDetails.getValue() != null) { |
| deleteVolume = false; |
| |
| break; |
| } |
| } |
| |
| if (deleteVolume) { |
| SolidFireUtil.deleteVolume(sfConnection, sfVolumeId); |
| } |
| } |
| |
| private void deleteSolidFireSnapshot(SolidFireUtil.SolidFireConnection sfConnection, long csSnapshotId, long sfSnapshotId) { |
| SolidFireUtil.deleteSnapshot(sfConnection, sfSnapshotId); |
| |
| final long volumeId; |
| final VolumeVO volume; |
| |
| SnapshotVO snapshot = snapshotDao.findById(csSnapshotId); |
| SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(csSnapshotId, SolidFireUtil.ORIG_CS_VOLUME_ID); |
| |
| if (snapshotDetails != null && snapshotDetails.getValue() != null) { |
| volumeId = Long.valueOf(snapshotDetails.getValue()); |
| } |
| else { |
| volumeId = snapshot.getVolumeId(); |
| } |
| |
| volume = volumeDao.findById(volumeId); |
| |
| if (volume == null) { // if the CloudStack volume has been deleted |
| List<SnapshotVO> lstSnapshots = getNonDestroyedSnapshots(snapshot.getVolumeId()); |
| |
| List<SnapshotVO> lstSnapshots2 = new ArrayList<>(); |
| |
| for (SnapshotVO snapshotVo : lstSnapshots) { |
| // The CloudStack volume snapshot has not yet been set to the DESTROYED state, so check to make |
| // sure snapshotVo.getId() != csSnapshotId when determining if any volume snapshots remain for the given CloudStack volume. |
| if (snapshotVo.getId() != csSnapshotId) { |
| snapshotDetails = snapshotDetailsDao.findDetail(snapshotVo.getId(), SolidFireUtil.SNAPSHOT_ID); |
| |
| // We are only interested here in volume snapshots that make use of SolidFire snapshots (as opposed to ones |
| // that make use of SolidFire volumes). |
| if (snapshotDetails != null && snapshotDetails.getValue() != null) { |
| lstSnapshots2.add(snapshotVo); |
| } |
| } |
| } |
| |
| if (lstSnapshots2.isEmpty()) { |
| VolumeVO volumeToDelete = volumeDao.findByIdIncludingRemoved(volumeId); |
| |
| SolidFireUtil.deleteVolume(sfConnection, Long.parseLong(volumeToDelete.getFolder())); |
| } |
| } |
| } |
| |
| private List<SnapshotVO> getNonDestroyedSnapshots(long csVolumeId) { |
| List<SnapshotVO> lstSnapshots = snapshotDao.listByVolumeId(csVolumeId); |
| |
| if (lstSnapshots == null) { |
| lstSnapshots = new ArrayList<>(); |
| } |
| |
| List<SnapshotVO> lstSnapshots2 = new ArrayList<>(); |
| |
| for (SnapshotVO snapshot : lstSnapshots) { |
| if (!State.Destroyed.equals(snapshot.getState())) { |
| lstSnapshots2.add(snapshot); |
| } |
| } |
| |
| return lstSnapshots2; |
| } |
| |
| @Override |
| public boolean canProvideStorageStats() { |
| return false; |
| } |
| |
| @Override |
| public Pair<Long, Long> getStorageStats(StoragePool storagePool) { |
| return null; |
| } |
| |
| @Override |
| public boolean canProvideVolumeStats() { |
| return false; |
| } |
| |
| @Override |
| public Pair<Long, Long> getVolumeStats(StoragePool storagePool, String volumeId) { |
| return null; |
| } |
| |
| @Override |
| public boolean canHostAccessStoragePool(Host host, StoragePool pool) { |
| return true; |
| } |
| |
| @Override |
| public boolean isVmInfoNeeded() { |
| return false; |
| } |
| |
| @Override |
| public void provideVmInfo(long vmId, long volumeId) { |
| } |
| |
| @Override |
| public boolean isVmTagsNeeded(String tagKey) { |
| return false; |
| } |
| |
| @Override |
| public void provideVmTags(long vmId, long volumeId, String tagValue) { |
| } |
| |
| @Override |
| public boolean isStorageSupportHA(StoragePoolType type) { |
| return false; |
| } |
| |
| @Override |
| public void detachVolumeFromAllStorageNodes(Volume volume) { |
| } |
| |
| @Override |
| public boolean volumesRequireGrantAccessWhenUsed() { |
| return true; |
| } |
| } |