Skip to content

Commit 0e8387f

Browse files
Eugenio Grossogenegr
authored andcommitted
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 3166e64 commit 0e8387f

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;
@@ -453,18 +456,79 @@ public void disconnect() {
453456
@Override
454457
public ProviderVolumeStorageStats getManagedStorageStats() {
455458
FlashArrayPod pod = getVolumeNamespace(this.pod);
456-
// just in case
457-
if (pod == null || pod.getFootprint() == 0) {
459+
if (pod == null) {
458460
return null;
459461
}
460462
Long capacityBytes = pod.getQuotaLimit();
461-
Long usedBytes = pod.getQuotaLimit() - (pod.getQuotaLimit() - pod.getFootprint());
463+
if (capacityBytes == null || capacityBytes == 0) {
464+
// Pod has no explicit quota set; report the array total physical
465+
// capacity so the CloudStack allocator has a real ceiling to plan
466+
// against rather than bailing out with a zero-capacity pool.
467+
capacityBytes = getArrayTotalCapacity();
468+
}
469+
if (capacityBytes == null || capacityBytes == 0) {
470+
return null;
471+
}
472+
Long usedBytes = pod.getFootprint();
473+
if (usedBytes == null) {
474+
usedBytes = 0L;
475+
}
462476
ProviderVolumeStorageStats stats = new ProviderVolumeStorageStats();
463477
stats.setCapacityInBytes(capacityBytes);
464478
stats.setActualUsedInBytes(usedBytes);
465479
return stats;
466480
}
467481

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

0 commit comments

Comments
 (0)