From 30ee7e2eee08fe152b9ac5e17bfc8999e3573e28 Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:46:54 +0530 Subject: [PATCH 1/3] Create volume on a specified storage pool --- .../api/command/user/volume/CreateVolumeCmd.java | 14 ++++++++++++++ .../com/cloud/storage/VolumeApiServiceImpl.java | 13 +++++++++++++ 2 files changed, 27 insertions(+) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java index 27b592aa8f15..fa149f35c49d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java @@ -32,6 +32,7 @@ import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; @@ -109,6 +110,12 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC description = "The ID of the Instance; to be used with snapshot Id, Instance to which the volume gets attached after creation") private Long virtualMachineId; + @Parameter(name = ApiConstants.STORAGE_ID, + type = CommandType.UUID, + entityType = StoragePoolResponse.class, + description = "Storage pool ID to create the volume in. Exclusive with SnapshotId parameter.") + private Long storageId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -153,6 +160,13 @@ private Long getProjectId() { return projectId; } + public Long getStorageId() { + if (snapshotId != null && storageId != null) { + throw new IllegalArgumentException("StorageId parameter cannot be specified with the SnapshotId parameter."); + } + return storageId; + } + public Boolean getDisplayVolume() { return displayVolume; } diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index ca3d31d4fad3..7e94b41b473a 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -1043,6 +1043,17 @@ public boolean validateVolumeSizeInBytes(long size) { return true; } + private VolumeVO allocateVolumeOnStorage(Long volumeId, Long storageId) throws ExecutionException, InterruptedException { + DataStore destStore = dataStoreMgr.getDataStore(storageId, DataStoreRole.Primary); + VolumeInfo destVolume = volFactory.getVolume(volumeId, destStore); + AsyncCallFuture createVolumeFuture = volService.createVolumeAsync(destVolume, destStore); + VolumeApiResult createVolumeResult = createVolumeFuture.get(); + if (createVolumeResult.isFailed()) { + throw new CloudRuntimeException("Creation of a dest volume failed: " + createVolumeResult.getResult()); + } + return _volsDao.findById(destVolume.getId()); + } + @Override @DB @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", async = true) @@ -1074,6 +1085,8 @@ public VolumeVO createVolume(CreateVolumeCmd cmd) { throw new CloudRuntimeException(message.toString()); } } + } else if (cmd.getStorageId() != null) { + volume = allocateVolumeOnStorage(cmd.getEntityId(), cmd.getStorageId()); } return volume; } catch (Exception e) { From 3dbfbf7c5e152bdec8f6d52419602d9b233f49cb Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:14:07 +0530 Subject: [PATCH 2/3] Add checks --- .../command/user/volume/CreateVolumeCmd.java | 3 ++- .../cloud/storage/VolumeApiServiceImpl.java | 23 +++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java index fa149f35c49d..e05d6cd90641 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java @@ -113,7 +113,8 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC @Parameter(name = ApiConstants.STORAGE_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, - description = "Storage pool ID to create the volume in. Exclusive with SnapshotId parameter.") + description = "Storage pool ID to create the volume in. Exclusive with SnapshotId parameter.", + authorized = {RoleType.Admin}) private Long storageId; ///////////////////////////////////////////////////// diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 7e94b41b473a..41aac4b8b7d6 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -1043,7 +1043,26 @@ public boolean validateVolumeSizeInBytes(long size) { return true; } - private VolumeVO allocateVolumeOnStorage(Long volumeId, Long storageId) throws ExecutionException, InterruptedException { + private VolumeVO createVolumeOnStoragePool(Long volumeId, Long storageId) throws ExecutionException, InterruptedException { + VolumeVO volume = _volsDao.findById(volumeId); + StoragePool destPool = (StoragePool) dataStoreMgr.getDataStore(storageId, DataStoreRole.Primary); + if (destPool == null) { + throw new InvalidParameterValueException("Failed to find the destination storage pool: " + storageId); + } else if (destPool.isInMaintenance()) { + throw new InvalidParameterValueException(String.format("Cannot create volume %s on storage pool %s as the storage pool is in maintenance mode.", + volume.getUuid(), destPool.getName())); + } + + if (destPool.getDataCenterId() != volume.getDataCenterId()) { + throw new InvalidParameterValueException(String.format("Cannot create volume %s in zone %s on storage pool %s in zone %s.", + volume.getUuid(), volume.getDataCenterId(), destPool.getUuid(), destPool.getDataCenterId())); + } + + DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); + if (!doesStoragePoolSupportDiskOffering(destPool, diskOffering)) { + throw new InvalidParameterValueException(String.format("Disk offering: %s is not compatible with the storage pool", diskOffering.getUuid())); + } + DataStore destStore = dataStoreMgr.getDataStore(storageId, DataStoreRole.Primary); VolumeInfo destVolume = volFactory.getVolume(volumeId, destStore); AsyncCallFuture createVolumeFuture = volService.createVolumeAsync(destVolume, destStore); @@ -1086,7 +1105,7 @@ public VolumeVO createVolume(CreateVolumeCmd cmd) { } } } else if (cmd.getStorageId() != null) { - volume = allocateVolumeOnStorage(cmd.getEntityId(), cmd.getStorageId()); + volume = createVolumeOnStoragePool(cmd.getEntityId(), cmd.getStorageId()); } return volume; } catch (Exception e) { From b420f90822b6b682d408a7dfbc63122fce2c3869 Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Fri, 10 Apr 2026 06:57:17 +0530 Subject: [PATCH 3/3] review comments --- .../command/user/volume/CreateVolumeCmd.java | 2 +- .../cloud/storage/VolumeApiServiceImpl.java | 28 +++---- ui/public/locales/en.json | 2 + ui/src/views/storage/CreateVolume.vue | 74 +++++++++++++++++++ 4 files changed, 91 insertions(+), 15 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java index e05d6cd90641..84dd4525265b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java @@ -113,7 +113,7 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC @Parameter(name = ApiConstants.STORAGE_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, - description = "Storage pool ID to create the volume in. Exclusive with SnapshotId parameter.", + description = "Storage pool ID to create the volume in. Cannot be used with the snapshotid parameter.", authorized = {RoleType.Admin}) private Long storageId; diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 41aac4b8b7d6..d9f54e7de026 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -1045,32 +1045,32 @@ public boolean validateVolumeSizeInBytes(long size) { private VolumeVO createVolumeOnStoragePool(Long volumeId, Long storageId) throws ExecutionException, InterruptedException { VolumeVO volume = _volsDao.findById(volumeId); - StoragePool destPool = (StoragePool) dataStoreMgr.getDataStore(storageId, DataStoreRole.Primary); - if (destPool == null) { - throw new InvalidParameterValueException("Failed to find the destination storage pool: " + storageId); - } else if (destPool.isInMaintenance()) { - throw new InvalidParameterValueException(String.format("Cannot create volume %s on storage pool %s as the storage pool is in maintenance mode.", - volume.getUuid(), destPool.getName())); + StoragePool storagePool = (StoragePool) dataStoreMgr.getDataStore(storageId, DataStoreRole.Primary); + if (storagePool == null) { + throw new InvalidParameterValueException("Failed to find the storage pool: " + storageId); + } else if (!storagePool.getStatus().equals(StoragePoolStatus.Up)) { + throw new InvalidParameterValueException(String.format("Cannot create volume %s on storage pool %s as the storage pool is not in Up state.", + volume.getUuid(), storagePool.getName())); } - if (destPool.getDataCenterId() != volume.getDataCenterId()) { + if (storagePool.getDataCenterId() != volume.getDataCenterId()) { throw new InvalidParameterValueException(String.format("Cannot create volume %s in zone %s on storage pool %s in zone %s.", - volume.getUuid(), volume.getDataCenterId(), destPool.getUuid(), destPool.getDataCenterId())); + volume.getUuid(), volume.getDataCenterId(), storagePool.getUuid(), storagePool.getDataCenterId())); } DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); - if (!doesStoragePoolSupportDiskOffering(destPool, diskOffering)) { + if (!doesStoragePoolSupportDiskOffering(storagePool, diskOffering)) { throw new InvalidParameterValueException(String.format("Disk offering: %s is not compatible with the storage pool", diskOffering.getUuid())); } - DataStore destStore = dataStoreMgr.getDataStore(storageId, DataStoreRole.Primary); - VolumeInfo destVolume = volFactory.getVolume(volumeId, destStore); - AsyncCallFuture createVolumeFuture = volService.createVolumeAsync(destVolume, destStore); + DataStore dataStore = dataStoreMgr.getDataStore(storageId, DataStoreRole.Primary); + VolumeInfo volumeInfo = volFactory.getVolume(volumeId, dataStore); + AsyncCallFuture createVolumeFuture = volService.createVolumeAsync(volumeInfo, dataStore); VolumeApiResult createVolumeResult = createVolumeFuture.get(); if (createVolumeResult.isFailed()) { - throw new CloudRuntimeException("Creation of a dest volume failed: " + createVolumeResult.getResult()); + throw new CloudRuntimeException("Volume creation on storage failed: " + createVolumeResult.getResult()); } - return _volsDao.findById(destVolume.getId()); + return _volsDao.findById(volumeInfo.getId()); } @Override diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 6494995bf223..4d8d32f87bbb 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -681,6 +681,7 @@ "label.create.sharedfs": "Create Shared FileSystem", "label.create.network": "Create new Network", "label.create.nfs.secondary.staging.storage": "Create NFS secondary staging storage", +"label.create.on.storage": "Create on Storage", "label.create.project": "Create Project", "label.create.project.role": "Create Project Role", "label.create.routing.policy": "Create Routing Policy", @@ -697,6 +698,7 @@ "label.create.tier.networkofferingid.description": "The Network offering for the Network Tier.", "label.create.tungsten.routing.policy": "Create Tungsten-Fabric routing policy", "label.create.user": "Create User", +"label.create.volume.on.primary.storage": "Create Volume on the specified Primary Storage", "label.create.vm": "Create Instance", "label.create.vm.and.stay": "Create Instance & stay on this page", "label.create.vpn.connection": "Create VPN connection", diff --git a/ui/src/views/storage/CreateVolume.vue b/ui/src/views/storage/CreateVolume.vue index e99cc7491707..f5185091f31b 100644 --- a/ui/src/views/storage/CreateVolume.vue +++ b/ui/src/views/storage/CreateVolume.vue @@ -116,6 +116,42 @@ :placeholder="apiParams.maxiops.description"/> + + + + + + + + + + + + + {{ pool.name }} + + + + +