1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2024-12-25 15:45:23 +00:00

Merge pull request #270 from catborise/master

New options for instance create & handle nvram & migrate options
This commit is contained in:
Anatoliy Guskov 2019-12-19 17:35:22 +02:00 committed by GitHub
commit fa852566aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1094 additions and 296 deletions

View file

@ -2,7 +2,7 @@ language: python
python: python:
- "2.7" - "2.7"
env: env:
- DJANGO=1.11.23 - DJANGO=1.11.26
install: install:
- pip install -r dev/requirements.txt - pip install -r dev/requirements.txt
script: script:

View file

@ -72,6 +72,10 @@ Setup libvirt and KVM on server
```bash ```bash
wget -O - https://clck.ru/9V9fH | sudo sh wget -O - https://clck.ru/9V9fH | sudo sh
``` ```
Done!!
Go to http://serverip and you should see the login screen.
### Install WebVirtCloud panel (CentOS) ### Install WebVirtCloud panel (CentOS)
@ -268,7 +272,7 @@ datasource:
### How To Update ### How To Update
```bash ```bash
sudo virtualenv venv cd <installation-directory>
sudo source venv/bin/activate sudo source venv/bin/activate
git pull git pull
pip install -U -r conf/requirements.txt pip install -U -r conf/requirements.txt

View file

@ -53,31 +53,30 @@
<div class="col-xs-8 col-sm-9"> <div class="col-xs-8 col-sm-9">
<p>{{ hostname }}</p> <p>{{ hostname }}</p>
<p><div class="btn-group" style="margin-left: 8px"> <p><div class="btn-group" style="margin-left: 8px">
{% for arch, hpv in hypervisor.items %} {% for arch, hpv in hypervisor.items|slice:":4" %}
{% if forloop.counter < 4 %} <div class="btn-group" >
<div class="btn-group" > <button class="btn btn-xs btn-default dropdown-toggle" type="button" id="dropdownMenuButton{{ forloop.counter0 }}" data-toggle="dropdown">
{{ arch }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton{{ forloop.counter0 }}" role="menu">
{% for h in hpv %}
<li><a href="#">{{ h }}</a></li>
{% endfor %}
</ul>
</div>
{% endfor %}
{% if hypervisor.items|length > 4 %}
<button class="btn btn-xs btn-default dropdown-toggle" type="button" id="dropdownMenuButton{{ forloop.counter0 }}" data-toggle="dropdown"> <button class="btn btn-xs btn-default dropdown-toggle" type="button" id="dropdownMenuButton{{ forloop.counter0 }}" data-toggle="dropdown">
{{ arch }} {{ hypervisor.items|slice:"4:"|length }} {% trans 'more' %}...
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton{{ forloop.counter0 }}" role="menu"> <ul class="dropdown-menu" aria-labelledby="dropdownMenuButton{{ forloop.counter0 }}" role="menu">
{% for h in hpv %} {% for arc in hypervisor.keys|slice:"4:" %}
<li><a href="#">{{ h }}</a></li> <li><a tabindex="-1" href="#">{{ arc }}</a></li>
{% endfor %}
</ul>
</div>
{% else %}
<button class="btn btn-xs btn-default dropdown-toggle" type="button" id="dropdownMenuButton{{ forloop.counter0 }}" data-toggle="dropdown">
{{ hypervisor|length }} {% trans 'more' %}...
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton{{ forloop.counter0 }}" role="menu">
{% for arc in hypervisor.keys %}
<li><a href="#">{{ arc }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
{% endfor %}
</div></p> </div></p>
<p>{{ emulator }}</p> <p>{{ emulator }}</p>
<p> <p>

View file

@ -2,9 +2,9 @@ from django.conf.urls import url
from storages.views import storages, storage, get_volumes from storages.views import storages, storage, get_volumes
from networks.views import networks, network from networks.views import networks, network
from secrets.views import secrets 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 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 instances.views import instances
from nwfilters.views import nwfilter, nwfilters 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]+)/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]+)/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]+)/secrets/$', secrets, name='secrets'),
url(r'^(?P<compute_id>[0-9]+)/create/$', create_instance, name='create_instance'), url(r'^(?P<compute_id>[0-9]+)/create/$', create_instance_select_type, name='create_instance_select_type'),
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/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'),
] ]

View file

@ -155,7 +155,7 @@ def overview(request, compute_id):
compute.password, compute.password,
compute.type) compute.type)
hostname, host_arch, host_memory, logical_cpu, model_cpu, uri_conn = conn.get_node_info() 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() mem_usage = conn.get_memory_usage()
emulator = conn.get_emulator(host_arch) emulator = conn.get_emulator(host_arch)
version = conn.get_version() version = conn.get_version()
@ -198,8 +198,8 @@ def compute_graph(request, compute_id):
@login_required @login_required
def get_compute_disk_buses(request, compute_id, disk): def get_compute_disk_buses(request, compute_id, arch, machine, disk):
data = {} data = dict()
compute = get_object_or_404(Compute, pk=compute_id) compute = get_object_or_404(Compute, pk=compute_id)
try: try:
conn = wvmConnect(compute.hostname, conn = wvmConnect(compute.hostname,
@ -207,7 +207,7 @@ def get_compute_disk_buses(request, compute_id, disk):
compute.password, compute.password,
compute.type) 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 in disk_device_types:
if disk == 'disk': if disk == 'disk':
@ -223,3 +223,51 @@ def get_compute_disk_buses(request, compute_id, disk):
return HttpResponse(json.dumps(data)) 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))

View file

@ -1,7 +1,7 @@
Django==1.11.25 Django==1.11.26
websockify==0.9.0 websockify==0.9.0
gunicorn==19.9.0 gunicorn==19.9.0
lxml==4.2.5 lxml==4.4.2
libvirt-python==5.3.0 libvirt-python==5.10.0
pytz pytz
rwlock rwlock

View file

@ -33,8 +33,9 @@ class FlavorAddForm(forms.Form):
class NewVMForm(forms.Form): class NewVMForm(forms.Form):
name = forms.CharField(error_messages={'required': _('No Virtual Machine name has been entered')}, name = forms.CharField(error_messages={'required': _('No Virtual Machine name has been entered')},
max_length=64) max_length=64)
firmware = forms.CharField(max_length=50, required=False)
vcpu = forms.IntegerField(error_messages={'required': _('No VCPU has been entered')}) 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) disk = forms.IntegerField(required=False)
memory = forms.IntegerField(error_messages={'required': _('No RAM size has been entered')}) memory = forms.IntegerField(error_messages={'required': _('No RAM size has been entered')})
networks = forms.CharField(error_messages={'required': _('No Network pool has been choosen')}) 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) virtio = forms.BooleanField(required=False)
qemu_ga = forms.BooleanField(required=False) qemu_ga = forms.BooleanField(required=False)
mac = forms.CharField(required=False) mac = forms.CharField(required=False)
console_pass = forms.CharField(required=False,empty_value="", widget=forms.PasswordInput()) console_pass = forms.CharField(required=False, empty_value="", widget=forms.PasswordInput())
video = forms.CharField(error_messages={'required': _('Please select a graphic display')}) 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) listener_addr = forms.ChoiceField(required=True, widget=forms.RadioSelect, choices=QEMU_CONSOLE_LISTEN_ADDRESSES)
def clean_name(self): def clean_name(self):

View file

@ -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-sm-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 %}

View file

