Skip to content
Open
Show file tree
Hide file tree
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 @@ -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,63 @@ public boolean canCopy(DataObject srcData, DataObject destData) {
}

@Override
public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {}
public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
CreateCmdResult result = null;
try {
if (!(data instanceof VolumeInfo)) {
throw new CloudRuntimeException("resize: Expected VolumeInfo but received " +
(data != null ? data.getClass().getSimpleName() : "null"));
}
VolumeInfo volumeInfo = (VolumeInfo) data;
Comment thread
sathvikaragi marked this conversation as resolved.
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());
}

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());

StorageStrategy storageStrategy = OntapStorageUtils.getStrategyByStoragePoolDetails(details);
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(
"Storage pool %s does not support shrinking a volume.", storagePool.getName()));
}

CloudStackVolume cloudStackVolume = new CloudStackVolume();
cloudStackVolume.setVolumeInfo(volumeInfo);

// delegates to UnifiedSANStrategy (PATCH /api/storage/luns/{uuid}) for iSCSI
// or to UnifiedNASStrategy (ResizeVolumeCommand to KVM agent) for NFS3;
// protocol-specific setup (e.g. LUN UUID lookup) is handled inside each strategy
storageStrategy.resizeCloudStackVolume(cloudStackVolume, payload.newSize);

volumeVO.setSize(payload.newSize);
volumeDao.update(volumeVO.getId(), volumeVO);
String instanceName = payload.instanceName != null ? payload.instanceName : "none";

ResizeVolumeCommand resizeCmd = new ResizeVolumeCommand(volumeVO.getPath(),
new StorageFilerTO(storagePool), volumeVO.getSize(), payload.newSize,
false, instanceName);
result = new CreateCmdResult(volumeVO.getPath(), new Answer(resizeCmd, true, null));
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.getId(), 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 +1079,48 @@ private boolean isTemplateCachedOnPool(VMTemplateStoragePoolVO templatePoolRef,
return StringUtils.isNotBlank(templatePoolRef.getInstallPath());
}

/**
* Returns the bytes available on the FlexVolume backing this pool, read directly from ONTAP
* ({@code space.available}).
*
* <p>Returns {@code 0} if the ONTAP REST call fails for any reason (array unreachable, auth
* error, etc.). Throws if the FlexVolume UUID is not recorded in pool details, since that
* indicates the pool was never fully provisioned.</p>
*
* @throws InvalidParameterValueException if {@code storagePool} is null
* @throws CloudRuntimeException if the pool has no FlexVolume UUID in its details
*/
@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 || flexVol.getSpace() == null) {
logger.warn("getUsedBytes: FlexVolume [{}] not found or has no space info for pool [{}]; returning 0",
flexVolUuid, storagePool.getId());
return 0;
}

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.
} catch (Exception e) {
logger.warn("getUsedBytes: Could not read used space from ONTAP for pool [{}]; returning 0",
storagePool.getId(), e);
return 0;
}
}

@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) {
return null;
return getStorageVolume(volume.getUuid());
}

public Volume getStorageVolume(String uuid) {
if (uuid == null || uuid.isBlank()) {
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.
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,23 @@

package org.apache.cloudstack.storage.service;

import com.cloud.host.HostVO;
import com.cloud.utils.exception.CloudRuntimeException;
import feign.FeignException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.feign.model.CliSnapshotRestoreRequest;
import org.apache.cloudstack.storage.feign.model.Igroup;
import org.apache.cloudstack.storage.feign.model.Initiator;
import org.apache.cloudstack.storage.feign.model.Svm;
import org.apache.cloudstack.storage.feign.model.OntapStorage;
import org.apache.cloudstack.storage.feign.model.Lun;
import org.apache.cloudstack.storage.feign.model.LunMap;
import org.apache.cloudstack.storage.feign.model.LunSpace;
import org.apache.cloudstack.storage.feign.model.CliSnapshotRestoreRequest;
import org.apache.cloudstack.storage.feign.model.OntapStorage;
import org.apache.cloudstack.storage.feign.model.Svm;
import org.apache.cloudstack.storage.feign.model.response.JobResponse;
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
import org.apache.cloudstack.storage.service.model.AccessGroup;
Expand All @@ -43,16 +46,21 @@
import org.apache.commons.collections.CollectionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.cloud.host.HostVO;
import com.cloud.storage.VolumeDetailVO;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.utils.exception.CloudRuntimeException;

import feign.FeignException;

public class UnifiedSANStrategy extends SANStrategy {

private static final Logger logger = LogManager.getLogger(UnifiedSANStrategy.class);
@Inject
private StoragePoolDetailsDao storagePoolDetailsDao;
@Inject
private VolumeDetailsDao volumeDetailsDao;

public UnifiedSANStrategy(OntapStorage ontapStorage) {
super(ontapStorage);
Expand Down Expand Up @@ -280,13 +288,26 @@ private void validateCreatedLun(Lun lun, String requestName, String operation) {
*/
@Override
public void resizeCloudStackVolume(CloudStackVolume cloudstackVolume, long sizeInBytes) {
if (cloudstackVolume == null || cloudstackVolume.getLun() == null || cloudstackVolume.getLun().getUuid() == null) {
if (cloudstackVolume == null || cloudstackVolume.getVolumeInfo() == null) {
logger.error("resizeCloudStackVolume: Lun resize failed. Invalid request: {}", cloudstackVolume);
throw new CloudRuntimeException("Failed to resize Lun, invalid request");
}
if (sizeInBytes <= 0) {
throw new CloudRuntimeException("Failed to resize Lun, invalid size " + sizeInBytes);
}

// Resolve LUN UUID from volume details when not pre-populated on the cloudstackVolume
if (cloudstackVolume.getLun() == null || cloudstackVolume.getLun().getUuid() == null) {
long volumeId = cloudstackVolume.getVolumeInfo().getId();
VolumeDetailVO lunUuidDetail = volumeDetailsDao.findDetail(volumeId, OntapStorageConstants.LUN_DOT_UUID);
if (lunUuidDetail == null || lunUuidDetail.getValue() == null) {
throw new CloudRuntimeException("LUN UUID not found in volume details for volume " + volumeId);
}
Lun resolvedLun = new Lun();
resolvedLun.setUuid(lunUuidDetail.getValue());
cloudstackVolume.setLun(resolvedLun);
}

String lunUuid = cloudstackVolume.getLun().getUuid();
logger.trace("resizeCloudStackVolume: Resizing Lun {} to {} bytes", lunUuid, sizeInBytes);
try {
Expand All @@ -298,12 +319,19 @@ public void resizeCloudStackVolume(CloudStackVolume cloudstackVolume, long sizeI
sanFeignClient.updateLun(authHeader, lunUuid, patch);
logger.debug("resizeCloudStackVolume: Lun {} resized to {} bytes", lunUuid, sizeInBytes);
} catch (FeignException e) {
logger.error("FeignException occurred while resizing LUN: {}, Status: {}, Exception: {}",
logger.error("FeignException occurred while resizing LUN [{}], Status: {}, Exception: {}",
lunUuid, e.status(), e.getMessage());
throw new CloudRuntimeException("Failed to resize Lun: " + e.getMessage());
if (OntapStorageUtils.isOntapObjectNotFoundError(e)) {
throw new CloudRuntimeException(String.format(
"LUN [%s] no longer exists on ONTAP; it may have been deleted externally. " +
"Verify the LUN is present before retrying the resize.", lunUuid));
}
throw new CloudRuntimeException(String.format(
"Failed to resize LUN [%s]: %s",lunUuid, e.getMessage()));
} catch (Exception e) {
logger.error("Exception occurred while resizing LUN: {}, Exception: {}", lunUuid, e.getMessage());
throw new CloudRuntimeException("Failed to resize Lun: " + e.getMessage());
logger.error("Exception occurred while resizing LUN [{}]: {}", lunUuid, e.getMessage());
throw new CloudRuntimeException(String.format(
"Unexpected error while resizing LUN [%s]: %s", lunUuid, e.getMessage()));
}
}

Expand Down
Loading
Loading