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 @@ </button> <ul class="dropdown-menu" aria-labelledby="dropdownMenuButton{{ forloop.counter0 }}" role="menu"> {% for arc in hypervisor.keys|slice:"4:" %} - <li><a href="#">{{ arc }}</a></li> + <li><a tabindex="-1" href="#">{{ arc }}</a></li> {% endfor %} </ul> {% 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<compute_id>[0-9]+)/nwfilters/$', nwfilters, name='nwfilters'), url(r'^(?P<compute_id>[0-9]+)/nwfilter/(?P<nwfltr>[\w\-\.\:]+)/$', nwfilter, name='nwfilter'), url(r'^(?P<compute_id>[0-9]+)/secrets/$', secrets, name='secrets'), - url(r'^(?P<compute_id>[0-9]+)/create/$', create_instance, name='create_instance'), - url(r'^(?P<compute_id>[0-9]+)/disk/(?P<disk>[\w\-\.\/]+)/buses$', get_compute_disk_buses, name='buses'), + url(r'^(?P<compute_id>[0-9]+)/create/$', create_instance_select_type, name='create_instance_select_type'), + url(r'^(?P<compute_id>[0-9]+)/create/archs/(?P<arch>[\w\-\.\/]+)/machines/(?P<machine>[\w\-\.\/]+)$', create_instance, name='create_instance'), + url(r'^(?P<compute_id>[0-9]+)/archs/(?P<arch>[\w\-\.\/]+)/machines$', get_compute_machine_types, name='machines'), + url(r'^(?P<compute_id>[0-9]+)/archs/(?P<arch>[\w\-\.\/]+)/machines/(?P<machine>[\w\-\.\/]+)/disks/(?P<disk>[\w\-\.\/]+)/buses$', get_compute_disk_buses, name='buses'), + url(r'^(?P<compute_id>[0-9]+)/archs/(?P<arch>[\w\-\.\/]+)/machines/(?P<machine>[\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 %} + <!-- Page Heading --> + <div class="row"> + <div class="col-lg-12"> + <h1 class="page-header">{% trans "New instance on" %} {{ compute.name }}</h1> + </div> + </div> + <!-- /.row --> + {% include 'errors_block.html' %} + {% include 'pleasewaitdialog.html' %} + + + {% if form.errors %} + {% for field in form %} + {% for error in field.errors %} + <div class="alert alert-danger"> + <strong>{{ error|escape }}</strong> + </div> + {% endfor %} + {% endfor %} + {% for error in form.non_field_errors %} + <div class="alert alert-danger"> + <strong>{{ error|escape }}</strong> + </div> + {% endfor %} + {% endif %} + + <div class="row" id="max-width-page"> + <div class="col-lg-12"> + <div role="tabpanel"> + <!-- Nav tabs --> + <ul class="nav nav-tabs" role="tablist"> + <li role="presentation" class="active"> + <a href="#select_architecture" aria-controls="flavor" role="tab" data-toggle="tab"> + {% trans "Architecture" %} + </a> + </li> + <li role="presentation"> + <a href="#addFromXML" aria-controls="addFromXML" role="tab" data-toggle="tab"> + {% trans "XML" %} + </a> + </li> + </ul> + <!-- Tab panes --> + <div class="tab-content"> + <div role="tabpanel" class="tab-pane tab-pane-bordered active" id="select_architecture"> + <div class="well"> + <div class="center-block"> + <form class="form-horizontal" method="post" role="form">{% csrf_token %} + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Architecture" %}</label> + <div class="col-sm-6"> + <select class="form-control" id="select_archs" name="archs" onchange="get_machine_types({{ compute_id }}, value);"> + {% for hpv in hypervisors %} + <option value="{{ hpv }}" {% if hpv == default_arch %}selected{% endif %}>{{ hpv }}</option> + {% endfor %} + </select> + </div> + </div> + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Chipset" %}</label> + <div class="col-sm-6"> + <select class="form-control" id="select_chipset" name="chipset"> + <!-- fill with script --> + </select> + </div> + </div> + <div class="form-group"> + <div class="col-sm-6 col-lg-offset-3"> + <button class="btn btn-block btn-primary" type="button" name="create_instance" onclick="goto_create()"> + {% trans "Next >" %} + </button> + </div> + </div> + </form> + </div> + </div> + <div class="clearfix"></div> + </div> + <div role="tabpanel" class="tab-pane tab-pane-bordered" id="addFromXML"> + <div class="well"> + <form class="form-horizontal" method="post" role="form">{% csrf_token %} + <div class="col-sm-12" id="xmlheight"> + <input type="hidden" name="dom_xml"/> + <textarea id="editor"></textarea> + </div> + <button type="submit" class="btn btn-primary" name="create_xml" onclick="showPleaseWaitDialog()"> + {% trans "Create" %} + </button> + </form> + </div> + <div class="clearfix"/> + </div> + </div> + </div> + </div> + </div> +{% endblock %} +{% block script %} +<script> + $(document).ready(function () { + let arch = $("#select_archs").val(); + get_machine_types({{ compute_id }}, arch); + }); + + function get_machine_types(compute_id, arch) { + get_machine_type_url = "/computes/" + compute_id + "/archs/" + arch + "/machines"; + $.getJSON(get_machine_type_url, function (data) { + $("#select_chipset").find('option').remove(); + $("#select_archs").val(arch); + $.each(data['machines'], function(i, item) { + if (item == '{{ default_machine }}') { + var selected = 'selected'; + }else{ + var selected = ''; + } + $("#select_chipset").append('<option value="' + item + '"' + selected +'>' + item + '</option>'); + }); + }); + } +</script> + +<script src="{% static "js/ace.js" %}"></script> +<script> + var editor = ace.edit("editor"); + editor.getSession().setMode("ace/mode/xml"); + + var input = $('input[name="dom_xml"]'); + editor.getSession().on("change",function () { + input.val(editor.getSession().getValue()); + }) +</script> +{% if request.user.is_superuser %} + <script> + function goto_create() { + let compute = '{{ compute.id }}'; + let arch = $("#select_archs").val(); + let machine = $("#select_chipset").val(); + create_machine_url = "/computes/" + compute + "/create/archs/" + arch + "/machines/" + machine; + {#url = "{% url 'create_instance' compute.id 'x86_64' 'pc' %}".replace(/x86_64/, arch).replace(/pc/, machine);#} + window.location.href = create_machine_url; + } + + </script> +{% 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 @@ <!-- Page Heading --> <div class="row"> <div class="col-lg-12"> - <h1 class="page-header">{% trans "New instance on" %} {{ compute.name }}</h1> + <h1 class="page-header"> + {% trans "New instance on" %} {{ compute.name }} + </h1> </div> </div> <!-- /.row --> @@ -37,6 +39,11 @@ <div role="tabpanel"> <!-- Nav tabs --> <ul class="nav nav-tabs" role="tablist"> + <li role="presentation"> + <a class="pull-right" href="#" role="tab" data-toggle="tab" onclick="goto_compute()"> + <span class="glyphicon glyphicon-arrow-left"></span> + </a> + </li> <li role="presentation" class="active"> <a href="#flavor" aria-controls="flavor" role="tab" data-toggle="tab"> {% trans "Flavor" %} @@ -52,11 +59,6 @@ {% trans "Template" %} </a> </li> - <li role="presentation"> - <a href="#addFromXML" aria-controls="addFromXML" role="tab" data-toggle="tab"> - {% trans "XML" %} - </a> - </li> </ul> <!-- Tab panes --> <div class="tab-content"> @@ -112,9 +114,43 @@ <input type="hidden" name="hdd_size" value="{{ flavor.disk }}"> </div> </div> + {% if firmwares %} + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Firmware" %}</label> + <div class="col-sm-6"> + <select class="form-control" id="select_firmware" name="firmware"> + {% for frm in firmwares %} + <option value="{{ frm }}" {% if frm == default_firmware %}selected{% endif %}>{{ frm }}</option> + {% endfor %} + </select> + </div> + </div> + {% endif %} + {% if dom_caps.cpu_modes %} + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "VCPU Config" %}</label> + <div class="col-sm-6"> + <select id="vcpu_mode" name="vcpu_mode" class="form-control"> + <option value=""> {% trans 'no-mode' %}</option> + {% for mode in dom_caps.cpu_modes %} + {% if mode == 'custom' %} + <optgroup label="Custom CPU Models"> + {% for model in dom_caps.cpu_custom_models %} + <option value="{{ model }}"> {% trans model %}</option> + {% endfor %} + </optgroup> + {% else %} + <option value="{{ mode }}" {% ifequal mode default_cpu_mode %}selected {% endifequal %}> + {% trans mode %} + </option> + {% endif %} + {% endfor %} + </select> + </div> + </div> + {% endif %} <div class="form-group"> <label class="col-sm-3 control-label">{% trans "Storage" %}</label> - <div class="col-sm-6"> <input type="hidden" name="cache_mode" value="default"> <select name="storage" class="form-control"> @@ -166,6 +202,18 @@ <input type="text" class="form-control" name="mac" maxlength="17" value="{{ mac_auto }}" required pattern="[a-zA-Z0-9:]+"> </div> </div> + {% if dom_caps.graphics_support == 'yes' %} + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Graphics" %}</label> + <div class="col-sm-6"> + <select name="graphics" class="form-control"> + {% for graphics in dom_caps.graphics_types %} + <option value="{{ graphics }}" {% if default_graphics == graphics %}selected{% endif %}>{{ graphics }}</option> + {% endfor %} + </select> + </div> + </div> + {% endif %} <div class="form-group"> <label class="col-sm-3 control-label">{% trans "Video" %}</label> <div class="col-sm-6"> @@ -196,13 +244,6 @@ </select> </div> </div> - <div class="form-group"> - <label class="col-sm-3 control-label">{% trans "Host-Model" %}</label> - <div class="col-sm-6"> - <input type="checkbox" name="host_model" value="true" checked> - </div> - <label class="col-lg-1 control-label">{% trans "CPU" %}</label> - </div> <div class="form-group"> <label class="col-sm-3 control-label">{% trans "Guest Agent" %}</label> <div class="col-sm-6"> @@ -258,19 +299,47 @@ <input type="text" class="form-control" name="name" placeholder="{% trans "Name" %}" maxlength="64" required pattern="[a-zA-Z0-9\.\-_]+"> </div> </div> + {% if firmwares %} + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Firmware" %}</label> + <div class="col-sm-7"> + <select class="form-control" id="select_firmware" name="firmware"> + {% for frm in firmwares %} + <option value="{{ frm }}" {% if frm == default_firmware %}selected{% endif %}>{{ frm }}</option> + {% endfor %} + </select> + </div> + </div> + {% endif %} <div class="form-group"> <label class="col-sm-3 control-label">{% trans "VCPU" %}</label> <div class="col-sm-7"> <input type="text" class="form-control" name="vcpu" value="1" maxlength="2" required pattern="[0-9]"> </div> </div> - <div class="form-group"> - <label class="col-sm-3 control-label">{% trans "Host-Model" %}</label> + {% if dom_caps.cpu_modes %} + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "VCPU Config" %}</label> <div class="col-sm-7"> - <input type="checkbox" name="host_model" value="true" checked> + <select id="vcpu_mode" name="vcpu_mode" class="form-control"> + <option value=""> {% trans 'no-mode' %}</option> + {% for mode in dom_caps.cpu_modes %} + {% if mode == 'custom' %} + <optgroup label="Custom CPU Models"> + {% for model in dom_caps.cpu_custom_models %} + <option value="{{ model }}"> {% trans model %}</option> + {% endfor %} + </optgroup> + {% else %} + <option value="{{ mode }}" {% ifequal mode default_cpu_mode %}selected {% endifequal %}> + {% trans mode %} + </option> + {% endif %} + {% endfor %} + </select> </div> - <label class="col-sm-1 control-label">{% trans "CPU" %}</label> </div> + {% endif %} <div class="form-group"> <label class="col-sm-3 control-label">{% trans "RAM" %}</label> <div class="col-sm-7"> @@ -351,6 +420,18 @@ </select> </div> </div> + {% if dom_caps.graphics_support == 'yes' %} + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Graphics" %}</label> + <div class="col-sm-7"> + <select name="graphics" class="form-control"> + {% for graphics in dom_caps.graphics_types %} + <option value="{{ graphics }}" {% if default_graphics == graphics %}selected{% endif %}>{{ graphics }}</option> + {% endfor %} + </select> + </div> + </div> + {% endif %} <div class="form-group"> <label class="col-sm-3 control-label">{% trans "Video" %}</label> <div class="col-sm-7"> @@ -393,15 +474,18 @@ <input type="checkbox" name="virtio" value="true" checked> </div> </div> + <div class="form-group"> + <div class="col-sm-7 col-sm-offset-3"> {% if storages %} - <button type="submit" class="btn btn-primary" name="create" formnovalidate onclick="showPleaseWaitDialog()" value="1"> + <button type="submit" class="btn btn-block btn-primary" name="create" formnovalidate onclick="showPleaseWaitDialog()" value="1"> {% trans "Create" %} </button> {% else %} - <button class="btn btn-primary disabled"> + <button class="btn btn-block btn-primary disabled"> {% trans "Create" %} </button> {% endif %} + </div></div> </form> </div> <div class="clearfix"></div> @@ -416,6 +500,18 @@ <input type="text" class="form-control" name="name" placeholder="{% trans "Name" %}" maxlength="64" required pattern="[a-zA-Z0-9\.\-_]+"> </div> </div> + {% if firmwares %} + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Firmware" %}</label> + <div class="col-sm-7"> + <select class="form-control" id="select_firmware" name="firmware"> + {% for frm in firmwares %} + <option value="{{ frm }}" {% if frm == default_firmware %}selected{% endif %}>{{ frm }}</option> + {% endfor %} + </select> + </div> + </div> + {% endif %} <div class="form-group"> <label class="col-sm-3 control-label">{% trans "VCPU" %}</label> <div class="col-sm-7"> @@ -423,11 +519,25 @@ </div> </div> <div class="form-group"> - <label class="col-sm-3 control-label">{% trans "Host-Model" %}</label> + <label class="col-sm-3 control-label">{% trans "VCPU Config" %}</label> <div class="col-sm-7"> - <input type="checkbox" name="host_model" value="true" checked> + <select id="vcpu_mode" name="vcpu_mode" class="form-control"> + <option value=""> {% trans 'no-mode' %}</option> + {% for mode in dom_caps.cpu_modes %} + {% if mode == 'custom' %} + <optgroup label="Custom CPU Models"> + {% for model in dom_caps.cpu_custom_models %} + <option value="{{ model }}"> {% trans model %}</option> + {% endfor %} + </optgroup> + {% else %} + <option value="{{ mode }}" {% ifequal mode default_cpu_mode %}selected {% endifequal %}> + {% trans mode %} + </option> + {% endif %} + {% endfor %} + </select> </div> - <label class="col-sm-1 control-label">{% trans "CPU" %}</label> </div> <div class="form-group"> <label class="col-sm-3 control-label">{% trans "RAM" %}</label> @@ -437,7 +547,7 @@ <label class="col-sm-1 control-label">{% trans "MB" %}</label> </div> <div class="form-group"> - <label class="col-sm-3 control-label">{% trans "HDD" %}</label> + <label class="col-sm-3 control-label">{% trans "Template Disk" %}</label> <input id="images" name="images" type="hidden" value=""/> <div class="col-sm-3"> <select class="form-control" onchange="get_template_vols({{ compute_id }}, value);"> @@ -510,6 +620,18 @@ </select> </div> </div> + {% if dom_caps.graphics_support == 'yes' %} + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "Graphics" %}</label> + <div class="col-sm-7"> + <select name="graphics" class="form-control"> + {% for graphics in dom_caps.graphics_types %} + <option value="{{ graphics }}" {% if default_graphics == graphics %}selected{% endif %}>{{ graphics }}</option> + {% endfor %} + </select> + </div> + </div> + {% endif %} <div class="form-group"> <label class="col-sm-3 control-label">{% trans "Video" %}</label> <div class="col-sm-7"> @@ -552,31 +674,19 @@ <input type="checkbox" name="virtio" value="true" checked> </div> </div> - - {% if storages %} - <button type="submit" class="btn btn-primary" name="create" value="1" formnovalidate onclick="showPleaseWaitDialog()"> - {% trans "Create" %} - </button> - {% else %} - <button class="btn btn-primary disabled"> - {% trans "Create" %} - </button> - {% endif %} - </form> - </div> - <div class="clearfix"></div> - </div> - - <div role="tabpanel" class="tab-pane tab-pane-bordered" id="addFromXML"> - <div class="well"> - <form class="form-horizontal" method="post" role="form">{% csrf_token %} - <div class="col-sm-12" id="xmlheight"> - <input type="hidden" name="dom_xml"/> - <textarea id="editor"></textarea> + <div class="form-group"> + <div class="col-sm-7 col-sm-offset-3"> + {% if storages %} + <button type="submit" class="btn btn-block btn-primary" name="create" value="1" formnovalidate onclick="showPleaseWaitDialog()"> + {% trans "Create" %} + </button> + {% else %} + <button class="btn btn-primary disabled"> + {% trans "Create" %} + </button> + {% endif %} + </div> </div> - <button type="submit" class="btn btn-primary" name="create_xml" onclick="showPleaseWaitDialog()"> - {% trans "Create" %} - </button> </form> </div> <div class="clearfix"></div> @@ -589,7 +699,7 @@ <script src="{% static "js/bootstrap-multiselect.js" %}"></script> <script> function toggleValue(string, updated_value, checked) { - var result = ''; + let result = ''; if (checked) { result = string; if (result != '') result += ','; @@ -620,15 +730,15 @@ return ''; }, onChange: function (element, checked) { - var input_value = toggleValue($('#images').val(), element.val(), checked); + let input_value = toggleValue($('#images').val(), element.val(), checked); $('#images').val(input_value); - var selected_list_html = ''; - var counter = 0; + let selected_list_html = ''; + let counter = 0; if (input_value != '') { $('#disk_list_div').show(); $.each(input_value.split(','), function (index, value) { - var li = '<li>hdd' + counter + ' - ' + + let li = '<li>hdd' + counter + ' - ' + '<select name="device' + counter + '" class="image-format" onchange="get_disk_bus_choices({{ compute_id }},' + counter + ', value);">' + '{% for dev in disk_devices %}' + '<option value=' + '"{{ dev }}">' + '{% trans dev %}</option>' + @@ -668,13 +778,13 @@ return '100%'; }, onChange: function (element, checked) { - var input_value = toggleValue($('#networks').val(), element.val(), checked); + let input_value = toggleValue($('#networks').val(), element.val(), checked); $('#networks').val(input_value); - var selected_list_html = ''; - var counter = 0; + let selected_list_html = ''; + let counter = 0; if (input_value != '') { $.each(input_value.split(','), function (index, value) { - var li = '<li>eth' + counter + + let li = '<li>eth' + counter + ' -> ' + value + ' ' + '<a class="btn-link pull-right" onclick="javascript:$(\'#network-control\').multiselect(\'deselect\', \'' + value + '\', true)"><i class="fa fa-remove"></i></a></a></li>'; selected_list_html += li; @@ -686,6 +796,8 @@ }); }); + $("id[vcpu_mode]").multiselect(); + function get_cust_vols(compute_id, pool) { get_vol_url = "/computes/" + compute_id + "/storage/" + pool + "/volumes"; $.getJSON(get_vol_url, function (data) { @@ -708,11 +820,12 @@ $("#template").removeAttr("disabled"); $("#storage").val(pool).change(); $("#storage").removeAttr("disabled"); - } function get_disk_bus_choices(compute_id, dev_idx, disk_type){ - get_diskBus_url = "/computes/" + compute_id + "/disk/" + disk_type + "/buses"; + let arch = $('select[name="arch"]').val(); + let machine = $("select[id='machine-control']").val(); + get_diskBus_url = "/computes/" + compute_id + "/archs/" + arch + "/machines/" + machine + "/disks/" + disk_type + "/buses"; $.getJSON(get_diskBus_url, function (data) { $("#bus" + dev_idx).find('option').remove(); $.each(data['bus'], function(i, item) { @@ -721,15 +834,12 @@ }); } </script> - -<script src="{% static "js/ace.js" %}"></script> -<script> - var editor = ace.edit("editor"); - editor.getSession().setMode("ace/mode/xml"); - - var input = $('input[name="dom_xml"]'); - editor.getSession().on("change",function () { - input.val(editor.getSession().getValue()); - }) -</script> +{% if request.user.is_superuser %} + <script> + function goto_compute() { + let compute = {{ compute.id }} + window.location.href = "{% url 'create_instance_select_type' 1 %}".replace(1, compute); + } + </script> +{% 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 %} <script> function goto_compute() { - var compute = $("#compute_select").val(); - window.location.href = "{% url 'create_instance' 1 %}".replace(1, compute); + let compute = $("#compute_select").val(); + {#window.location.href = "{% url 'create_instance' 1 %}".replace(1, compute);#} + window.location.href = "{% url 'create_instance_select_type' 1 %}".replace(1, compute); } </script> {% 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 @@ <div class="row"> <div class="col-lg-12"> {% if request.user.is_superuser %} - <a href="{% url 'create_instance' compute.id %}" type="button" class="btn btn-success btn-header pull-right" data-toggle="modal"> + <a href="{% url 'create_instance_select_type' compute.id %}" type="button" class="btn btn-success btn-header pull-right" data-toggle="modal"> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> </a> {% endif %} @@ -161,7 +161,7 @@ <script src="{% static "js/sortable.min.js" %}"></script> <script> function open_console(uuid) { - window.open("{% url 'console' %}?token=" + uuid, "", "width=850,height=485"); + window.open("{% url 'console' %}?token=" + uuid, "", "width=850,height=685"); } </script> <script> @@ -189,12 +189,4 @@ window.location = "/instances/" + compute + "/" + instance + "/#clone"; } </script> -{% if request.user.is_superuser %} - <script> - function goto_compute() { - var compute = $("#compute_select").val(); - window.location.href = "{% url 'create_instance' 1 %}".replace(1, compute); - } - </script> -{% 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 = """ <domain type='%s'> @@ -180,23 +181,46 @@ class wvmCreate(wvmConnect): <description>None</description> <uuid>%s</uuid> <memory unit='KiB'>%s</memory> - <vcpu>%s</vcpu>""" % (hypervisor_type, name, uuid, memory, vcpu) - if host_model: + <vcpu>%s</vcpu>""" % (dom_caps["domain"], name, uuid, memory, vcpu) + + if dom_caps["os_support"] == 'yes': + xml += """<os> + <type arch='%s' machine='%s'>%s</type>""" % (arch, machine, caps["os_type"]) + xml += """ <boot dev='hd'/> + <boot dev='cdrom'/> + <bootmenu enable='yes'/>""" + if 'UEFI' in firmware: + xml += """<loader readonly='yes' type='pflash'>%s</loader>""" % firmware.split(":")[1].strip() + xml += """</os>""" + + if caps["features"]: + xml += """<features>""" + if 'acpi' in caps["features"]: + xml += """<acpi/>""" + if 'apic' in caps["features"]: + xml += """<apic/>""" + if 'pae' in caps["features"]: + xml += """<pae/>""" + xml += """</features>""" + + if vcpu_mode == "host-model": xml += """<cpu mode='host-model'/>""" - xml += """<os> - <type arch='%s'>%s</type> - <boot dev='hd'/> - <boot dev='cdrom'/> - <bootmenu enable='yes'/> - </os>""" % (self.get_host_arch(), self.get_os_type()) - xml += """<features> - <acpi/><apic/><pae/> - </features> + elif vcpu_mode == "host-passthrough": + xml += """<cpu mode='host-passthrough'/>""" + elif vcpu_mode == "": + pass + else: + xml += """<cpu mode='custom' match='exact' check='none'> + <model fallback='allow'>{}</model> + </cpu>""".format(vcpu_mode) + + xml += """ <clock offset="utc"/> <on_poweroff>destroy</on_poweroff> <on_reboot>restart</on_reboot> <on_crash>restart</on_crash> - <devices>""" + """ + xml += """<devices>""" 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 += """<disk type='network' device='disk'> - <driver name='qemu' type='%s' cache='%s'/> - <auth username='%s'> + <driver name='qemu' type='%s' cache='%s' %s />""" % (volume['type'], cache_mode, OPTS.get("network", '')) + xml += """ <auth username='%s'> <secret type='ceph' uuid='%s'/> </auth> - <source protocol='rbd' name='%s'>""" % (volume['type'], cache_mode, ceph_user, secret_uuid, volume['path']) + <source protocol='rbd' name='%s'>""" % (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 name='%s'/>""" % host.get('name') - xml += """ - </source>""" + xml += """</source>""" else: - xml += """<disk type='file' device='%s'> - <driver name='qemu' type='%s' cache='%s'/> - <source file='%s'/>""" % (volume['device'], volume['type'], cache_mode, volume['path']) + xml += """<disk type='file' device='%s'>""" % volume['device'] + xml += """ <driver name='qemu' type='%s' cache='%s' %s/>""" % (volume['type'], cache_mode, OPTS.get("file", '')) + xml += """ <source file='%s'/>""" % volume['path'] if volume['bus'] == 'virtio': xml += """<target dev='vd%s' bus='%s'/>""" % (vd_disk_letters.pop(0), volume['bus']) @@ -242,12 +265,23 @@ class wvmCreate(wvmConnect): xml += """<target dev='sd%s' bus='%s'/>""" % (sd_disk_letters.pop(0), volume['bus']) xml += """</disk>""" if add_cd: - xml += """ <disk type='file' device='cdrom'> + xml += """<disk type='file' device='cdrom'> <driver name='qemu' type='raw'/> - <source file=''/> - <target dev='hd%s' bus='ide'/> - <readonly/> - </disk>""" % (hd_disk_letters.pop(0),) + <source file = '' /> + <readonly/>""" + if 'ide' in dom_caps['disk_bus']: + xml += """<target dev='hd%s' bus='%s'/>""" % (hd_disk_letters.pop(0), 'ide') + elif 'sata' in dom_caps['disk_bus']: + xml += """<target dev='sd%s' bus='%s'/>""" % (sd_disk_letters.pop(0), 'sata') + elif 'scsi' in dom_caps['disk_bus']: + xml += """<target dev='sd%s' bus='%s'/>""" % (sd_disk_letters.pop(0), 'scsi') + else: + xml += """<target dev='vd%s' bus='%s'/>""" % (vd_disk_letters.pop(0), 'virtio') + xml += """</disk>""" + + if volume['bus'] == 'scsi': + xml += """<controller type='scsi' model='%s'/>""" % INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER + for net in networks.split(','): xml += """<interface type='network'>""" if mac: @@ -265,10 +299,11 @@ class wvmCreate(wvmConnect): if not console_pass == "": console_pass = "passwd='" + console_pass + "'" - xml += """ <input type='mouse' bus='ps2'/> - <input type='tablet' bus='usb'/> - <graphics type='%s' port='-1' autoport='yes' %s listen='%s'/> - <console type='pty'/> """ % (QEMU_CONSOLE_DEFAULT_TYPE, console_pass, listen_addr) + xml += """<input type='mouse' bus='virtio'/>""" + xml += """<input type='tablet' bus='virtio'/>""" + xml += """ + <graphics type='%s' port='-1' autoport='yes' %s listen='%s'/> + <console type='pty'/> """ % (graphics, console_pass, listen_addr) if qemu_ga: xml += """ <channel type='unix'> @@ -278,7 +313,6 @@ class wvmCreate(wvmConnect): xml += """ <video> <model type='%s'/> </video> - <memballoon model='virtio'/> </devices> </domain>""" % 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