@ -9,7 +9,9 @@
<!-- Page Heading --> <!-- Page Heading -->
<div class="row"> <div class="row">
<div class="col-lg-12"> <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>
</div> </div>
<!-- /.row --> <!-- /.row -->
@ -37,6 +39,11 @@
<div role="tabpanel"> <div role="tabpanel">
<!-- Nav tabs --> <!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist"> <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"> <li role="presentation" class="active">
<a href="#flavor" aria-controls="flavor" role="tab" data-toggle="tab"> <a href="#flavor" aria-controls="flavor" role="tab" data-toggle="tab">
{% trans "Flavor" %} {% trans "Flavor" %}
@ -52,11 +59,6 @@
{% trans "Template" %} {% trans "Template" %}
</a> </a>
</li> </li>
<li role="presentation">
<a href="#addFromXML" aria-controls="addFromXML" role="tab" data-toggle="tab">
{% trans "XML" %}
</a>
</li>
</ul> </ul>
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content"> <div class="tab-content">
@ -112,9 +114,43 @@
<input type="hidden" name="hdd_size" value="{{ flavor.disk }}"> <input type="hidden" name="hdd_size" value="{{ flavor.disk }}">
</div> </div>
</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"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Storage" %}</label> <label class="col-sm-3 control-label">{% trans "Storage" %}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="hidden" name="cache_mode" value="default"> <input type="hidden" name="cache_mode" value="default">
<select name="storage" class="form-control"> <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:]+"> <input type="text" class="form-control" name="mac" maxlength="17" value="{{ mac_auto }}" required pattern="[a-zA-Z0-9:]+">
</div> </div>
</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"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Video" %}</label> <label class="col-sm-3 control-label">{% trans "Video" %}</label>
<div class="col-sm-6"> <div class="col-sm-6">
@ -196,25 +244,20 @@
</select> </select>
</div> </div>
</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"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Guest Agent" %}</label> <label class="col-sm-3 control-label">{% trans "Guest Agent" %}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="checkbox" name="qemu_ga" value="true" checked> <input type="checkbox" name="qemu_ga" value="true" checked>
</div> </div>
</div> </div>
{% if virtio_support %}
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "VirtIO" %}</label> <label class="col-sm-3 control-label">{% trans "VirtIO" %}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="checkbox" name="virtio" value="true" checked> <input type="checkbox" name="virtio" value="{{ virtio_support }}" checked>
</div> </div>
</div> </div>
{% endif %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button> <button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button>
@ -258,19 +301,47 @@
<input type="text" class="form-control" name="name" placeholder="{% trans "Name" %}" maxlength="64" required pattern="[a-zA-Z0-9\.\-_]+"> <input type="text" class="form-control" name="name" placeholder="{% trans "Name" %}" maxlength="64" required pattern="[a-zA-Z0-9\.\-_]+">
</div> </div>
</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"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "VCPU" %}</label> <label class="col-sm-3 control-label">{% trans "VCPU" %}</label>
<div class="col-sm-7"> <div class="col-sm-7">
<input type="text" class="form-control" name="vcpu" value="1" maxlength="2" required pattern="[0-9]"> <input type="text" class="form-control" name="vcpu" value="1" maxlength="2" required pattern="[0-9]">
</div> </div>
</div> </div>
<div class="form-group"> {% if dom_caps.cpu_modes %}
<label class="col-sm-3 control-label">{% trans "Host-Model" %}</label> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "VCPU Config" %}</label>
<div class="col-sm-7"> <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> </div>
<label class="col-sm-1 control-label">{% trans "CPU" %}</label>
</div> </div>
{% endif %}
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "RAM" %}</label> <label class="col-sm-3 control-label">{% trans "RAM" %}</label>
<div class="col-sm-7"> <div class="col-sm-7">
@ -351,6 +422,18 @@
</select> </select>
</div> </div>
</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"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Video" %}</label> <label class="col-sm-3 control-label">{% trans "Video" %}</label>
<div class="col-sm-7"> <div class="col-sm-7">
@ -387,21 +470,26 @@
<input type="checkbox" name="qemu_ga" value="true" checked> <input type="checkbox" name="qemu_ga" value="true" checked>
</div> </div>
</div> </div>
{% if virtio_support %}
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "VirtIO" %}</label> <label class="col-sm-3 control-label">{% trans "VirtIO" %}</label>
<div class="col-sm-7"> <div class="col-sm-6">
<input type="checkbox" name="virtio" value="true" checked> <input type="checkbox" name="virtio" value="{{ virtio_support }}" checked>
</div> </div>
</div> </div>
{% endif %}
<div class="form-group">
<div class="col-sm-7 col-sm-offset-3">
{% if storages %} {% 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" %} {% trans "Create" %}
</button> </button>
{% else %} {% else %}
<button class="btn btn-primary disabled"> <button class="btn btn-block btn-primary disabled">
{% trans "Create" %} {% trans "Create" %}
</button> </button>
{% endif %} {% endif %}
</div></div>
</form> </form>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
@ -416,6 +504,18 @@
<input type="text" class="form-control" name="name" placeholder="{% trans "Name" %}" maxlength="64" required pattern="[a-zA-Z0-9\.\-_]+"> <input type="text" class="form-control" name="name" placeholder="{% trans "Name" %}" maxlength="64" required pattern="[a-zA-Z0-9\.\-_]+">
</div> </div>
</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"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "VCPU" %}</label> <label class="col-sm-3 control-label">{% trans "VCPU" %}</label>
<div class="col-sm-7"> <div class="col-sm-7">
@ -423,11 +523,25 @@
</div> </div>
</div> </div>
<div class="form-group"> <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"> <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> </div>
<label class="col-sm-1 control-label">{% trans "CPU" %}</label>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "RAM" %}</label> <label class="col-sm-3 control-label">{% trans "RAM" %}</label>
@ -437,7 +551,7 @@
<label class="col-sm-1 control-label">{% trans "MB" %}</label> <label class="col-sm-1 control-label">{% trans "MB" %}</label>
</div> </div>
<div class="form-group"> <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=""/> <input id="images" name="images" type="hidden" value=""/>
<div class="col-sm-3"> <div class="col-sm-3">
<select class="form-control" onchange="get_template_vols({{ compute_id }}, value);"> <select class="form-control" onchange="get_template_vols({{ compute_id }}, value);">
@ -510,6 +624,18 @@
</select> </select>
</div> </div>
</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"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Video" %}</label> <label class="col-sm-3 control-label">{% trans "Video" %}</label>
<div class="col-sm-7"> <div class="col-sm-7">
@ -546,37 +672,27 @@
<input type="checkbox" name="qemu_ga" value="true" checked> <input type="checkbox" name="qemu_ga" value="true" checked>
</div> </div>
</div> </div>
{% if virtio_support %}
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "VirtIO" %}</label> <label class="col-sm-3 control-label">{% trans "VirtIO" %}</label>
<div class="col-sm-7"> <div class="col-sm-6">
<input type="checkbox" name="virtio" value="true" checked> <input type="checkbox" name="virtio" value="{{ virtio_support }}" checked>
</div> </div>
</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 %} {% endif %}
</form> <div class="form-group">
</div> <div class="col-sm-7 col-sm-offset-3">
<div class="clearfix"></div> {% if storages %}
</div> <button type="submit" class="btn btn-block btn-primary" name="create" value="1" formnovalidate onclick="showPleaseWaitDialog()">
{% trans "Create" %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="addFromXML"> </button>
<div class="well"> {% else %}
<form class="form-horizontal" method="post" role="form">{% csrf_token %} <button class="btn btn-primary disabled">
<div class="col-sm-12" id="xmlheight"> {% trans "Create" %}
<input type="hidden" name="dom_xml"/> </button>
<textarea id="editor"></textarea> {% endif %}
</div>
</div> </div>
<button type="submit" class="btn btn-primary" name="create_xml" onclick="showPleaseWaitDialog()">
{% trans "Create" %}
</button>
</form> </form>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
@ -589,7 +705,7 @@
<script src="{% static "js/bootstrap-multiselect.js" %}"></script> <script src="{% static "js/bootstrap-multiselect.js" %}"></script>
<script> <script>
function toggleValue(string, updated_value, checked) { function toggleValue(string, updated_value, checked) {
var result = ''; let result = '';
if (checked) { if (checked) {
result = string; result = string;
if (result != '') result += ','; if (result != '') result += ',';
@ -620,15 +736,15 @@
return ''; return '';
}, },
onChange: function (element, checked) { 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); $('#images').val(input_value);
var selected_list_html = ''; let selected_list_html = '';
var counter = 0; let counter = 0;
if (input_value != '') { if (input_value != '') {
$('#disk_list_div').show(); $('#disk_list_div').show();
$.each(input_value.split(','), function (index, value) { $.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);">' + '<select name="device' + counter + '" class="image-format" onchange="get_disk_bus_choices({{ compute_id }},' + counter + ', value);">' +
'{% for dev in disk_devices %}' + '{% for dev in disk_devices %}' +
'<option value=' + '"{{ dev }}">' + '{% trans dev %}</option>' + '<option value=' + '"{{ dev }}">' + '{% trans dev %}</option>' +
@ -668,13 +784,13 @@
return '100%'; return '100%';
}, },
onChange: function (element, checked) { 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); $('#networks').val(input_value);
var selected_list_html = ''; let selected_list_html = '';
var counter = 0; let counter = 0;
if (input_value != '') { if (input_value != '') {
$.each(input_value.split(','), function (index, value) { $.each(input_value.split(','), function (index, value) {
var li = '<li>eth' + counter + let li = '<li>eth' + counter +
' -> ' + value + ' ' + ' -> ' + value + ' ' +
'<a class="btn-link pull-right" onclick="javascript:$(\'#network-control\').multiselect(\'deselect\', \'' + value + '\', true)"><i class="fa fa-remove"></i></a></a></li>'; '<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; selected_list_html += li;
@ -686,6 +802,8 @@
}); });
}); });
$("id[vcpu_mode]").multiselect();
function get_cust_vols(compute_id, pool) { function get_cust_vols(compute_id, pool) {
get_vol_url = "/computes/" + compute_id + "/storage/" + pool + "/volumes"; get_vol_url = "/computes/" + compute_id + "/storage/" + pool + "/volumes";
$.getJSON(get_vol_url, function (data) { $.getJSON(get_vol_url, function (data) {
@ -708,11 +826,12 @@
$("#template").removeAttr("disabled"); $("#template").removeAttr("disabled");
$("#storage").val(pool).change(); $("#storage").val(pool).change();
$("#storage").removeAttr("disabled"); $("#storage").removeAttr("disabled");
} }
function get_disk_bus_choices(compute_id, dev_idx, disk_type){ 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) { $.getJSON(get_diskBus_url, function (data) {
$("#bus" + dev_idx).find('option').remove(); $("#bus" + dev_idx).find('option').remove();
$.each(data['bus'], function(i, item) { $.each(data['bus'], function(i, item) {
@ -721,15 +840,12 @@
}); });
} }
</script> </script>
{% if request.user.is_superuser %}
<script src="{% static "js/ace.js" %}"></script> <script>
<script> function goto_compute() {
var editor = ace.edit("editor"); let compute = {{ compute.id }}
editor.getSession().setMode("ace/mode/xml"); window.location.href = "{% url 'create_instance_select_type' 1 %}".replace(1, compute);
}
var input = $('input[name="dom_xml"]'); </script>
editor.getSession().on("change",function () { {% endif %}
input.val(editor.getSession().getValue());
})
</script>
{% endblock %} {% endblock %}

View file

@ -13,25 +13,76 @@ from libvirt import libvirtError
from webvirtcloud.settings import QEMU_CONSOLE_LISTEN_ADDRESSES from webvirtcloud.settings import QEMU_CONSOLE_LISTEN_ADDRESSES
from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_CACHE from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_CACHE
from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_BUS 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 django.contrib import messages
from logs.views import addlogmsg from logs.views import addlogmsg
@login_required @login_required
def create_instance(request, compute_id): def create_instance_select_type(request, compute_id):
"""
:param request:
:param compute_id:
:return:
"""
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
conn = None conn = None
error_messages = [] error_messages = list()
storages = [] storages = list()
networks = [] 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 meta_prealloc = False
compute = get_object_or_404(Compute, pk=compute_id) compute = get_object_or_404(Compute, pk=compute_id)
flavors = Flavor.objects.filter().order_by('id') flavors = Flavor.objects.filter().order_by('id')
@ -42,18 +93,35 @@ def create_instance(request, compute_id):
compute.password, compute.password,
compute.type) compute.type)
default_cpu_mode = INSTANCE_CPU_DEFAULT_MODE
instances = conn.get_instances() instances = conn.get_instances()
videos = conn.get_video_models() videos = conn.get_video_models(arch, machine)
cache_modes = sorted(conn.get_cache_modes().items()) cache_modes = sorted(conn.get_cache_modes().items())
default_cache = INSTANCE_VOLUME_DEFAULT_CACHE default_cache = INSTANCE_VOLUME_DEFAULT_CACHE
listener_addr = QEMU_CONSOLE_LISTEN_ADDRESSES listener_addr = QEMU_CONSOLE_LISTEN_ADDRESSES
mac_auto = util.randomMAC() mac_auto = util.randomMAC()
disk_devices = conn.get_disk_device_types() disk_devices = conn.get_disk_device_types(arch, machine)
disk_buses = conn.get_disk_bus_types() disk_buses = conn.get_disk_bus_types(arch, machine)
default_bus = INSTANCE_VOLUME_DEFAULT_BUS default_bus = INSTANCE_VOLUME_DEFAULT_BUS
networks = sorted(conn.get_networks()) networks = sorted(conn.get_networks())
nwfilters = conn.get_nwfilters() nwfilters = conn.get_nwfilters()
storages = sorted(conn.get_storages(only_actives=True)) 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)
virtio_support = conn.is_supports_virtio(arch, machine)
hv_supports_uefi = conn.supports_uefi_xml(dom_caps["loader_enums"])
# Add BIOS
label = conn.label_for_firmware_path(arch, None)
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: except libvirtError as lib_err:
error_messages.append(lib_err) error_messages.append(lib_err)
@ -81,24 +149,10 @@ def create_instance(request, compute_id):
delete_flavor = Flavor.objects.get(id=flavor_id) delete_flavor = Flavor.objects.get(id=flavor_id)
delete_flavor.delete() delete_flavor.delete()
return HttpResponseRedirect(request.get_full_path()) 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: if 'create' in request.POST:
volume_list = [] firmware = dict()
volume_list = list()
is_disk_created = False
clone_path = "" clone_path = ""
form = NewVMForm(request.POST) form = NewVMForm(request.POST)
if form.is_valid(): if form.is_valid():
@ -124,8 +178,10 @@ def create_instance(request, compute_id):
volume['path'] = path volume['path'] = path
volume['type'] = conn.get_volume_type(path) volume['type'] = conn.get_volume_type(path)
volume['device'] = 'disk' volume['device'] = 'disk'
volume['bus'] = 'virtio' if data['virtio']:
volume['bus'] = INSTANCE_VOLUME_DEFAULT_BUS
volume_list.append(volume) volume_list.append(volume)
is_disk_created = True
except libvirtError as lib_err: except libvirtError as lib_err:
error_messages.append(lib_err.message) error_messages.append(lib_err.message)
elif data['template']: elif data['template']:
@ -140,8 +196,10 @@ def create_instance(request, compute_id):
volume['path'] = clone_path volume['path'] = clone_path
volume['type'] = conn.get_volume_type(clone_path) volume['type'] = conn.get_volume_type(clone_path)
volume['device'] = 'disk' volume['device'] = 'disk'
volume['bus'] = 'virtio' if data['virtio']:
volume['bus'] = INSTANCE_VOLUME_DEFAULT_BUS
volume_list.append(volume) volume_list.append(volume)
is_disk_created = True
else: else:
if not data['images']: if not data['images']:
error_msg = _("First you need to create or select an image") error_msg = _("First you need to create or select an image")
@ -161,13 +219,30 @@ def create_instance(request, compute_id):
if data['cache_mode'] not in conn.get_cache_modes(): if data['cache_mode'] not in conn.get_cache_modes():
error_msg = _("Invalid cache mode") error_msg = _("Invalid cache mode")
error_messages.append(error_msg) error_messages.append(error_msg)
if 'UEFI' in data["firmware"]:
firmware["loader"] = data["firmware"].split(":")[1].strip()
firmware["secure"] = 'no'
firmware["readonly"] = 'yes'
firmware["type"] = 'pflash'
if 'secboot' in firmware["loader"] and machine != 'q35':
messages.warning(request, "Changing machine type from '%s' to 'q35' "
"which is required for UEFI secure boot." % machine)
machine = 'q35'
firmware["secure"] = 'yes'
if not error_messages: if not error_messages:
uuid = util.randomUUID() uuid = util.randomUUID()
try: try:
conn.create_instance(data['name'], data['memory'], data['vcpu'], data['host_model'], conn.create_instance(name=data['name'], memory=data['memory'], vcpu=data['vcpu'],
uuid, volume_list, data['cache_mode'], data['networks'], data['virtio'], vcpu_mode=data['vcpu_mode'], uuid=uuid, arch=arch, machine=machine,
data["listener_addr"], data["nwfilter"], data["video"], data["console_pass"], firmware=firmware,
data['mac'], data['qemu_ga']) 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 = Instance(compute_id=compute_id, name=data['name'], uuid=uuid)
create_instance.save() create_instance.save()
msg = _("Instance is created.") msg = _("Instance is created.")
@ -176,8 +251,9 @@ def create_instance(request, compute_id):
return HttpResponseRedirect(reverse('instance', args=[compute_id, data['name']])) return HttpResponseRedirect(reverse('instance', args=[compute_id, data['name']]))
except libvirtError as lib_err: except libvirtError as lib_err:
if data['hdd_size'] or len(volume_list) > 0: if data['hdd_size'] or len(volume_list) > 0:
for vol in volume_list: if is_disk_created:
conn.delete_volume(vol['path']) for vol in volume_list:
conn.delete_volume(vol['path'])
error_messages.append(lib_err) error_messages.append(lib_err)
conn.close() conn.close()
return render(request, 'create_instance.html', locals()) return render(request, 'create_instance_w2.html', locals())

View file

@ -61,7 +61,7 @@
<label class="col-sm-3 control-label">{% trans "Bus" %}</label> <label class="col-sm-3 control-label">{% trans "Bus" %}</label>
<div class="col-sm-4"> <div class="col-sm-4">
<select name="bus" class="form-control image-format"> <select name="bus" class="form-control image-format">
{% for bus in busses %} {% for bus in bus_host %}
<option value="{{ bus }}" {% if bus == default_bus %}selected{% endif %}>{% trans bus %}</option> <option value="{{ bus }}" {% if bus == default_bus %}selected{% endif %}>{% trans bus %}</option>
{% endfor %} {% endfor %}
</select> </select>

View file

@ -176,8 +176,9 @@
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
<script> <script>
function goto_compute() { function goto_compute() {
var compute = $("#compute_select").val(); let compute = $("#compute_select").val();
window.location.href = "{% url 'create_instance' 1 %}".replace(1, compute); {#window.location.href = "{% url 'create_instance' 1 %}".replace(1, compute);#}
window.location.href = "{% url 'create_instance_select_type' 1 %}".replace(1, compute);
} }
</script> </script>
{% endif %} {% endif %}

View file

@ -1035,7 +1035,7 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Live migration" %}</label> <label class="col-sm-3 control-label">{% trans "Live migration" %}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="checkbox" name="live_migrate" value="true" id="vm_live_migrate" {% ifequal status 1 %}checked{% endifequal %}> <input type="checkbox" name="live_migrate" value="true" id="vm_live_migrate" {% ifnotequal status 5 %}checked{% else %}disabled{% endifnotequal %}>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -1053,7 +1053,25 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Offline migration" %}</label> <label class="col-sm-3 control-label">{% trans "Offline migration" %}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="checkbox" name="offline_migrate" value="true" id="offline_migrate" {% ifequal status 5 %}checked{% endifequal %}> <input type="checkbox" name="offline_migrate" value="true" id="offline_migrate" {% ifequal status 5 %}checked{% else %}disabled{% endifequal %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Post copy" %}</label>
<div class="col-sm-6">
<input type="checkbox" name="postcopy" value="true" id="postcopy" {% ifnotequal status 1 %}disabled{% endifnotequal %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" title="{% trans 'Forces CPU convergence during live migration' %}">{% trans "Auto converge" %}</label>
<div class="col-sm-6">
<input type="checkbox" name="autoconverge" value="true" id="autoconverge" {% ifnotequal status 1 %}disabled{% endifnotequal %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" title="{% trans 'Compress instance memory for fast migration' %}">{% trans "Compressed" %}</label>
<div class="col-sm-6">
<input type="checkbox" name="compress" value="true" id="compress" {% ifnotequal status 1 %}disabled{% endifnotequal %}>
</div> </div>
</div> </div>
{% if computes_count != 1 %} {% if computes_count != 1 %}
@ -1524,9 +1542,17 @@
<div class="checkbox" style="margin-left: 8px;"> <div class="checkbox" style="margin-left: 8px;">
<label> <label>
<input type="checkbox" name="delete_disk" value="true" checked> <input type="checkbox" name="delete_disk" value="true" checked>
<strong>{% trans "Remove Instance's data" %}</strong> <strong>{% trans "Remove Instance's disks" %}</strong>
</label> </label>
</div> </div>
{% if nvram %}
<div class="checkbox" style="margin-left: 8px;">
<label>
<input type="checkbox" name="delete_nvram" value="true" checked>
<strong>{% trans "Remove Instance's NVRAM" %}</strong>
</label>
</div>
{% endif %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="delete">{% trans "Destroy" %}</button> <button type="submit" class="btn btn-lg btn-success pull-right" name="delete">{% trans "Destroy" %}</button>
</form> </form>
{% endifequal %} {% endifequal %}

View file

@ -10,7 +10,7 @@
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
{% if request.user.is_superuser %} {% 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> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
</a> </a>
{% endif %} {% endif %}
@ -161,7 +161,7 @@
<script src="{% static "js/sortable.min.js" %}"></script> <script src="{% static "js/sortable.min.js" %}"></script>
<script> <script>
function open_console(uuid) { 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>
<script> <script>
@ -189,12 +189,4 @@
window.location = "/instances/" + compute + "/" + instance + "/#clone"; window.location = "/instances/" + compute + "/" + instance + "/#clone";
} }
</script> </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 %} {% endblock %}

View file

@ -22,7 +22,7 @@ from vrtManager.connection import connection_manager
from vrtManager.create import wvmCreate from vrtManager.create import wvmCreate
from vrtManager.storage import wvmStorage from vrtManager.storage import wvmStorage
from vrtManager.util import randomPasswd 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 logs.views import addlogmsg
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
@ -230,20 +230,25 @@ def instance(request, compute_id, vname):
else: else:
return network_source_pack[0], 'net' return network_source_pack[0], 'net'
def migrate_instance(new_compute, instance, live=False, unsafe=False, xml_del=False, offline=False): def migrate_instance(new_compute, instance, live=False, unsafe=False, xml_del=False, offline=False, autoconverge=False, compress=False, postcopy=False):
status = connection_manager.host_is_up(new_compute.type, new_compute.hostname) status = connection_manager.host_is_up(new_compute.type, new_compute.hostname)
if not status: if not status:
return return
if new_compute == instance.compute: if new_compute == instance.compute:
return return
conn_migrate = wvmInstances(new_compute.hostname, try:
new_compute.login, conn_migrate = wvmInstances(new_compute.hostname,
new_compute.password, new_compute.login,
new_compute.type) new_compute.password,
conn_migrate.moveto(conn, instance.name, live, unsafe, xml_del, offline) new_compute.type)
conn_migrate.moveto(conn, instance.name, live, unsafe, xml_del, offline, autoconverge, compress, postcopy)
finally:
conn_migrate.close()
instance.compute = new_compute instance.compute = new_compute
instance.save() instance.save()
conn_migrate.close()
conn_new = wvmInstance(new_compute.hostname, conn_new = wvmInstance(new_compute.hostname,
new_compute.login, new_compute.login,
new_compute.password, new_compute.password,
@ -265,6 +270,10 @@ def instance(request, compute_id, vname):
autostart = conn.get_autostart() autostart = conn.get_autostart()
bootmenu = conn.get_bootmenu() bootmenu = conn.get_bootmenu()
boot_order = conn.get_bootorder() boot_order = conn.get_bootorder()
arch = conn.get_arch()
machine = conn.get_machine_type()
firmware = conn.get_loader()
nvram = conn.get_nvram()
vcpu = conn.get_vcpu() vcpu = conn.get_vcpu()
cur_vcpu = conn.get_cur_vcpu() cur_vcpu = conn.get_cur_vcpu()
vcpus = conn.get_vcpus() vcpus = conn.get_vcpus()
@ -330,8 +339,8 @@ def instance(request, compute_id, vname):
# Host resources # Host resources
vcpu_host = len(vcpu_range) vcpu_host = len(vcpu_range)
memory_host = conn.get_max_memory() memory_host = conn.get_max_memory()
bus_host = conn.get_disk_bus_types() bus_host = conn.get_disk_bus_types(arch, machine)
videos_host = conn.get_video_models() videos_host = conn.get_video_models(arch, machine)
networks_host = sorted(conn.get_networks()) networks_host = sorted(conn.get_networks())
interfaces_host = sorted(conn.get_ifaces()) interfaces_host = sorted(conn.get_ifaces())
nwfilters_host = conn.get_nwfilters() nwfilters_host = conn.get_nwfilters()
@ -374,7 +383,11 @@ def instance(request, compute_id, vname):
for snap in snapshots: for snap in snapshots:
conn.snapshot_delete(snap['name']) conn.snapshot_delete(snap['name'])
conn.delete_all_disks() 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 = Instance.objects.get(compute_id=compute_id, name=vname)
instance_name = instance.name instance_name = instance.name
@ -799,16 +812,24 @@ def instance(request, compute_id, vname):
return HttpResponseRedirect(request.get_full_path() + '#options') return HttpResponseRedirect(request.get_full_path() + '#options')
if 'migrate' in request.POST: if 'migrate' in request.POST:
compute_id = request.POST.get('compute_id', '') compute_id = request.POST.get('compute_id', '')
live = request.POST.get('live_migrate', False) live = request.POST.get('live_migrate', False)
unsafe = request.POST.get('unsafe_migrate', False) unsafe = request.POST.get('unsafe_migrate', False)
xml_del = request.POST.get('xml_delete', False) xml_del = request.POST.get('xml_delete', False)
offline = request.POST.get('offline_migrate', False) offline = request.POST.get('offline_migrate', False)
autoconverge = request.POST.get('autoconverge', False)
compress = request.POST.get('compress', False)
postcopy = request.POST.get('postcopy', False)
new_compute = Compute.objects.get(id=compute_id) new_compute = Compute.objects.get(id=compute_id)
migrate_instance(new_compute, instance, live, unsafe, xml_del, offline) try:
migrate_instance(new_compute, instance, live, unsafe, xml_del, offline)
return HttpResponseRedirect(reverse('instance', args=[new_compute.id, vname])) return HttpResponseRedirect(reverse('instance', args=[new_compute.id, vname]))
except libvirtError as err:
messages.error(request, err)
addlogmsg(request.user.username, instance.name, err)
return HttpResponseRedirect(request.get_full_path() + '#migrate')
if 'change_network' in request.POST: if 'change_network' in request.POST:
msg = _("Change network") msg = _("Change network")
@ -942,16 +963,17 @@ def instance(request, compute_id, vname):
error_messages.append(msg) error_messages.append(msg)
else: else:
new_instance = Instance(compute_id=compute_id, name=clone_data['name']) new_instance = Instance(compute_id=compute_id, name=clone_data['name'])
new_instance.save() #new_instance.save()
try: try:
new_uuid = conn.clone_instance(clone_data) new_uuid = conn.clone_instance(clone_data)
new_instance.uuid = new_uuid new_instance.uuid = new_uuid
new_instance.save() new_instance.save()
except Exception as e: except Exception as e:
new_instance.delete() #new_instance.delete()
raise e raise e
userinstance = UserInstance(instance_id=new_instance.id, user_id=request.user.id, is_delete=True)
userinstance.save() user_instance = UserInstance(instance_id=new_instance.id, user_id=request.user.id, is_delete=True)
user_instance.save()
msg = _("Clone of '%s'" % instance.name) msg = _("Clone of '%s'" % instance.name)
addlogmsg(request.user.username, new_instance.name, msg) addlogmsg(request.user.username, new_instance.name, msg)
@ -1226,7 +1248,8 @@ def inst_graph(request, compute_id, vname):
def _get_dhcp_mac_address(vname): def _get_dhcp_mac_address(vname):
dhcp_file = '/srv/webvirtcloud/dhcpd.conf'
dhcp_file = settings.BASE_DIR + '/dhcpd.conf'
mac = '' mac = ''
if os.path.isfile(dhcp_file): if os.path.isfile(dhcp_file):
with open(dhcp_file, 'r') as f: with open(dhcp_file, 'r') as f:

View file

@ -226,7 +226,7 @@ def get_volumes(request, compute_id, pool):
compute.type, compute.type,
pool) pool)
conn.refresh() conn.refresh()
data['vols'] = sorted(conn.get_volumes())
except libvirtError: except libvirtError:
pass pass
data['vols'] = sorted(conn.get_volumes())
return HttpResponse(json.dumps(data)) return HttpResponse(json.dumps(data))

View file

@ -1,6 +1,7 @@
import libvirt import libvirt
import threading import threading
import socket import socket
import re
from vrtManager import util from vrtManager import util
from vrtManager.rwlock import ReadWriteLock from vrtManager.rwlock import ReadWriteLock
from django.conf import settings from django.conf import settings
@ -272,7 +273,7 @@ class wvmConnectionManager(object):
if connection is None: if connection is None:
self._connections_lock.acquireWrite() self._connections_lock.acquireWrite()
try: try:
# we have to search for the connection again after aquireing the write lock # we have to search for the connection again after acquiring the write lock
# as the thread previously holding the write lock may have already added our connection # as the thread previously holding the write lock may have already added our connection
connection = self._search_connection(host, login, passwd, conn) connection = self._search_connection(host, login, passwd, conn)
if connection is None: if connection is None:
@ -340,19 +341,95 @@ class wvmConnect(object):
# get connection from connection manager # get connection from connection manager
self.wvm = connection_manager.get_connection(host, login, passwd, conn) self.wvm = connection_manager.get_connection(host, login, passwd, conn)
def is_qemu(self):
return self.wvm.getURI().startswith("qemu")
def get_cap_xml(self): def get_cap_xml(self):
"""Return xml capabilities""" """Return xml capabilities"""
return self.wvm.getCapabilities() return self.wvm.getCapabilities()
def get_dom_cap_xml(self): def get_dom_cap_xml(self, arch, machine):
""" Return domcapabilities xml""" """ Return domain capabilities xml"""
arch = self.wvm.getInfo()[0]
machine = self.get_machines(arch)
emulatorbin = self.get_emulator(arch) 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) 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): def get_version(self):
ver = self.wvm.getVersion() ver = self.wvm.getVersion()
major = ver / 1000000 major = ver / 1000000
@ -417,7 +494,7 @@ class wvmConnect(object):
'unsafe': 'Unsafe', # since libvirt 0.9.7 'unsafe': 'Unsafe', # since libvirt 0.9.7
} }
def hypervisor_type(self): def get_hypervisors_domain_types(self):
"""Return hypervisor type""" """Return hypervisor type"""
def hypervisors(ctx): def hypervisors(ctx):
result = {} result = {}
@ -428,10 +505,34 @@ class wvmConnect(object):
return result return result
return util.get_xml_path(self.get_cap_xml(), func=hypervisors) 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): def get_emulator(self, arch):
"""Return emulator """ """Return emulator """
return util.get_xml_path(self.get_cap_xml(), "/capabilities/guest/arch[@name='{}']/emulator".format(arch)) 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 get_emulators(self):
def emulators(ctx): def emulators(ctx):
result = {} result = {}
@ -442,35 +543,76 @@ class wvmConnect(object):
return result return result
return util.get_xml_path(self.get_cap_xml(), func=emulators) return util.get_xml_path(self.get_cap_xml(), func=emulators)
def get_machines(self, arch): def get_os_loaders(self, arch='x86_64', machine='pc'):
""" Return machine type of emulation""" """Get available os loaders list"""
return util.get_xml_path(self.get_cap_xml(), "/capabilities/guest/arch[@name='{}']/machine".format(arch)) 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""" """Get available disk bus types list"""
def get_bus_list(ctx): def get_bus_list(ctx):
result = [] return [v.text for v in ctx.xpath("/domainCapabilities/devices/disk/enum[@name='bus']/value")]
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 [ 'ide', 'scsi', 'usb', 'virtio' ] # 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""" """Get available disk device type list"""
def get_device_list(ctx): def get_device_list(ctx):
result = [] return [v.text for v in ctx.xpath("/domainCapabilities/devices/disk/enum[@name='diskDevice']/value")]
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 [ 'disk', 'cdrom', 'floppy', 'lun' ] # 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): def get_image_formats(self):
"""Get available image formats""" """Get available image formats"""
@ -480,7 +622,7 @@ class wvmConnect(object):
"""Get available image filename extensions""" """Get available image filename extensions"""
return ['img', 'qcow', 'qcow2'] return ['img', 'qcow', 'qcow2']
def get_video_models(self): def get_video_models(self, arch, machine):
""" Get available graphics video types """ """ Get available graphics video types """
def get_video_list(ctx): def get_video_list(ctx):
result = [] result = []
@ -488,7 +630,7 @@ class wvmConnect(object):
if video_enum.xpath("@name")[0] == "modelType": if video_enum.xpath("@name")[0] == "modelType":
for values in video_enum: result.append(values.text) for values in video_enum: result.append(values.text)
return result 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): def get_iface(self, name):
return self.wvm.interfaceLookupByName(name) return self.wvm.interfaceLookupByName(name)
@ -624,3 +766,63 @@ class wvmConnect(object):
# to-do: do not close connection ;) # to-do: do not close connection ;)
# self.wvm.close() # self.wvm.close()
pass 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"))
def is_supports_virtio(self, arch, machine):
if not self.is_qemu():
return False
# These _only_ support virtio so don't check the OS
if arch in ["aarch64", "armv7l", "ppc64", "ppc64le", "s390x", "riscv64", "riscv32"] and \
machine in ["virt", "pseries"]:
return True
if arch in ["x86_64", "i686"]:
return True
return False

View file

@ -1,9 +1,11 @@
import string import string
from vrtManager import util from vrtManager import util
from vrtManager.connection import wvmConnect 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_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): 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") ceph_user = util.get_xml_path(xml, "/pool/source/auth/@username")
def get_ceph_hosts(doc): def get_ceph_hosts(doc):
hosts = [] hosts = list()
for host in doc.xpath("/pool/source/host"): for host in doc.xpath("/pool/source/host"):
name = host.prop("name") name = host.prop("name")
if name: if name:
@ -29,7 +31,7 @@ class wvmCreate(wvmConnect):
""" """
Function return all images on all storages Function return all images on all storages
""" """
images = [] images = list()
storages = self.get_storages(only_actives=True) storages = self.get_storages(only_actives=True)
for storage in storages: for storage in storages:
stg = self.get_storage(storage) stg = self.get_storage(storage)
@ -45,14 +47,14 @@ class wvmCreate(wvmConnect):
return images return images
def get_os_type(self): 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") return util.get_xml_path(self.get_cap_xml(), "/capabilities/guest/os_type")
def get_host_arch(self): def get_host_arch(self):
"""Get guest capabilities""" """Get guest capabilities"""
return util.get_xml_path(self.get_cap_xml(), "/capabilities/host/cpu/arch") 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 size = int(size) * 1073741824
stg = self.get_storage(storage) stg = self.get_storage(storage)
storage_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type") 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) vol = self.get_volume_by_path(vol_path)
return vol.storagePoolLookupByVolume() 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) vol = self.get_volume_by_path(template)
if not storage: if not storage:
stg = vol.storagePoolLookupByVolume() stg = vol.storagePoolLookupByVolume()
@ -163,16 +165,15 @@ class wvmCreate(wvmConnect):
vol = self.get_volume_by_path(path) vol = self.get_volume_by_path(path)
vol.delete() 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 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(): memory = int(memory) * 1024
hypervisor_type = 'kvm' #hypervisor_type = 'kvm' if self.is_kvm_supported() else 'qemu'
else:
hypervisor_type = 'qemu'
xml = """ xml = """
<domain type='%s'> <domain type='%s'>
@ -180,23 +181,56 @@ class wvmCreate(wvmConnect):
<description>None</description> <description>None</description>
<uuid>%s</uuid> <uuid>%s</uuid>
<memory unit='KiB'>%s</memory> <memory unit='KiB'>%s</memory>
<vcpu>%s</vcpu>""" % (hypervisor_type, name, uuid, memory, vcpu) <vcpu>%s</vcpu>""" % (dom_caps["domain"], name, uuid, memory, vcpu)
if host_model:
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 firmware:
if firmware["secure"] == 'yes':
xml += """<loader readonly='%s' type='%s' secure='%s'>%s</loader>""" % (firmware["readonly"],
firmware["type"],
firmware["secure"],
firmware["loader"])
if firmware["secure"] == 'no':
xml += """<loader readonly='%s' type='%s'>%s</loader>""" % (firmware["readonly"],
firmware["type"],
firmware["loader"])
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/>"""
if 'yes' == firmware.get("secure", 'no'):
xml += """<smm state="on"/>"""
xml += """</features>"""
if vcpu_mode == "host-model":
xml += """<cpu mode='host-model'/>""" xml += """<cpu mode='host-model'/>"""
xml += """<os> elif vcpu_mode == "host-passthrough":
<type arch='%s'>%s</type> xml += """<cpu mode='host-passthrough'/>"""
<boot dev='hd'/> elif vcpu_mode == "":
<boot dev='cdrom'/> pass
<bootmenu enable='yes'/> else:
</os>""" % (self.get_host_arch(), self.get_os_type()) xml += """<cpu mode='custom' match='exact' check='none'>
xml += """<features> <model fallback='allow'>%s</model>""" % vcpu_mode
<acpi/><apic/><pae/> xml += """</cpu>"""
</features>
xml += """
<clock offset="utc"/> <clock offset="utc"/>
<on_poweroff>destroy</on_poweroff> <on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot> <on_reboot>restart</on_reboot>
<on_crash>restart</on_crash> <on_crash>restart</on_crash>
<devices>""" """
xml += """<devices>"""
vd_disk_letters = list(string.lowercase) vd_disk_letters = list(string.lowercase)
fd_disk_letters = list(string.lowercase) fd_disk_letters = list(string.lowercase)
@ -212,11 +246,11 @@ class wvmCreate(wvmConnect):
if stg_type == 'rbd': if stg_type == 'rbd':
ceph_user, secret_uuid, ceph_hosts = get_rbd_storage_data(stg) ceph_user, secret_uuid, ceph_hosts = get_rbd_storage_data(stg)
xml += """<disk type='network' device='disk'> xml += """<disk type='network' device='disk'>
<driver name='qemu' type='%s' cache='%s'/> <driver name='qemu' type='%s' cache='%s' %s />""" % (volume['type'], cache_mode, OPTS.get("network", ''))
<auth username='%s'> xml += """ <auth username='%s'>
<secret type='ceph' uuid='%s'/> <secret type='ceph' uuid='%s'/>
</auth> </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): if isinstance(ceph_hosts, list):
for host in ceph_hosts: for host in ceph_hosts:
if host.get('port'): if host.get('port'):
@ -225,29 +259,41 @@ class wvmCreate(wvmConnect):
else: else:
xml += """ xml += """
<host name='%s'/>""" % host.get('name') <host name='%s'/>""" % host.get('name')
xml += """ xml += """</source>"""
</source>"""
else: else:
xml += """<disk type='file' device='%s'> xml += """<disk type='file' device='%s'>""" % volume['device']
<driver name='qemu' type='%s' cache='%s'/> xml += """ <driver name='qemu' type='%s' cache='%s' %s/>""" % (volume['type'], cache_mode, OPTS.get("file", ''))
<source file='%s'/>""" % (volume['device'], volume['type'], cache_mode, volume['path']) xml += """ <source file='%s'/>""" % volume['path']
if volume['bus'] == 'virtio': if volume.get('bus') == 'virtio':
xml += """<target dev='vd%s' bus='%s'/>""" % (vd_disk_letters.pop(0), volume['bus']) xml += """<target dev='vd%s' bus='%s'/>""" % (vd_disk_letters.pop(0), volume.get('bus'))
elif volume['bus'] == 'ide': elif volume.get('bus') == 'ide':
xml += """<target dev='hd%s' bus='%s'/>""" % (hd_disk_letters.pop(0), volume['bus']) xml += """<target dev='hd%s' bus='%s'/>""" % (hd_disk_letters.pop(0), volume.get('bus'))
elif volume['bus'] == 'fdc': elif volume.get('bus') == 'fdc':
xml += """<target dev='fd%s' bus='%s'/>""" % (fd_disk_letters.pop(0), volume['bus']) xml += """<target dev='fd%s' bus='%s'/>""" % (fd_disk_letters.pop(0), volume.get('bus'))
elif volume.get('bus') == 'sata' or volume.get('bus') == 'scsi':
xml += """<target dev='sd%s' bus='%s'/>""" % (sd_disk_letters.pop(0), volume.get('bus'))
else: else:
xml += """<target dev='sd%s' bus='%s'/>""" % (sd_disk_letters.pop(0), volume['bus']) xml += """<target dev='sd%s'/>""" % sd_disk_letters.pop(0)
xml += """</disk>""" xml += """</disk>"""
if add_cd: if add_cd:
xml += """ <disk type='file' device='cdrom'> xml += """<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/> <driver name='qemu' type='raw'/>
<source file=''/> <source file = '' />
<target dev='hd%s' bus='ide'/> <readonly/>"""
<readonly/> if 'ide' in dom_caps['disk_bus']:
</disk>""" % (hd_disk_letters.pop(0),) 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.get('bus') == 'scsi':
xml += """<controller type='scsi' model='%s'/>""" % INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER
for net in networks.split(','): for net in networks.split(','):
xml += """<interface type='network'>""" xml += """<interface type='network'>"""
if mac: if mac:
@ -265,12 +311,15 @@ class wvmCreate(wvmConnect):
if not console_pass == "": if not console_pass == "":
console_pass = "passwd='" + console_pass + "'" console_pass = "passwd='" + console_pass + "'"
xml += """ <input type='mouse' bus='ps2'/> if 'usb' in dom_caps['disk_bus']:
<input type='tablet' bus='usb'/> xml += """<input type='mouse' bus='{}'/>""".format('virtio' if virtio else 'usb')
<graphics type='%s' port='-1' autoport='yes' %s listen='%s'/> xml += """<input type='tablet' bus='{}'/>""".format('virtio' if virtio else 'usb')
<console type='pty'/> """ % (QEMU_CONSOLE_DEFAULT_TYPE, console_pass, listen_addr)
if qemu_ga: xml += """
<graphics type='%s' port='-1' autoport='yes' %s listen='%s'/>
<console type='pty'/> """ % (graphics, console_pass, listen_addr)
if qemu_ga and virtio:
xml += """ <channel type='unix'> xml += """ <channel type='unix'>
<target type='virtio' name='org.qemu.guest_agent.0'/> <target type='virtio' name='org.qemu.guest_agent.0'/>
</channel>""" </channel>"""
@ -278,7 +327,6 @@ class wvmCreate(wvmConnect):
xml += """ <video> xml += """ <video>
<model type='%s'/> <model type='%s'/>
</video> </video>
<memballoon model='virtio'/>
</devices> </devices>
</domain>""" % video </domain>""" % video
self._defineXML(xml) self._defineXML(xml)

View file

@ -1,8 +1,16 @@
import time import time
import os.path import os.path
try: try:
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE, VIR_MIGRATE_UNSAFE, VIR_DOMAIN_RUNNING, \ from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_DOMAIN_RUNNING, VIR_DOMAIN_AFFECT_LIVE, \
VIR_DOMAIN_AFFECT_LIVE, VIR_DOMAIN_AFFECT_CONFIG VIR_DOMAIN_AFFECT_CONFIG, VIR_DOMAIN_UNDEFINE_NVRAM, VIR_DOMAIN_UNDEFINE_KEEP_NVRAM, VIR_DOMAIN_START_PAUSED
from libvirt import VIR_MIGRATE_LIVE, \
VIR_MIGRATE_UNSAFE, \
VIR_MIGRATE_PERSIST_DEST, \
VIR_MIGRATE_UNDEFINE_SOURCE, \
VIR_MIGRATE_OFFLINE,\
VIR_MIGRATE_COMPRESSED, \
VIR_MIGRATE_AUTO_CONVERGE, \
VIR_MIGRATE_POSTCOPY
except: except:
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE
@ -12,9 +20,9 @@ from lxml import etree
from datetime import datetime from datetime import datetime
from collections import OrderedDict from collections import OrderedDict
from vrtManager.connection import wvmConnect from vrtManager.connection import wvmConnect
from vrtManager.storage import wvmStorage from vrtManager.storage import wvmStorage, wvmStorages
from webvirtcloud.settings import QEMU_CONSOLE_TYPES from webvirtcloud.settings import QEMU_CONSOLE_TYPES
from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_OWNER as owner from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_OWNER as OWNER
class wvmInstances(wvmConnect): class wvmInstances(wvmConnect):
@ -72,19 +80,32 @@ class wvmInstances(wvmConnect):
dom = self.get_instance(name) dom = self.get_instance(name)
dom.resume() dom.resume()
def moveto(self, conn, name, live, unsafe, undefine, offline): def moveto(self, conn, name, live, unsafe, undefine, offline, autoconverge=False, compress=False, postcopy=False):
flags = 0 flags = VIR_MIGRATE_PERSIST_DEST
if live and conn.get_status() == 1: if live and conn.get_status() != 5:
flags |= VIR_MIGRATE_LIVE flags |= VIR_MIGRATE_LIVE
if unsafe and conn.get_status() == 1: if unsafe and conn.get_status() == 1:
flags |= VIR_MIGRATE_UNSAFE flags |= VIR_MIGRATE_UNSAFE
dom = conn.get_instance(name) if offline and conn.get_status() == 5:
xml = dom.XMLDesc(VIR_DOMAIN_XML_SECURE) flags |= VIR_MIGRATE_OFFLINE
if not offline: if not offline and autoconverge:
dom.migrate(self.wvm, flags, None, None, 0) flags |= VIR_MIGRATE_AUTO_CONVERGE
if not offline and compress and conn.get_status() == 1:
flags |= VIR_MIGRATE_COMPRESSED
if not offline and postcopy and conn.get_status() == 1:
flags |= VIR_MIGRATE_POSTCOPY
if undefine: if undefine:
dom.undefine() flags |= VIR_MIGRATE_UNDEFINE_SOURCE
self.wvm.defineXML(xml)
dom = conn.get_instance(name)
dom_arch = conn.get_arch()
dom_emulator = conn.get_dom_emulator()
if dom_emulator != self.get_emulator(dom_arch):
raise libvirtError('Destination host emulator is different. Cannot be migrated')
dom.migrate(self.wvm, flags, None, None, 0)
def graphics_type(self, name): def graphics_type(self, name):
inst = self.get_instance(name) inst = self.get_instance(name)
@ -150,8 +171,8 @@ class wvmInstance(wvmConnect):
def resume(self): def resume(self):
self.instance.resume() self.instance.resume()
def delete(self): def delete(self, flags=0):
self.instance.undefine() self.instance.undefineFlags(flags)
def _XMLDesc(self, flag): def _XMLDesc(self, flag):
return self.instance.XMLDesc(flag) return self.instance.XMLDesc(flag)
@ -186,6 +207,25 @@ class wvmInstance(wvmConnect):
if cur_vcpu: if cur_vcpu:
return int(cur_vcpu) return int(cur_vcpu)
def get_arch(self):
return util.get_xml_path(self._XMLDesc(0), "/domain/os/type/@arch")
def get_machine_type(self):
return util.get_xml_path(self._XMLDesc(0), "/domain/os/type/@machine")
def get_dom_emulator(self):
return util.get_xml_path(self._XMLDesc(0), "/domain/devices/emulator")
def get_nvram(self):
return util.get_xml_path(self._XMLDesc(0), "/domain/os/nvram")
def get_loader(self):
xml = self._XMLDesc(0)
loader = util.get_xml_path(xml, "/domain/os/loader")
type = util.get_xml_path(xml, "/domain/os/loader/@type")
readonly = util.get_xml_path(xml, "/domain/os/loader/@readonly")
return {"loader": loader, "type": type, "readonly": readonly}
def get_vcpus(self): def get_vcpus(self):
vcpus = OrderedDict() vcpus = OrderedDict()
tree = etree.fromstring(self._XMLDesc(0)) tree = etree.fromstring(self._XMLDesc(0))
@ -942,12 +982,10 @@ class wvmInstance(wvmConnect):
return self.instance.hasManagedSaveImage(0) return self.instance.hasManagedSaveImage(0)
def get_wvmStorage(self, pool): def get_wvmStorage(self, pool):
storage = wvmStorage(self.host, return wvmStorage(self.host, self.login, self.passwd, self.conn, pool)
self.login,
self.passwd, def get_wvmStorages(self):
self.conn, return wvmStorages(self.host, self.login, self.passwd, self.conn)
pool)
return storage
def fix_mac(self, mac): def fix_mac(self, mac):
if ":" in mac: if ":" in mac:
@ -961,12 +999,32 @@ class wvmInstance(wvmConnect):
clone_dev_path = [] clone_dev_path = []
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE) xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
tree = ElementTree.fromstring(xml) tree = etree.fromstring(xml)
name = tree.find('name') name = tree.find('name')
name.text = clone_data['name'] name.text = clone_data['name']
uuid = tree.find('uuid') uuid = tree.find('uuid')
tree.remove(uuid) tree.remove(uuid)
src_nvram_path = self.get_nvram()
if src_nvram_path:
# Change XML for nvram
nvram = tree.find('os/nvram')
nvram.getparent().remove(nvram)
# NVRAM CLONE: create pool if nvram is not in a pool. then clone it
src_nvram_name = os.path.basename(src_nvram_path)
nvram_dir = os.path.dirname(src_nvram_path)
nvram_pool_name = os.path.basename(nvram_dir)
try:
self.get_volume_by_path(src_nvram_path)
except libvirtError:
stg_conn = self.get_wvmStorages()
stg_conn.create_storage('dir', nvram_pool_name, None, nvram_dir)
new_nvram_name = "%s_VARS" % clone_data['name']
nvram_stg = self.get_wvmStorage(nvram_pool_name)
nvram_stg.clone_volume(src_nvram_name, new_nvram_name, file_suffix='fd')
for num, net in enumerate(tree.findall('devices/interface')): for num, net in enumerate(tree.findall('devices/interface')):
elm = net.find('mac') elm = net.find('mac')
mac_address = self.fix_mac(clone_data['clone-net-mac-' + str(num)]) mac_address = self.fix_mac(clone_data['clone-net-mac-' + str(num)])
@ -1015,7 +1073,7 @@ class wvmInstance(wvmConnect):
<lazy_refcounts/> <lazy_refcounts/>
</features> </features>
</target> </target>
</volume>""" % (target_file, vol_format, owner['uid'], owner['guid']) </volume>""" % (target_file, vol_format, OWNER['uid'], OWNER['guid'])
stg = vol.storagePoolLookupByVolume() stg = vol.storagePoolLookupByVolume()
stg.createXMLFrom(vol_clone_xml, vol, meta_prealloc) stg.createXMLFrom(vol_clone_xml, vol, meta_prealloc)

View file

@ -1,6 +1,6 @@
from vrtManager import util from vrtManager import util
from vrtManager.connection import wvmConnect from vrtManager.connection import wvmConnect
from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_OWNER as owner from webvirtcloud.settings import INSTANCE_VOLUME_DEFAULT_OWNER as OWNER
class wvmStorages(wvmConnect): class wvmStorages(wvmConnect):
@ -49,6 +49,8 @@ class wvmStorages(wvmConnect):
stg.create(0) stg.create(0)
stg.setAutostart(1) stg.setAutostart(1)
return stg
def create_storage_ceph(self, stg_type, name, ceph_pool, ceph_host, ceph_user, secret): def create_storage_ceph(self, stg_type, name, ceph_pool, ceph_host, ceph_user, secret):
xml = """ xml = """
<pool type='%s'> <pool type='%s'>
@ -84,6 +86,8 @@ class wvmStorages(wvmConnect):
stg.create(0) stg.create(0)
stg.setAutostart(1) stg.setAutostart(1)
return stg
class wvmStorage(wvmConnect): class wvmStorage(wvmConnect):
def __init__(self, host, login, passwd, conn, pool): def __init__(self, host, login, passwd, conn, pool):
@ -206,7 +210,7 @@ class wvmStorage(wvmConnect):
) )
return vol_list return vol_list
def create_volume(self, name, size, vol_fmt='qcow2', metadata=False, owner=owner): def create_volume(self, name, size, vol_fmt='qcow2', metadata=False, owner=OWNER):
size = int(size) * 1073741824 size = int(size) * 1073741824
storage_type = self.get_type() storage_type = self.get_type()
alloc = size alloc = size
@ -243,17 +247,18 @@ class wvmStorage(wvmConnect):
self._createXML(xml, metadata) self._createXML(xml, metadata)
return name return name
def clone_volume(self, name, target_file, vol_fmt=None, metadata=False, owner=owner): def clone_volume(self, name, target_file, vol_fmt=None, metadata=False, mode='0644', file_suffix='img', owner=OWNER):
vol = self.get_volume(name) vol = self.get_volume(name)
if not vol_fmt: if not vol_fmt:
vol_fmt = self.get_volume_type(name) vol_fmt = self.get_volume_type(name)
storage_type = self.get_type() storage_type = self.get_type()
if storage_type == 'dir': if storage_type == 'dir':
if vol_fmt in ('qcow', 'qcow2'): if vol_fmt in ['qcow', 'qcow2']:
target_file += '.' + vol_fmt target_file += '.' + vol_fmt
else: else:
target_file += '.img' suffix = '.' + file_suffix
target_file += suffix if len(suffix) > 1 else ''
xml = """ xml = """
<volume> <volume>
@ -265,9 +270,9 @@ class wvmStorage(wvmConnect):
<permissions> <permissions>
<owner>%s</owner> <owner>%s</owner>
<group>%s</group> <group>%s</group>
<mode>0644</mode> <mode>%s</mode>
<label>virt_image_t</label> <label>virt_image_t</label>
</permissions>""" % (target_file, vol_fmt, owner['uid'], owner['guid']) </permissions>""" % (target_file, vol_fmt, owner['uid'], owner['guid'], mode)
if vol_fmt == 'qcow2': if vol_fmt == 'qcow2':
xml += """ xml += """
<compat>1.1</compat> <compat>1.1</compat>

View file

@ -166,3 +166,27 @@ def validate_macaddr(val):
form = re.match("^([0-9a-fA-F]{1,2}:){5}[0-9a-fA-F]{1,2}$", val) form = re.match("^([0-9a-fA-F]{1,2}:){5}[0-9a-fA-F]{1,2}$", val)
if form is None: if form is None:
raise ValueError("MAC address must be of the format AA:BB:CC:DD:EE:FF, was '%s'" % val) raise ValueError("MAC address must be of the format AA:BB:CC:DD:EE:FF, was '%s'" % val)
# Mapping of UEFI binary names to their associated architectures. We
# only use this info to do things automagically for the user, it shouldn't
# validate anything the user explicitly enters.
uefi_arch_patterns = {
"i686": [
r".*ovmf-ia32.*", # fedora, gerd's firmware repo
],
"x86_64": [
r".*OVMF_CODE\.fd", # RHEL
r".*ovmf-x64/OVMF.*\.fd", # gerd's firmware repo
r".*ovmf-x86_64-.*", # SUSE
r".*ovmf.*", ".*OVMF.*", # generic attempt at a catchall
],
"aarch64": [
r".*AAVMF_CODE\.fd", # RHEL
r".*aarch64/QEMU_EFI.*", # gerd's firmware repo
r".*aarch64.*", # generic attempt at a catchall
],
"armv7l": [
r".*arm/QEMU_EFI.*", # fedora, gerd's firmware repo
],
}

View file

@ -149,11 +149,31 @@ SHOW_PROFILE_EDIT_PASSWORD = False
# available: default (grid), list # available: default (grid), list
VIEW_ACCOUNTS_STYLE = 'grid' VIEW_ACCOUNTS_STYLE = 'grid'
# available: default (grouped), nongrouped # available list style: default (grouped), nongrouped
VIEW_INSTANCES_LIST_STYLE = 'grouped' VIEW_INSTANCES_LIST_STYLE = 'grouped'
# available volume format: raw, qcow2, qcow
INSTANCE_VOLUME_DEFAULT_FORMAT = 'qcow2' INSTANCE_VOLUME_DEFAULT_FORMAT = 'qcow2'
# available bus types: virtio, scsi, ide, usb, sata
INSTANCE_VOLUME_DEFAULT_BUS = 'virtio' 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' INSTANCE_VOLUME_DEFAULT_CACHE = 'directsync'
# up to os, 0=root, 107=qemu or libvirt-bin(for ubuntu) # up to os, 0=root, 107=qemu or libvirt-bin(for ubuntu)
INSTANCE_VOLUME_DEFAULT_OWNER = {'uid': 0, 'guid': 0} 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'