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:
commit
fa852566aa
22 changed files with 1094 additions and 296 deletions
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
151
create/templates/create_instance_w1.html
Normal file
151
create/templates/create_instance_w1.html
Normal 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 %}
|
|
@ -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 %}
|
154
create/views.py
154
create/views.py
|
@ -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())
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
|
@ -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:
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
],
|
||||||
|
}
|
|
@ -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'
|
||||||
|
|
Loading…
Reference in a new issue