Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.Map;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.http.Header;
import org.apache.http.NameValuePair;
import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapter;
Expand Down Expand Up @@ -452,18 +455,79 @@ public void disconnect() {
@Override
public ProviderVolumeStorageStats getManagedStorageStats() {
FlashArrayPod pod = getVolumeNamespace(this.pod);
// just in case
if (pod == null || pod.getFootprint() == 0) {
if (pod == null) {
return null;
}
Long capacityBytes = pod.getQuotaLimit();
Long usedBytes = pod.getQuotaLimit() - (pod.getQuotaLimit() - pod.getFootprint());
if (capacityBytes == null || capacityBytes == 0) {
// Pod has no explicit quota set; report the array total physical
// capacity so the CloudStack allocator has a real ceiling to plan
// against rather than bailing out with a zero-capacity pool.
capacityBytes = getArrayTotalCapacity();
}
Comment on lines +462 to +467
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling GET("/arrays?space=true") inside getManagedStorageStats() can add a second REST request on every storage-stats refresh whenever a pod has no quota. Since FlashArrayAdapterFactory constructs a new adapter per call, this likely won’t be amortized/cached and could create avoidable API load. Consider memoizing the array capacity (e.g., static cache keyed by URL with a TTL) or persisting the discovered capacity into CloudStack pool details so subsequent stats calls don’t need to re-query /arrays each time.

Copilot uses AI. Check for mistakes.
if (capacityBytes == null || capacityBytes == 0) {
return null;
}
Long usedBytes = pod.getFootprint();
if (usedBytes == null) {
usedBytes = 0L;
}
ProviderVolumeStorageStats stats = new ProviderVolumeStorageStats();
stats.setCapacityInBytes(capacityBytes);
stats.setActualUsedInBytes(usedBytes);
return stats;
}

/**
* Cache of array total capacity keyed by FlashArray URL. The capacity of a
* physical FlashArray changes only when hardware is added or removed, so a
* several-minute TTL is safe and avoids an extra REST call on every
* storage stats refresh for every pool that has no pod quota set.
*/
private static final ConcurrentMap<String, CachedCapacity> ARRAY_CAPACITY_CACHE = new ConcurrentHashMap<>();
private static final long ARRAY_CAPACITY_CACHE_TTL_MS = 5L * 60L * 1000L;

private static final class CachedCapacity {
final long capacityBytes;
final long expiresAtMs;

CachedCapacity(long capacityBytes, long ttlMs) {
this.capacityBytes = capacityBytes;
this.expiresAtMs = System.currentTimeMillis() + ttlMs;
}

boolean isExpired() {
return System.currentTimeMillis() > expiresAtMs;
}
}

private Long getArrayTotalCapacity() {
CachedCapacity cached = ARRAY_CAPACITY_CACHE.get(this.url);
if (cached != null && !cached.isExpired()) {
return cached.capacityBytes;
}
try {
FlashArrayList<Map<String, Object>> list = GET("/arrays?space=true",
new TypeReference<FlashArrayList<Map<String, Object>>>() {
});
if (list != null && CollectionUtils.isNotEmpty(list.getItems())) {
Object cap = list.getItems().get(0).get("capacity");
if (cap instanceof Number) {
long capacityBytes = ((Number) cap).longValue();
ARRAY_CAPACITY_CACHE.put(this.url,
new CachedCapacity(capacityBytes, ARRAY_CAPACITY_CACHE_TTL_MS));
return capacityBytes;
}
}
} catch (Exception e) {
logger.warn("Could not retrieve total capacity for FlashArray [{}] (pod [{}]): {}",
this.url, this.pod, e.getMessage());
logger.debug("Stack trace for array total capacity lookup failure on FlashArray [{}] (pod [{}])",
this.url, this.pod, e);
}
return null;
}

@Override
public ProviderVolumeStats getVolumeStats(ProviderAdapterContext context, ProviderAdapterDataObject dataObject) {
ProviderVolume vol = getVolume(dataObject.getExternalName());
Expand Down
Loading