From bcd23ebb8acdb68a20032c3069a952ed80198236 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Mon, 12 May 2025 08:58:21 +0200 Subject: [PATCH 1/4] server: check if redundant router is supported when restart network with makeredundant = true (#10612) --- .../src/main/java/com/cloud/network/NetworkServiceImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index 0b4e2a7db13..cbe0def4e48 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -2739,6 +2739,12 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C Account callerAccount = _accountMgr.getActiveAccountById(user.getAccountId()); _accountMgr.checkAccess(callerAccount, AccessType.OperateEntry, true, network); if (!network.isRedundant() && makeRedundant) { + NetworkOffering networkOffering = _entityMgr.findById(NetworkOffering.class, network.getNetworkOfferingId()); + Map sourceNatCapabilities = getNetworkOfferingServiceCapabilities(networkOffering, Service.SourceNat); + String isRedundantRouterSupported = sourceNatCapabilities.get(Capability.RedundantRouter); + if (!Boolean.parseBoolean(isRedundantRouterSupported)) { + throw new InvalidParameterValueException(String.format("Redundant router is not supported by the network offering %s", networkOffering)); + } network.setRedundant(true); if (!_networksDao.update(network.getId(), network)) { throw new CloudRuntimeException("Failed to update network into a redundant one, please try again"); From 3959dbdbe6371cdb8328edb529f2cd30b2915d55 Mon Sep 17 00:00:00 2001 From: dahn Date: Mon, 12 May 2025 13:08:35 +0200 Subject: [PATCH 2/4] refactor create duplicate alert check (#10544) --- .../com/cloud/alert/AlertManagerImpl.java | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java index f550d80b51a..3fae6b453f2 100644 --- a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java +++ b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java @@ -19,6 +19,7 @@ package com.cloud.alert; import java.io.UnsupportedEncodingException; import java.text.DecimalFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -82,8 +83,21 @@ import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.SearchCriteria; +import org.jetbrains.annotations.Nullable; public class AlertManagerImpl extends ManagerBase implements AlertManager, Configurable { + public static final List ALERTS = Arrays.asList(AlertType.ALERT_TYPE_HOST + , AlertType.ALERT_TYPE_USERVM + , AlertType.ALERT_TYPE_DOMAIN_ROUTER + , AlertType.ALERT_TYPE_CONSOLE_PROXY + , AlertType.ALERT_TYPE_SSVM + , AlertType.ALERT_TYPE_STORAGE_MISC + , AlertType.ALERT_TYPE_MANAGEMENT_NODE + , AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED + , AlertType.ALERT_TYPE_UPLOAD_FAILED + , AlertType.ALERT_TYPE_OOBM_AUTH_ERROR + , AlertType.ALERT_TYPE_HA_ACTION + , AlertType.ALERT_TYPE_CA_CERT); protected Logger logger = Logger.getLogger(AlertManagerImpl.class.getName()); private static final long INITIAL_CAPACITY_CHECK_DELAY = 30L * 1000L; // Thirty seconds expressed in milliseconds. @@ -723,15 +737,7 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi public void sendAlert(AlertType alertType, long dataCenterId, Long podId, Long clusterId, String subject, String content) throws MessagingException, UnsupportedEncodingException { logger.warn(String.format("alertType=[%s] dataCenterId=[%s] podId=[%s] clusterId=[%s] message=[%s].", alertType, dataCenterId, podId, clusterId, subject)); - AlertVO alert = null; - if ((alertType != AlertManager.AlertType.ALERT_TYPE_HOST) && (alertType != AlertManager.AlertType.ALERT_TYPE_USERVM) - && (alertType != AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER) && (alertType != AlertManager.AlertType.ALERT_TYPE_CONSOLE_PROXY) - && (alertType != AlertManager.AlertType.ALERT_TYPE_SSVM) && (alertType != AlertManager.AlertType.ALERT_TYPE_STORAGE_MISC) - && (alertType != AlertManager.AlertType.ALERT_TYPE_MANAGEMENT_NODE) && (alertType != AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED) - && (alertType != AlertManager.AlertType.ALERT_TYPE_UPLOAD_FAILED) && (alertType != AlertManager.AlertType.ALERT_TYPE_OOBM_AUTH_ERROR) - && (alertType != AlertManager.AlertType.ALERT_TYPE_HA_ACTION) && (alertType != AlertManager.AlertType.ALERT_TYPE_CA_CERT)) { - alert = _alertDao.getLastAlert(alertType.getType(), dataCenterId, podId, clusterId); - } + AlertVO alert = getAlertForTrivialAlertType(alertType, dataCenterId, podId, clusterId); if (alert == null) { AlertVO newAlert = new AlertVO(); @@ -773,6 +779,15 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi } + @Nullable + private AlertVO getAlertForTrivialAlertType(AlertType alertType, long dataCenterId, Long podId, Long clusterId) { + AlertVO alert = null; + if (!ALERTS.contains(alertType)) { + alert = _alertDao.getLastAlert(alertType.getType(), dataCenterId, podId, clusterId); + } + return alert; + } + protected void sendMessage(SMTPMailProperties mailProps) { _executor.execute(new Runnable() { @Override From 47a268202bca0c10ef5de729df99815bd3e64bd1 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Mon, 12 May 2025 16:51:04 +0530 Subject: [PATCH 3/4] [VMware] Sync the disk path or datastore changes for IDE disks, and before any volume resize during start vm (for the volumes on datastore cluster) (#10748) --- .../vmware/resource/VmwareResource.java | 111 ++++++++++-------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 20495530909..9e105749da9 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -2350,13 +2350,16 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes continue; } + VirtualMachineDiskInfo matchingExistingDisk = getMatchingExistingDisk(diskInfoBuilder, vol, hyperHost, context); + Pair volumeDsDetails = getVolumeDatastoreDetails(vol, dataStoresDetails); + syncVolumeDatastoreAndPathForDatastoreCluster(vol, diskInfoBuilder, matchingExistingDisk, volumeDsDetails, diskDatastores, hyperHost, context); + if (deployAsIs && vol.getType() == Volume.Type.ROOT) { rootDiskTO = vol; resizeRootDiskOnVMStart(vmMo, rootDiskTO, hyperHost, context); continue; } - VirtualMachineDiskInfo matchingExistingDisk = getMatchingExistingDisk(diskInfoBuilder, vol, hyperHost, context); String diskController = getDiskController(vmMo, matchingExistingDisk, vol, chosenDiskControllers, deployAsIs); if (DiskControllerType.getType(diskController) == DiskControllerType.ide) { controllerKey = vmMo.getIDEControllerKey(ideUnitNumber); @@ -2365,7 +2368,7 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes // Ensure maximum of 2 data volumes over IDE controller, 3 includeing root volume if (vmMo.getNumberOfVirtualDisks() > 3) { throw new CloudRuntimeException("Found more than 3 virtual disks attached to this VM [" + vmMo.getVmName() + "]. Unable to implement the disks over " - + diskController + " controller, as maximum number of devices supported over IDE controller is 4 includeing CDROM device."); + + diskController + " controller, as maximum number of devices supported over IDE controller is 4 including CDROM device."); } } } else { @@ -2385,51 +2388,6 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes if (!hasSnapshot) { deviceConfigSpecArray[i] = new VirtualDeviceConfigSpec(); - VolumeObjectTO volumeTO = (VolumeObjectTO) vol.getData(); - DataStoreTO primaryStore = volumeTO.getDataStore(); - Map details = vol.getDetails(); - boolean managed = false; - String iScsiName = null; - - if (details != null) { - managed = Boolean.parseBoolean(details.get(DiskTO.MANAGED)); - iScsiName = details.get(DiskTO.IQN); - } - - String primaryStoreUuid = primaryStore.getUuid(); - // if the storage is managed, iScsiName should not be null - String datastoreName = managed ? VmwareResource.getDatastoreName(iScsiName) : primaryStoreUuid; - Pair volumeDsDetails = dataStoresDetails.get(datastoreName); - - assert (volumeDsDetails != null); - if (volumeDsDetails == null) { - throw new Exception("Primary datastore " + primaryStore.getUuid() + " is not mounted on host."); - } - - if (vol.getDetails().get(DiskTO.PROTOCOL_TYPE) != null && vol.getDetails().get(DiskTO.PROTOCOL_TYPE).equalsIgnoreCase("DatastoreCluster")) { - if (diskInfoBuilder != null && matchingExistingDisk != null) { - String[] diskChain = matchingExistingDisk.getDiskChain(); - if (diskChain != null && diskChain.length > 0) { - DatastoreFile file = new DatastoreFile(diskChain[0]); - if (!file.getFileBaseName().equalsIgnoreCase(volumeTO.getPath())) { - if (s_logger.isInfoEnabled()) - s_logger.info("Detected disk-chain top file change on volume: " + volumeTO.getId() + " " + volumeTO.getPath() + " -> " + file.getFileBaseName()); - volumeTO.setPath(file.getFileBaseName()); - } - } - DatastoreMO diskDatastoreMofromVM = getDataStoreWhereDiskExists(hyperHost, context, diskInfoBuilder, vol, diskDatastores); - if (diskDatastoreMofromVM != null) { - String actualPoolUuid = diskDatastoreMofromVM.getCustomFieldValue(CustomFieldConstants.CLOUD_UUID); - if (actualPoolUuid != null && !actualPoolUuid.equalsIgnoreCase(primaryStore.getUuid())) { - volumeDsDetails = new Pair<>(diskDatastoreMofromVM.getMor(), diskDatastoreMofromVM); - if (s_logger.isInfoEnabled()) - s_logger.info("Detected datastore uuid change on volume: " + volumeTO.getId() + " " + primaryStore.getUuid() + " -> " + actualPoolUuid); - ((PrimaryDataStoreTO)primaryStore).setUuid(actualPoolUuid); - } - } - } - } - String[] diskChain = syncDiskChain(dcMo, vmMo, vol, matchingExistingDisk, volumeDsDetails.second()); int deviceNumber = -1; @@ -2441,6 +2399,7 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes scsiUnitNumber++; } + VolumeObjectTO volumeTO = (VolumeObjectTO) vol.getData(); Long maxIops = volumeTO.getIopsWriteRate() + volumeTO.getIopsReadRate(); VirtualDevice device = VmwareHelper.prepareDiskDevice(vmMo, null, controllerKey, diskChain, volumeDsDetails.first(), deviceNumber, i + 1, maxIops); s_logger.debug(LogUtils.logGsonWithoutException("The following definitions will be used to start the VM: virtual device [%s], volume [%s].", device, volumeTO)); @@ -2709,6 +2668,64 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes } } + private Pair getVolumeDatastoreDetails(DiskTO vol, HashMap> dataStoresDetails) throws Exception { + boolean managed = false; + String iScsiName = null; + Map details = vol.getDetails(); + if (MapUtils.isNotEmpty(details)) { + managed = Boolean.parseBoolean(details.get(DiskTO.MANAGED)); + iScsiName = details.get(DiskTO.IQN); + } + + VolumeObjectTO volumeTO = (VolumeObjectTO) vol.getData(); + DataStoreTO primaryStore = volumeTO.getDataStore(); + String primaryStoreUuid = primaryStore.getUuid(); + // if the storage is managed, iScsiName should not be null + String datastoreName = managed ? VmwareResource.getDatastoreName(iScsiName) : primaryStoreUuid; + Pair volumeDsDetails = dataStoresDetails.get(datastoreName); + if (volumeDsDetails == null) { + throw new Exception("Primary datastore " + primaryStore.getUuid() + " is not mounted on host."); + } + + return volumeDsDetails; + } + + private void syncVolumeDatastoreAndPathForDatastoreCluster(DiskTO vol, VirtualMachineDiskInfoBuilder diskInfoBuilder, VirtualMachineDiskInfo matchingExistingDisk, + Pair volumeDsDetails, List> diskDatastores, + VmwareHypervisorHost hyperHost, VmwareContext context) throws Exception { + if (vol.getDetails() == null || vol.getDetails().get(DiskTO.PROTOCOL_TYPE) == null || !vol.getDetails().get(DiskTO.PROTOCOL_TYPE).equalsIgnoreCase("DatastoreCluster")) { + return; + } + + if (diskInfoBuilder != null && matchingExistingDisk != null) { + String[] diskChain = matchingExistingDisk.getDiskChain(); + if (diskChain != null && diskChain.length > 0) { + DatastoreFile file = new DatastoreFile(diskChain[0]); + VolumeObjectTO volumeTO = (VolumeObjectTO) vol.getData(); + if (!file.getFileBaseName().equalsIgnoreCase(volumeTO.getPath())) { + if (s_logger.isInfoEnabled()) { + s_logger.info("Detected disk-chain top file change on volume: " + volumeTO.getId() + " " + volumeTO.getPath() + " -> " + file.getFileBaseName()); + } + volumeTO.setPath(file.getFileBaseName()); + vol.setPath(file.getFileBaseName()); + } + } + DatastoreMO diskDatastoreMofromVM = getDataStoreWhereDiskExists(hyperHost, context, diskInfoBuilder, vol, diskDatastores); + if (diskDatastoreMofromVM != null) { + String actualPoolUuid = diskDatastoreMofromVM.getCustomFieldValue(CustomFieldConstants.CLOUD_UUID); + VolumeObjectTO volumeTO = (VolumeObjectTO) vol.getData(); + DataStoreTO primaryStore = volumeTO.getDataStore(); + if (actualPoolUuid != null && !actualPoolUuid.equalsIgnoreCase(primaryStore.getUuid())) { + volumeDsDetails = new Pair<>(diskDatastoreMofromVM.getMor(), diskDatastoreMofromVM); + if (s_logger.isInfoEnabled()) { + s_logger.info("Detected datastore uuid change on volume: " + volumeTO.getId() + " " + primaryStore.getUuid() + " -> " + actualPoolUuid); + } + ((PrimaryDataStoreTO)primaryStore).setUuid(actualPoolUuid); + } + } + } + } + private boolean powerOnVM(final VirtualMachineMO vmMo, final String vmInternalCSName, final String vmNameOnVcenter) throws Exception { int retry = 20; while (retry-- > 0) { From 88ce639255dac458c12d8f56547c63d259265f08 Mon Sep 17 00:00:00 2001 From: Rene Peinthor Date: Tue, 13 May 2025 10:06:35 +0200 Subject: [PATCH 4/4] Linstor: implement volume and storage stats (#10850) --- plugins/storage/volume/linstor/CHANGELOG.md | 5 ++ .../LinstorPrimaryDataStoreDriverImpl.java | 78 ++++++++++++++++--- .../util/LinstorConfigurationManager.java | 9 ++- .../storage/datastore/util/LinstorUtil.java | 32 +++++++- pom.xml | 2 +- 5 files changed, 112 insertions(+), 14 deletions(-) diff --git a/plugins/storage/volume/linstor/CHANGELOG.md b/plugins/storage/volume/linstor/CHANGELOG.md index 7e9d754b9f6..2abda3ebc50 100644 --- a/plugins/storage/volume/linstor/CHANGELOG.md +++ b/plugins/storage/volume/linstor/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to Linstor CloudStack plugin will be documented in this file The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2025-05-07] + +### Added +- Implemented storage/volume stats + ## [2025-03-13] ### Fixed diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java index a0cb5d17444..3b384831518 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java @@ -28,11 +28,11 @@ import com.linbit.linstor.api.model.ResourceDefinition; import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest; import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted; import com.linbit.linstor.api.model.ResourceDefinitionCreate; - import com.linbit.linstor.api.model.ResourceDefinitionModify; import com.linbit.linstor.api.model.ResourceGroup; import com.linbit.linstor.api.model.ResourceGroupSpawn; import com.linbit.linstor.api.model.ResourceMakeAvailable; +import com.linbit.linstor.api.model.ResourceWithVolumes; import com.linbit.linstor.api.model.Snapshot; import com.linbit.linstor.api.model.SnapshotRestore; import com.linbit.linstor.api.model.VolumeDefinition; @@ -132,6 +132,9 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver @Inject private HostDao _hostDao; + private long volumeStatsLastUpdate = 0L; + private final Map> volumeStats = new HashMap<>(); + public LinstorPrimaryDataStoreDriverImpl() { } @@ -401,9 +404,9 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver } } - private String getRscGrp(StoragePoolVO storagePoolVO) { - return storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ? - storagePoolVO.getUserInfo() : "DfltRscGrp"; + private String getRscGrp(StoragePool storagePool) { + return storagePool.getUserInfo() != null && !storagePool.getUserInfo().isEmpty() ? + storagePool.getUserInfo() : "DfltRscGrp"; } /** @@ -616,7 +619,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver */ private void updateRscGrpIfNecessary(DevelopersApi api, String rscName, String tgtRscGrp) throws ApiException { List rscDfns = api.resourceDefinitionList( - Collections.singletonList(rscName), null, null, null); + Collections.singletonList(rscName), false, null, null, null); if (rscDfns != null && !rscDfns.isEmpty()) { ResourceDefinition rscDfn = rscDfns.get(0); @@ -646,7 +649,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver private void deleteTemplateForProps( DevelopersApi api, String rscName) throws ApiException { List rdList = api.resourceDefinitionList( - Collections.singletonList(rscName), null, null, null); + Collections.singletonList(rscName), false, null, null, null); if (CollectionUtils.isNotEmpty(rdList)) { ResourceDefinitionModify rdm = new ResourceDefinitionModify(); @@ -1504,22 +1507,77 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver @Override public boolean canProvideStorageStats() { - return false; + return true; } @Override public Pair getStorageStats(StoragePool storagePool) { - return null; + s_logger.debug(String.format("Requesting storage stats: %s", storagePool)); + return LinstorUtil.getStorageStats(storagePool.getHostAddress(), getRscGrp(storagePool)); } @Override public boolean canProvideVolumeStats() { - return false; + return LinstorConfigurationManager.VolumeStatsCacheTime.value() > 0; + } + + /** + * Updates the cache map containing current allocated size data. + * @param api Linstor Developers api object + */ + private void fillVolumeStatsCache(DevelopersApi api) { + try { + s_logger.trace("Start volume stats cache update"); + List resources = api.viewResources( + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + null, + null, + null); + + List rscDfns = api.resourceDefinitionList( + Collections.emptyList(), true, null, null, null); + + HashMap resSizeMap = new HashMap<>(); + for (ResourceDefinition rscDfn : rscDfns) { + if (CollectionUtils.isNotEmpty(rscDfn.getVolumeDefinitions())) { + resSizeMap.put(rscDfn.getName(), rscDfn.getVolumeDefinitions().get(0).getSizeKib() * 1024); + } + } + + HashMap allocSizeMap = new HashMap<>(); + for (ResourceWithVolumes rsc : resources) { + if (!LinstorUtil.isRscDiskless(rsc) && !rsc.getVolumes().isEmpty()) { + long allocatedBytes = allocSizeMap.getOrDefault(rsc.getName(), 0L); + allocSizeMap.put(rsc.getName(), Math.max(allocatedBytes, rsc.getVolumes().get(0).getAllocatedSizeKib() * 1024)); + } + } + + volumeStats.clear(); + for (Map.Entry entry : allocSizeMap.entrySet()) { + Long reserved = resSizeMap.getOrDefault(entry.getKey(), 0L); + Pair volStat = new Pair<>(entry.getValue(), reserved); + volumeStats.put(entry.getKey(), volStat); + } + volumeStatsLastUpdate = System.currentTimeMillis(); + s_logger.trace("Done volume stats cache update: " + volumeStats.size()); + } catch (ApiException e) { + s_logger.error("Unable to fetch Linstor resources: " + e.getBestMessage()); + } } @Override public Pair getVolumeStats(StoragePool storagePool, String volumeId) { - return null; + final DevelopersApi api = LinstorUtil.getLinstorAPI(storagePool.getHostAddress()); + synchronized (volumeStats) { + long invalidateCacheTime = volumeStatsLastUpdate + + LinstorConfigurationManager.VolumeStatsCacheTime.value() * 1000; + if (invalidateCacheTime < System.currentTimeMillis()) { + fillVolumeStatsCache(api); + } + return volumeStats.get(LinstorUtil.RSC_PREFIX + volumeId); + } } @Override diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java index 90ebf30f7cd..85a0804dbab 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java @@ -24,7 +24,14 @@ public class LinstorConfigurationManager implements Configurable public static final ConfigKey BackupSnapshots = new ConfigKey<>(Boolean.class, "lin.backup.snapshots", "Advanced", "true", "Backup Linstor primary storage snapshots to secondary storage (deleting ps snapshot)", true, ConfigKey.Scope.Global, null); - public static final ConfigKey[] CONFIG_KEYS = new ConfigKey[] { BackupSnapshots }; + public static final ConfigKey VolumeStatsCacheTime = new ConfigKey<>("Advanced", Integer.class, + "lin.volumes.stats.cachetime", "300", + "Cache time of volume stats for Linstor volumes. 0 to disable volume stats", + false); + + public static final ConfigKey[] CONFIG_KEYS = new ConfigKey[] { + BackupSnapshots, VolumeStatsCacheTime + }; @Override public String getConfigComponentName() diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java index e252753502c..60d06590006 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java @@ -195,6 +195,30 @@ public class LinstorUtil { } } + public static Pair getStorageStats(String linstorUrl, String rscGroupName) { + DevelopersApi linstorApi = getLinstorAPI(linstorUrl); + try { + List storagePools = LinstorUtil.getRscGroupStoragePools(linstorApi, rscGroupName); + + long capacity = storagePools.stream() + .filter(sp -> sp.getProviderKind() != ProviderKind.DISKLESS) + .mapToLong(sp -> sp.getTotalCapacity() != null ? sp.getTotalCapacity() : 0L) + .sum() * 1024; // linstor uses kiB + + long used = storagePools.stream() + .filter(sp -> sp.getProviderKind() != ProviderKind.DISKLESS) + .mapToLong(sp -> sp.getTotalCapacity() != null && sp.getFreeCapacity() != null ? + sp.getTotalCapacity() - sp.getFreeCapacity() : 0L) + .sum() * 1024; // linstor uses Kib + s_logger.debug( + String.format("Linstor(%s;%s): storageStats -> %d/%d", linstorUrl, rscGroupName, capacity, used)); + return new Pair<>(capacity, used); + } catch (ApiException apiEx) { + s_logger.error(apiEx.getMessage()); + throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx); + } + } + /** * Check if any resource of the given name is InUse on any host. * @@ -303,7 +327,7 @@ public class LinstorUtil { public static List getRDListStartingWith(DevelopersApi api, String startWith) throws ApiException { - List rscDfns = api.resourceDefinitionList(null, null, null, null); + List rscDfns = api.resourceDefinitionList(null, false, null, null, null); return rscDfns.stream() .filter(rscDfn -> rscDfn.getName().toLowerCase().startsWith(startWith.toLowerCase())) @@ -386,7 +410,7 @@ public class LinstorUtil { */ public static ResourceDefinition findResourceDefinition(DevelopersApi api, String rscName, String rscGrpName) throws ApiException { - List rscDfns = api.resourceDefinitionList(null, null, null, null); + List rscDfns = api.resourceDefinitionList(null, false, null, null, null); List rdsStartingWith = rscDfns.stream() .filter(rscDfn -> rscDfn.getName().toLowerCase().startsWith(rscName.toLowerCase())) @@ -402,4 +426,8 @@ public class LinstorUtil { return rd.orElseGet(() -> rdsStartingWith.get(0)); } + + public static boolean isRscDiskless(ResourceWithVolumes rsc) { + return rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS); + } } diff --git a/pom.xml b/pom.xml index 483d380cb9c..4662356f203 100644 --- a/pom.xml +++ b/pom.xml @@ -169,7 +169,7 @@ 10.1 2.6.6 0.6.0 - 0.6.0 + 0.6.1 0.10.2 3.4.4_1 4.0.1