Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.cloudstack.api.ApiCommandResourceType;

import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiArgValidator;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
Expand Down Expand Up @@ -53,7 +54,8 @@ public class UpdateStoragePoolCmd extends BaseCmd {
@Parameter(name = ApiConstants.TAGS, type = CommandType.LIST, collectionType = CommandType.STRING, description = "Comma-separated list of tags for the storage pool")
private List<String> tags;

@Parameter(name = ApiConstants.CAPACITY_IOPS, type = CommandType.LONG, required = false, description = "IOPS CloudStack can provision from this storage pool")
@Parameter(name = ApiConstants.CAPACITY_IOPS, type = CommandType.LONG, required = false, description = "IOPS CloudStack can provision from this storage pool",
validations = {ApiArgValidator.PositiveNumber})
private Long capacityIops;

@Parameter(name = ApiConstants.CAPACITY_BYTES, type = CommandType.LONG, required = false, description = "Bytes CloudStack can provision from this storage pool")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,6 @@
import com.cloud.vm.dao.VMInstanceDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;

import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS;
import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS;

@Component
public class CloudOrchestrator implements OrchestrationService {

Expand Down Expand Up @@ -205,8 +202,8 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String
Map<String, String> userVmDetails = _vmInstanceDetailsDao.listDetailsKeyPairs(vm.getId());

if (userVmDetails != null) {
String minIops = userVmDetails.get(MIN_IOPS);
String maxIops = userVmDetails.get(MAX_IOPS);
String minIops = userVmDetails.get("minIops");
String maxIops = userVmDetails.get("maxIops");

rootDiskOfferingInfo.setMinIops(minIops != null && minIops.trim().length() > 0 ? Long.parseLong(minIops) : null);
rootDiskOfferingInfo.setMaxIops(maxIops != null && maxIops.trim().length() > 0 ? Long.parseLong(maxIops) : null);
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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.feign.client;

import java.util.Map;

import org.apache.cloudstack.storage.feign.model.VolumeQosPolicy;
import org.apache.cloudstack.storage.feign.model.response.JobResponse;
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;

import feign.Headers;
import feign.Param;
import feign.QueryMap;
import feign.RequestLine;

public interface QosFeignClient {

@RequestLine("POST /api/storage/qos/policies")
@Headers({"Authorization: {authHeader}"})
JobResponse createPolicy(@Param("authHeader") String authHeader, VolumeQosPolicy policy);

@RequestLine("GET /api/storage/qos/policies")
@Headers({"Authorization: {authHeader}"})
OntapResponse<VolumeQosPolicy> getPolicies(@Param("authHeader") String authHeader,
@QueryMap Map<String, Object> queryParams);

@RequestLine("GET /api/storage/qos/policies/{uuid}")
@Headers({"Authorization: {authHeader}"})
VolumeQosPolicy getPolicy(@Param("authHeader") String authHeader, @Param("uuid") String uuid,
@QueryMap Map<String, Object> queryParams);

@RequestLine("DELETE /api/storage/qos/policies/{uuid}")
@Headers({"Authorization: {authHeader}"})
JobResponse deletePolicy(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ public interface SANFeignClient {

@RequestLine("PATCH /api/storage/luns/{uuid}")
@Headers({"Authorization: {authHeader}", "Content-Type: application/json"})
void updateLun(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Lun lun);
JobResponse updateLun(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Lun lun);

@RequestLine("DELETE /api/storage/luns/{uuid}")
@Headers({"Authorization: {authHeader}"})
void deleteLun(@Param("authHeader") String authHeader, @Param("uuid") String uuid, @QueryMap Map<String, Object> queryMap);
JobResponse deleteLun(@Param("authHeader") String authHeader, @Param("uuid") String uuid, @QueryMap Map<String, Object> queryMap);

// iGroup Operation APIs
@RequestLine("POST /api/protocols/san/igroups?return_records={returnRecords}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public class FileInfo {
private Boolean overwriteEnabled = null;
@JsonProperty("path")
private String path = null;
@JsonProperty("qos_policy")
private VolumeQosPolicy qosPolicy = null;
@JsonProperty("size")
private Long size = null;
@JsonProperty("target")
Expand Down Expand Up @@ -178,6 +180,15 @@ public String getPath() {
public void setPath(String path) {
this.path = path;
}

public VolumeQosPolicy getQosPolicy() {
return qosPolicy;
}

public void setQosPolicy(VolumeQosPolicy qosPolicy) {
this.qosPolicy = qosPolicy;
}

public Long getSize() {
return size;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ public static PropertyClassEnum fromValue(String value) {
@JsonProperty("name")
private String name = null;

@JsonProperty("qos_policy")
private VolumeQosPolicy qosPolicy = null;

@JsonProperty("clone")
private Clone clone = null;

Expand Down Expand Up @@ -202,6 +205,14 @@ public void setName(String name) {
this.name = name;
}

public VolumeQosPolicy getQosPolicy() {
return qosPolicy;
}

public void setQosPolicy(VolumeQosPolicy qosPolicy) {
this.qosPolicy = qosPolicy;
}

public Lun osType(OsTypeEnum osType) {
this.osType = osType;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,43 +26,23 @@
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class VolumeQosPolicy {
@JsonProperty("max_throughput_iops")
private Integer maxThroughputIops = null;

@JsonProperty("max_throughput_mbps")
private Integer maxThroughputMbps = null;

@JsonProperty("min_throughput_iops")
private Integer minThroughputIops = null;

@JsonProperty("fixed")
private Fixed fixed;
@JsonProperty("name")
private String name = null;

@JsonProperty("uuid")
private String uuid = null;
@JsonProperty("svm")
private Svm svm;
@JsonProperty("object_count")
private Integer objectCount;

public Integer getMaxThroughputIops() {
return maxThroughputIops;
}

public void setMaxThroughputIops(Integer maxThroughputIops) {
this.maxThroughputIops = maxThroughputIops;
}

public Integer getMaxThroughputMbps() {
return maxThroughputMbps;
public Fixed getFixed() {
return fixed;
}

public void setMaxThroughputMbps(Integer maxThroughputMbps) {
this.maxThroughputMbps = maxThroughputMbps;
}

public Integer getMinThroughputIops() {
return minThroughputIops;
}

public void setMinThroughputIops(Integer minThroughputIops) {
this.minThroughputIops = minThroughputIops;
public void setFixed(Fixed fixed) {
this.fixed = fixed;
}

public String getName() {
Expand All @@ -80,4 +60,57 @@ public String getUuid() {
public void setUuid(String uuid) {
this.uuid = uuid;
}

public Svm getSvm() {
return svm;
}

public void setSvm(Svm svm) {
this.svm = svm;
}

public Integer getObjectCount() {
return objectCount;
}

public void setObjectCount(Integer objectCount) {
this.objectCount = objectCount;
}

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Fixed {
@JsonProperty("capacity_shared")
private Boolean capacityShared;

@JsonProperty("min_throughput_iops")
private Long minThroughputIops;

@JsonProperty("max_throughput_iops")
private Long maxThroughputIops;

public Boolean getCapacityShared() {
return capacityShared;
}

public void setCapacityShared(Boolean capacityShared) {
this.capacityShared = capacityShared;
}

public Long getMinThroughputIops() {
return minThroughputIops;
}

public void setMinThroughputIops(Long minThroughputIops) {
this.minThroughputIops = minThroughputIops;
}

public Long getMaxThroughputIops() {
return maxThroughputIops;
}

public void setMaxThroughputIops(Long maxThroughputIops) {
this.maxThroughputIops = maxThroughputIops;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@

import com.cloud.agent.api.StoragePoolInfo;
import com.cloud.alert.AlertManager;
import com.cloud.capacity.CapacityManager;
import com.cloud.dc.ClusterVO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.exception.InvalidParameterValueException;
Expand All @@ -81,6 +82,7 @@ public class OntapPrimaryDatastoreLifecycle extends BasePrimaryDataStoreLifeCycl
@Inject private PrimaryDataStoreDao storagePoolDao;
@Inject private StoragePoolDetailsDao storagePoolDetailsDao;
@Inject private AlertManager _alertMgr;
@Inject private CapacityManager _capacityMgr;
private static final Logger logger = LogManager.getLogger(OntapPrimaryDatastoreLifecycle.class);

private static final long ONTAP_MIN_VOLUME_SIZE_IN_BYTES = 20971520L;
Expand All @@ -101,6 +103,7 @@ public DataStore initialize(Map<String, Object> dsInfos) {
String storagePoolName = (String) dsInfos.get("name");
String providerName = (String) dsInfos.get("providerName");
Long capacityBytes = (Long) dsInfos.get("capacityBytes");
Long capacityIops = (Long) dsInfos.get("capacityIops");
boolean managed = (boolean) dsInfos.get("managed");
String tags = (String) dsInfos.get("tags");
Boolean isTagARule = (Boolean) dsInfos.get("isTagARule");
Expand All @@ -113,7 +116,7 @@ public DataStore initialize(Map<String, Object> dsInfos) {
@SuppressWarnings("unchecked")
Map<String, String> details = (Map<String, String>) dsInfos.get("details");

validateInitializeInputs(capacityBytes, podId, clusterId, zoneId, storagePoolName, providerName, managed, details);
validateInitializeInputs(capacityBytes, capacityIops, podId, clusterId, zoneId, storagePoolName, providerName, managed, details);

PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters();
if (clusterId != null) {
Expand All @@ -140,6 +143,7 @@ public DataStore initialize(Map<String, Object> dsInfos) {
StorageStrategy storageStrategy = StorageProviderFactory.getStrategy(ontapStorage);
boolean isValid = storageStrategy.connect();
if (isValid) {
details.put(OntapStorageConstants.IS_AFF, Boolean.toString(storageStrategy.isAff()));
if (storageStrategy.getResolvedSvmUuid() != null && !storageStrategy.getResolvedSvmUuid().isEmpty()) {
details.put(OntapStorageConstants.SVM_UUID, storageStrategy.getResolvedSvmUuid());
}
Expand Down Expand Up @@ -207,12 +211,13 @@ public DataStore initialize(Map<String, Object> dsInfos) {
parameters.setProviderName(providerName);
parameters.setManaged(managed);
parameters.setCapacityBytes(capacityBytes);
parameters.setCapacityIops(capacityIops);
parameters.setUsedBytes(0);

return _dataStoreHelper.createPrimaryDataStore(parameters);
}

private void validateInitializeInputs(Long capacityBytes, Long podId, Long clusterId, Long zoneId,
private void validateInitializeInputs(Long capacityBytes, Long capacityIops, Long podId, Long clusterId, Long zoneId,
String storagePoolName, String providerName, boolean managed, Map<String, String> details) {

if (capacityBytes == null || capacityBytes <= 0) {
Expand All @@ -223,6 +228,10 @@ private void validateInitializeInputs(Long capacityBytes, Long podId, Long clust
throw new InvalidParameterValueException("Storage pool capacity " + capacityBytes + " bytes is below the ONTAP minimum volume size of "
+ ONTAP_MIN_VOLUME_SIZE_IN_BYTES + " bytes (20 MB)");
}
// IOPS capacity is optional; when left blank no pool-level IOPS ceiling is enforced.
if (capacityIops != null && capacityIops <= 0) {
throw new InvalidParameterValueException("Storage pool IOPS capacity must be greater than 0");
}

// Validate scope
if (podId == null ^ clusterId == null) {
Expand Down Expand Up @@ -551,6 +560,11 @@ public boolean migrateToObjectStore(DataStore store) {

@Override
public void updateStoragePool(StoragePool storagePool, Map<String, String> details) {
String newCapacityIopsStr = details.get(PrimaryDataStoreLifeCycle.CAPACITY_IOPS);
if (newCapacityIopsStr != null) {
validateUpdatedCapacityIops(storagePool, newCapacityIopsStr);
}

String newCapacityStr = details.get(PrimaryDataStoreLifeCycle.CAPACITY_BYTES);
if (newCapacityStr == null) {
logger.debug("No capacity change requested for pool: {}, skipping FlexVolume resize", storagePool.getName());
Expand Down Expand Up @@ -581,6 +595,29 @@ public void updateStoragePool(StoragePool storagePool, Map<String, String> detai
}
}

private void validateUpdatedCapacityIops(StoragePool storagePool, String newCapacityIopsStr) {
long newCapacityIops;
try {
newCapacityIops = Long.parseLong(newCapacityIopsStr);
} catch (NumberFormatException e) {
throw new InvalidParameterValueException("Invalid storage pool IOPS capacity: " + newCapacityIopsStr);
}
if (newCapacityIops <= 0) {
throw new InvalidParameterValueException("Storage pool IOPS capacity must be greater than 0");
}

StoragePoolVO storagePoolVO = storagePoolDao.findById(storagePool.getId());
if (storagePoolVO == null) {
throw new InvalidParameterValueException("Storage pool not found for id: " + storagePool.getId());
}
long allocatedIops = _capacityMgr.getUsedIops(storagePoolVO);
if (newCapacityIops < allocatedIops) {
throw new InvalidParameterValueException(String.format(
"Cannot set IOPS capacity of storage pool %s to %d IOPS because %d IOPS are already allocated",
storagePool.getName(), newCapacityIops, allocatedIops));
}
}

@Override
public void enableStoragePool(DataStore store) {
_dataStoreHelper.enable(store);
Expand Down
Loading
Loading