You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2020/08/20 08:44:39 UTC

[cloudstack-primate] branch master updated: storage: Allow creating snapshot from VM snapshot (#587)

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 bbd4fdd  storage: Allow creating snapshot from VM snapshot (#587)
bbd4fdd is described below

commit bbd4fddefdba3726f16b79b070749a90cad8a71b
Author: Rakesh <ra...@gmail.com>
AuthorDate: Thu Aug 20 10:44:24 2020 +0200

    storage: Allow creating snapshot from VM snapshot (#587)
    
    In old UI we support creating snapshot from
    VM snapshot by selecting one of the volume
    belonging to VM. Allow the same in new UI
    
    Co-authored-by: Rakesh Venkatesh <r....@global.leaseweb.com>
---
 src/config/section/storage.js                      |  13 +-
 src/locales/en.json                                |   5 +
 src/views/storage/CreateSnapshotFromVMSnapshot.vue | 151 +++++++++++++++++++++
 3 files changed, 168 insertions(+), 1 deletion(-)

diff --git a/src/config/section/storage.js b/src/config/section/storage.js
index a18401b..2e7c205 100644
--- a/src/config/section/storage.js
+++ b/src/config/section/storage.js
@@ -256,6 +256,7 @@ export default {
       columns: () => {
         var fields = ['name', 'state', 'volumename', 'intervaltype', 'created']
         if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
+          fields.push('domain')
           fields.push('account')
         }
         return fields
@@ -315,7 +316,7 @@ export default {
       permission: ['listVMSnapshot'],
       resourceType: 'VMSnapshot',
       columns: () => {
-        var fields = ['displayname', 'state', 'name', 'type', 'current', 'parentName', 'created']
+        const fields = ['displayname', 'state', 'name', 'type', 'current', 'parentName', 'created']
         if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
           fields.push('domain')
           fields.push('account')
@@ -326,6 +327,16 @@ export default {
       searchFilters: ['name', 'domainid', 'account', 'tags'],
       actions: [
         {
+          api: 'createSnapshotFromVMSnapshot',
+          icon: 'camera',
+          label: 'label.action.create.snapshot.from.vmsnapshot',
+          message: 'message.action.create.snapshot.from.vmsnapshot',
+          dataView: true,
+          popup: true,
+          show: (record) => { return (record.state === 'Ready' && record.hypervisor === 'KVM') },
+          component: () => import('@/views/storage/CreateSnapshotFromVMSnapshot.vue')
+        },
+        {
           api: 'revertToVMSnapshot',
           icon: 'sync',
           label: 'label.action.vmsnapshot.revert',
diff --git a/src/locales/en.json b/src/locales/en.json
index 63cbba7..2566cab 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -83,6 +83,7 @@
 "label.action.create.template.from.vm": "Create Template from VM",
 "label.action.create.template.from.volume": "Create Template from Volume",
 "label.action.create.template.processing": "Creating Template....",
+"label.action.create.snapshot.from.vmsnapshot": "Create Snapshot from VM Snapshot",
 "label.action.create.vm": "Create VM",
 "label.action.create.vm.processing": "Creating VM....",
 "label.action.create.volume": "Create Volume",
@@ -2341,6 +2342,7 @@
 "message.action.cancel.maintenance.mode": "Please confirm that you want to cancel this maintenance.",
 "message.action.change.service.warning.for.instance": "Your instance must be stopped before attempting to change its current service offering.",
 "message.action.change.service.warning.for.router": "Your router must be stopped before attempting to change its current service offering.",
+"message.action.create.snapshot.from.vmsnapshot": "Please confirm that you want to create snapshot from VM Snapshot",
 "message.action.delete.backup.offering": "Please confirm that you want to delete this backup offering?",
 "message.action.delete.cluster": "Please confirm that you want to delete this cluster.",
 "message.action.delete.disk.offering": "Please confirm that you want to delete this disk offering.",
@@ -2595,6 +2597,8 @@
 "message.create.internallb.failed": "Failed to create Internal LB",
 "message.create.internallb.processing": "Creation of Internal LB is in progress",
 "message.create.service.offering": "Service offering created",
+"message.create.snapshot.from.vmsnapshot.failed": "Failed to create Snapshot from VM Snapshot",
+"message.create.snapshot.from.vmsnapshot.progress": "Snapshot Creation in progress",
 "message.create.template": "Are you sure you want to create template?",
 "message.create.template.vm": "Create VM from template <b id=\"p_name\"></b>",
 "message.create.template.volume": "Please specify the following information before creating a template of your disk volume: <b><span id=\"volume_name\"></span></b>. Creation of the template can range from several minutes to longer depending on the size of the volume.",
@@ -3064,6 +3068,7 @@
 "message.success.create.keypair": "Successfully created SSH key pair",
 "message.success.create.kubernetes.cluter": "Successfully created Kubernetes cluster",
 "message.success.create.l2.network": "Successfully created L2 network",
+"message.success.create.snapshot.from.vmsnapshot": "Successfully created Snapshot from VM snapshot",
 "message.success.create.volume": "Successfully created volume",
 "message.success.delete": "Delete success",
 "message.success.delete.acl.rule": "Successfully removed ACL rule",
diff --git a/src/views/storage/CreateSnapshotFromVMSnapshot.vue b/src/views/storage/CreateSnapshotFromVMSnapshot.vue
new file mode 100644
index 0000000..6e3be3c
--- /dev/null
+++ b/src/views/storage/CreateSnapshotFromVMSnapshot.vue
@@ -0,0 +1,151 @@
+// 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>
+  <a-spin :spinning="loading">
+    <a-form class="form" :form="form" @submit="handleSubmit" layout="vertical">
+      <a-form-item :label="$t('label.name')">
+        <a-input
+          v-decorator="['name', {
+            rules: [{ required: true, message: $t('message.error.name') }]
+          }]"
+          :placeholder="$t('label.snapshot.name')"/>
+      </a-form-item>
+      <a-form-item :label="$t('label.volume')">
+        <a-select
+          v-decorator="['volumeid', {
+            initialValue: selectedVolumeId,
+            rules: [{ required: true, message: $t('message.error.select') }]}]"
+          :loading="loading"
+          @change="id => (volumes.filter(x => x.id === id))"
+        >
+          <a-select-option
+            v-for="(volume, index) in volumes"
+            :value="volume.id"
+            :key="index">
+            {{ volume.displaytext || volume.name }}
+          </a-select-option>
+        </a-select>
+      </a-form-item>
+      <div :span="24" class="action-button">
+        <a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
+        <a-button type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
+      </div>
+    </a-form>
+  </a-spin>
+</template>
+
+<script>
+import { api } from '@/api'
+
+export default {
+  name: 'CreateSnapshotFromVMSnapshot',
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      volumes: [],
+      selectedVolumeId: '',
+      loading: false
+    }
+  },
+  beforeCreate () {
+    this.form = this.$form.createForm(this)
+  },
+  mounted () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      this.loading = true
+      api('listVolumes', {
+        virtualmachineid: this.resource.virtualmachineid,
+        listall: true
+      }).then(json => {
+        this.volumes = json.listvolumesresponse.volume || []
+        this.selectedVolumeId = this.volumes[0].id || ''
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    handleSubmit (e) {
+      this.form.validateFields((err, values) => {
+        if (err) {
+          return
+        }
+        this.loading = true
+        api('createSnapshotFromVMSnapshot', {
+          name: values.name,
+          volumeid: values.volumeid,
+          vmsnapshotid: this.resource.id
+        }).then(response => {
+          this.$pollJob({
+            jobId: response.createsnapshotfromvmsnapshotresponse.jobid,
+            successMessage: this.$t('message.success.create.snapshot.from.vmsnapshot'),
+            successMethod: () => {
+              this.$store.dispatch('AddAsyncJob', {
+                title: this.$t('message.success.create.snapshot.from.vmsnapshot'),
+                jobid: response.createsnapshotfromvmsnapshotresponse.jobid,
+                description: values.name,
+                status: 'progress'
+              })
+              this.$emit('refresh-data')
+            },
+            errorMessage: this.$t('message.create.snapshot.from.vmsnapshot.failed'),
+            errorMethod: () => {
+              this.$emit('refresh-data')
+            },
+            loadingMessage: this.$t('message.create.snapshot.from.vmsnapshot.progress'),
+            catchMessage: this.$t('error.fetching.async.job.result')
+          })
+        }).catch(error => {
+          this.$notifyError(error)
+        }).finally(() => {
+          this.loading = false
+          this.$emit('refresh-data')
+          this.closeModal()
+        })
+      })
+    },
+    closeModal () {
+      this.$emit('close-action')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.form {
+  width: 80vw;
+
+  @media (min-width: 500px) {
+    width: 400px;
+  }
+}
+
+.action-button {
+  text-align: right;
+
+  button {
+    margin-right: 5px;
+  }
+}
+</style>