Skip to content

Commit dca606b

Browse files
author
Eugenio Grosso
committed
flasharray: fall back to array capacity when pod has no quota
FlashArrayAdapter.getManagedStorageStats() returns null whenever the backing pod has no volumes (footprint == 0) and never reports anything other than the pod quota otherwise. A freshly-registered pool that sits on a pod without an explicit quota therefore shows disksizetotal=0, disksizeused=0 and the ClusterScopeStoragePoolAllocator refuses to allocate any volume against it (zero-capacity pool is skipped). The plugin is unusable until a pod quota is set manually on the array - which is not documented anywhere and not discoverable from the CloudStack side. Fix: fall back to the arrays total physical capacity (retrieved via GET /arrays?space=true) when the pod has no quota, or when the quota is zero. The used value falls back to the pod footprint, defaulting to 0 when absent. Only return null when no capacity value is obtainable at all, which now only happens if the array itself is unreachable. The math for usedBytes was also simplified: the previous form pod.getQuotaLimit() - (pod.getQuotaLimit() - pod.getFootprint()) is just pod.getFootprint() with an extra NPE risk when getQuotaLimit() is null.
1 parent 9f96c9d commit dca606b

1 file changed

Lines changed: 67 additions & 3 deletions

File tree

  • plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray

plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@
2626
import java.text.SimpleDateFormat;
2727
import java.util.ArrayList;
2828
import java.util.HashMap;
29+
import java.util.concurrent.ConcurrentHashMap;
30+
import java.util.concurrent.ConcurrentMap;
2931
import java.util.Map;
3032

3133
import javax.net.ssl.HostnameVerifier;
3234
import javax.net.ssl.SSLContext;
3335

36+
import org.apache.commons.collections4.CollectionUtils;
3437
import org.apache.http.Header;
3538
import org.apache.http.NameValuePair;
3639
import org.apache.cloudstack.storage.datastore.adapter.ProviderAdapter;
@@ -452,18 +455,79 @@ public void disconnect() {
452455
@Override
453456
public ProviderVolumeStorageStats getManagedStorageStats() {
454457
FlashArrayPod pod = getVolumeNamespace(this.pod);
455-
// just in case
456-
if (pod == null || pod.getFootprint() == 0) {
458+
if (pod == null) {
457459
return null;
458460
}
459461
Long capacityBytes = pod.getQuotaLimit();
460-
Long usedBytes = pod.getQuotaLimit() - (pod.getQuotaLimit() - pod.getFootprint());
462+
if (capacityBytes == null || capacityBytes == 0) {
463+
// Pod has no explicit quota set; report the array total physical
464+
// capacity so the CloudStack allocator has a real ceiling to plan
465+
// against rather than bailing out with a zero-capacity pool.
466+
capacityBytes = getArrayTotalCapacity();
467+
}
468+
if (capacityBytes == null || capacityBytes == 0) {
469+
return null;
470+
}
471+
Long usedBytes = pod.getFootprint();
472+
if (usedBytes == null) {
473+
usedBytes = 0L;
474+
}
461475
ProviderVolumeStorageStats stats = new ProviderVolumeStorageStats();
462476
stats.setCapacityInBytes(capacityBytes);
463477
stats.setActualUsedInBytes(usedBytes);
464478
return stats;
465479
}
466480

481+
/**
482+
* Cache of array total capacity keyed by FlashArray URL. The capacity of a
483+
* physical FlashArray changes only when hardware is added or removed, so a
484+
* several-minute TTL is safe and avoids an extra REST call on every
485+
* storage stats refresh for every pool that has no pod quota set.
486+
*/
487+
private static final ConcurrentMap<String, CachedCapacity> ARRAY_CAPACITY_CACHE = new ConcurrentHashMap<>();
488+
private static final long ARRAY_CAPACITY_CACHE_TTL_MS = 5L * 60L * 1000L;
489+
490+
private static final class CachedCapacity {
491+
final long capacityBytes;
492+
final long expiresAtMs;
493+
494+
CachedCapacity(long capacityBytes, long ttlMs) {
495+
this.capacityBytes = capacityBytes;
496+
this.expiresAtMs = System.currentTimeMillis() + ttlMs;
497+
}
498+
499+
boolean isExpired() {
500+
return System.currentTimeMillis() > expiresAtMs;
501+
}
502+
}
503+
504+
private Long getArrayTotalCapacity() {
505+
CachedCapacity cached = ARRAY_CAPACITY_CACHE.get(this.url);
506+
if (cached != null && !cached.isExpired()) {
507+
return cached.capacityBytes;
508+
}
509+
try {
510+
FlashArrayList<Map<String, Object>> list = GET("/arrays?space=true",
511+
new TypeReference<FlashArrayList<Map<String, Object>>>() {
512+
});
513+
if (list != null && CollectionUtils.isNotEmpty(list.getItems())) {
514+
Object cap = list.getItems().get(0).get("capacity");
515+
if (cap instanceof Number) {
516+
long capacityBytes = ((Number) cap).longValue();
517+
ARRAY_CAPACITY_CACHE.put(this.url,
518+
new CachedCapacity(capacityBytes, ARRAY_CAPACITY_CACHE_TTL_MS));
519+
return capacityBytes;
520+
}
521+
}
522+
} catch (Exception e) {
523+
logger.warn("Could not retrieve total capacity for FlashArray [{}] (pod [{}]): {}",
524+
this.url, this.pod, e.getMessage());
525+
logger.debug("Stack trace for array total capacity lookup failure on FlashArray [{}] (pod [{}])",
526+
this.url, this.pod, e);
527+
}
528+
return null;
529+
}
530+
467531
@Override
468532
public ProviderVolumeStats getVolumeStats(ProviderAdapterContext context, ProviderAdapterDataObject dataObject) {
469533
ProviderVolume vol = getVolume(dataObject.getExternalName());

0 commit comments

Comments
 (0)