diff --git a/instances/models.py b/instances/models.py index 2946715..978d118 100644 --- a/instances/models.py +++ b/instances/models.py @@ -173,6 +173,10 @@ class Instance(models.Model): def snapshots(self): return sorted(self.proxy.get_snapshot(), reverse=True, key=lambda k: k["date"]) + @cached_property + def external_snapshots(self): + return sorted(self.proxy.get_external_snapshots(), reverse=True, key=lambda k: k["date"]) + @cached_property def inst_xml(self): return self.proxy._XMLDesc(VIR_DOMAIN_XML_SECURE) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index bf48bbe..748c0df 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -679,7 +679,7 @@ } }); } - if (~$.inArray(hash, ['#resize', "resizevm_cpu", "resizevm_mem", "resizevm_disk"])) { + if (~$.inArray(hash, ['#resize', "#resizevm_cpu", "#resizevm_mem", "#resizevm_disk"])) { var btnsect = $('#navbtn>li>a'); $(btnsect).each(function () { if ($(this).attr('href') === '#resize') { @@ -702,7 +702,7 @@ } }); } - if (~$.inArray(hash, ['#takesnapshot', '#managesnapshot'])) { + if (~$.inArray(hash, ['#takesnapshot', "#takeextsnapshot", "#managesnapshot"])) { var btnsect = $('#navbtn>li>a'); $(btnsect).each(function () { if ($(this).attr('href') === '#snapshots') { diff --git a/instances/templates/instances/snapshots_tab.html b/instances/templates/instances/snapshots_tab.html index 986bf90..5048e7e 100755 --- a/instances/templates/instances/snapshots_tab.html +++ b/instances/templates/instances/snapshots_tab.html @@ -5,7 +5,12 @@
@@ -39,7 +34,7 @@ | - {% if external_snapshots|length > 0 %} + {% if instance.external_snapshots|length > 0 %} {% else %} @@ -47,137 +42,109 @@
- + +
+ {% if instance.status != 5 %} +

{% trans "You can get external snapshots within this tab." %}

+

{% trans "External snapshots are experimental in this stage, use it if you know what you are doing. It may require manual intervention." %}

+ {% else %} +

{% trans "Create an external snapshot" %}

+ {% endif %} +

{% trans "Give your External Snapshot a distinctive description so it wouldn't get mixed with other snapshots." %}

+
+ {% csrf_token %} +
+ + | + + {% if instance.external_snapshots|length > 0 or instance.snapshots|length > 0 %} + + {% else %} + + {% endif %} +
+

{% trans "WebVirtCloud supports only one external snapshot at the moment." %}

+
+
+
- {% if instance.snapshots %} + {% if instance.snapshots or instance.external_snapshots %}

{% trans "Choose a snapshot for restore/delete" %}

- + - {% for snap in instance.snapshots %} - - - - - - - - {% endfor %} + {% if instance.snapshots %} + {% for snap in instance.snapshots %} + + + + + + + + {% endfor %} + {% elif instance.external_snapshots %} + {% for ext_snap in instance.external_snapshots %} + + + + + + + + {% endfor %} + {% endif %}
{% trans "Date" %} {% trans "Name" %}{% trans "Description" %}{% trans "Type - Description" %} {% trans "Action" %}
{{ snap.date|date:"M d H:i:s" }}{{ snap.name }}{{ snap.description }} -
- {% csrf_token %} - - {% if instance.status == 5 %} - - {% else %} - - {% endif %} -
-
-
{% csrf_token %} - - -
-
{{ snap.date|date:"M d H:i:s" }}{{ snap.name }}({% trans "Internal" %}) - {{ snap.description }} +
+ {% csrf_token %} + + {% if instance.status == 5 %} + + {% else %} + + {% endif %} +
+
+
{% csrf_token %} + + +
+
{{ ext_snap.date|date:"M d H:i:s" }}{{ ext_snap.name }}({% trans "External" %}) - {{ ext_snap.description }} +
+ {% csrf_token %} + + + + +
+
+
{% csrf_token %} + + +
+
{% else %}

{% trans "You do not have any snapshots" %}

{% endif %} -
-
- {% if instance.status != 5 %} -

You can get external snapshots within this tab.

-

External snapshots are experimental in this stage, use it if you know what you are doing.

- {% else %} -

Create an external snapshot

- {% endif %} -

Give your External Snapshot a distinctive description so it wouldn't get mixed with other snapshots.

-
- {% csrf_token %} -
- - - {% if external_snapshots|length > 0 or instance.snapshots|length > 0 %} -

WebVirtCloud supports only one External Snapshot at the moment.

- - {% else %} - - {% endif %} - -
-
-
-
-
- {% if external_snapshots %} -
- - - - - - - - - {% for external_snapshot in external_snapshots %} - - {% for snapshot_cols in external_snapshot %} - - {% endfor %} - - - - {% endfor %} - -
NameDateDescription{% trans "Action" %}
{{snapshot_cols}} -
- {% csrf_token %} - - - - {% if instance.status == 5 %} - - {% else %} - - {% endif %} -
-
-
{% csrf_token %} - - {% if instance.status != 5 %} - - {% else %} - - {% endif %} -
-
-
- {% else%} -

{% trans "You do not have any snapshots" %}

- {% endif %} -
- - + + + diff --git a/instances/views.py b/instances/views.py index 7bfd8ce..eb6d0bb 100755 --- a/instances/views.py +++ b/instances/views.py @@ -20,7 +20,9 @@ from django.http import Http404, HttpResponse, JsonResponse from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from libvirt import (VIR_DOMAIN_UNDEFINE_KEEP_NVRAM, VIR_DOMAIN_UNDEFINE_NVRAM, +from libvirt import (VIR_DOMAIN_UNDEFINE_KEEP_NVRAM, + VIR_DOMAIN_UNDEFINE_NVRAM, + VIR_DOMAIN_START_PAUSED, libvirtError) from logs.views import addlogmsg from vrtManager import util @@ -146,8 +148,6 @@ def instance(request, pk): instance.drbd = drbd_status(request, pk) instance.save() - external_snapshots = get_external_snapshots(request,pk) - return render(request, "instance.html", locals(),) @@ -1016,6 +1016,7 @@ def revert_snapshot(request, pk): addlogmsg(request.user.username, instance.compute.name, instance.name, msg) return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot") + def create_external_snapshot(request, pk): instance = get_instance(request.user, pk) allow_admin_or_not_template = ( @@ -1027,10 +1028,11 @@ def create_external_snapshot(request, pk): ): name = request.POST.get("name", "") desc = request.POST.get("description", "") - instance.proxy.create_external_snapshot(name, instance, desc=desc) + instance.proxy.create_external_snapshot("s1." + name, instance, desc=desc) msg = _("Create external snapshot: %(snap)s") % {"snap": name} addlogmsg(request.user.username, instance.compute.name, instance.name, msg) - return redirect(request.META.get("HTTP_REFERER") + "#manageExternalSnapshots") + return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot") + def get_external_snapshots(request, pk): instance = get_instance(request.user, pk) @@ -1044,6 +1046,7 @@ def get_external_snapshots(request, pk): external_snapshots = instance.proxy.get_external_snapshots() return external_snapshots + def revert_external_snapshot(request, pk): instance = get_instance(request.user, pk) allow_admin_or_not_template = ( @@ -1059,10 +1062,12 @@ def revert_external_snapshot(request, pk): instance.proxy.revert_external_snapshot(name, instance, date, desc) msg = _("Revert external snapshot: %(snap)s") % {"snap": name} addlogmsg(request.user.username, instance.compute.name, instance.name, msg) - return redirect(request.META.get("HTTP_REFERER") + "#manageExternalSnapshots") + return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot") + def delete_external_snapshot(request, pk): instance = get_instance(request.user, pk) + instance_state = True if instance.proxy.get_status() == 5 else False allow_admin_or_not_template = ( request.user.is_superuser or request.user.is_staff or not instance.is_template ) @@ -1071,10 +1076,18 @@ def delete_external_snapshot(request, pk): "instances.snapshot_instances" ): name = request.POST.get("name", "") - instance.proxy.delete_external_snapshot(name, instance) - msg = _("Delete external snapshot: %(snap)s") % {"snap": name} - addlogmsg(request.user.username, instance.compute.name, instance.name, msg) - return redirect(request.META.get("HTTP_REFERER") + "#manageExternalSnapshots") + + instance.proxy.start(VIR_DOMAIN_START_PAUSED) if instance_state else None + + try: + instance.proxy.delete_external_snapshot(name) + msg = _("Delete external snapshot: %(snap)s") % {"snap": name} + addlogmsg(request.user.username, instance.compute.name, instance.name, msg) + finally: + instance.proxy.force_shutdown() if instance_state else None + + return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot") + @superuser_only def set_vcpu(request, pk): diff --git a/vrtManager/instance.py b/vrtManager/instance.py index f8658f6..680dd09 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -209,8 +209,8 @@ class wvmInstance(wvmConnect): return info_results - def start(self): - self.instance.create() + def start(self, flags=0): + self.instance.createWithFlags(flags) def shutdown(self): self.instance.shutdown() @@ -1295,24 +1295,22 @@ class wvmInstance(wvmConnect): ) self._defineXML(xml_temp) - def create_external_snapshot(self, name, instance, date=None, desc=None): - if self.instance.isActive() == False: - result = self.instance.create() - if result < 0: - return 0 - + def create_external_snapshot(self, name, date=None, desc=None): creation_time = time.time() state = "shutoff" if self.get_status() == 5 else "running" xml = """ %s %s %s - %d""" % ( + %d + + """ % ( name, desc, state, creation_time, ) + self.change_snapshot_xml() xml += self._XMLDesc(VIR_DOMAIN_XML_SECURE) xml += """0 @@ -1320,87 +1318,54 @@ class wvmInstance(wvmConnect): self._snapshotCreateXML(xml, VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) - disk_info = self.get_disk_devices() - for disk in disk_info: - backing_file = disk["backing_file"] - vol_base = self.get_volume_by_path(backing_file) - pool = vol_base.storagePoolLookupByVolume() - pool.refresh(0) def get_external_snapshots(self): - external_snapshots = [] - temp_snapshots = self.get_snapshot(VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL) - for temp_snapshot in temp_snapshots: - external_snapshot = [] - external_snapshot.append(temp_snapshot['name']) - external_snapshot.append(temp_snapshot['date']) - external_snapshot.append(temp_snapshot['description']) - external_snapshots.append(external_snapshot) - return external_snapshots + return self.get_snapshot(VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL) - def delete_external_snapshot(self, name, instance): + def delete_external_snapshot(self, name): disk_info = self.get_disk_devices() for disk in disk_info: target_dev = disk["dev"] backing_file = disk["backing_file"] - source_file = disk["path"] - self.instance.blockCommit(target_dev, backing_file, source_file, - flags=VIR_DOMAIN_BLOCK_COMMIT_DELETE|VIR_DOMAIN_BLOCK_COMMIT_ACTIVE) + snap_source_file = disk["path"] + self.instance.blockCommit(target_dev, backing_file, snap_source_file, + flags=VIR_DOMAIN_BLOCK_COMMIT_DELETE| + VIR_DOMAIN_BLOCK_COMMIT_ACTIVE) while True: info = self.instance.blockJobInfo(target_dev, 0) if info.get('cur') == info.get('end'): self.instance.blockJobAbort(target_dev,flags=VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT) break + # Check again pool for snapshot delta volume; if it exist, remove it manually + with contextlib.suppress(libvirtError): + vol_snap = self.get_volume_by_path(snap_source_file) + pool = vol_snap.storagePoolLookupByVolume() + pool.refresh(0) + vol_snap.delete(0) snap = self.instance.snapshotLookupByName(name, 0) snap.delete(VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) + - def revert_external_snapshot(self, name, instance, date, desc): + def revert_external_snapshot(self, name, date, desc): snap = self.instance.snapshotLookupByName(name, 0) - snapXML = ElementTree.fromstring(snap.getXMLDesc(0)) - disks = [] - for disk_backup in snapXML.findall('inactiveDomain/devices/disk'): - if disk_backup.get('device') == 'disk': - disk_dict = {} - if disk_backup.find('source') is not None: - disk_dict['backing_file'] = disk_backup.find('source').get('file') - if disk_backup.find('driver') is not None: - disk_dict['driver_name'] = disk_backup.find('driver').get('name') - disk_dict['driver_type'] = disk_backup.find('driver').get('type') - if disk_backup.find('target') is not None: - disk_dict['target_dev'] = disk_backup.find('target').get('dev') - disk_dict['target_bus'] = disk_backup.find('target').get('bus') - if disk_backup.find('boot') is not None: - disk_dict['boot_order'] = disk_backup.find('boot').get('order') - disks.append(disk_dict) + snap_xml = snap.getXMLDesc(0) + snapXML = ElementTree.fromstring(snap_xml) + disks = snapXML.findall('inactiveDomain/devices/disk') + if not disks: disks = snapXML.findall('domain/devices/disk') - snap.delete(VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) disk_info = self.get_disk_devices() for disk in disk_info: - source_file = disk["path"] - backing_file = disk["backing_file"] - vol_base = self.get_volume_by_path(backing_file) - pool = vol_base.storagePoolLookupByVolume() + vol_snap = self.get_volume_by_path(disk["path"]) + pool = vol_snap.storagePoolLookupByVolume() pool.refresh(0) - vol_snap = self.get_volume_by_path(source_file) - vol_snap.wipe(0) vol_snap.delete(0) + snap.delete(VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) for disk in disks: - self.instance.updateDeviceFlags( - """ - - - - - """.format(disk["driver_name"], - disk["driver_type"], - disk["backing_file"], - disk["target_dev"], - disk["target_bus"], - disk["boot_order"])) + self.instance.updateDeviceFlags(ElementTree.tostring(disk).decode("UTF-8")) - self.create_external_snapshot(name, instance, date, desc) + self.create_external_snapshot(name, date, desc) def get_snapshot(self, flag=VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL): snapshots = []