Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
531c80a
CSTACKEX-261: enabling cloudstack volume resize (GROW) for iscsi and nfs
sathvikaragi Sep 15, 2026
91847be
CSTACKEX-261: RESOLVING COMMENTS
sathvikaragi Sep 23, 2026
3b2be0c
CSTACKEX-261: RESOLVING COMMENTS - PT2
sathvikaragi Sep 23, 2026
067268d
CSTACKEX-261: resolving comments and build issues
sathvikaragi Sep 23, 2026
c9f59ea
CSTACKEX-261: Adding more UT's
sathvikaragi Sep 23, 2026
82c7d15
CSTACKEX-261: handling casee where lun is deleted on ontap and trying…
sathvikaragi Sep 23, 2026
57f98f2
CSTACKEX-261: adding more UT's and any null checks
sathvikaragi Sep 24, 2026
ca6e07e
CSTACKEX-261: resolving comments
sathvikaragi Sep 29, 2026
6e53989
CSTACKEX-261: Updating the UT's based on changes
sathvikaragi Sep 29, 2026
44ad4e0
CSTACKEX-261: changing the exception for shrinking to unable to shrin…
sathvikaragi Sep 29, 2026
ba5e7bc
CSTCAKEX-261: fetching only space field from ontap instead of the who…
sathvikaragi Oct 5, 2026
c6ecf8f
Merge branch 'main' into feature/CSTACKEX-261
sathvikaragi Oct 5, 2026
d974282
CSTCAKEX-261: fixing build failures
sathvikaragi Oct 5, 2026
009571d
CSTACKEX-261: small fixes
sathvikaragi Oct 5, 2026
dea8937
Merge branch 'main' into feature/CSTACKEX-261
sathvikaragi Oct 8, 2026
d89b007
CSTACKEX-261: fixing build failures
sathvikaragi Oct 8, 2026
a68c1dc
CSTACKEX-261: fixing build failures-2
sathvikaragi Oct 8, 2026
8e9d98c
Merge branch 'main' into feature/CSTACKEX-261
sathvikaragi Oct 8, 2026
04da9b6
CSTACKEX-261: fixing the build failure after resolving merge conflicts
sathvikaragi Oct 8, 2026
d66f74e
[CSTACKEX-328] Rebase and Resolve Merge conflicts
Oct 8, 2026
189be5a
[CSTACKEX-328] Rebase and Resolve Merge conflicts
Oct 8, 2026
cdcdf7d
[CSTACKEX-328] Fix ISCSI qos issue
Oct 8, 2026
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 @@ -15,14 +15,14 @@
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.user.volume;
import org.apache.cloudstack.api.BaseAsyncCmd;

import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiArgValidator;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.ServerApiException;
Expand Down Expand Up @@ -60,7 +60,7 @@ public class ResizeVolumeCmd extends BaseAsyncCmd implements UserCmd {
@Parameter(name = ApiConstants.MAX_IOPS, type = CommandType.LONG, required = false, description = "New maximum number of IOPS")
private Long maxIops;

@Parameter(name = ApiConstants.SIZE, type = CommandType.LONG, required = false, description = "New volume size in GB")
@Parameter(name = ApiConstants.SIZE, type = CommandType.LONG, required = false, description = "New volume size in GB",validations = {ApiArgValidator.PositiveNumber})
Comment thread
sathvikaragi marked this conversation as resolved.
private Long size;

@Parameter(name = ApiConstants.SHRINK_OK, type = CommandType.BOOLEAN, required = false, description = "Verify OK to Shrink")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,14 @@
*/
package org.apache.cloudstack.storage.driver;

import org.apache.cloudstack.storage.utils.OntapStorageConstants;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.Storage;
import com.cloud.storage.StoragePool;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeDetailVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.ScopeType;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.VMTemplateStoragePoolVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.SnapshotDetailsDao;
import com.cloud.storage.dao.SnapshotDetailsVO;
import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
Expand All @@ -55,7 +38,6 @@
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
Expand All @@ -78,17 +60,40 @@
import org.apache.cloudstack.storage.service.model.CloudStackVolume;
import org.apache.cloudstack.storage.service.model.ProtocolType;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.utils.OntapStorageConstants;
import org.apache.cloudstack.storage.utils.OntapStorageUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;

import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.storage.ResizeVolumeCommand;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.StorageFilerTO;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.ResizeVolumePayload;
import com.cloud.storage.ScopeType;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Storage;
import com.cloud.storage.StoragePool;
import com.cloud.storage.VMTemplateStoragePoolVO;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeDetailVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.SnapshotDetailsDao;
import com.cloud.storage.dao.SnapshotDetailsVO;
import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;

/**
* Primary datastore driver for NetApp ONTAP storage systems.
Expand Down Expand Up @@ -596,7 +601,66 @@ public boolean canCopy(DataObject srcData, DataObject destData) {
}

@Override
public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {}
public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
Comment thread
sathvikaragi marked this conversation as resolved.
CreateCmdResult result = null;
try {
if (!(data instanceof VolumeInfo)) {
throw new CloudRuntimeException("resize: Expected VolumeInfo but received " +
Comment thread
sathvikaragi marked this conversation as resolved.
Outdated
(data != null ? data.getClass().getSimpleName() : "null"));
}
VolumeInfo volumeInfo = (VolumeInfo) data;
Comment thread
sathvikaragi marked this conversation as resolved.
Outdated
Object rawPayload = volumeInfo.getpayload();
ResizeVolumePayload payload = (rawPayload instanceof ResizeVolumePayload)
? (ResizeVolumePayload) rawPayload : null;
if (payload == null || payload.newSize == null) {
throw new CloudRuntimeException("Invalid resize payload for volume " + volumeInfo.getId());
}
if (volumeInfo.getDataStore() == null) {
throw new CloudRuntimeException("Data store not found for volume " + volumeInfo.getId());
}

StoragePoolVO storagePool = storagePoolDao.findById(volumeInfo.getDataStore().getId());
if (storagePool == null) {
throw new CloudRuntimeException("Storage pool not found for volume " + volumeInfo.getId());
}
Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(storagePool.getId());

VolumeVO volumeVO = volumeDao.findById(volumeInfo.getId());
if (volumeVO == null) {
throw new CloudRuntimeException("Volume not found for id " + volumeInfo.getId());
}
if (payload.newSize < volumeVO.getSize()) {
throw new CloudRuntimeException(String.format(
Comment thread
sathvikaragi marked this conversation as resolved.
Outdated
"Storage pool %s does not support shrinking a volume.", storagePool.getName()));
}

StorageStrategy storageStrategy = OntapStorageUtils.getStrategyByStoragePoolDetails(details);
CloudStackVolume cloudStackVolume = new CloudStackVolume();
cloudStackVolume.setVolumeInfo(volumeInfo);
storageStrategy.resizeCloudStackVolume(cloudStackVolume, payload.newSize);

long currentSize = volumeVO.getSize();
volumeVO.setSize(payload.newSize);
if (!volumeDao.update(volumeVO.getId(), volumeVO)) {
throw new CloudRuntimeException("Failed to update volume " + volumeVO.getId()
+ " after resizing the ONTAP backing object");
}
String instanceName = payload.instanceName != null ? payload.instanceName : "none";

ResizeVolumeCommand resizeCmd = new ResizeVolumeCommand(volumeVO.getPath(),
Comment thread
sathvikaragi marked this conversation as resolved.
Outdated
Comment thread
sathvikaragi marked this conversation as resolved.
Outdated
new StorageFilerTO(storagePool), currentSize, payload.newSize,
false, instanceName);
result = new CreateCmdResult(volumeVO.getPath(), new Answer(resizeCmd, true, null));
Comment thread
sathvikaragi marked this conversation as resolved.
Outdated
logger.info("resize: Successfully resized volume [{}] to [{}] bytes", volumeInfo.getId(), payload.newSize);
} catch (Exception e) {
String errMsg = e.getMessage();
logger.error("resize: Failed for volume [{}]: {}", data != null ? data.getId() : null, errMsg, e);
result = new CreateCmdResult(null, new Answer(null, false, errMsg));
result.setResult(errMsg);
} finally {
callback.complete(result);
}
}

@Override
public ChapInfo getChapInfo(DataObject dataObject) {
Expand Down Expand Up @@ -1018,9 +1082,57 @@ private boolean isTemplateCachedOnPool(VMTemplateStoragePoolVO templatePoolRef,
return StringUtils.isNotBlank(templatePoolRef.getInstallPath());
}

/**
* Returns the bytes used on the FlexVolume backing this pool, read directly from ONTAP
* ({@code space.used}).
*
* <p>Fails closed when ONTAP cannot provide trustworthy usage data. Returning zero for an
* unreachable or incomplete backend would make capacity checks treat an unknown pool as empty
* and could incorrectly authorize a volume grow.</p>
*
* @throws InvalidParameterValueException if {@code storagePool} is null
* @throws CloudRuntimeException if the pool has no FlexVolume UUID in its details, ONTAP
* cannot be queried, or used-space data is missing
*/
@Override
public long getUsedBytes(StoragePool storagePool) {
return 0;
if (storagePool == null) {
throw new InvalidParameterValueException("storagePool is null, ensure the pool exists and is fully initialised before querying used bytes");
}

Map<String, String> poolDetails = storagePoolDetailsDao.listDetailsKeyPairs(storagePool.getId());
String flexVolUuid = poolDetails != null ? poolDetails.get(OntapStorageConstants.VOLUME_UUID) : null;

if (StringUtils.isBlank(flexVolUuid)) {
throw new CloudRuntimeException("FlexVolume UUID not found in pool details for pool " + storagePool.getId());
}

try {
StorageStrategy strategy = OntapStorageUtils.getStrategyByStoragePoolDetails(poolDetails);
var flexVol = strategy.getStorageVolume(flexVolUuid);

if (flexVol == null) {
throw new CloudRuntimeException(String.format(
"FlexVolume [%s] backing pool [%s] was not found on ONTAP",
flexVolUuid, storagePool.getId()));
}
if (flexVol.getSpace() == null) {
throw new CloudRuntimeException(String.format(
"ONTAP returned no space information for FlexVolume [%s] backing pool [%s]",
flexVolUuid, storagePool.getId()));
}

logger.debug("getUsedBytes: FlexVolume [{}] backing pool [{}] reports {} bytes used",
flexVolUuid, storagePool.getId(), flexVol.getSpace().getUsed());
return flexVol.getSpace().getUsed();
Comment thread
sathvikaragi marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

check the return type of getUsed and have the validation

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@JsonInclude(NON_NULL) only excludes null fields when sending JSON. if used is omitted or null during deserialization, primitive long defaults to 0.

} catch (CloudRuntimeException e) {
logger.error("getUsedBytes: Failed to get used bytes for pool [{}]", storagePool.getId(), e);
throw e;
} catch (Exception e) {
logger.error("getUsedBytes: Failed to get used bytes for pool [{}]", storagePool.getId(), e);
throw new CloudRuntimeException(
String.format("Could not read used space for pool [%s]: %s", storagePool.getId(), e.getMessage()), e);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,27 @@ public void deleteStorageVolume(Volume volume) {
* @return the retrieved Volume object
*/
public Volume getStorageVolume(Volume volume) {
Comment thread
sathvikaragi marked this conversation as resolved.
Outdated
return null;
return getStorageVolume(volume.getUuid());
}

public Volume getStorageVolume(String uuid) {
if (uuid == null || uuid.isBlank()) {
Comment thread
sathvikaragi marked this conversation as resolved.
Outdated
throw new CloudRuntimeException("Cannot fetch ONTAP volume: UUID is null or empty");
}
logger.info("getStorageVolume: Fetching ONTAP volume by UUID: {}", uuid);
Comment thread
sathvikaragi marked this conversation as resolved.
Outdated
String authHeader = OntapStorageUtils.generateAuthHeader(storage.getUsername(), storage.getPassword());
try {
Volume fetchedVolume = volumeFeignClient.getVolumeByUUID(authHeader, uuid);
logger.info("getStorageVolume: Volume [{}] fetched successfully", uuid);
return fetchedVolume;
} catch (FeignException e) {
if (OntapStorageUtils.isOntapObjectNotFoundError(e)) {
logger.warn("getStorageVolume: Volume [{}] not found in ONTAP", uuid);
return null;
}
logger.error("getStorageVolume: Exception while fetching volume [{}]: ", uuid, e);
throw new CloudRuntimeException("Failed to fetch volume: " + e.getMessage());
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import com.cloud.agent.api.storage.ResizeVolumeCommand;
import com.cloud.agent.api.to.StorageFilerTO;
import com.cloud.host.HostVO;
import com.cloud.storage.ResizeVolumePayload;
import com.cloud.storage.Storage;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VolumeDao;
Expand Down Expand Up @@ -223,8 +224,15 @@ private Answer resizeVolumeOnKVMHost(DataObject volumeInfo, long sizeInBytes) {
throw new CloudRuntimeException("Storage Pool not found for id: " + volume.getPoolId());
}

ResizeVolumeCommand cmd = new ResizeVolumeCommand(volume.getPath(), new StorageFilerTO(storagePool),
volume.getSize(), sizeInBytes, false, null);
// instanceName is set by VolumeApiServiceImpl.orchestrateResizeVolume() before calling the
// driver — it is the VM instance name when attached, or "none" when the volume is detached.
ResizeVolumePayload resizePayload = volumeObject.getpayload() instanceof ResizeVolumePayload
Comment thread
sathvikaragi marked this conversation as resolved.
? (ResizeVolumePayload) volumeObject.getpayload()
: null;
String instanceName = resizePayload != null ? resizePayload.instanceName : "none";
ResizeVolumeCommand cmd = new ResizeVolumeCommand(volume.getPath(), new StorageFilerTO(storagePool),
Comment thread
sathvikaragi marked this conversation as resolved.
volume.getSize(), sizeInBytes, false, instanceName);

EndPoint ep = epSelector.select(volumeInfo);
if (ep == null) {
String errMsg = "No remote endpoint to send ResizeVolumeCommand, check if host is up";
Expand Down
Loading
Loading