This is an automated email from the ASF dual-hosted git repository.
rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack-primate.git
The following commit(s) were added to refs/heads/master by this push:
new 1ca5dc9 storage: Volume storage migration action (#72)
1ca5dc9 is described below
commit 1ca5dc9e62ab1f62b54168c47464d92ab0832e49
Author: Ritchie Vincent <[email protected]>
AuthorDate: Wed Dec 18 12:00:31 2019 +0000
storage: Volume storage migration action (#72)
Adds a custom volume storage migration form
This fixes #70
Signed-off-by: Rohit Yadav <[email protected]>
---
src/config/section/image.js | 7 +-
src/config/section/storage.js | 4 +-
src/main.js | 2 +
src/utils/plugins.js | 76 +++++++++++++++
src/views/AutogenView.vue | 6 +-
src/views/compute/MigrateWizard.vue | 4 -
src/views/storage/MigrateVolume.vue | 189 ++++++++++++++++++++++++++++++++++++
7 files changed, 281 insertions(+), 7 deletions(-)
diff --git a/src/config/section/image.js b/src/config/section/image.js
index 3a614f8..e00de65 100644
--- a/src/config/section/image.js
+++ b/src/config/section/image.js
@@ -140,7 +140,12 @@ export default {
icon: 'cloud-download',
label: 'Download ISO',
dataView: true,
- args: ['zoneid', 'mode']
+ args: ['zoneid', 'mode'],
+ mapping: {
+ mode: {
+ value: (record) => { return 'HTTP_DOWNLOAD' }
+ }
+ }
},
{
api: 'updateIsoPermissions',
diff --git a/src/config/section/storage.js b/src/config/section/storage.js
index 649e7be..51e8383 100644
--- a/src/config/section/storage.js
+++ b/src/config/section/storage.js
@@ -116,7 +116,9 @@ export default {
label: 'Migrate Volume',
args: ['volumeid', 'storageid', 'livemigrate'],
dataView: true,
- show: (record) => { return 'virtualmachineid' in record &&
record.virtualmachineid },
+ show: (record) => { return record && record.state === 'Ready' },
+ popup: true,
+ component: () => import('@/views/storage/MigrateVolume.vue'),
mapping: {
volumeid: {
value: (record) => { return record.id }
diff --git a/src/main.js b/src/main.js
index f06f340..1d83fd5 100644
--- a/src/main.js
+++ b/src/main.js
@@ -27,9 +27,11 @@ import './core/use'
import './core/ext'
import './permission' // permission control
import './utils/filter' // global filter
+import { pollJobPlugin } from './utils/plugins'
Vue.config.productionTip = false
Vue.use(VueAxios, router)
+Vue.use(pollJobPlugin)
new Vue({
router,
diff --git a/src/utils/plugins.js b/src/utils/plugins.js
new file mode 100644
index 0000000..0204916
--- /dev/null
+++ b/src/utils/plugins.js
@@ -0,0 +1,76 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { api } from '@/api'
+import { message, notification } from 'ant-design-vue'
+
+export const pollJobPlugin = {
+
+ install (Vue) {
+ Vue.prototype.$pollJob = function (options) {
+ /**
+ * @param {String} jobId
+ * @param {String} [successMessage=Success]
+ * @param {Function} [successMethod=() => {}]
+ * @param {String} [errorMessage=Error]
+ * @param {Function} [errorMethod=() => {}]
+ * @param {String} [loadingMessage=Loading...]
+ * @param {String} [catchMessage=Error caught]
+ * @param {Function} [catchMethod=() => {}]
+ * @param {Number} [loadingDuration=3]
+ */
+ const {
+ jobId,
+ successMessage = 'Success',
+ successMethod = () => {},
+ errorMessage = 'Error',
+ errorMethod = () => {},
+ loadingMessage = 'Loading...',
+ catchMessage = 'Error caught',
+ catchMethod = () => {},
+ loadingDuration = 3
+ } = options
+
+ api('queryAsyncJobResult', { jobId }).then(json => {
+ const result = json.queryasyncjobresultresponse
+
+ if (result.jobstatus === 1) {
+ message.success(successMessage)
+ successMethod()
+ } else if (result.jobstatus === 2) {
+ notification.error({
+ message: errorMessage,
+ description: result.jobresult.errortext
+ })
+ errorMethod()
+ } else if (result.jobstatus === 0) {
+ message
+ .loading(loadingMessage, loadingDuration)
+ .then(() => this.$pollJob(options))
+ }
+ }).catch(e => {
+ console.error(`${catchMessage} - ${e}`)
+ notification.error({
+ message: 'Error',
+ description: catchMessage
+ })
+ catchMethod && catchMethod()
+ })
+ }
+ }
+
+}
diff --git a/src/views/AutogenView.vue b/src/views/AutogenView.vue
index 924b5e4..2533f50 100644
--- a/src/views/AutogenView.vue
+++ b/src/views/AutogenView.vue
@@ -265,7 +265,8 @@ export default {
mixins: [mixinDevice],
provide: function () {
return {
- parentFetchData: this.fetchData
+ parentFetchData: this.fetchData,
+ parentToggleLoading: this.toggleLoading
}
},
data () {
@@ -718,6 +719,9 @@ export default {
changeResource (resource) {
this.treeSelected = resource
this.resource = this.treeSelected
+ },
+ toggleLoading () {
+ this.loading = !this.loading
}
}
}
diff --git a/src/views/compute/MigrateWizard.vue
b/src/views/compute/MigrateWizard.vue
index ee9d858..80417e9 100644
--- a/src/views/compute/MigrateWizard.vue
+++ b/src/views/compute/MigrateWizard.vue
@@ -179,7 +179,6 @@ export default {
display: flex;
justify-content: flex-end;
}
-
}
.host-item {
@@ -199,7 +198,6 @@ export default {
@media (min-width: 760px) {
flex-direction: row;
}
-
}
&__value {
@@ -216,9 +214,7 @@ export default {
margin-right: 40px;
margin-left: 40px;
}
-
}
-
}
&__title {
diff --git a/src/views/storage/MigrateVolume.vue
b/src/views/storage/MigrateVolume.vue
new file mode 100644
index 0000000..56ecfe7
--- /dev/null
+++ b/src/views/storage/MigrateVolume.vue
@@ -0,0 +1,189 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+ <div class="migrate-volume-container">
+
+ <div class="modal-form">
+ <p class="modal-form__label">{{ $t('storagePool') }}</p>
+ <a-select v-model="selectedStoragePool" style="width: 100%;">
+ <a-select-option v-for="(storagePool, index) in storagePools"
:value="storagePool.id" :key="index">
+ {{ storagePool.name }}
+ </a-select-option>
+ </a-select>
+ <template v-if="this.resource.virtualmachineid">
+ <p class="modal-form__label" @click="replaceDiskOffering =
!replaceDiskOffering" style="cursor:pointer;">
+ {{ $t('useNewDiskOffering') }}
+ </p>
+ <a-checkbox v-model="replaceDiskOffering" />
+
+ <template v-if="replaceDiskOffering">
+ <p class="modal-form__label">{{ $t('newDiskOffering') }}</p>
+ <a-select v-model="selectedDiskOffering" style="width: 100%;">
+ <a-select-option v-for="(diskOffering, index) in diskOfferings"
:value="diskOffering.id" :key="index">
+ {{ diskOffering.displaytext }}
+ </a-select-option>
+ </a-select>
+ </template>
+
+ </template>
+ </div>
+
+ <a-divider />
+
+ <div class="actions">
+ <a-button @click="closeModal">
+ {{ $t('Cancel') }}
+ </a-button>
+ <a-button type="primary" @click="submitMigrateVolume">
+ {{ $t('OK') }}
+ </a-button>
+ </div>
+
+ </div>
+</template>
+
+<script>
+import { api } from '@/api'
+
+export default {
+ name: 'MigrateVolume',
+ props: {
+ resource: {
+ type: Object,
+ required: true
+ }
+ },
+ inject: ['parentFetchData', 'parentToggleLoading'],
+ data () {
+ return {
+ storagePools: [],
+ selectedStoragePool: null,
+ diskOfferings: [],
+ replaceDiskOffering: !!this.resource.virtualmachineid,
+ selectedDiskOffering: null
+ }
+ },
+ created () {
+ this.fetchStoragePools()
+ this.resource.virtualmachineid && this.fetchDiskOfferings()
+ },
+ methods: {
+ fetchStoragePools () {
+ api('listStoragePools', {
+ zoneid: this.resource.zoneid
+ }).then(response => {
+ this.storagePools = response.liststoragepoolsresponse.storagepool
+ this.selectedStoragePool = this.storagePools[0].id
+ }).catch(error => {
+ this.$notification.error({
+ message: `Error ${error.response.status}`,
+ description: error.response.data.errorresponse.errortext
+ })
+ this.closeModal()
+ })
+ },
+ fetchDiskOfferings () {
+ api('listDiskOfferings', {
+ listall: true
+ }).then(response => {
+ this.diskOfferings = response.listdiskofferingsresponse.diskoffering
+ this.selectedDiskOffering = this.diskOfferings[0].id
+ }).catch(error => {
+ this.$notification.error({
+ message: `Error ${error.response.status}`,
+ description: error.response.data.errorresponse.errortext
+ })
+ this.closeModal()
+ })
+ },
+ closeModal () {
+ this.$parent.$parent.close()
+ },
+ submitMigrateVolume () {
+ this.closeModal()
+ this.parentToggleLoading()
+ api('migrateVolume', {
+ livemigrate: this.resource.vmstate === 'Running',
+ storageid: this.selectedStoragePool,
+ volumeid: this.resource.id,
+ newdiskofferingid: this.replaceDiskOffering ?
this.selectedDiskOffering : null
+ }).then(response => {
+ this.$pollJob({
+ jobId: response.migratevolumeresponse.jobid,
+ successMessage: `Successfully migrated volume`,
+ successMethod: () => {
+ this.parentFetchData()
+ this.parentToggleLoading()
+ },
+ errorMessage: 'Migrating volume failed',
+ errorMethod: () => {
+ this.parentFetchData()
+ this.parentToggleLoading()
+ },
+ loadingMessage: `Migrating volume...`,
+ catchMessage: 'Error encountered while fetching async job result',
+ catchMethod: () => {
+ this.parentFetchData()
+ this.parentToggleLoading()
+ }
+ })
+ }).catch(error => {
+ this.$notification.error({
+ message: `Error ${error.response.status}`,
+ description: error.response.data.errorresponse.errortext
+ })
+ this.closeModal()
+ })
+ }
+ }
+}
+</script>
+
+<style scoped lang="scss">
+ .migrate-volume-container {
+ width: 95vw;
+ max-width: 100%;
+
+ @media (min-width: 760px) {
+ width: 50vw;
+ }
+ }
+
+ .actions {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 20px;
+
+ button {
+ &:not(:last-child) {
+ margin-right: 10px;
+ }
+ }
+ }
+
+ .modal-form {
+ margin-top: -20px;
+
+ &__label {
+ font-weight: bold;
+ margin-top: 10px;
+ margin-bottom: 5px;
+ }
+
+ }
+</style>