Skip to content

Commit 8627c60

Browse files
authored
ui: option to migrate vm with volumes to same pool (#11703)
Signed-off-by: Abhishek Kumar <[email protected]>
1 parent 8dcfc7c commit 8627c60

File tree

5 files changed

+101
-41
lines changed

5 files changed

+101
-41
lines changed

ui/public/locales/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@
380380
"label.app.name": "CloudStack",
381381
"label.application.policy.set": "Application Policy Set",
382382
"label.apply": "Apply",
383+
"label.apply.to.all": "Apply to all",
383384
"label.apply.tungsten.firewall.policy": "Apply Firewall Policy",
384385
"label.apply.tungsten.network.policy": "Apply Network Policy",
385386
"label.apply.tungsten.tag": "Apply tag",
@@ -3692,6 +3693,7 @@
36923693
"message.vnf.nic.move.down.fail": "Failed to move down this NIC",
36933694
"message.vnf.no.credentials": "No credentials found for the VNF appliance.",
36943695
"message.vnf.select.networks": "Please select the relevant network for each VNF NIC.",
3696+
"message.volume.pool.apply.to.all": "Selected storage pool will be applied to all existing volumes of the instance.",
36953697
"message.volume.state.allocated": "The volume is allocated but has not been created yet.",
36963698
"message.volume.state.attaching": "The volume is attaching to a volume from Ready state.",
36973699
"message.volume.state.copying": "The volume is being copied from the image store to primary storage, in case it's an uploaded volume.",

ui/src/components/view/InstanceVolumesStoragePoolSelectListView.vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,19 @@ export default {
206206
closeVolumeStoragePoolSelector () {
207207
this.selectedVolumeForStoragePoolSelection = {}
208208
},
209-
handleVolumeStoragePoolSelection (volumeId, storagePool) {
209+
handleVolumeStoragePoolSelection (volumeId, storagePool, applyToAll) {
210210
for (const volume of this.volumes) {
211-
if (volume.id === volumeId) {
211+
if (applyToAll) {
212212
volume.selectedstorageid = storagePool.id
213213
volume.selectedstoragename = storagePool.name
214214
volume.selectedstorageclusterid = storagePool.clusterid
215-
break
215+
} else {
216+
if (volume.id === volumeId) {
217+
volume.selectedstorageid = storagePool.id
218+
volume.selectedstoragename = storagePool.name
219+
volume.selectedstorageclusterid = storagePool.clusterid
220+
break
221+
}
216222
}
217223
}
218224
this.updateVolumeToStoragePoolSelection()

ui/src/components/view/VolumeStoragePoolSelectForm.vue

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@
2525
:autoAssignAllowed="autoAssignAllowed"
2626
@select="handleSelect" />
2727

28+
<a-form-item
29+
class="top-spaced">
30+
<template #label>
31+
<tooltip-label :title="$t('label.apply.to.all')" :tooltip="$t('message.volume.pool.apply.to.all')"/>
32+
</template>
33+
<a-switch
34+
v-model:checked="applyToAll" />
35+
</a-form-item>
36+
2837
<a-divider />
2938

3039
<div class="actions">
@@ -36,11 +45,13 @@
3645
</template>
3746

3847
<script>
48+
import TooltipLabel from '@/components/widgets/TooltipLabel'
3949
import StoragePoolSelectView from '@/components/view/StoragePoolSelectView'
4050
4151
export default {
4252
name: 'VolumeStoragePoolSelectionForm',
4353
components: {
54+
TooltipLabel,
4455
StoragePoolSelectView
4556
},
4657
props: {
@@ -70,7 +81,8 @@ export default {
7081
},
7182
data () {
7283
return {
73-
selectedStoragePool: null
84+
selectedStoragePool: null,
85+
applyToAll: false
7486
}
7587
},
7688
watch: {
@@ -95,7 +107,7 @@ export default {
95107
}
96108
},
97109
submitForm () {
98-
this.$emit('select', this.resource.id, this.selectedStoragePool)
110+
this.$emit('select', this.resource.id, this.selectedStoragePool, this.applyToAll)
99111
this.closeModal()
100112
}
101113
}

ui/src/views/compute/MigrateWizard.vue

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
class="top-spaced"
2727
:placeholder="$t('label.search')"
2828
v-model:value="searchQuery"
29-
@search="fetchData"
29+
@search="fetchHostsForMigration"
3030
v-focus="true" />
3131
<a-table
3232
class="top-spaced"
@@ -97,7 +97,7 @@
9797
</a-pagination>
9898

9999
<a-form-item
100-
v-if="isUserVm"
100+
v-if="isUserVm && hasVolumes"
101101
class="top-spaced">
102102
<template #label>
103103
<tooltip-label :title="$t('label.migrate.with.storage')" :tooltip="$t('message.migrate.with.storage')"/>
@@ -106,9 +106,29 @@
106106
v-model:checked="migrateWithStorage"
107107
:disabled="!selectedHost || !selectedHost.id || selectedHost.id === -1" />
108108
</a-form-item>
109+
110+
<a-radio-group
111+
v-if="migrateWithStorage"
112+
v-model:value="migrateMode"
113+
@change="e => { handleMigrateModeChange(e.target.value) }">
114+
<a-radio class="radio-style" :value="1">
115+
{{ $t('label.migrate.instance.single.storage') }}
116+
</a-radio>
117+
<a-radio class="radio-style" :value="2">
118+
{{ $t('label.migrate.instance.specific.storages') }}
119+
</a-radio>
120+
</a-radio-group>
121+
122+
<div v-if="migrateWithStorage && migrateMode == 1">
123+
<storage-pool-select-view
124+
ref="storagePoolSelection"
125+
:autoAssignAllowed="false"
126+
:resource="resource"
127+
@select="handleStoragePoolChange" />
128+
</div>
109129
<instance-volumes-storage-pool-select-list-view
110130
ref="volumeToPoolSelect"
111-
v-if="migrateWithStorage"
131+
v-if="migrateWithStorage && migrateMode !== 1"
112132
class="top-spaced"
113133
:resource="resource"
114134
:clusterId="selectedHost.id ? selectedHost.clusterid : null"
@@ -118,20 +138,22 @@
118138

119139
<div class="actions">
120140
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
121-
<a-button type="primary" ref="submit" :disabled="!selectedHost.id" @click="submitForm">{{ $t('label.ok') }}</a-button>
141+
<a-button type="primary" ref="submit" :disabled="!selectedHost.id || (migrateWithStorage && migrateMode === 1 && !volumeToPoolSelection.length)" @click="submitForm">{{ $t('label.ok') }}</a-button>
122142
</div>
123143
</div>
124144
</template>
125145

126146
<script>
127147
import { api } from '@/api'
128148
import TooltipLabel from '@/components/widgets/TooltipLabel'
149+
import StoragePoolSelectView from '@/components/view/StoragePoolSelectView'
129150
import InstanceVolumesStoragePoolSelectListView from '@/components/view/InstanceVolumesStoragePoolSelectListView'
130151
131152
export default {
132153
name: 'VMMigrateWizard',
133154
components: {
134155
TooltipLabel,
156+
StoragePoolSelectView,
135157
InstanceVolumesStoragePoolSelectListView
136158
},
137159
props: {
@@ -188,6 +210,7 @@ export default {
188210
}
189211
],
190212
migrateWithStorage: false,
213+
migrateMode: 1,
191214
volumeToPoolSelection: [],
192215
volumes: []
193216
}
@@ -198,6 +221,9 @@ export default {
198221
computed: {
199222
isUserVm () {
200223
return this.$route.meta.resourceType === 'UserVm'
224+
},
225+
hasVolumes () {
226+
return this.volumes && this.volumes.length > 0
201227
}
202228
},
203229
watch: {
@@ -212,6 +238,10 @@ export default {
212238
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
213239
},
214240
fetchData () {
241+
this.fetchHostsForMigration()
242+
this.fetchVolumes()
243+
},
244+
fetchHostsForMigration () {
215245
this.loading = true
216246
api('findHostsForMigration', {
217247
virtualmachineid: this.resource.id,
@@ -239,17 +269,16 @@ export default {
239269
handleChangePage (page, pageSize) {
240270
this.page = page
241271
this.pageSize = pageSize
242-
this.fetchData()
272+
this.fetchHostsForMigration()
243273
},
244274
handleChangePageSize (currentPage, pageSize) {
245275
this.page = currentPage
246276
this.pageSize = pageSize
247-
this.fetchData()
277+
this.fetchHostsForMigration()
248278
},
249279
handleSelectedHostChange (host) {
250280
if (host.id === -1) {
251281
this.migrateWithStorage = false
252-
this.fetchVolumes()
253282
}
254283
this.selectedHost = host
255284
this.selectedVolumeForStoragePoolSelection = {}
@@ -258,6 +287,17 @@ export default {
258287
this.$refs.volumeToPoolSelect.resetSelection()
259288
}
260289
},
290+
handleMigrateModeChange () {
291+
this.volumeToPoolSelection = []
292+
},
293+
handleStoragePoolChange (storagePool) {
294+
this.volumeToPoolSelection = []
295+
for (const volume of this.volumes) {
296+
if (storagePool && storagePool.id && storagePool.id !== -1) {
297+
this.volumeToPoolSelection.push({ volume: volume.id, pool: storagePool.id })
298+
}
299+
}
300+
},
261301
handleVolumeToPoolChange (volumeToPool) {
262302
this.volumeToPoolSelection = volumeToPool
263303
},
@@ -268,7 +308,7 @@ export default {
268308
listAll: true,
269309
virtualmachineid: this.resource.id
270310
}).then(response => {
271-
this.volumes = response.listvolumesresponse.volume
311+
this.volumes = response?.listvolumesresponse?.volume || []
272312
}).finally(() => {
273313
this.loading = false
274314
})
@@ -277,7 +317,7 @@ export default {
277317
if (this.selectedHost.requiresStorageMotion || this.volumeToPoolSelection.length > 0) {
278318
return true
279319
}
280-
if (this.selectedHost.id === -1 && this.volumes && this.volumes.length > 0) {
320+
if (this.selectedHost.id === -1 && this.hasVolumes) {
281321
for (var volume of this.volumes) {
282322
if (volume.storagetype === 'local') {
283323
return true
@@ -305,7 +345,7 @@ export default {
305345
var params = this.selectedHost.id === -1
306346
? { autoselect: true, virtualmachineid: this.resource.id }
307347
: { hostid: this.selectedHost.id, virtualmachineid: this.resource.id }
308-
if (this.migrateWithStorage) {
348+
if (this.migrateWithStorage && this.volumeToPoolSelection && this.volumeToPoolSelection.length > 0) {
309349
for (var i = 0; i < this.volumeToPoolSelection.length; i++) {
310350
const mapping = this.volumeToPoolSelection[i]
311351
params['migrateto[' + i + '].volume'] = mapping.volume

0 commit comments

Comments
 (0)