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,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) {
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());
}
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(
"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(),
new StorageFilerTO(storagePool), currentSize, 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 != 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.
} 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) {
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
Loading
Loading