From 3752ad5160c05bc8d28a5d0fedca135b82fa7e70 Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 5 Dec 2019 11:33:07 +0300 Subject: [PATCH 01/16] Hypervisor list rearrange view on Host overview, if it is more than 4 --- computes/templates/overview.html | 33 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/computes/templates/overview.html b/computes/templates/overview.html index e6f824f..d230df5 100644 --- a/computes/templates/overview.html +++ b/computes/templates/overview.html @@ -53,31 +53,30 @@

{{ hostname }}

- {% for arch, hpv in hypervisor.items %} - {% if forloop.counter < 4 %} -
+ {% for arch, hpv in hypervisor.items|slice:":4" %} +
+ + +
+ {% endfor %} + {% if hypervisor.items|length > 4 %} -
- {% else %} - - {% endif %} - {% endfor %}

{{ emulator }}

From b27d27d532280917c27144ce8582ec5668ab5937 Mon Sep 17 00:00:00 2001 From: catborise Date: Mon, 9 Dec 2019 13:41:31 +0300 Subject: [PATCH 02/16] fix variable to show bus types --- instances/templates/add_instance_volume.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instances/templates/add_instance_volume.html b/instances/templates/add_instance_volume.html index b242bcc..8409a44 100644 --- a/instances/templates/add_instance_volume.html +++ b/instances/templates/add_instance_volume.html @@ -61,7 +61,7 @@

From b91f1cc36b102b4560946522394ee62a13a62567 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 13 Dec 2019 16:36:34 +0300 Subject: [PATCH 03/16] Add UEFI Architecture Patterns --- vrtManager/util.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/vrtManager/util.py b/vrtManager/util.py index 054e94f..626269b 100644 --- a/vrtManager/util.py +++ b/vrtManager/util.py @@ -166,3 +166,27 @@ def validate_macaddr(val): form = re.match("^([0-9a-fA-F]{1,2}:){5}[0-9a-fA-F]{1,2}$", val) if form is None: raise ValueError("MAC address must be of the format AA:BB:CC:DD:EE:FF, was '%s'" % val) + + +# Mapping of UEFI binary names to their associated architectures. We +# only use this info to do things automagically for the user, it shouldn't +# validate anything the user explicitly enters. +uefi_arch_patterns = { + "i686": [ + r".*ovmf-ia32.*", # fedora, gerd's firmware repo + ], + "x86_64": [ + r".*OVMF_CODE\.fd", # RHEL + r".*ovmf-x64/OVMF.*\.fd", # gerd's firmware repo + r".*ovmf-x86_64-.*", # SUSE + r".*ovmf.*", ".*OVMF.*", # generic attempt at a catchall + ], + "aarch64": [ + r".*AAVMF_CODE\.fd", # RHEL + r".*aarch64/QEMU_EFI.*", # gerd's firmware repo + r".*aarch64.*", # generic attempt at a catchall + ], + "armv7l": [ + r".*arm/QEMU_EFI.*", # fedora, gerd's firmware repo + ], +} \ No newline at end of file From 3a925af3c2af1035183f086080104e8f69d83b71 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 13 Dec 2019 16:38:21 +0300 Subject: [PATCH 04/16] fix connection undefined error while getting volumes --- storages/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storages/views.py b/storages/views.py index b5708de..8b2ec8b 100644 --- a/storages/views.py +++ b/storages/views.py @@ -226,7 +226,7 @@ def get_volumes(request, compute_id, pool): compute.type, pool) conn.refresh() + data['vols'] = sorted(conn.get_volumes()) except libvirtError: pass - data['vols'] = sorted(conn.get_volumes()) return HttpResponse(json.dumps(data)) From 28b001e7cbedc932968242af0478219afe34df7e Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 13 Dec 2019 16:40:59 +0300 Subject: [PATCH 05/16] Add capability to remove UEFI instances: Add option for NVRAM delete --- instances/templates/instance.html | 10 +++++++++- vrtManager/instance.py | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index 5b6d3cd..ffa492c 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -1524,9 +1524,17 @@
+ {% if nvram %} +
+ +
+ {% endif %} {% endifequal %} diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 08ab9c2..87a5e95 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -2,7 +2,7 @@ import time import os.path try: from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE, VIR_MIGRATE_UNSAFE, VIR_DOMAIN_RUNNING, \ - VIR_DOMAIN_AFFECT_LIVE, VIR_DOMAIN_AFFECT_CONFIG + VIR_DOMAIN_AFFECT_LIVE, VIR_DOMAIN_AFFECT_CONFIG, VIR_DOMAIN_UNDEFINE_NVRAM, VIR_DOMAIN_UNDEFINE_KEEP_NVRAM except: from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE @@ -150,8 +150,8 @@ class wvmInstance(wvmConnect): def resume(self): self.instance.resume() - def delete(self): - self.instance.undefine() + def delete(self, flags=0): + self.instance.undefineFlags(flags) def _XMLDesc(self, flag): return self.instance.XMLDesc(flag) @@ -186,6 +186,15 @@ class wvmInstance(wvmConnect): if cur_vcpu: return int(cur_vcpu) + def get_arch(self): + return util.get_xml_path(self._XMLDesc(0), "/domain/os/type/@arch") + + def get_machine_type(self): + return util.get_xml_path(self._XMLDesc(0), "/domain/os/type/@machine") + + def get_nvram(self): + return util.get_xml_path(self._XMLDesc(0), "/domain/os/nvram") + def get_vcpus(self): vcpus = OrderedDict() tree = etree.fromstring(self._XMLDesc(0)) From dd16a5b2d5f2434780c7c4a7e8441f3388cce101 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 13 Dec 2019 16:47:51 +0300 Subject: [PATCH 06/16] Enrich Instance Create operation: -add capability to create arm, ppc, i686, aarch64 instances, -add option to choose firmware, -add options to choose chipset. -add capability to choose volume driver options. -add new default settings --- README.md | 4 + computes/templates/overview.html | 2 +- computes/urls.py | 11 +- computes/views.py | 56 +++- create/forms.py | 8 +- create/templates/create_instance_w1.html | 151 +++++++++++ ..._instance.html => create_instance_w2.html} | 250 +++++++++++++----- create/views.py | 138 +++++++--- instances/templates/allinstances.html | 5 +- instances/templates/instances.html | 12 +- instances/views.py | 15 +- vrtManager/connection.py | 245 ++++++++++++++--- vrtManager/create.py | 120 ++++++--- webvirtcloud/settings.py.template | 22 +- 14 files changed, 828 insertions(+), 211 deletions(-) create mode 100644 create/templates/create_instance_w1.html rename create/templates/{create_instance.html => create_instance_w2.html} (77%) diff --git a/README.md b/README.md index b63caad..52e51a4 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,10 @@ Setup libvirt and KVM on server ```bash wget -O - https://clck.ru/9V9fH | sudo sh ``` +Done!! + +Go to http://serverip and you should see the login screen. + ### Install WebVirtCloud panel (CentOS) diff --git a/computes/templates/overview.html b/computes/templates/overview.html index d230df5..7b1965e 100644 --- a/computes/templates/overview.html +++ b/computes/templates/overview.html @@ -73,7 +73,7 @@ {% endif %} diff --git a/computes/urls.py b/computes/urls.py index 51276f6..4bf95bc 100644 --- a/computes/urls.py +++ b/computes/urls.py @@ -2,9 +2,9 @@ from django.conf.urls import url from storages.views import storages, storage, get_volumes from networks.views import networks, network from secrets.views import secrets -from create.views import create_instance +from create.views import create_instance, create_instance_select_type from interfaces.views import interfaces, interface -from computes.views import overview, compute_graph, computes, get_compute_disk_buses +from computes.views import overview, compute_graph, computes, get_compute_disk_buses, get_compute_machine_types, get_dom_capabilities from instances.views import instances from nwfilters.views import nwfilter, nwfilters @@ -23,6 +23,9 @@ urlpatterns = [ url(r'^(?P[0-9]+)/nwfilters/$', nwfilters, name='nwfilters'), url(r'^(?P[0-9]+)/nwfilter/(?P[\w\-\.\:]+)/$', nwfilter, name='nwfilter'), url(r'^(?P[0-9]+)/secrets/$', secrets, name='secrets'), - url(r'^(?P[0-9]+)/create/$', create_instance, name='create_instance'), - url(r'^(?P[0-9]+)/disk/(?P[\w\-\.\/]+)/buses$', get_compute_disk_buses, name='buses'), + url(r'^(?P[0-9]+)/create/$', create_instance_select_type, name='create_instance_select_type'), + url(r'^(?P[0-9]+)/create/archs/(?P[\w\-\.\/]+)/machines/(?P[\w\-\.\/]+)$', create_instance, name='create_instance'), + url(r'^(?P[0-9]+)/archs/(?P[\w\-\.\/]+)/machines$', get_compute_machine_types, name='machines'), + url(r'^(?P[0-9]+)/archs/(?P[\w\-\.\/]+)/machines/(?P[\w\-\.\/]+)/disks/(?P[\w\-\.\/]+)/buses$', get_compute_disk_buses, name='buses'), + url(r'^(?P[0-9]+)/archs/(?P[\w\-\.\/]+)/machines/(?P[\w\-\.\/]+)/capabilities$', get_dom_capabilities, name='domcaps'), ] diff --git a/computes/views.py b/computes/views.py index 80c0693..dd84592 100644 --- a/computes/views.py +++ b/computes/views.py @@ -155,7 +155,7 @@ def overview(request, compute_id): compute.password, compute.type) hostname, host_arch, host_memory, logical_cpu, model_cpu, uri_conn = conn.get_node_info() - hypervisor = conn.hypervisor_type() + hypervisor = conn.get_hypervisors_domain_types() mem_usage = conn.get_memory_usage() emulator = conn.get_emulator(host_arch) version = conn.get_version() @@ -198,8 +198,8 @@ def compute_graph(request, compute_id): @login_required -def get_compute_disk_buses(request, compute_id, disk): - data = {} +def get_compute_disk_buses(request, compute_id, arch, machine, disk): + data = dict() compute = get_object_or_404(Compute, pk=compute_id) try: conn = wvmConnect(compute.hostname, @@ -207,7 +207,7 @@ def get_compute_disk_buses(request, compute_id, disk): compute.password, compute.type) - disk_device_types = conn.get_disk_device_types() + disk_device_types = conn.get_disk_device_types(arch, machine) if disk in disk_device_types: if disk == 'disk': @@ -223,3 +223,51 @@ def get_compute_disk_buses(request, compute_id, disk): return HttpResponse(json.dumps(data)) + +@login_required +def get_compute_machine_types(request, compute_id, arch): + data = dict() + try: + compute = get_object_or_404(Compute, pk=compute_id) + conn = wvmConnect(compute.hostname, + compute.login, + compute.password, + compute.type) + data['machines'] = conn.get_machine_types(arch) + except libvirtError: + pass + + return HttpResponse(json.dumps(data)) + + +@login_required +def get_compute_video_models(request, compute_id, arch, machine): + data = dict() + try: + compute = get_object_or_404(Compute, pk=compute_id) + conn = wvmConnect(compute.hostname, + compute.login, + compute.password, + compute.type) + data['videos'] = conn.get_video_models(arch, machine) + except libvirtError: + pass + + return HttpResponse(json.dumps(data)) + + +@login_required +def get_dom_capabilities(request, compute_id, arch, machine): + data = dict() + try: + compute = get_object_or_404(Compute, pk=compute_id) + conn = wvmConnect(compute.hostname, + compute.login, + compute.password, + compute.type) + data['videos'] = conn.get_disk_device_types(arch, machine) + data['bus'] = conn.get_disk_device_types(arch, machine) + except libvirtError: + pass + + return HttpResponse(json.dumps(data)) diff --git a/create/forms.py b/create/forms.py index 18c0084..a869ca4 100644 --- a/create/forms.py +++ b/create/forms.py @@ -33,8 +33,9 @@ class FlavorAddForm(forms.Form): class NewVMForm(forms.Form): name = forms.CharField(error_messages={'required': _('No Virtual Machine name has been entered')}, max_length=64) + firmware = forms.CharField(max_length=50, required=False) vcpu = forms.IntegerField(error_messages={'required': _('No VCPU has been entered')}) - host_model = forms.BooleanField(required=False) + vcpu_mode = forms.CharField(max_length=20, required=False) disk = forms.IntegerField(required=False) memory = forms.IntegerField(error_messages={'required': _('No RAM size has been entered')}) networks = forms.CharField(error_messages={'required': _('No Network pool has been choosen')}) @@ -48,8 +49,9 @@ class NewVMForm(forms.Form): virtio = forms.BooleanField(required=False) qemu_ga = forms.BooleanField(required=False) mac = forms.CharField(required=False) - console_pass = forms.CharField(required=False,empty_value="", widget=forms.PasswordInput()) - video = forms.CharField(error_messages={'required': _('Please select a graphic display')}) + console_pass = forms.CharField(required=False, empty_value="", widget=forms.PasswordInput()) + graphics = forms.CharField(error_messages={'required': _('Please select a graphics type')}) + video = forms.CharField(error_messages={'required': _('Please select a video driver')}) listener_addr = forms.ChoiceField(required=True, widget=forms.RadioSelect, choices=QEMU_CONSOLE_LISTEN_ADDRESSES) def clean_name(self): diff --git a/create/templates/create_instance_w1.html b/create/templates/create_instance_w1.html new file mode 100644 index 0000000..62a6caa --- /dev/null +++ b/create/templates/create_instance_w1.html @@ -0,0 +1,151 @@ +{% extends "base.html" %} +{% load i18n %} +{% load staticfiles %} +{% block title %}{% trans "Create new instance - Select Type" %}{% endblock %} + +{% block content %} + +
+
+

