From bea6b1454beee24705bd6467f52a285ff37221ba Mon Sep 17 00:00:00 2001 From: catborise Date: Mon, 13 Mar 2023 11:22:40 +0300 Subject: [PATCH 1/8] fix grouped non grouped instance view --- instances/templates/allinstances.html | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/instances/templates/allinstances.html b/instances/templates/allinstances.html index 095d99c..457d9f6 100644 --- a/instances/templates/allinstances.html +++ b/instances/templates/allinstances.html @@ -25,10 +25,12 @@ {% endif %} {% endfor %}
- {% if app_settings.VIEW_INSTANCES_LIST_STYLE == 'grouped' and request.user.is_superuser or 'instances.view_instances' in perms %} - {% include 'allinstances_index_grouped.html' %} - {% else %} - {% include 'allinstances_index_nongrouped.html' %} + {% if 'instances.view_instances' in perms %} + {% if app_settings.VIEW_INSTANCES_LIST_STYLE == 'grouped' and request.user.is_superuser %} + {% include 'allinstances_index_grouped.html' %} + {% else %} + {% include 'allinstances_index_nongrouped.html' %} + {% endif %} {% endif %}
{% endblock content %} From ad9f1db643c26b9b72a047f11aad222228450303 Mon Sep 17 00:00:00 2001 From: cserma Date: Wed, 1 Mar 2023 14:11:30 +0300 Subject: [PATCH 2/8] added filter for logs --- admin/templates/admin/logs.html | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/admin/templates/admin/logs.html b/admin/templates/admin/logs.html index 3305641..a6c8f0d 100644 --- a/admin/templates/admin/logs.html +++ b/admin/templates/admin/logs.html @@ -6,6 +6,12 @@ {% block page_heading %}{% trans "Logs" %}{% endblock page_heading %} +{% block page_heading_extra %} + {% include 'search_block.html' %} +{% endblock page_heading_extra %} + + + {% block content %}
@@ -28,7 +34,7 @@ {% trans "Message" %} - + {% for log in logs %} {{ log.id }} @@ -47,3 +53,6 @@
{% endblock %} +{% block script %} + +{% endblock script %} From fd6b2ec4bfc7855ba2ab8f45f6cb218eb39819a2 Mon Sep 17 00:00:00 2001 From: cserma Date: Tue, 4 Apr 2023 09:51:59 +0300 Subject: [PATCH 3/8] external snapshot implementation --- .../templates/instances/snapshots_tab.html | 101 +++++++++++- instances/urls.py | 3 + instances/views.py | 65 +++++++- vrtManager/instance.py | 153 +++++++++++++++++- 4 files changed, 315 insertions(+), 7 deletions(-) mode change 100644 => 100755 instances/templates/instances/snapshots_tab.html mode change 100644 => 100755 instances/urls.py mode change 100644 => 100755 instances/views.py diff --git a/instances/templates/instances/snapshots_tab.html b/instances/templates/instances/snapshots_tab.html old mode 100644 new mode 100755 index d6eb88e..986bf90 --- a/instances/templates/instances/snapshots_tab.html +++ b/instances/templates/instances/snapshots_tab.html @@ -13,6 +13,16 @@ {% trans "Manage Snapshots" %} + +
@@ -29,7 +39,11 @@ | - + {% if external_snapshots|length > 0 %} + + {% else %} + + {% endif %}
@@ -56,11 +70,11 @@ {% csrf_token %} {% if instance.status == 5 %} - {% else %} - @@ -84,5 +98,86 @@

