mirror of https://github.com/apache/cloudstack.git
Create volume on a specified storage pool (#12966)
This commit is contained in:
parent
273699cf56
commit
df7ff97271
|
|
@ -32,6 +32,7 @@ import org.apache.cloudstack.api.response.DiskOfferingResponse;
|
|||
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,13 @@ 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. Cannot be used with the snapshotid parameter.",
|
||||
authorized = {RoleType.Admin})
|
||||
private Long storageId;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -153,6 +161,13 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC
|
|||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1043,6 +1043,36 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||
return true;
|
||||
}
|
||||
|
||||
private VolumeVO createVolumeOnStoragePool(Long volumeId, Long storageId) throws ExecutionException, InterruptedException {
|
||||
VolumeVO volume = _volsDao.findById(volumeId);
|
||||
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 (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(), storagePool.getUuid(), storagePool.getDataCenterId()));
|
||||
}
|
||||
|
||||
DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
|
||||
if (!doesStoragePoolSupportDiskOffering(storagePool, diskOffering)) {
|
||||
throw new InvalidParameterValueException(String.format("Disk offering: %s is not compatible with the storage pool", diskOffering.getUuid()));
|
||||
}
|
||||
|
||||
DataStore dataStore = dataStoreMgr.getDataStore(storageId, DataStoreRole.Primary);
|
||||
VolumeInfo volumeInfo = volFactory.getVolume(volumeId, dataStore);
|
||||
AsyncCallFuture<VolumeApiResult> createVolumeFuture = volService.createVolumeAsync(volumeInfo, dataStore);
|
||||
VolumeApiResult createVolumeResult = createVolumeFuture.get();
|
||||
if (createVolumeResult.isFailed()) {
|
||||
throw new CloudRuntimeException("Volume creation on storage failed: " + createVolumeResult.getResult());
|
||||
}
|
||||
return _volsDao.findById(volumeInfo.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@DB
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", async = true)
|
||||
|
|
@ -1074,6 +1104,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
|||
throw new CloudRuntimeException(message.toString());
|
||||
}
|
||||
}
|
||||
} else if (cmd.getStorageId() != null) {
|
||||
volume = createVolumeOnStoragePool(cmd.getEntityId(), cmd.getStorageId());
|
||||
}
|
||||
return volume;
|
||||
} catch (Exception e) {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -116,6 +116,42 @@
|
|||
:placeholder="apiParams.maxiops.description"/>
|
||||
</a-form-item>
|
||||
</span>
|
||||
<a-form-item name="createOnStorage" ref="createOnStorage" v-if="showStoragePoolSelect">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.create.on.storage')" :tooltip="$t('label.create.volume.on.primary.storage')" />
|
||||
</template>
|
||||
<a-switch
|
||||
v-model:checked="form.createOnStorage"
|
||||
:checked="createOnStorage"
|
||||
@change="onChangeCreateOnStorage" />
|
||||
</a-form-item>
|
||||
<span v-if="showStoragePoolSelect && createOnStorage">
|
||||
<a-form-item ref="storageid" name="storageid">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.storageid')" />
|
||||
</template>
|
||||
<a-select
|
||||
v-model:value="form.storageid"
|
||||
:loading="loading"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}" >
|
||||
<a-select-option
|
||||
v-for="(pool, index) in storagePools"
|
||||
:value="pool.id"
|
||||
:key="index"
|
||||
:label="pool.name">
|
||||
<span>
|
||||
<resource-icon v-if="pool.icon" :image="pool.icon.base64image" size="1x" style="margin-right: 5px"/>
|
||||
<hdd-outlined v-else style="margin-right: 5px"/>
|
||||
{{ pool.name }}
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</span>
|
||||
<a-form-item name="attachVolume" ref="attachVolume" v-if="!createVolumeFromVM">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.action.attach.to.instance')" :tooltip="$t('label.attach.vol.to.instance')" />
|
||||
|
|
@ -170,6 +206,7 @@
|
|||
import { ref, reactive, toRaw } from 'vue'
|
||||
import { getAPI, postAPI } from '@/api'
|
||||
import { mixinForm } from '@/utils/mixin'
|
||||
import { isAdmin } from '@/role'
|
||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
import OwnershipSelection from '@/views/compute/wizard/OwnershipSelection.vue'
|
||||
|
|
@ -203,11 +240,16 @@ export default {
|
|||
loading: false,
|
||||
isCustomizedDiskIOps: false,
|
||||
virtualmachines: [],
|
||||
createOnStorage: false,
|
||||
storagePools: [],
|
||||
attachVolume: false,
|
||||
vmidtoattach: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showStoragePoolSelect () {
|
||||
return isAdmin() && !this.createVolumeFromSnapshot
|
||||
},
|
||||
createVolumeFromVM () {
|
||||
return this.$route.path.startsWith('/vm/')
|
||||
},
|
||||
|
|
@ -299,6 +341,9 @@ export default {
|
|||
this.zones = json.listzonesresponse.zone || []
|
||||
this.form.zoneid = this.zones[0].id || ''
|
||||
this.fetchDiskOfferings(this.form.zoneid)
|
||||
if (this.createOnStorage) {
|
||||
this.fetchStoragePools(this.form.zoneid)
|
||||
}
|
||||
if (this.attachVolume) {
|
||||
this.fetchVirtualMachines(this.form.zoneid)
|
||||
}
|
||||
|
|
@ -355,6 +400,25 @@ export default {
|
|||
this.loading = false
|
||||
})
|
||||
},
|
||||
fetchStoragePools (zoneId) {
|
||||
if (!zoneId) {
|
||||
this.storagePools = []
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
getAPI('listStoragePools', {
|
||||
zoneid: zoneId,
|
||||
showicon: true
|
||||
}).then(json => {
|
||||
const pools = json.liststoragepoolsresponse.storagepool || []
|
||||
this.storagePools = pools.filter(p => p.state === 'Up')
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
this.storagePools = []
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
fetchVirtualMachines (zoneId) {
|
||||
var params = {
|
||||
zoneid: zoneId,
|
||||
|
|
@ -394,6 +458,7 @@ export default {
|
|||
if (this.customDiskOffering) {
|
||||
values.size = values.size.trim()
|
||||
}
|
||||
delete values.createOnStorage
|
||||
if (this.createVolumeFromSnapshot) {
|
||||
values.snapshotid = this.resource.id
|
||||
}
|
||||
|
|
@ -467,6 +532,15 @@ export default {
|
|||
this.attachVolumeApiParams = this.$getApiParams('attachVolume')
|
||||
this.fetchVirtualMachines(this.form.zoneid)
|
||||
}
|
||||
},
|
||||
onChangeCreateOnStorage () {
|
||||
this.createOnStorage = !this.createOnStorage
|
||||
if (this.createOnStorage) {
|
||||
this.fetchStoragePools(this.form.zoneid)
|
||||
this.form.storageid = this.storagePools[0]?.id || undefined
|
||||
} else {
|
||||
this.form.storageid = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue