cloudstack-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ro...@apache.org
Subject [cloudstack-primate] branch master updated: storage: Volume storage migration action (#72)
Date Wed, 18 Dec 2019 12:00:39 GMT
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 <rfcvincent@gmail.com>
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 <rohit.yadav@shapeblue.com>
---
 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>


Mime
View raw message