{% trans "New instance on" %} {{ compute.name }}

+
+
+ + {% include 'errors_block.html' %} + {% include 'pleasewaitdialog.html' %} + + + {% if form.errors %} + {% for field in form %} + {% for error in field.errors %} +
+ {{ error|escape }} +
+ {% endfor %} + {% endfor %} + {% for error in form.non_field_errors %} +
+ {{ error|escape }} +
+ {% endfor %} + {% endif %} + +
+
+
+ + + +
+
+
+
+
{% csrf_token %} +
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
{% csrf_token %} +
+ + +
+ +
+
+
+
+
+
+
+
+{% endblock %} +{% block script %} + + + + +{% if request.user.is_superuser %} + +{% endif %} +{% endblock %} diff --git a/create/templates/create_instance.html b/create/templates/create_instance_w2.html similarity index 77% rename from create/templates/create_instance.html rename to create/templates/create_instance_w2.html index c77d4b4..1e73feb 100644 --- a/create/templates/create_instance.html +++ b/create/templates/create_instance_w2.html @@ -9,7 +9,9 @@
-

{% trans "New instance on" %} {{ compute.name }}

+

+ {% trans "New instance on" %} {{ compute.name }} +

@@ -37,6 +39,11 @@ + {% if firmwares %} +
+ +
+ +
+
+ {% endif %} + {% if dom_caps.cpu_modes %} +
+ +
+ +
+
+ {% endif %}
-
+ {% if dom_caps.graphics_support == 'yes' %} +
+ +
+ +
+
+ {% endif %}
@@ -196,13 +244,6 @@
-
- -
- -
- -
@@ -258,19 +299,47 @@
+ {% if firmwares %} +
+ +
+ +
+
+ {% endif %}
-
- + {% if dom_caps.cpu_modes %} +
+
- +
-
+ {% endif %}
@@ -351,6 +420,18 @@
+ {% if dom_caps.graphics_support == 'yes' %} +
+ +
+ +
+
+ {% endif %}
@@ -393,15 +474,18 @@
+
+
{% if storages %} - {% else %} - {% endif %} +
@@ -416,6 +500,18 @@
+ {% if firmwares %} +
+ +
+ +
+
+ {% endif %}
@@ -423,11 +519,25 @@
- +
- +
-
@@ -437,7 +547,7 @@
- +
+ {% if dom_caps.graphics_support == 'yes' %} +
+ +
+ +
+
+ {% endif %}
@@ -552,31 +674,19 @@
- - {% if storages %} - - {% else %} - - {% endif %} - -
-
- - -
-
-
{% csrf_token %} -
- - +
+
+ {% if storages %} + + {% else %} + + {% endif %} +
-
@@ -589,7 +699,7 @@ - - - +{% if request.user.is_superuser %} + +{% endif %} {% endblock %} diff --git a/create/views.py b/create/views.py index f8b4bc1..f0c8633 100644 --- a/create/views.py +++ b/create/views.py @@ -13,25 +13,76 @@ from libvirt import libvirtError from webvirtcloud.settings import QEMU_CONSOLE_LISTEN_ADDRESSES from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_CACHE from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_BUS +from webvirtcloud.settings import INSTANCE_CPU_DEFAULT_MODE +from webvirtcloud.settings import INSTANCE_MACHINE_DEFAULT_TYPE +from webvirtcloud.settings import QEMU_CONSOLE_DEFAULT_TYPE from django.contrib import messages from logs.views import addlogmsg - @login_required -def create_instance(request, compute_id): - """ - :param request: - :param compute_id: - :return: - """ +def create_instance_select_type(request, compute_id): if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) conn = None - error_messages = [] - storages = [] - networks = [] + error_messages = list() + storages = list() + networks = list() + hypervisors = list() + meta_prealloc = False + compute = get_object_or_404(Compute, pk=compute_id) + + try: + conn = wvmCreate(compute.hostname, + compute.login, + compute.password, + compute.type) + instances = conn.get_instances() + all_hypervisors = conn.get_hypervisors_machines() + # Supported hypervisors by webvirtcloud: i686, x86_64(for now) + supported_arch = ["x86_64", "i686", "aarch64", "armv7l", "ppc64", "ppc64le", "s390x"] + hypervisors = [hpv for hpv in all_hypervisors.keys() if hpv in supported_arch ] + default_machine = INSTANCE_MACHINE_DEFAULT_TYPE + + if request.method == 'POST': + if 'create_xml' in request.POST: + xml = request.POST.get('dom_xml', '') + try: + name = util.get_xml_path(xml, '/domain/name') + except util.etree.Error as err: + name = None + if name in instances: + error_msg = _("A virtual machine with this name already exists") + error_messages.append(error_msg) + else: + try: + conn._defineXML(xml) + return HttpResponseRedirect(reverse('instance', args=[compute_id, name])) + except libvirtError as lib_err: + error_messages.append(lib_err.message) + + except libvirtError as lib_err: + error_messages.append(lib_err) + + return render(request, 'create_instance_w1.html', locals()) + +@login_required +def create_instance(request, compute_id, arch, machine): + """ + :param request: + :param compute_id: + :return: + """ + if not request.user.is_superuser: + return HttpResponseRedirect(reverse('index')) + + conn = None + error_messages = list() + storages = list() + networks = list() + hypervisors = list() + firmwares = list() meta_prealloc = False compute = get_object_or_404(Compute, pk=compute_id) flavors = Flavor.objects.filter().order_by('id') @@ -42,18 +93,34 @@ def create_instance(request, compute_id): compute.password, compute.type) + default_cpu_mode = INSTANCE_CPU_DEFAULT_MODE instances = conn.get_instances() - videos = conn.get_video_models() + videos = conn.get_video_models(arch, machine) cache_modes = sorted(conn.get_cache_modes().items()) default_cache = INSTANCE_VOLUME_DEFAULT_CACHE listener_addr = QEMU_CONSOLE_LISTEN_ADDRESSES mac_auto = util.randomMAC() - disk_devices = conn.get_disk_device_types() - disk_buses = conn.get_disk_bus_types() + disk_devices = conn.get_disk_device_types(arch, machine) + disk_buses = conn.get_disk_bus_types(arch, machine) default_bus = INSTANCE_VOLUME_DEFAULT_BUS networks = sorted(conn.get_networks()) nwfilters = conn.get_nwfilters() storages = sorted(conn.get_storages(only_actives=True)) + default_graphics = QEMU_CONSOLE_DEFAULT_TYPE + + dom_caps = conn.get_dom_capabilities(arch, machine) + caps = conn.get_capabilities(arch) + + hv_supports_uefi = conn.supports_uefi_xml(dom_caps["loader_enums"]) + # Add BIOS + label = conn.label_for_firmware_path(arch, None) + if label: firmwares.append(label) + # Add UEFI + loader_path = conn.find_uefi_path_for_arch(arch, dom_caps["loaders"]) + label = conn.label_for_firmware_path(arch, loader_path) + if label: firmwares.append(label) + firmwares = list(set(firmwares)) + except libvirtError as lib_err: error_messages.append(lib_err) @@ -81,24 +148,9 @@ def create_instance(request, compute_id): delete_flavor = Flavor.objects.get(id=flavor_id) delete_flavor.delete() return HttpResponseRedirect(request.get_full_path()) - if 'create_xml' in request.POST: - xml = request.POST.get('dom_xml', '') - try: - name = util.get_xml_path(xml, '/domain/name') - except util.etree.Error as err: - name = None - if name in instances: - error_msg = _("A virtual machine with this name already exists") - error_messages.append(error_msg) - else: - try: - conn._defineXML(xml) - return HttpResponseRedirect(reverse('instance', args=[compute_id, name])) - except libvirtError as lib_err: - error_messages.append(lib_err.message) if 'create' in request.POST: - volume_list = [] - + volume_list = list() + is_disk_created = False clone_path = "" form = NewVMForm(request.POST) if form.is_valid(): @@ -124,8 +176,9 @@ def create_instance(request, compute_id): volume['path'] = path volume['type'] = conn.get_volume_type(path) volume['device'] = 'disk' - volume['bus'] = 'virtio' + volume['bus'] = INSTANCE_VOLUME_DEFAULT_BUS volume_list.append(volume) + is_disk_created = True except libvirtError as lib_err: error_messages.append(lib_err.message) elif data['template']: @@ -140,8 +193,9 @@ def create_instance(request, compute_id): volume['path'] = clone_path volume['type'] = conn.get_volume_type(clone_path) volume['device'] = 'disk' - volume['bus'] = 'virtio' + volume['bus'] = INSTANCE_VOLUME_DEFAULT_BUS volume_list.append(volume) + is_disk_created = True else: if not data['images']: error_msg = _("First you need to create or select an image") @@ -164,10 +218,15 @@ def create_instance(request, compute_id): if not error_messages: uuid = util.randomUUID() try: - conn.create_instance(data['name'], data['memory'], data['vcpu'], data['host_model'], - uuid, volume_list, data['cache_mode'], data['networks'], data['virtio'], - data["listener_addr"], data["nwfilter"], data["video"], data["console_pass"], - data['mac'], data['qemu_ga']) + conn.create_instance(name=data['name'], memory=data['memory'], vcpu=data['vcpu'], + vcpu_mode=data['vcpu_mode'], uuid=uuid, arch=arch, machine=machine, + firmware=data["firmware"], + images=volume_list, cache_mode=data['cache_mode'], + networks=data['networks'], virtio=data['virtio'], + listen_addr=data["listener_addr"], nwfilter=data["nwfilter"], + graphics=data["graphics"], video=data["video"], + console_pass=data["console_pass"], mac=data['mac'], + qemu_ga=data['qemu_ga']) create_instance = Instance(compute_id=compute_id, name=data['name'], uuid=uuid) create_instance.save() msg = _("Instance is created.") @@ -176,8 +235,9 @@ def create_instance(request, compute_id): return HttpResponseRedirect(reverse('instance', args=[compute_id, data['name']])) except libvirtError as lib_err: if data['hdd_size'] or len(volume_list) > 0: - for vol in volume_list: - conn.delete_volume(vol['path']) + if is_disk_created: + for vol in volume_list: + conn.delete_volume(vol['path']) error_messages.append(lib_err) conn.close() - return render(request, 'create_instance.html', locals()) + return render(request, 'create_instance_w2.html', locals()) diff --git a/instances/templates/allinstances.html b/instances/templates/allinstances.html index 0703bce..4f7ad80 100644 --- a/instances/templates/allinstances.html +++ b/instances/templates/allinstances.html @@ -176,8 +176,9 @@ {% if request.user.is_superuser %} {% endif %} diff --git a/instances/templates/instances.html b/instances/templates/instances.html index a228c43..1bda386 100644 --- a/instances/templates/instances.html +++ b/instances/templates/instances.html @@ -10,7 +10,7 @@
{% if request.user.is_superuser %} - + {% endif %} @@ -161,7 +161,7 @@ -{% if request.user.is_superuser %} - -{% endif %} {% endblock %} \ No newline at end of file diff --git a/instances/views.py b/instances/views.py index 019234a..368f3e4 100644 --- a/instances/views.py +++ b/instances/views.py @@ -22,7 +22,7 @@ from vrtManager.connection import connection_manager from vrtManager.create import wvmCreate from vrtManager.storage import wvmStorage from vrtManager.util import randomPasswd -from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE +from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_DOMAIN_UNDEFINE_KEEP_NVRAM, VIR_DOMAIN_UNDEFINE_NVRAM from logs.views import addlogmsg from django.conf import settings from django.contrib import messages @@ -265,6 +265,8 @@ def instance(request, compute_id, vname): autostart = conn.get_autostart() bootmenu = conn.get_bootmenu() boot_order = conn.get_bootorder() + arch = conn.get_arch() + machine = conn.get_machine_type() vcpu = conn.get_vcpu() cur_vcpu = conn.get_cur_vcpu() vcpus = conn.get_vcpus() @@ -288,6 +290,7 @@ def instance(request, compute_id, vname): insort(memory_range, memory) if cur_memory not in memory_range: insort(memory_range, cur_memory) + nvram = conn.get_nvram() telnet_port = conn.get_telnet_port() console_type = conn.get_console_type() console_port = conn.get_console_port() @@ -330,8 +333,8 @@ def instance(request, compute_id, vname): # Host resources vcpu_host = len(vcpu_range) memory_host = conn.get_max_memory() - bus_host = conn.get_disk_bus_types() - videos_host = conn.get_video_models() + bus_host = conn.get_disk_bus_types(arch, machine) + videos_host = conn.get_video_models(arch, machine) networks_host = sorted(conn.get_networks()) interfaces_host = sorted(conn.get_ifaces()) nwfilters_host = conn.get_nwfilters() @@ -374,7 +377,11 @@ def instance(request, compute_id, vname): for snap in snapshots: conn.snapshot_delete(snap['name']) conn.delete_all_disks() - conn.delete() + + if request.POST.get('delete_nvram', ''): + conn.delete(VIR_DOMAIN_UNDEFINE_NVRAM) + else: + conn.delete(VIR_DOMAIN_UNDEFINE_KEEP_NVRAM) instance = Instance.objects.get(compute_id=compute_id, name=vname) instance_name = instance.name diff --git a/vrtManager/connection.py b/vrtManager/connection.py index e186854..d6ebca2 100644 --- a/vrtManager/connection.py +++ b/vrtManager/connection.py @@ -1,6 +1,7 @@ import libvirt import threading import socket +import re from vrtManager import util from vrtManager.rwlock import ReadWriteLock from django.conf import settings @@ -344,15 +345,88 @@ class wvmConnect(object): """Return xml capabilities""" return self.wvm.getCapabilities() - def get_dom_cap_xml(self): - """ Return domcapabilities xml""" - - arch = self.wvm.getInfo()[0] - machine = self.get_machines(arch) + def get_dom_cap_xml(self, arch, machine): + """ Return domain capabilities xml""" emulatorbin = self.get_emulator(arch) - virttype = self.hypervisor_type()[arch][0] + virttype = self.get_hypervisors_domain_types()[arch][0] + + machine_types = self.get_machine_types(arch) + if not machine or machine not in machine_types: + machine = 'pc' if 'pc' in machine_types else machine_types[0] return self.wvm.getDomainCapabilities(emulatorbin, arch, machine, virttype) + def get_capabilities(self, arch): + """ Host Capabilities for specified architecture """ + def guests(ctx): + result = dict() + for arch_el in ctx.xpath("/capabilities/guest/arch[@name='{}']".format(arch)): + result["wordsize"] = arch_el.find("wordsize").text + result["emulator"] = arch_el.find("emulator").text + result["domain"] = [v for v in arch_el.xpath("domain/@type")] + + result["machines"] = [] + for m in arch_el.xpath("machine"): + result["machines"].append({"machine": m.text, + "max_cpu": m.get("maxCpus"), + "canonical": m.get("canonical")}) + + guest_el = arch_el.getparent() + for f in guest_el.xpath("features"): + result["features"] = [t.tag for t in f.getchildren()] + + result["os_type"] = guest_el.find("os_type").text + + return result + return util.get_xml_path(self.get_cap_xml(), func=guests) + + def get_dom_capabilities(self, arch, machine): + """Return domain capabilities""" + result = dict() + + xml = self.get_dom_cap_xml(arch, machine) + result["path"] = util.get_xml_path(xml,"/domainCapabilities/path") + result["domain"] = util.get_xml_path(xml, "/domainCapabilities/domain") + result["machine"] = util.get_xml_path(xml, "/domainCapabilities/machine") + result["vcpu_max"] = util.get_xml_path(xml, "/domainCapabilities/vcpu/@max") + result["iothreads_support"] = util.get_xml_path(xml, "/domainCapabilities/iothreads/@supported") + result["os_support"] = util.get_xml_path(xml, "/domainCapabilities/os/@supported") + + result["loader_support"] = util.get_xml_path(xml, "/domainCapabilities/os/loader/@supported") + if result["loader_support"] == 'yes': + result["loaders"] = self.get_os_loaders(arch, machine) + result["loader_enums"] = self.get_os_loader_enums(arch, machine) + + result["cpu_modes"] = self.get_cpu_modes(arch, machine) + if "custom" in result["cpu_modes"]: + # supported and unknown cpu models + result["cpu_custom_models"] = self.get_cpu_custom_types(arch, machine) + + result["disk_support"] = util.get_xml_path(xml, "/domainCapabilities/devices/disk/@supported") + if result["disk_support"] == 'yes': + result["disk_devices"] = self.get_disk_device_types(arch, machine) + result["disk_bus"] = self.get_disk_bus_types(arch, machine) + + result["graphics_support"] = util.get_xml_path(xml, "/domainCapabilities/devices/graphics/@supported") + if result["graphics_support"] == 'yes': + result["graphics_types"] = self.get_graphics_types(arch, machine) + + result["video_support"] = util.get_xml_path(xml, "/domainCapabilities/devices/video/@supported") + if result["video_support"] == 'yes': + result["video_types"] = self.get_video_models(arch, machine) + + result["hostdev_support"] = util.get_xml_path(xml, "/domainCapabilities/devices/hostdev/@supported") + if result["hostdev_support"] == 'yes': + result["hostdev_types"] = self.get_hostdev_modes(arch, machine) + result["hostdev_startup_policies"] = self.get_hostdev_startup_policies(arch, machine) + result["hostdev_subsys_types"] = self.get_hostdev_subsys_types(arch, machine) + + result["features_gic_support"] = util.get_xml_path(xml, "/domainCapabilities/features/gic/@supported") + result["features_genid_support"] = util.get_xml_path(xml, "/domainCapabilities/features/genid/@supported") + result["features_vmcoreinfo_support"] = util.get_xml_path(xml, "/domainCapabilities/features/vmcoreinfo/@supported") + result["features_sev_support"] = util.get_xml_path(xml, "/domainCapabilities/features/sev/@supported") + + return result + def get_version(self): ver = self.wvm.getVersion() major = ver / 1000000 @@ -417,7 +491,7 @@ class wvmConnect(object): 'unsafe': 'Unsafe', # since libvirt 0.9.7 } - def hypervisor_type(self): + def get_hypervisors_domain_types(self): """Return hypervisor type""" def hypervisors(ctx): result = {} @@ -428,10 +502,34 @@ class wvmConnect(object): return result return util.get_xml_path(self.get_cap_xml(), func=hypervisors) + def get_hypervisors_machines(self): + """Return hypervisor and its machine types""" + def machines(ctx): + result = dict() + for arche in ctx.xpath('/capabilities/guest/arch'): + arch = arche.get("name") + + result[arch] = self.get_machine_types(arch) + return result + return util.get_xml_path(self.get_cap_xml(), func=machines) + def get_emulator(self, arch): """Return emulator """ return util.get_xml_path(self.get_cap_xml(), "/capabilities/guest/arch[@name='{}']/emulator".format(arch)) + def get_machine_types(self, arch): + """Return canonical(if exist) name of machine types """ + def machines(ctx): + result = list() + canonical_name = ctx.xpath("/capabilities/guest/arch[@name='{}']/machine[@canonical]".format(arch)) + if not canonical_name: + canonical_name = ctx.xpath("/capabilities/guest/arch[@name='{}']/machine".format(arch)) + for archi in canonical_name: + result.append(archi.text) + return result + + return util.get_xml_path(self.get_cap_xml(), func=machines) + def get_emulators(self): def emulators(ctx): result = {} @@ -442,35 +540,76 @@ class wvmConnect(object): return result return util.get_xml_path(self.get_cap_xml(), func=emulators) - def get_machines(self, arch): - """ Return machine type of emulation""" - return util.get_xml_path(self.get_cap_xml(), "/capabilities/guest/arch[@name='{}']/machine".format(arch)) + def get_os_loaders(self, arch='x86_64', machine='pc'): + """Get available os loaders list""" + def get_os_loaders(ctx): + return [v.text for v in ctx.xpath("/domainCapabilities/os/loader[@supported='yes']/value")] + return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_os_loaders) - def get_disk_bus_types(self): + def get_os_loader_enums(self, arch, machine): + """Get available os loaders list""" + def get_os_loader_enums(ctx): + result = dict() + enums = [v for v in ctx.xpath("/domainCapabilities/os/loader[@supported='yes']/enum/@name")] + for enum in enums: + path = "/domainCapabilities/os/loader[@supported='yes']/enum[@name='{}']/value".format(enum) + result[enum] = [v.text for v in ctx.xpath(path)] + return result + return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_os_loader_enums) + + def get_disk_bus_types(self, arch, machine): """Get available disk bus types list""" - def get_bus_list(ctx): - result = [] - for disk_enum in ctx.xpath('/domainCapabilities/devices/disk/enum'): - if disk_enum.xpath("@name")[0] == "bus": - for values in disk_enum: result.append(values.text) - return result - + return [v.text for v in ctx.xpath("/domainCapabilities/devices/disk/enum[@name='bus']/value")] # return [ 'ide', 'scsi', 'usb', 'virtio' ] - return util.get_xml_path(self.get_dom_cap_xml(), func=get_bus_list) + return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_bus_list) - def get_disk_device_types(self): + def get_disk_device_types(self, arch, machine): """Get available disk device type list""" - def get_device_list(ctx): - result = [] - for disk_enum in ctx.xpath('/domainCapabilities/devices/disk/enum'): - if disk_enum.xpath("@name")[0] == "diskDevice": - for values in disk_enum: result.append(values.text) - return result - + return [v.text for v in ctx.xpath("/domainCapabilities/devices/disk/enum[@name='diskDevice']/value")] # return [ 'disk', 'cdrom', 'floppy', 'lun' ] - return util.get_xml_path(self.get_dom_cap_xml(), func=get_device_list) + return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_device_list) + + def get_graphics_types(self, arch, machine): + """Get available graphics types """ + def get_graphics_list(ctx): + return [ v.text for v in ctx.xpath("/domainCapabilities/devices/graphics/enum[@name='type']/value")] + return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_graphics_list) + + def get_cpu_modes(self, arch, machine): + """Get available cpu modes """ + def get_cpu_modes(ctx): + return [v for v in ctx.xpath("/domainCapabilities/cpu/mode[@supported='yes']/@name")] + return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_cpu_modes) + + def get_cpu_custom_types(self, arch, machine): + """Get available graphics types """ + def get_custom_list(ctx): + usable_yes = "/domainCapabilities/cpu/mode[@name='custom'][@supported='yes']/model[@usable='yes']" + usable_unknown = "/domainCapabilities/cpu/mode[@name='custom'][@supported='yes']/model[@usable='unknown']" + result = [v.text for v in ctx.xpath(usable_yes)] + result += [v.text for v in ctx.xpath(usable_unknown)] + return result + return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_custom_list) + + def get_hostdev_modes(self, arch, machine): + """Get available nodedev modes """ + def get_hostdev_list(ctx): + return [v.text for v in ctx.xpath("/domainCapabilities/devices/hostdev/enum[@name='mode']/value")] + return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_hostdev_list) + + def get_hostdev_startup_policies(self, arch, machine): + """Get available hostdev modes """ + def get_hostdev_list(ctx): + return [v.text for v in ctx.xpath("/domainCapabilities/devices/hostdev/enum[@name='startupPolicy']/value")] + return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_hostdev_list) + + def get_hostdev_subsys_types(self, arch, machine): + """Get available nodedev sub system types """ + def get_hostdev_list(ctx): + return [v.text for v in ctx.xpath("/domainCapabilities/devices/hostdev/enum[@name='subsysType']/value")] + return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_hostdev_list) def get_image_formats(self): """Get available image formats""" @@ -480,7 +619,7 @@ class wvmConnect(object): """Get available image filename extensions""" return ['img', 'qcow', 'qcow2'] - def get_video_models(self): + def get_video_models(self, arch, machine): """ Get available graphics video types """ def get_video_list(ctx): result = [] @@ -488,7 +627,7 @@ class wvmConnect(object): if video_enum.xpath("@name")[0] == "modelType": for values in video_enum: result.append(values.text) return result - return util.get_xml_path(self.get_dom_cap_xml(), func=get_video_list) + return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_video_list) def get_iface(self, name): return self.wvm.interfaceLookupByName(name) @@ -624,3 +763,49 @@ class wvmConnect(object): # to-do: do not close connection ;) # self.wvm.close() pass + + def find_uefi_path_for_arch(self, arch, machine): + """ + Search the loader paths for one that matches the passed arch + """ + if not self.arch_can_uefi(arch): + return + + loaders = self.get_os_loaders(arch, machine) + patterns = util.uefi_arch_patterns.get(arch) + for pattern in patterns: + for path in loaders: + if re.match(pattern, path): + return path + + def label_for_firmware_path(self, arch, path): + """ + Return a pretty label for passed path, based on if we know + about it or not + """ + if not path: + if arch in ["i686", "x86_64"]: + return "BIOS" + return + + for arch, patterns in util.uefi_arch_patterns.items(): + for pattern in patterns: + if re.match(pattern, path): + return ("UEFI %(arch)s: %(path)s" % + {"arch": arch, "path": path}) + + return "Custom: %(path)s" % {"path": path} + + def arch_can_uefi(self, arch): + """ + Return True if we know how to setup UEFI for the passed arch + """ + return arch in list(util.uefi_arch_patterns.keys()) + + def supports_uefi_xml(self, loader_enums): + """ + Return True if libvirt advertises support for proper UEFI setup + """ + return ("readonly" in loader_enums and + "yes" in loader_enums.get("readonly")) + diff --git a/vrtManager/create.py b/vrtManager/create.py index 671de97..d11e2eb 100644 --- a/vrtManager/create.py +++ b/vrtManager/create.py @@ -1,9 +1,11 @@ import string from vrtManager import util from vrtManager.connection import wvmConnect -from webvirtcloud.settings import QEMU_CONSOLE_DEFAULT_TYPE -from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_OWNER as default_owner +from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_OWNER as DEFAULT_OWNER from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_FORMAT +from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER +from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_DRIVER_OPTS as OPTS + def get_rbd_storage_data(stg): @@ -11,7 +13,7 @@ def get_rbd_storage_data(stg): ceph_user = util.get_xml_path(xml, "/pool/source/auth/@username") def get_ceph_hosts(doc): - hosts = [] + hosts = list() for host in doc.xpath("/pool/source/host"): name = host.prop("name") if name: @@ -29,7 +31,7 @@ class wvmCreate(wvmConnect): """ Function return all images on all storages """ - images = [] + images = list() storages = self.get_storages(only_actives=True) for storage in storages: stg = self.get_storage(storage) @@ -45,14 +47,14 @@ class wvmCreate(wvmConnect): return images def get_os_type(self): - """Get guest capabilities""" + """Get guest os type""" return util.get_xml_path(self.get_cap_xml(), "/capabilities/guest/os_type") def get_host_arch(self): """Get guest capabilities""" return util.get_xml_path(self.get_cap_xml(), "/capabilities/host/cpu/arch") - def create_volume(self, storage, name, size, image_format=image_format, metadata=False, owner=default_owner): + def create_volume(self, storage, name, size, image_format=image_format, metadata=False, owner=DEFAULT_OWNER): size = int(size) * 1073741824 stg = self.get_storage(storage) storage_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type") @@ -120,7 +122,7 @@ class wvmCreate(wvmConnect): vol = self.get_volume_by_path(vol_path) return vol.storagePoolLookupByVolume() - def clone_from_template(self, clone, template, storage=None, metadata=False, owner=default_owner): + def clone_from_template(self, clone, template, storage=None, metadata=False, owner=DEFAULT_OWNER): vol = self.get_volume_by_path(template) if not storage: stg = vol.storagePoolLookupByVolume() @@ -163,16 +165,15 @@ class wvmCreate(wvmConnect): vol = self.get_volume_by_path(path) vol.delete() - def create_instance(self, name, memory, vcpu, host_model, uuid, images, cache_mode, networks, virtio, listen_addr, nwfilter=None, video="cirrus", console_pass="random", mac=None, qemu_ga=False): + def create_instance(self, name, memory, vcpu, vcpu_mode, uuid, arch, machine, firmware, images, cache_mode, networks, nwfilter, graphics, virtio, listen_addr, video="vga", console_pass="random", mac=None, qemu_ga=False): """ Create VM function """ - memory = int(memory) * 1024 + caps = self.get_capabilities(arch) + dom_caps = self.get_dom_capabilities(arch, machine) - if self.is_kvm_supported(): - hypervisor_type = 'kvm' - else: - hypervisor_type = 'qemu' + memory = int(memory) * 1024 + #hypervisor_type = 'kvm' if self.is_kvm_supported() else 'qemu' xml = """ @@ -180,23 +181,46 @@ class wvmCreate(wvmConnect): None %s %s - %s""" % (hypervisor_type, name, uuid, memory, vcpu) - if host_model: + %s""" % (dom_caps["domain"], name, uuid, memory, vcpu) + + if dom_caps["os_support"] == 'yes': + xml += """ + %s""" % (arch, machine, caps["os_type"]) + xml += """ + + """ + if 'UEFI' in firmware: + xml += """%s""" % firmware.split(":")[1].strip() + xml += """""" + + if caps["features"]: + xml += """""" + if 'acpi' in caps["features"]: + xml += """""" + if 'apic' in caps["features"]: + xml += """""" + if 'pae' in caps["features"]: + xml += """""" + xml += """""" + + if vcpu_mode == "host-model": xml += """""" - xml += """ - %s - - - - """ % (self.get_host_arch(), self.get_os_type()) - xml += """ - - + elif vcpu_mode == "host-passthrough": + xml += """""" + elif vcpu_mode == "": + pass + else: + xml += """ + {} + """.format(vcpu_mode) + + xml += """ destroy restart restart - """ + """ + xml += """""" vd_disk_letters = list(string.lowercase) fd_disk_letters = list(string.lowercase) @@ -212,11 +236,11 @@ class wvmCreate(wvmConnect): if stg_type == 'rbd': ceph_user, secret_uuid, ceph_hosts = get_rbd_storage_data(stg) xml += """ - - + """ % (volume['type'], cache_mode, OPTS.get("network", '')) + xml += """ - """ % (volume['type'], cache_mode, ceph_user, secret_uuid, volume['path']) + """ % (ceph_user, secret_uuid, volume['path']) if isinstance(ceph_hosts, list): for host in ceph_hosts: if host.get('port'): @@ -225,12 +249,11 @@ class wvmCreate(wvmConnect): else: xml += """ """ % host.get('name') - xml += """ - """ + xml += """""" else: - xml += """ - - """ % (volume['device'], volume['type'], cache_mode, volume['path']) + xml += """""" % volume['device'] + xml += """ """ % (volume['type'], cache_mode, OPTS.get("file", '')) + xml += """ """ % volume['path'] if volume['bus'] == 'virtio': xml += """""" % (vd_disk_letters.pop(0), volume['bus']) @@ -242,12 +265,23 @@ class wvmCreate(wvmConnect): xml += """""" % (sd_disk_letters.pop(0), volume['bus']) xml += """""" if add_cd: - xml += """ + xml += """ - - - - """ % (hd_disk_letters.pop(0),) + + """ + if 'ide' in dom_caps['disk_bus']: + xml += """""" % (hd_disk_letters.pop(0), 'ide') + elif 'sata' in dom_caps['disk_bus']: + xml += """""" % (sd_disk_letters.pop(0), 'sata') + elif 'scsi' in dom_caps['disk_bus']: + xml += """""" % (sd_disk_letters.pop(0), 'scsi') + else: + xml += """""" % (vd_disk_letters.pop(0), 'virtio') + xml += """""" + + if volume['bus'] == 'scsi': + xml += """""" % INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER + for net in networks.split(','): xml += """""" if mac: @@ -265,10 +299,11 @@ class wvmCreate(wvmConnect): if not console_pass == "": console_pass = "passwd='" + console_pass + "'" - xml += """ - - - """ % (QEMU_CONSOLE_DEFAULT_TYPE, console_pass, listen_addr) + xml += """""" + xml += """""" + xml += """ + + """ % (graphics, console_pass, listen_addr) if qemu_ga: xml += """ @@ -278,7 +313,6 @@ class wvmCreate(wvmConnect): xml += """ - """ % video self._defineXML(xml) diff --git a/webvirtcloud/settings.py.template b/webvirtcloud/settings.py.template index 58469f0..587cc75 100644 --- a/webvirtcloud/settings.py.template +++ b/webvirtcloud/settings.py.template @@ -149,11 +149,31 @@ SHOW_PROFILE_EDIT_PASSWORD = False # available: default (grid), list VIEW_ACCOUNTS_STYLE = 'grid' -# available: default (grouped), nongrouped +# available list style: default (grouped), nongrouped VIEW_INSTANCES_LIST_STYLE = 'grouped' +# available volume format: raw, qcow2, qcow INSTANCE_VOLUME_DEFAULT_FORMAT = 'qcow2' + +# available bus types: virtio, scsi, ide, usb, sata INSTANCE_VOLUME_DEFAULT_BUS = 'virtio' + +#SCSI types: 'virtio-scsi', 'lsilogic' +INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER = 'virtio-scsi' + +# Volume optionals: two variable: disk driver type is file and network(rbd, iscsi), +# optionals : discard='unmap|ignore', detect_zeroes='on|off|unmap', copy_on_read='on|off' +# Example: {"file": "discard='unmap' copy_on_read='on'", "network": "detect_zeroes='unmap'"} +INSTANCE_VOLUME_DEFAULT_DRIVER_OPTS = {"file": "", "network": ""} + +# available cache types: none, unsafe, writeback, writethrough INSTANCE_VOLUME_DEFAULT_CACHE = 'directsync' + # up to os, 0=root, 107=qemu or libvirt-bin(for ubuntu) INSTANCE_VOLUME_DEFAULT_OWNER = {'uid': 0, 'guid': 0} + +# Cpu modes: host-model, host-passthrough, custom +INSTANCE_CPU_DEFAULT_MODE = 'host-model' + +# Chipset/Machine: pc or q35 for x86_64 +INSTANCE_MACHINE_DEFAULT_TYPE = 'q35' \ No newline at end of file From 8df71efdcaef64a709abbec26d0ee167dc5b1a01 Mon Sep 17 00:00:00 2001 From: catborise Date: Wed, 18 Dec 2019 09:04:50 +0300 Subject: [PATCH 07/16] Update django requirement 1.11.23 -> 1.11.26, update libvirt-api: 5.3 -> 5.10 and lxml --- .travis.yml | 2 +- conf/requirements.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 131aa6d..747a498 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: python python: - "2.7" env: - - DJANGO=1.11.23 + - DJANGO=1.11.26 install: - pip install -r dev/requirements.txt script: diff --git a/conf/requirements.txt b/conf/requirements.txt index 28ca300..fc57c17 100644 --- a/conf/requirements.txt +++ b/conf/requirements.txt @@ -1,7 +1,7 @@ -Django==1.11.25 +Django==1.11.26 websockify==0.9.0 gunicorn==19.9.0 -lxml==4.2.5 -libvirt-python==5.3.0 +lxml==4.4.2 +libvirt-python==5.10.0 pytz rwlock From a5e77ef01e21f67d2c0b5d06c6b6447edb206f8a Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 19 Dec 2019 12:00:24 +0300 Subject: [PATCH 08/16] How to update section change: remove creating virtual env command --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52e51a4..6d810b5 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ datasource: ### How To Update ```bash -sudo virtualenv venv +cd sudo source venv/bin/activate git pull pip install -U -r conf/requirements.txt From 28ea64cd166f2fd6d9f7c12b2f4399a7cc8aa16b Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 19 Dec 2019 12:01:34 +0300 Subject: [PATCH 09/16] Fix column offset for small screens --- create/templates/create_instance_w1.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/create/templates/create_instance_w1.html b/create/templates/create_instance_w1.html index 62a6caa..e4a5f2c 100644 --- a/create/templates/create_instance_w1.html +++ b/create/templates/create_instance_w1.html @@ -71,7 +71,7 @@
-
+
From ff96ce664857ac48e3f0f13a7d0d5d5d8e1d7de1 Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 19 Dec 2019 12:03:25 +0300 Subject: [PATCH 10/16] Storage.py upper constant variable names. make mode and suffix variables a parameter --- vrtManager/storage.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/vrtManager/storage.py b/vrtManager/storage.py index b0d6b52..bd51b1d 100644 --- a/vrtManager/storage.py +++ b/vrtManager/storage.py @@ -1,6 +1,6 @@ from vrtManager import util from vrtManager.connection import wvmConnect -from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_OWNER as owner +from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_OWNER as OWNER class wvmStorages(wvmConnect): @@ -49,6 +49,8 @@ class wvmStorages(wvmConnect): stg.create(0) stg.setAutostart(1) + return stg + def create_storage_ceph(self, stg_type, name, ceph_pool, ceph_host, ceph_user, secret): xml = """ @@ -84,6 +86,8 @@ class wvmStorages(wvmConnect): stg.create(0) stg.setAutostart(1) + return stg + class wvmStorage(wvmConnect): def __init__(self, host, login, passwd, conn, pool): @@ -206,7 +210,7 @@ class wvmStorage(wvmConnect): ) return vol_list - def create_volume(self, name, size, vol_fmt='qcow2', metadata=False, owner=owner): + def create_volume(self, name, size, vol_fmt='qcow2', metadata=False, owner=OWNER): size = int(size) * 1073741824 storage_type = self.get_type() alloc = size @@ -243,17 +247,18 @@ class wvmStorage(wvmConnect): self._createXML(xml, metadata) return name - def clone_volume(self, name, target_file, vol_fmt=None, metadata=False, owner=owner): + def clone_volume(self, name, target_file, vol_fmt=None, metadata=False, mode='0644', file_suffix='img', owner=OWNER): vol = self.get_volume(name) if not vol_fmt: vol_fmt = self.get_volume_type(name) storage_type = self.get_type() if storage_type == 'dir': - if vol_fmt in ('qcow', 'qcow2'): + if vol_fmt in ['qcow', 'qcow2']: target_file += '.' + vol_fmt else: - target_file += '.img' + suffix = '.' + file_suffix + target_file += suffix if len(suffix) > 1 else '' xml = """ @@ -265,9 +270,9 @@ class wvmStorage(wvmConnect): %s %s - 0644 + %s - """ % (target_file, vol_fmt, owner['uid'], owner['guid']) + """ % (target_file, vol_fmt, owner['uid'], owner['guid'], mode) if vol_fmt == 'qcow2': xml += """ 1.1 From ab024acc787dc450700a293c706526b3f962903e Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 19 Dec 2019 12:05:35 +0300 Subject: [PATCH 11/16] setting.py.template add blank line at the end of file --- webvirtcloud/settings.py.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webvirtcloud/settings.py.template b/webvirtcloud/settings.py.template index 587cc75..e1ecc74 100644 --- a/webvirtcloud/settings.py.template +++ b/webvirtcloud/settings.py.template @@ -176,4 +176,4 @@ INSTANCE_VOLUME_DEFAULT_OWNER = {'uid': 0, 'guid': 0} INSTANCE_CPU_DEFAULT_MODE = 'host-model' # Chipset/Machine: pc or q35 for x86_64 -INSTANCE_MACHINE_DEFAULT_TYPE = 'q35' \ No newline at end of file +INSTANCE_MACHINE_DEFAULT_TYPE = 'q35' From 1b0324e3e37606df24e93435c5550bfe36be52fd Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 19 Dec 2019 12:06:42 +0300 Subject: [PATCH 12/16] Add is_support_virtio function and is_qemu functions --- vrtManager/connection.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/vrtManager/connection.py b/vrtManager/connection.py index d6ebca2..8e826f0 100644 --- a/vrtManager/connection.py +++ b/vrtManager/connection.py @@ -273,7 +273,7 @@ class wvmConnectionManager(object): if connection is None: self._connections_lock.acquireWrite() try: - # we have to search for the connection again after aquireing the write lock + # we have to search for the connection again after acquiring the write lock # as the thread previously holding the write lock may have already added our connection connection = self._search_connection(host, login, passwd, conn) if connection is None: @@ -341,6 +341,9 @@ class wvmConnect(object): # get connection from connection manager self.wvm = connection_manager.get_connection(host, login, passwd, conn) + def is_qemu(self): + return self.wvm.getURI().startswith("qemu") + def get_cap_xml(self): """Return xml capabilities""" return self.wvm.getCapabilities() @@ -809,3 +812,17 @@ class wvmConnect(object): return ("readonly" in loader_enums and "yes" in loader_enums.get("readonly")) + def is_supports_virtio(self, arch, machine): + if not self.is_qemu(): + return False + + # These _only_ support virtio so don't check the OS + if arch in ["aarch64", "armv7l", "ppc64", "ppc64le", "s390x", "riscv64", "riscv32"] and \ + machine in ["virt", "pseries"]: + return True + + if arch in ["x86_64", "i686"]: + return True + + return False + From 6634207ef55670a87a65f40b2db23fa66906c3a3 Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 19 Dec 2019 13:47:07 +0300 Subject: [PATCH 13/16] Add nvram clone, rewrite instance migration, add some helper functions --- vrtManager/instance.py | 87 ++++++++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 20 deletions(-) diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 87a5e95..20d78a2 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -1,8 +1,16 @@ import time import os.path try: - from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE, VIR_MIGRATE_UNSAFE, VIR_DOMAIN_RUNNING, \ - VIR_DOMAIN_AFFECT_LIVE, VIR_DOMAIN_AFFECT_CONFIG, VIR_DOMAIN_UNDEFINE_NVRAM, VIR_DOMAIN_UNDEFINE_KEEP_NVRAM + from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_DOMAIN_RUNNING, VIR_DOMAIN_AFFECT_LIVE, \ + VIR_DOMAIN_AFFECT_CONFIG, VIR_DOMAIN_UNDEFINE_NVRAM, VIR_DOMAIN_UNDEFINE_KEEP_NVRAM,\ + VIR_DOMAIN_START_PAUSED + from libvirt import VIR_MIGRATE_LIVE, \ + VIR_MIGRATE_UNSAFE, \ + VIR_MIGRATE_PERSIST_DEST, \ + VIR_MIGRATE_UNDEFINE_SOURCE, \ + VIR_MIGRATE_OFFLINE,\ + VIR_MIGRATE_COMPRESSED, \ + VIR_MIGRATE_AUTO_CONVERGE except: from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE @@ -12,9 +20,9 @@ from lxml import etree from datetime import datetime from collections import OrderedDict from vrtManager.connection import wvmConnect -from vrtManager.storage import wvmStorage +from vrtManager.storage import wvmStorage, wvmStorages from webvirtcloud.settings import QEMU_CONSOLE_TYPES -from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_OWNER as owner +from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_OWNER as OWNER class wvmInstances(wvmConnect): @@ -72,19 +80,30 @@ class wvmInstances(wvmConnect): dom = self.get_instance(name) dom.resume() - def moveto(self, conn, name, live, unsafe, undefine, offline): - flags = 0 + def moveto(self, conn, name, live, unsafe, undefine, offline, autoconverge=False, compress=False): + flags = VIR_MIGRATE_PERSIST_DEST if live and conn.get_status() == 1: flags |= VIR_MIGRATE_LIVE if unsafe and conn.get_status() == 1: flags |= VIR_MIGRATE_UNSAFE - dom = conn.get_instance(name) - xml = dom.XMLDesc(VIR_DOMAIN_XML_SECURE) - if not offline: - dom.migrate(self.wvm, flags, None, None, 0) if undefine: - dom.undefine() - self.wvm.defineXML(xml) + flags |= VIR_MIGRATE_UNDEFINE_SOURCE + if offline: + flags |= VIR_MIGRATE_OFFLINE + if not offline and autoconverge: + flags |= VIR_MIGRATE_AUTO_CONVERGE + if compress: + flags |= VIR_MIGRATE_COMPRESSED + + dom = conn.get_instance(name) + + dom_arch = conn.get_arch() + dom_emulator = conn.get_dom_emulator() + + if dom_emulator != self.get_emulator(dom_arch): + raise libvirtError('Destination host emulator is different. Cannot be migrated') + + dom.migrate(self.wvm, flags, None, None, 0) def graphics_type(self, name): inst = self.get_instance(name) @@ -192,9 +211,19 @@ class wvmInstance(wvmConnect): def get_machine_type(self): return util.get_xml_path(self._XMLDesc(0), "/domain/os/type/@machine") + def get_dom_emulator(self): + return util.get_xml_path(self._XMLDesc(0), "/domain/devices/emulator") + def get_nvram(self): return util.get_xml_path(self._XMLDesc(0), "/domain/os/nvram") + def get_loader(self): + xml = self._XMLDesc(0) + loader = util.get_xml_path(xml, "/domain/os/loader") + type = util.get_xml_path(xml, "/domain/os/loader/@type") + readonly = util.get_xml_path(xml, "/domain/os/loader/@readonly") + return {"loader": loader, "type": type, "readonly": readonly} + def get_vcpus(self): vcpus = OrderedDict() tree = etree.fromstring(self._XMLDesc(0)) @@ -951,12 +980,10 @@ class wvmInstance(wvmConnect): return self.instance.hasManagedSaveImage(0) def get_wvmStorage(self, pool): - storage = wvmStorage(self.host, - self.login, - self.passwd, - self.conn, - pool) - return storage + return wvmStorage(self.host, self.login, self.passwd, self.conn, pool) + + def get_wvmStorages(self): + return wvmStorages(self.host, self.login, self.passwd, self.conn) def fix_mac(self, mac): if ":" in mac: @@ -970,12 +997,32 @@ class wvmInstance(wvmConnect): clone_dev_path = [] xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE) - tree = ElementTree.fromstring(xml) + tree = etree.fromstring(xml) name = tree.find('name') name.text = clone_data['name'] uuid = tree.find('uuid') tree.remove(uuid) + src_nvram_path = self.get_nvram() + if src_nvram_path: + # Change XML for nvram + nvram = tree.find('os/nvram') + nvram.getparent().remove(nvram) + + # NVRAM CLONE: create pool if nvram is not in a pool. then clone it + src_nvram_name = os.path.basename(src_nvram_path) + nvram_dir = os.path.dirname(src_nvram_path) + nvram_pool_name = os.path.basename(nvram_dir) + try: + self.get_volume_by_path(src_nvram_path) + except libvirtError: + stg_conn = self.get_wvmStorages() + stg_conn.create_storage('dir', nvram_pool_name, None, nvram_dir) + + new_nvram_name = "%s_VARS" % clone_data['name'] + nvram_stg = self.get_wvmStorage(nvram_pool_name) + nvram_stg.clone_volume(src_nvram_name, new_nvram_name, file_suffix='fd') + for num, net in enumerate(tree.findall('devices/interface')): elm = net.find('mac') mac_address = self.fix_mac(clone_data['clone-net-mac-' + str(num)]) @@ -1024,7 +1071,7 @@ class wvmInstance(wvmConnect): - """ % (target_file, vol_format, owner['uid'], owner['guid']) + """ % (target_file, vol_format, OWNER['uid'], OWNER['guid']) stg = vol.storagePoolLookupByVolume() stg.createXMLFrom(vol_clone_xml, vol, meta_prealloc) From d401d2f3ffa549449c3f103cb9f1bea15a78c6bb Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 19 Dec 2019 13:49:41 +0300 Subject: [PATCH 14/16] Add virtio support check, add more control while creating instance --- create/templates/create_instance_w2.html | 16 ++++++--- create/views.py | 21 +++++++++-- instances/views.py | 23 ++++++++----- vrtManager/create.py | 44 ++++++++++++++++-------- 4 files changed, 73 insertions(+), 31 deletions(-) diff --git a/create/templates/create_instance_w2.html b/create/templates/create_instance_w2.html index 1e73feb..09a9484 100644 --- a/create/templates/create_instance_w2.html +++ b/create/templates/create_instance_w2.html @@ -250,12 +250,14 @@
+ {% if virtio_support %}
- +
+ {% endif %}
+ {% if virtio_support %}
-
- +
+
+ {% endif %}
{% if storages %} @@ -668,12 +672,14 @@
+ {% if virtio_support %}
-
- +
+
+ {% endif %}
{% if storages %} diff --git a/create/views.py b/create/views.py index f0c8633..4aa5c39 100644 --- a/create/views.py +++ b/create/views.py @@ -111,6 +111,7 @@ def create_instance(request, compute_id, arch, machine): dom_caps = conn.get_dom_capabilities(arch, machine) caps = conn.get_capabilities(arch) + virtio_support = conn.is_supports_virtio(arch, machine) hv_supports_uefi = conn.supports_uefi_xml(dom_caps["loader_enums"]) # Add BIOS label = conn.label_for_firmware_path(arch, None) @@ -149,6 +150,7 @@ def create_instance(request, compute_id, arch, machine): delete_flavor.delete() return HttpResponseRedirect(request.get_full_path()) if 'create' in request.POST: + firmware = dict() volume_list = list() is_disk_created = False clone_path = "" @@ -176,7 +178,8 @@ def create_instance(request, compute_id, arch, machine): volume['path'] = path volume['type'] = conn.get_volume_type(path) volume['device'] = 'disk' - volume['bus'] = INSTANCE_VOLUME_DEFAULT_BUS + if data['virtio']: + volume['bus'] = INSTANCE_VOLUME_DEFAULT_BUS volume_list.append(volume) is_disk_created = True except libvirtError as lib_err: @@ -193,7 +196,8 @@ def create_instance(request, compute_id, arch, machine): volume['path'] = clone_path volume['type'] = conn.get_volume_type(clone_path) volume['device'] = 'disk' - volume['bus'] = INSTANCE_VOLUME_DEFAULT_BUS + if data['virtio']: + volume['bus'] = INSTANCE_VOLUME_DEFAULT_BUS volume_list.append(volume) is_disk_created = True else: @@ -215,12 +219,23 @@ def create_instance(request, compute_id, arch, machine): if data['cache_mode'] not in conn.get_cache_modes(): error_msg = _("Invalid cache mode") error_messages.append(error_msg) + if 'UEFI' in data["firmware"]: + firmware["loader"] = data["firmware"].split(":")[1].strip() + firmware["secure"] = 'no' + firmware["readonly"] = 'yes' + firmware["type"] = 'pflash' + if 'secboot' in firmware["loader"] and machine != 'q35': + messages.warning(request, "Changing machine type from '%s' to 'q35' " + "which is required for UEFI secure boot." % machine) + machine = 'q35' + firmware["secure"] = 'yes' + if not error_messages: uuid = util.randomUUID() try: conn.create_instance(name=data['name'], memory=data['memory'], vcpu=data['vcpu'], vcpu_mode=data['vcpu_mode'], uuid=uuid, arch=arch, machine=machine, - firmware=data["firmware"], + firmware=firmware, images=volume_list, cache_mode=data['cache_mode'], networks=data['networks'], virtio=data['virtio'], listen_addr=data["listener_addr"], nwfilter=data["nwfilter"], diff --git a/instances/views.py b/instances/views.py index 368f3e4..100a17a 100644 --- a/instances/views.py +++ b/instances/views.py @@ -236,14 +236,19 @@ def instance(request, compute_id, vname): return if new_compute == instance.compute: return - conn_migrate = wvmInstances(new_compute.hostname, + try: + conn_migrate = wvmInstances(new_compute.hostname, new_compute.login, new_compute.password, new_compute.type) - conn_migrate.moveto(conn, instance.name, live, unsafe, xml_del, offline) + + conn_migrate.moveto(conn, instance.name, live, unsafe, xml_del, offline) + finally: + conn_migrate.close() + instance.compute = new_compute instance.save() - conn_migrate.close() + conn_new = wvmInstance(new_compute.hostname, new_compute.login, new_compute.password, @@ -267,6 +272,8 @@ def instance(request, compute_id, vname): boot_order = conn.get_bootorder() arch = conn.get_arch() machine = conn.get_machine_type() + firmware = conn.get_loader() + nvram = conn.get_nvram() vcpu = conn.get_vcpu() cur_vcpu = conn.get_cur_vcpu() vcpus = conn.get_vcpus() @@ -290,7 +297,6 @@ def instance(request, compute_id, vname): insort(memory_range, memory) if cur_memory not in memory_range: insort(memory_range, cur_memory) - nvram = conn.get_nvram() telnet_port = conn.get_telnet_port() console_type = conn.get_console_type() console_port = conn.get_console_port() @@ -949,16 +955,17 @@ def instance(request, compute_id, vname): error_messages.append(msg) else: new_instance = Instance(compute_id=compute_id, name=clone_data['name']) - new_instance.save() + #new_instance.save() try: new_uuid = conn.clone_instance(clone_data) new_instance.uuid = new_uuid new_instance.save() except Exception as e: - new_instance.delete() + #new_instance.delete() raise e - userinstance = UserInstance(instance_id=new_instance.id, user_id=request.user.id, is_delete=True) - userinstance.save() + + user_instance = UserInstance(instance_id=new_instance.id, user_id=request.user.id, is_delete=True) + user_instance.save() msg = _("Clone of '%s'" % instance.name) addlogmsg(request.user.username, new_instance.name, msg) diff --git a/vrtManager/create.py b/vrtManager/create.py index d11e2eb..a889d69 100644 --- a/vrtManager/create.py +++ b/vrtManager/create.py @@ -189,8 +189,16 @@ class wvmCreate(wvmConnect): xml += """ """ - if 'UEFI' in firmware: - xml += """%s""" % firmware.split(":")[1].strip() + if firmware: + if firmware["secure"] == 'yes': + xml += """%s""" % (firmware["readonly"], + firmware["type"], + firmware["secure"], + firmware["loader"]) + if firmware["secure"] == 'no': + xml += """%s""" % (firmware["readonly"], + firmware["type"], + firmware["loader"]) xml += """""" if caps["features"]: @@ -201,6 +209,8 @@ class wvmCreate(wvmConnect): xml += """""" if 'pae' in caps["features"]: xml += """""" + if 'yes' == firmware["secure"]: + xml += """""" xml += """""" if vcpu_mode == "host-model": @@ -211,8 +221,8 @@ class wvmCreate(wvmConnect): pass else: xml += """ - {} - """.format(vcpu_mode) + %s""" % vcpu_mode + xml += """""" xml += """ @@ -255,14 +265,16 @@ class wvmCreate(wvmConnect): xml += """ """ % (volume['type'], cache_mode, OPTS.get("file", '')) xml += """ """ % volume['path'] - if volume['bus'] == 'virtio': - xml += """""" % (vd_disk_letters.pop(0), volume['bus']) - elif volume['bus'] == 'ide': - xml += """""" % (hd_disk_letters.pop(0), volume['bus']) - elif volume['bus'] == 'fdc': - xml += """""" % (fd_disk_letters.pop(0), volume['bus']) + if volume.get('bus') == 'virtio': + xml += """""" % (vd_disk_letters.pop(0), volume.get('bus')) + elif volume.get('bus') == 'ide': + xml += """""" % (hd_disk_letters.pop(0), volume.get('bus')) + elif volume.get('bus') == 'fdc': + xml += """""" % (fd_disk_letters.pop(0), volume.get('bus')) + elif volume.get('bus') == 'sata' or volume.get('bus') == 'scsi': + xml += """""" % (sd_disk_letters.pop(0), volume.get('bus')) else: - xml += """""" % (sd_disk_letters.pop(0), volume['bus']) + xml += """""" % sd_disk_letters.pop(0) xml += """""" if add_cd: xml += """ @@ -279,7 +291,7 @@ class wvmCreate(wvmConnect): xml += """""" % (vd_disk_letters.pop(0), 'virtio') xml += """""" - if volume['bus'] == 'scsi': + if volume.get('bus') == 'scsi': xml += """""" % INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER for net in networks.split(','): @@ -299,13 +311,15 @@ class wvmCreate(wvmConnect): if not console_pass == "": console_pass = "passwd='" + console_pass + "'" - xml += """""" - xml += """""" + if 'usb' in dom_caps['disk_bus']: + xml += """""".format('virtio' if virtio else 'usb') + xml += """""".format('virtio' if virtio else 'usb') + xml += """ """ % (graphics, console_pass, listen_addr) - if qemu_ga: + if qemu_ga and virtio: xml += """ """ From c6cdb4929cc6afd76c368cd05bcab795f6ff083b Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 19 Dec 2019 16:06:06 +0300 Subject: [PATCH 15/16] Check firmware secure keyword with safe way to prevent key not exist --- create/views.py | 1 + vrtManager/create.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/create/views.py b/create/views.py index 4aa5c39..e98b578 100644 --- a/create/views.py +++ b/create/views.py @@ -219,6 +219,7 @@ def create_instance(request, compute_id, arch, machine): if data['cache_mode'] not in conn.get_cache_modes(): error_msg = _("Invalid cache mode") error_messages.append(error_msg) + if 'UEFI' in data["firmware"]: firmware["loader"] = data["firmware"].split(":")[1].strip() firmware["secure"] = 'no' diff --git a/vrtManager/create.py b/vrtManager/create.py index a889d69..4f234c8 100644 --- a/vrtManager/create.py +++ b/vrtManager/create.py @@ -209,7 +209,7 @@ class wvmCreate(wvmConnect): xml += """""" if 'pae' in caps["features"]: xml += """""" - if 'yes' == firmware["secure"]: + if 'yes' == firmware.get("secure", 'no'): xml += """""" xml += """""" From 2fa5a988440336e1e8a402a5853cdefffa7a7cab Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 19 Dec 2019 16:06:51 +0300 Subject: [PATCH 16/16] Add new options for migrate: Auto converge, postcopy and compress --- instances/templates/instance.html | 22 ++++++++++++++++++++-- instances/views.py | 27 ++++++++++++++++++--------- vrtManager/instance.py | 20 +++++++++++--------- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index ffa492c..2cbd079 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -1035,7 +1035,7 @@
- +
@@ -1053,7 +1053,25 @@
- + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
{% if computes_count != 1 %} diff --git a/instances/views.py b/instances/views.py index 100a17a..6daa554 100644 --- a/instances/views.py +++ b/instances/views.py @@ -230,7 +230,7 @@ def instance(request, compute_id, vname): else: return network_source_pack[0], 'net' - def migrate_instance(new_compute, instance, live=False, unsafe=False, xml_del=False, offline=False): + def migrate_instance(new_compute, instance, live=False, unsafe=False, xml_del=False, offline=False, autoconverge=False, compress=False, postcopy=False): status = connection_manager.host_is_up(new_compute.type, new_compute.hostname) if not status: return @@ -238,11 +238,11 @@ def instance(request, compute_id, vname): return try: conn_migrate = wvmInstances(new_compute.hostname, - new_compute.login, - new_compute.password, - new_compute.type) + new_compute.login, + new_compute.password, + new_compute.type) - conn_migrate.moveto(conn, instance.name, live, unsafe, xml_del, offline) + conn_migrate.moveto(conn, instance.name, live, unsafe, xml_del, offline, autoconverge, compress, postcopy) finally: conn_migrate.close() @@ -812,16 +812,24 @@ def instance(request, compute_id, vname): return HttpResponseRedirect(request.get_full_path() + '#options') if 'migrate' in request.POST: + compute_id = request.POST.get('compute_id', '') live = request.POST.get('live_migrate', False) unsafe = request.POST.get('unsafe_migrate', False) xml_del = request.POST.get('xml_delete', False) offline = request.POST.get('offline_migrate', False) + autoconverge = request.POST.get('autoconverge', False) + compress = request.POST.get('compress', False) + postcopy = request.POST.get('postcopy', False) new_compute = Compute.objects.get(id=compute_id) - migrate_instance(new_compute, instance, live, unsafe, xml_del, offline) - - return HttpResponseRedirect(reverse('instance', args=[new_compute.id, vname])) + try: + migrate_instance(new_compute, instance, live, unsafe, xml_del, offline) + return HttpResponseRedirect(reverse('instance', args=[new_compute.id, vname])) + except libvirtError as err: + messages.error(request, err) + addlogmsg(request.user.username, instance.name, err) + return HttpResponseRedirect(request.get_full_path() + '#migrate') if 'change_network' in request.POST: msg = _("Change network") @@ -1240,7 +1248,8 @@ def inst_graph(request, compute_id, vname): def _get_dhcp_mac_address(vname): - dhcp_file = '/srv/webvirtcloud/dhcpd.conf' + + dhcp_file = settings.BASE_DIR + '/dhcpd.conf' mac = '' if os.path.isfile(dhcp_file): with open(dhcp_file, 'r') as f: diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 20d78a2..8a18559 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -2,15 +2,15 @@ import time import os.path try: from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_DOMAIN_RUNNING, VIR_DOMAIN_AFFECT_LIVE, \ - VIR_DOMAIN_AFFECT_CONFIG, VIR_DOMAIN_UNDEFINE_NVRAM, VIR_DOMAIN_UNDEFINE_KEEP_NVRAM,\ - VIR_DOMAIN_START_PAUSED + VIR_DOMAIN_AFFECT_CONFIG, VIR_DOMAIN_UNDEFINE_NVRAM, VIR_DOMAIN_UNDEFINE_KEEP_NVRAM, VIR_DOMAIN_START_PAUSED from libvirt import VIR_MIGRATE_LIVE, \ VIR_MIGRATE_UNSAFE, \ VIR_MIGRATE_PERSIST_DEST, \ VIR_MIGRATE_UNDEFINE_SOURCE, \ VIR_MIGRATE_OFFLINE,\ VIR_MIGRATE_COMPRESSED, \ - VIR_MIGRATE_AUTO_CONVERGE + VIR_MIGRATE_AUTO_CONVERGE, \ + VIR_MIGRATE_POSTCOPY except: from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE @@ -80,20 +80,22 @@ class wvmInstances(wvmConnect): dom = self.get_instance(name) dom.resume() - def moveto(self, conn, name, live, unsafe, undefine, offline, autoconverge=False, compress=False): + def moveto(self, conn, name, live, unsafe, undefine, offline, autoconverge=False, compress=False, postcopy=False): flags = VIR_MIGRATE_PERSIST_DEST - if live and conn.get_status() == 1: + if live and conn.get_status() != 5: flags |= VIR_MIGRATE_LIVE if unsafe and conn.get_status() == 1: flags |= VIR_MIGRATE_UNSAFE - if undefine: - flags |= VIR_MIGRATE_UNDEFINE_SOURCE - if offline: + if offline and conn.get_status() == 5: flags |= VIR_MIGRATE_OFFLINE if not offline and autoconverge: flags |= VIR_MIGRATE_AUTO_CONVERGE - if compress: + if not offline and compress and conn.get_status() == 1: flags |= VIR_MIGRATE_COMPRESSED + if not offline and postcopy and conn.get_status() == 1: + flags |= VIR_MIGRATE_POSTCOPY + if undefine: + flags |= VIR_MIGRATE_UNDEFINE_SOURCE dom = conn.get_instance(name)