{% 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/urls.py b/instances/urls.py old mode 100644 new mode 100755 index c7a74f9..acb5444 --- a/instances/urls.py +++ b/instances/urls.py @@ -39,6 +39,9 @@ urlpatterns = [ path("/snapshot/", views.snapshot, name="snapshot"), path("/delete_snapshot/", views.delete_snapshot, name="delete_snapshot"), path("/revert_snapshot/", views.revert_snapshot, name="revert_snapshot"), + path("/create_external_snapshot/", views.create_external_snapshot, name="create_external_snapshot"), + path("/revert_external_snapshot/", views.revert_external_snapshot, name="revert_external_snapshot"), + path("/delete_external_snapshot/", views.delete_external_snapshot, name="delete_external_snapshot"), path("/set_vcpu/", views.set_vcpu, name="set_vcpu"), path("/set_vcpu_hotplug/", views.set_vcpu_hotplug, name="set_vcpu_hotplug"), path("/set_autostart/", views.set_autostart, name="set_autostart"), diff --git a/instances/views.py b/instances/views.py old mode 100644 new mode 100755 index 3b018bb..f603c51 --- a/instances/views.py +++ b/instances/views.py @@ -146,7 +146,9 @@ def instance(request, pk): instance.drbd = drbd_status(request, pk) instance.save() - return render(request, "instance.html", locals()) + external_snapshots = get_external_snapshots(request,pk) + + return render(request, "instance.html", locals(),) def status(request, pk): @@ -1014,6 +1016,67 @@ 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 = ( + request.user.is_superuser or request.user.is_staff or not instance.is_template + ) + + if allow_admin_or_not_template and request.user.has_perm( + "instances.snapshot_instances" + ): + name = request.POST.get("name", "") + desc = request.POST.get("description", "") + instance.proxy.create_external_snapshot(name, instance, desc=desc) + #msg = _("Create snapshot: %(snap)s") % {"snap": name} + #addlogmsg(request.user.username, instance.compute.name, instance.name, msg) + return redirect(request.META.get("HTTP_REFERER") + "#manageExternalSnapshots") + +def get_external_snapshots(request, pk): + instance = get_instance(request.user, pk) + allow_admin_or_not_template = ( + request.user.is_superuser or request.user.is_staff or not instance.is_template + ) + + if allow_admin_or_not_template and request.user.has_perm( + "instances.snapshot_instances" + ): + external_snapshots = instance.proxy.get_external_snapshots() + #msg = _("Create snapshot: %(snap)s") % {"snap": name} + #addlogmsg(request.user.username, instance.compute.name, instance.name, msg) + return external_snapshots + +def revert_external_snapshot(request, pk): + instance = get_instance(request.user, pk) + allow_admin_or_not_template = ( + request.user.is_superuser or request.user.is_staff or not instance.is_template + ) + + if allow_admin_or_not_template and request.user.has_perm( + "instances.snapshot_instances" + ): + name = request.POST.get("name", "") + date = request.POST.get("date", "") + desc = request.POST.get("desc", "") + instance.proxy.revert_external_snapshot(name, instance, date, desc) + #msg = _("Create snapshot: %(snap)s") % {"snap": name} + #addlogmsg(request.user.username, instance.compute.name, instance.name, msg) + return redirect(request.META.get("HTTP_REFERER") + "#manageExternalSnapshots") + +def delete_external_snapshot(request, pk): + instance = get_instance(request.user, pk) + allow_admin_or_not_template = ( + request.user.is_superuser or request.user.is_staff or not instance.is_template + ) + + if allow_admin_or_not_template and request.user.has_perm( + "instances.snapshot_instances" + ): + name = request.POST.get("name", "") + instance.proxy.delete_external_snapshot(name, instance) + #msg = _("Create snapshot: %(snap)s") % {"snap": name} + #addlogmsg(request.user.username, instance.compute.name, instance.name, msg) + return redirect(request.META.get("HTTP_REFERER") + "#manageExternalSnapshots") @superuser_only def set_vcpu(request, pk): diff --git a/vrtManager/instance.py b/vrtManager/instance.py index e51f8bc..890d270 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -2,6 +2,7 @@ import contextlib import json import os.path import time +import subprocess try: from libvirt import ( @@ -18,6 +19,10 @@ try: VIR_MIGRATE_POSTCOPY, VIR_MIGRATE_UNDEFINE_SOURCE, VIR_MIGRATE_UNSAFE, + VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY, + VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY, + VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL, + VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL, libvirtError, ) from libvirt_qemu import VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT, qemuAgentCommand @@ -34,7 +39,6 @@ from vrtManager import util from vrtManager.connection import wvmConnect from vrtManager.storage import wvmStorage, wvmStorages - class wvmInstances(wvmConnect): def get_instance_status(self, name): inst = self.get_instance(name) @@ -1283,9 +1287,152 @@ class wvmInstance(wvmConnect): ) self._defineXML(xml_temp) - def get_snapshot(self): + 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 + + creation_time = time.time() + state = "shutoff" if self.get_status() == 5 else "running" + xml = """ + %s + %s + %s + %d""" % ( + name, + desc, + state, + creation_time, + ) + self.change_snapshot_xml() + xml += self._XMLDesc(VIR_DOMAIN_XML_SECURE) + xml += """0 + """ + # + # flag number for libvirt.VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY + # is 16 (0x10; 1 << 4) + # + self._snapshotCreateXML(xml, VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) + + tree = ElementTree.fromstring(self._XMLDesc(0)) + for disks in tree.findall("devices/disk"): + if disks.get('device') == "disk": + backingStore = disks.find("backingStore") + if backingStore is not None: + temp_backing_file = backingStore.find('source').get('file') + vol_base = self.get_volume_by_path(temp_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 + + def delete_external_snapshot(self, name, instance): + + base_xml = ElementTree.fromstring(self._XMLDesc(0)) + for disk in base_xml.findall('devices/disk'): + if disk.get('device') == 'disk': + backingStore = disk.find('backingStore') + if backingStore is not None: + if backingStore.find('source') is not None: + target_dev = disk.find('target').get('dev') + backing_file = backingStore.find('source').get('file') + source_file = disk.find('source').get('file') + self.instance.blockCommit(target_dev, backing_file, source_file, flags=4|2) + while True: + info = self.instance.blockJobInfo(target_dev, 0) + if info.get('cur') == info.get('end'): + self.instance.blockJobAbort(target_dev,flags=2) + break + + 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) + + 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"])) + + snap = self.instance.snapshotLookupByName(name, 0) + # flag number for delete snapshot metadata only + # is 2 (0x2; 1 << 1) + snap.delete(VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) + + def revert_external_snapshot(self, name, instance, 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) + + # flag number for delete snapshot metadata only + # is 2 (0x2; 1 << 1) + snap.delete(VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) + base_xml = ElementTree.fromstring(self._XMLDesc(0)) + for disk in base_xml.findall('devices/disk'): + if disk.get('device') == 'disk': + backingStore = disk.find('backingStore') + if backingStore is not None: + if backingStore.find('source') is not None: + vol_base = self.get_volume_by_path(backingStore.find('source').get('file')) + pool = vol_base.storagePoolLookupByVolume() + pool.refresh(0) + vol_snap = self.get_volume_by_path(disk.find('source').get('file')) + vol_snap.wipe(0) + vol_snap.delete(0) + + 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.create_external_snapshot(name, instance, date, desc) + + def get_snapshot(self, flag=VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL): snapshots = [] - snapshot_list = self.instance.snapshotListNames(0) + snapshot_list = self.instance.snapshotListNames(flag) for snapshot in snapshot_list: snap = self.instance.snapshotLookupByName(snapshot, 0) snap_description = util.get_xml_path( From 68b04943507e570a2747616ab26ae213bd137aff Mon Sep 17 00:00:00 2001 From: cserma Date: Tue, 4 Apr 2023 16:38:12 +0300 Subject: [PATCH 4/8] external snapshot logs and bug fixes --- instances/views.py | 14 ++--- vrtManager/instance.py | 126 ++++++++++++++++------------------------- 2 files changed, 55 insertions(+), 85 deletions(-) diff --git a/instances/views.py b/instances/views.py index f603c51..7bfd8ce 100755 --- a/instances/views.py +++ b/instances/views.py @@ -1028,8 +1028,8 @@ 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) - #msg = _("Create snapshot: %(snap)s") % {"snap": name} - #addlogmsg(request.user.username, instance.compute.name, instance.name, msg) + 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") def get_external_snapshots(request, pk): @@ -1042,8 +1042,6 @@ def get_external_snapshots(request, pk): "instances.snapshot_instances" ): external_snapshots = instance.proxy.get_external_snapshots() - #msg = _("Create snapshot: %(snap)s") % {"snap": name} - #addlogmsg(request.user.username, instance.compute.name, instance.name, msg) return external_snapshots def revert_external_snapshot(request, pk): @@ -1059,8 +1057,8 @@ def revert_external_snapshot(request, pk): date = request.POST.get("date", "") desc = request.POST.get("desc", "") instance.proxy.revert_external_snapshot(name, instance, date, desc) - #msg = _("Create snapshot: %(snap)s") % {"snap": name} - #addlogmsg(request.user.username, instance.compute.name, instance.name, msg) + 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") def delete_external_snapshot(request, pk): @@ -1074,8 +1072,8 @@ def delete_external_snapshot(request, pk): ): name = request.POST.get("name", "") instance.proxy.delete_external_snapshot(name, instance) - #msg = _("Create snapshot: %(snap)s") % {"snap": name} - #addlogmsg(request.user.username, instance.compute.name, instance.name, msg) + 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") @superuser_only diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 890d270..f8658f6 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -23,6 +23,9 @@ try: VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY, VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL, VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL, + VIR_DOMAIN_BLOCK_COMMIT_DELETE, + VIR_DOMAIN_BLOCK_COMMIT_ACTIVE, + VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT, libvirtError, ) from libvirt_qemu import VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT, qemuAgentCommand @@ -453,6 +456,7 @@ class wvmInstance(wvmConnect): disk_format = used_size = disk_size = None disk_cache = disk_io = disk_discard = disk_zeroes = "default" readonly = shareable = serial = None + backing_file = None device = disk.xpath("@device")[0] if device == "disk": @@ -484,6 +488,9 @@ class wvmInstance(wvmConnect): with contextlib.suppress(Exception): disk_zeroes = disk.xpath("driver/@detect_zeroes")[0] + with contextlib.suppress(Exception): + backing_file = disk.xpath("backingStore/source/@file")[0] + readonly = bool(disk.xpath("readonly")) shareable = bool(disk.xpath("shareable")) serial = ( @@ -513,6 +520,7 @@ class wvmInstance(wvmConnect): "storage": storage, "path": src_file, "format": disk_format, + "backing_file": backing_file, "size": disk_size, "used": used_size, "cache": disk_cache, @@ -1309,21 +1317,15 @@ class wvmInstance(wvmConnect): xml += self._XMLDesc(VIR_DOMAIN_XML_SECURE) xml += """0 """ - # - # flag number for libvirt.VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY - # is 16 (0x10; 1 << 4) - # + self._snapshotCreateXML(xml, VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) - tree = ElementTree.fromstring(self._XMLDesc(0)) - for disks in tree.findall("devices/disk"): - if disks.get('device') == "disk": - backingStore = disks.find("backingStore") - if backingStore is not None: - temp_backing_file = backingStore.find('source').get('file') - vol_base = self.get_volume_by_path(temp_backing_file) - pool = vol_base.storagePoolLookupByVolume() - pool.refresh(0) + 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 = [] @@ -1337,52 +1339,20 @@ class wvmInstance(wvmConnect): return external_snapshots def delete_external_snapshot(self, name, instance): - - base_xml = ElementTree.fromstring(self._XMLDesc(0)) - for disk in base_xml.findall('devices/disk'): - if disk.get('device') == 'disk': - backingStore = disk.find('backingStore') - if backingStore is not None: - if backingStore.find('source') is not None: - target_dev = disk.find('target').get('dev') - backing_file = backingStore.find('source').get('file') - source_file = disk.find('source').get('file') - self.instance.blockCommit(target_dev, backing_file, source_file, flags=4|2) - while True: - info = self.instance.blockJobInfo(target_dev, 0) - if info.get('cur') == info.get('end'): - self.instance.blockJobAbort(target_dev,flags=2) - break - - 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) - - 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"])) + 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) + 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 snap = self.instance.snapshotLookupByName(name, 0) - # flag number for delete snapshot metadata only - # is 2 (0x2; 1 << 1) snap.delete(VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) def revert_external_snapshot(self, name, instance, date, desc): @@ -1404,29 +1374,31 @@ class wvmInstance(wvmConnect): disk_dict['boot_order'] = disk_backup.find('boot').get('order') disks.append(disk_dict) - # flag number for delete snapshot metadata only - # is 2 (0x2; 1 << 1) snap.delete(VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) - base_xml = ElementTree.fromstring(self._XMLDesc(0)) - for disk in base_xml.findall('devices/disk'): - if disk.get('device') == 'disk': - backingStore = disk.find('backingStore') - if backingStore is not None: - if backingStore.find('source') is not None: - vol_base = self.get_volume_by_path(backingStore.find('source').get('file')) - pool = vol_base.storagePoolLookupByVolume() - pool.refresh(0) - vol_snap = self.get_volume_by_path(disk.find('source').get('file')) - vol_snap.wipe(0) - vol_snap.delete(0) + 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() + pool.refresh(0) + vol_snap = self.get_volume_by_path(source_file) + vol_snap.wipe(0) + vol_snap.delete(0) 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( + """ + + + + + """.format(disk["driver_name"], + disk["driver_type"], + disk["backing_file"], + disk["target_dev"], + disk["target_bus"], + disk["boot_order"])) self.create_external_snapshot(name, instance, date, desc) From 33d49a68a7fa30bc693752ddbde39b15427825ef Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 13 Apr 2023 12:24:13 +0300 Subject: [PATCH 5/8] experimental: external snapshot fixes --- instances/models.py | 4 + instances/templates/instance.html | 4 +- .../templates/instances/snapshots_tab.html | 227 ++++++++---------- instances/views.py | 33 ++- vrtManager/instance.py | 95 +++----- 5 files changed, 156 insertions(+), 207 deletions(-) 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 = [] From f4aa925e2a9264ee62da364479680466effd2400 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 14 Apr 2023 16:11:13 +0300 Subject: [PATCH 6/8] experimental: external snapshot fixes 2 --- instances/views.py | 5 ++++- vrtManager/instance.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/instances/views.py b/instances/views.py index eb6d0bb..5ba91e9 100755 --- a/instances/views.py +++ b/instances/views.py @@ -1056,10 +1056,13 @@ def revert_external_snapshot(request, pk): if allow_admin_or_not_template and request.user.has_perm( "instances.snapshot_instances" ): + instance_state = True if instance.proxy.get_status() != 5 else False name = request.POST.get("name", "") date = request.POST.get("date", "") desc = request.POST.get("desc", "") - instance.proxy.revert_external_snapshot(name, instance, date, desc) + instance.proxy.force_shutdown() if instance_state else None + instance.proxy.revert_external_snapshot(name, date, desc) + instance.proxy.start() if instance_state else None 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") + "#managesnapshot") diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 680dd09..b434751 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -1335,6 +1335,7 @@ class wvmInstance(wvmConnect): 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) + time.sleep(2) break # Check again pool for snapshot delta volume; if it exist, remove it manually with contextlib.suppress(libvirtError): From 71f05089f62723d10bd38c107aaa7fd615bf3864 Mon Sep 17 00:00:00 2001 From: catborise Date: Mon, 24 Apr 2023 14:38:51 +0300 Subject: [PATCH 7/8] experimental: external snapshot fixes 3 --- vrtManager/instance.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/vrtManager/instance.py b/vrtManager/instance.py index b434751..b85f0a0 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -26,6 +26,7 @@ try: VIR_DOMAIN_BLOCK_COMMIT_DELETE, VIR_DOMAIN_BLOCK_COMMIT_ACTIVE, VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT, + VIR_DOMAIN_START_PAUSED, libvirtError, ) from libvirt_qemu import VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT, qemuAgentCommand @@ -1298,12 +1299,12 @@ class wvmInstance(wvmConnect): 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 - """ % ( name, desc, @@ -1349,24 +1350,29 @@ class wvmInstance(wvmConnect): def revert_external_snapshot(self, name, date, desc): + pool = None snap = self.instance.snapshotLookupByName(name, 0) 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') - + + self.start(flags=VIR_DOMAIN_START_PAUSED) if self.get_status() == 5 else None + disk_info = self.get_disk_devices() for disk in disk_info: vol_snap = self.get_volume_by_path(disk["path"]) pool = vol_snap.storagePoolLookupByVolume() pool.refresh(0) vol_snap.delete(0) + self.force_shutdown() snap.delete(VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) for disk in disks: self.instance.updateDeviceFlags(ElementTree.tostring(disk).decode("UTF-8")) - + name = name.replace("s1", "s2") self.create_external_snapshot(name, date, desc) + pool.refresh() if pool else None def get_snapshot(self, flag=VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL): snapshots = [] From 012bc97a59b4c7f4901e453ae665951603501e92 Mon Sep 17 00:00:00 2001 From: catborise Date: Mon, 24 Apr 2023 14:39:22 +0300 Subject: [PATCH 8/8] add showPleasewait dialog while starting instance. --- instances/templates/instance_actions.html | 4 ++-- instances/templates/instances/power_tab.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/instances/templates/instance_actions.html b/instances/templates/instance_actions.html index f67dc1d..1948b42 100644 --- a/instances/templates/instance_actions.html +++ b/instances/templates/instance_actions.html @@ -7,7 +7,7 @@ {% icon 'clone' %} {% else %} - + {% icon 'play' %} {% endif %} @@ -32,7 +32,7 @@ {% endif %} {% if instance.proxy.instance.info.0 == 1 %} - {% icon 'play' %} + {% icon 'play' %} {% icon 'pause' %} {% icon 'power-off' %} diff --git a/instances/templates/instances/power_tab.html b/instances/templates/instances/power_tab.html index cf4fe1e..3811490 100644 --- a/instances/templates/instances/power_tab.html +++ b/instances/templates/instances/power_tab.html @@ -68,7 +68,7 @@

{% trans "This action forcibly powers off the instance and may cause data corruption." %}

-
+ {% csrf_token %}
@@ -119,7 +119,7 @@

{% trans "Template instance cannot be started." %}

{% else %} - + {% endif %}