From dd16a5b2d5f2434780c7c4a7e8441f3388cce101 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 13 Dec 2019 16:47:51 +0300 Subject: [PATCH] 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