Skip to content
Draft
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 @@ -52,5 +52,5 @@ public interface VolumeFeignClient {

@RequestLine("PATCH /api/storage/volumes/{uuid}")
@Headers({ "Authorization: {authHeader}"})
JobResponse updateVolumeRebalancing(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Volume volumeRequest);
JobResponse updateVolume(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Volume volumeRequest);
}
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,34 @@ public boolean migrateToObjectStore(DataStore store) {

@Override
public void updateStoragePool(StoragePool storagePool, Map<String, String> details) {
String newCapacityStr = details.get(PrimaryDataStoreLifeCycle.CAPACITY_BYTES);
if (newCapacityStr == null) {
logger.debug("No capacity change requested for pool: {}, skipping FlexVolume resize", storagePool.getName());
return;
}

long currentCapacityBytes = storagePool.getCapacityBytes();
long newCapacityBytes = Long.parseLong(newCapacityStr);
StorageStrategy storageStrategy = OntapStorageUtils.getStrategyByStoragePoolDetails(details);

String volumeUuid = details.get(OntapStorageConstants.VOLUME_UUID);
if (volumeUuid == null || volumeUuid.isEmpty()) {
logger.error("Volume UUID or name not found in details for pool: {}, cannot resize", storagePool.getName());
throw new CloudRuntimeException("Volume UUID or name not found in details, cannot resize ONTAP FlexVolume");
}

Volume volume = new Volume();
volume.setUuid(volumeUuid);
volume.setName(details.get(OntapStorageConstants.VOLUME_NAME));
volume.setSize(newCapacityBytes);
try {
storageStrategy.updateStorageVolume(volume);
logger.info("Successfully resized ONTAP FlexVolume '{}' (UUID: {}) for pool '{}' from {} bytes to {} bytes",
volume.getName(), volume.getUuid(), storagePool.getName(), currentCapacityBytes, newCapacityBytes);
} catch (Exception e) {
logger.error("Exception while resizing FlexVolume for pool: {}. Error: {}", storagePool.getName(), e.getMessage(), e);
throw new CloudRuntimeException("Failed to resize ONTAP FlexVolume for pool: " + storagePool.getName() + ". " + e.getMessage(), e);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,25 @@ public Volume createStorageVolume(String volumeName, Long size) {
* @return the updated Volume object
*/
public Volume updateStorageVolume(Volume volume) {
return null;
logger.info("Resizing ONTAP FlexVolume '{}' (UUID: {}) to {} bytes", volume.getName(), volume.getUuid(), volume.getSize());
String authHeader = OntapStorageUtils.generateAuthHeader(storage.getUsername(), storage.getPassword());
try {
Volume resizeRequest = new Volume();
resizeRequest.setSize(volume.getSize());
JobResponse jobResponse = volumeFeignClient.updateVolume(authHeader, volume.getUuid(), resizeRequest);
pollJobIfPresent(jobResponse, "resize FlexVolume [" + volume.getUuid() + "]",
OntapStorageConstants.ONTAP_VOLUME_JOB_MAX_RETRIES, OntapStorageConstants.ONTAP_VOLUME_JOB_POLL_INTERVAL_MS);
logger.info("FlexVolume '{}' (UUID: {}) resized successfully to {} bytes", volume.getName(), volume.getUuid(), volume.getSize());
} catch (FeignException e) {
if (OntapStorageUtils.isOntapObjectNotFoundError(e)) {
String msg = String.format("Cannot resize FlexVolume '%s' (UUID: %s): volume not found on ONTAP (404). ", volume.getName(), volume.getUuid());
logger.error(msg);
throw new CloudRuntimeException(msg, e);
}
logger.error("Exception while resizing FlexVolume '{}' (UUID: {}): {}", volume.getName(), volume.getUuid(), e.getMessage(), e);
throw new CloudRuntimeException("Failed to resize ONTAP FlexVolume: " + e.getMessage(), e);
}
return volume;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ private void assignExportPolicyToVolume(String volumeUuid, String policyName) {
volumeUpdate.setNas(nas);

try {
JobResponse jobResponse = volumeFeignClient.updateVolumeRebalancing(authHeader, volumeUuid, volumeUpdate);
JobResponse jobResponse = volumeFeignClient.updateVolume(authHeader, volumeUuid, volumeUpdate);
if (jobResponse == null || jobResponse.getJob() == null) {
throw new CloudRuntimeException("Failed to attach policy " + policyName + "to volume " + volumeUuid);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,22 @@ public class OntapStorageConstants {
public static final String ONTAP_SNAP_SIZE = "ontap_snap_size";
public static final String FILE_PATH = "file_path";
public static final int MAX_SNAPSHOT_NAME_LENGTH = 255;
public static final String ONTAP_TEMP_CG_PREFIX = "cs-temp-cg-";
/** ONTAP CG API: action required when referencing existing FlexVols in a consistency group. */
public static final String CG_VOLUME_PROVISIONING_ACTION_ADD = "add";
public static final int ONTAP_CG_JOB_MAX_RETRIES = 60;
public static final int ONTAP_CG_JOB_POLL_INTERVAL_MS = 2000;
public static final int ONTAP_CG_SNAPSHOT_RESOLVE_MAX_RETRIES = 30;
public static final int ONTAP_CG_SNAPSHOT_RESOLVE_POLL_INTERVAL_MS = 1000;
public static final int ONTAP_SFSR_JOB_MAX_RETRIES = 60;
public static final int ONTAP_SFSR_JOB_POLL_INTERVAL_MS = 2000;
public static final int ONTAP_SNAPSHOT_DELETE_JOB_MAX_RETRIES = 30;
public static final int ONTAP_SNAPSHOT_DELETE_JOB_POLL_INTERVAL_MS = 2000;
/** Retry settings for FlexVolume create/resize/delete job polling. */
public static final int ONTAP_VOLUME_JOB_MAX_RETRIES = 10;
public static final int ONTAP_VOLUME_JOB_POLL_INTERVAL_MS = 1000;
public static final int ONTAP_FLEXVOL_JOB_POLL_INTERVAL_MS = 2000;
public static final int ONTAP_FLEXVOL_RESOLVE_MAX_RETRIES = 30;

/** vm_snapshot_details key for ONTAP FlexVolume-level VM snapshots. */
public static final String ONTAP_FLEXVOL_SNAPSHOT = "ontapFlexVolSnapshot";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,15 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.withSettings;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.ArgumentMatchers.contains;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.util.HashMap;
import com.cloud.storage.StoragePool;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle;
import org.apache.cloudstack.storage.provider.StorageProviderFactory;
import org.apache.cloudstack.storage.service.StorageStrategy;
import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
Expand Down Expand Up @@ -854,4 +859,104 @@ public void testAttachZone_kvmHypervisorSetsAndUpdatesPool() throws Exception {
}
}

// ========== updateStoragePool() Tests ==========

@Test
public void testUpdateStoragePool_positive_resizesFlexVolume() {
// Setup
StoragePool storagePool = mock(StoragePool.class);
when(storagePool.getName()).thenReturn("test-pool");
when(storagePool.getCapacityBytes()).thenReturn(2147483648L); // 2 GB current

Map<String, String> details = new HashMap<>();
details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, String.valueOf(5368709120L)); // 5 GB new
details.put(OntapStorageConstants.VOLUME_UUID, "flex-vol-uuid-123");
details.put(OntapStorageConstants.VOLUME_NAME, "flexvol-name");
details.put("protocol", "NFS3");

Volume updatedVolume = new Volume();
updatedVolume.setUuid("flex-vol-uuid-123");
updatedVolume.setSize(5368709120L);
when(storageStrategy.updateStorageVolume(any(Volume.class))).thenReturn(updatedVolume);

try (MockedStatic<OntapStorageUtils> utilityMock = Mockito.mockStatic(OntapStorageUtils.class)) {
utilityMock.when(() -> OntapStorageUtils.getStrategyByStoragePoolDetails(any()))
.thenReturn(storageStrategy);

// Execute
ontapPrimaryDatastoreLifecycle.updateStoragePool(storagePool, details);

// Verify
verify(storageStrategy, times(1)).updateStorageVolume(any(Volume.class));
}
}

@Test
public void testUpdateStoragePool_noCapacityBytesInDetails_skipsResize() {
// Setup
StoragePool storagePool = mock(StoragePool.class);
when(storagePool.getName()).thenReturn("test-pool");

Map<String, String> details = new HashMap<>();
details.put(OntapStorageConstants.VOLUME_UUID, "flex-vol-uuid-123");
details.put("protocol", "NFS3");
// No CAPACITY_BYTES key — resize should be skipped

// Execute
ontapPrimaryDatastoreLifecycle.updateStoragePool(storagePool, details);

// Verify — storageStrategy should never be called
verify(storageStrategy, never()).updateStorageVolume(any());
}

@Test
public void testUpdateStoragePool_missingVolumeUuid_throwsCloudRuntimeException() {
// Setup
StoragePool storagePool = mock(StoragePool.class);
when(storagePool.getName()).thenReturn("test-pool");
when(storagePool.getCapacityBytes()).thenReturn(1073741824L);

Map<String, String> details = new HashMap<>();
details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, String.valueOf(3221225472L));
details.put("protocol", "NFS3");
// No VOLUME_UUID — cannot resize without it

try (MockedStatic<OntapStorageUtils> utilityMock = Mockito.mockStatic(OntapStorageUtils.class)) {
utilityMock.when(() -> OntapStorageUtils.getStrategyByStoragePoolDetails(any()))
.thenReturn(storageStrategy);

// Execute & Verify
assertThrows(CloudRuntimeException.class,
() -> ontapPrimaryDatastoreLifecycle.updateStoragePool(storagePool, details));
verify(storageStrategy, never()).updateStorageVolume(any());
}
}

@Test
public void testUpdateStoragePool_updateStorageVolumeThrows_propagatesCloudRuntimeException() {
// Setup
StoragePool storagePool = mock(StoragePool.class);
when(storagePool.getName()).thenReturn("test-pool");
when(storagePool.getCapacityBytes()).thenReturn(1073741824L);

Map<String, String> details = new HashMap<>();
details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, String.valueOf(3221225472L));
details.put(OntapStorageConstants.VOLUME_UUID, "flex-vol-uuid-err");
details.put(OntapStorageConstants.VOLUME_NAME, "flexvol-err");
details.put("protocol", "NFS3");

when(storageStrategy.updateStorageVolume(any(Volume.class)))
.thenThrow(new CloudRuntimeException("ONTAP resize failed"));

try (MockedStatic<OntapStorageUtils> utilityMock = Mockito.mockStatic(OntapStorageUtils.class)) {
utilityMock.when(() -> OntapStorageUtils.getStrategyByStoragePoolDetails(any()))
.thenReturn(storageStrategy);

// Execute & Verify
assertThrows(CloudRuntimeException.class,
() -> ontapPrimaryDatastoreLifecycle.updateStoragePool(storagePool, details));
verify(storageStrategy, times(1)).updateStorageVolume(any(Volume.class));
}
}

}
Loading