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

Instances overhaul

This commit is contained in:
Real-Gecko 2020-07-13 15:33:09 +06:00
parent f23e6b000f
commit 47009d47ca
69 changed files with 5011 additions and 4127 deletions

View file

@ -6,7 +6,7 @@ name: linter
# events but only for the master branch
on:
push:
branches: [ master ]
branches: [ '*' ]
pull_request:
branches: [ master ]

View file

@ -316,6 +316,23 @@ pip3 install -U -r conf/requirements.txt
python3 manage.py migrate
sudo service supervisor restart
```
### Running tests
Server on which tests will be performed must have libvirt up and running.
It must not contain vms.
It must have `default` storage which not contain any disk images.
It must have `default` network which must be on.
Setup venv
```bash
python -m venv venv
source venv/bin/activate
pip install -r conf/requirements.txt
```
Run tests
```bash
python menage.py test
```
### Screenshots
Instance Detail:
<img src="doc/images/instance.PNG" width="95%" align="center"/>

View file

@ -6,6 +6,9 @@ from django.utils.translation import ugettext_lazy as _
from instances.models import Instance
class UserInstanceManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related('instance', 'user')
class UserInstance(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
@ -14,6 +17,8 @@ class UserInstance(models.Model):
is_delete = models.BooleanField(default=False)
is_vnc = models.BooleanField(default=False)
objects = UserInstanceManager()
def __str__(self):
return _('Instance "%(inst)s" of user %(user)s') % {'inst': self.instance, 'user': self.user}

View file

@ -42,7 +42,7 @@
{% for inst in user_insts %}
<tr>
<td>{{ forloop.counter }}</td>
<td><a href="{% url 'instances:instance' inst.instance.compute.id inst.instance.name %}">{{ inst.instance.name }}</a></td>
<td><a href="{% url 'instances:instance' inst.instance.id %}">{{ inst.instance.name }}</a></td>
<td>{{ inst.is_vnc }}</td>
<td>{{ inst.is_change }}</td>
<td>{{ inst.is_delete }}</td>

View file

@ -55,7 +55,7 @@
</td>
<td>{% if user.is_staff %}<span class="fa fa-check"></span>{% endif %}</td>
<td>{% if user.is_superuser %}<span class="fa fa-check"></span>{% endif %}</td>
<td>{% if user.userattributes.can_clone_instances %}<span class="fa fa-check"></span>{% endif %}</td>
<td>{% if perms.instances.clone_instances %}<span class="fa fa-check"></span>{% endif %}</td>
</tr>
{% endfor %}
</tbody>

View file

@ -126,6 +126,10 @@ def user_instance_delete(request, pk):
if request.method == 'POST':
user = user_instance.user
user_instance.delete()
next = request.GET.get('next', None)
if next:
return redirect(next)
else:
return redirect(reverse('account', args=[user.id]))
return render(

View file

@ -1,8 +1,10 @@
from django.db.models import CharField, IntegerField, Model
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from libvirt import virConnect
from vrtManager.connection import connection_manager
from vrtManager.hostdetails import wvmHostDetails
class Compute(Model):
@ -15,7 +17,45 @@ class Compute(Model):
@cached_property
def status(self):
return connection_manager.host_is_up(self.type, self.hostname)
# return connection_manager.host_is_up(self.type, self.hostname)
# TODO: looks like socket has problems connecting via VPN
if isinstance(self.connection, virConnect):
return True
else:
return self.connection
@cached_property
def connection(self):
try:
return connection_manager.get_connection(
self.hostname,
self.login,
self.password,
self.type,
)
except Exception as e:
return e
@cached_property
def proxy(self):
return wvmHostDetails(
self.hostname,
self.login,
self.password,
self.type,
)
@cached_property
def cpu_count(self):
return self.proxy.get_node_info()[3]
@cached_property
def ram_size(self):
return self.proxy.get_node_info()[2]
@cached_property
def ram_usage(self):
return self.proxy.get_memory_usage()['percent']
def __str__(self):
return self.name

View file

@ -0,0 +1,121 @@
{% extends "base.html" %}
{% load i18n %}
{% load staticfiles %}
{% load icons %}
{% block title %}{% trans "Instances" %} - {{ compute.name }}{% endblock %}
{% block style %}
<link rel="stylesheet" href="{% static "css/sortable-theme-bootstrap.css" %}" />
{% endblock %}
{% block page_header %}{{ compute.name }}{% endblock page_header %}
{% block page_header_extra %}
<a href="{% url 'instances:create_instance_select_type' compute.id %}"
class="btn btn-success btn-header float-right">
{% icon 'plus' %}
</a>
{% if instances %}
<div class="float-right search">
<input id="filter" class="form-control" type="text" placeholder="{% trans 'Search' %}">
</div>
{% endif %}
{% endblock page_header_extra %}
{% block content %}
<div class="row">
<div class="col-lg-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb bg-light shadow-sm">
<li class="breadcrumb-item active">
<a href="{% url 'overview' compute.id %}">{% icon 'dashboard' %} {% trans "Overview" %}</a>
</li>
<li class="breadcrumb-item">
<span class="font-weight-bold">{% icon 'server' %} {% trans "Instances" %}</span>
</li>
<li class="breadcrumb-item">
<a href="{% url 'storages' compute.id %}">{% icon 'hdd-o' %} {% trans "Storages" %}</a>
</li>
<li class="breadcrumb-item">
<a href="{% url 'networks' compute.id %}">{% icon 'sitemap' %} {% trans "Networks" %}</a>
</li>
<li class="breadcrumb-item">
<a href="{% url 'interfaces' compute.id %}">{% icon 'wifi' %} {% trans "Interfaces" %}</a>
</li>
<li class="breadcrumb-item">
<a href="{% url 'nwfilters' compute.id %}">{% icon 'filter' %} {% trans "NWFilters" %}</a>
</li>
<li class="breadcrumb-item">
<a href="{% url 'secrets' compute.id %}">{% icon 'key' %} {% trans "Secrets" %}</a>
</li>
</ol>
</nav>
</div>
</div>
<div class="row">
<div class="col-lg-12">
{% if not instances %}
<div class="alert alert-warning alert-dismissable fade show">
{% icon 'exclamation-triangle' %} <strong>{% trans "Warning" %}:</strong>
{% trans "Hypervisor doesn't have any Instances" %}
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
</div>
</div>
{% else %}
<table class="table table-hover sortable-theme-bootstrap" data-sortable>
<thead>
<tr>
<th scope="col">{% trans 'Name' %}<br>{% trans 'Description' %}</th>
<th scope="col">{% trans 'User' %}</th>
<th scope="col">{% trans 'Status' %}</th>
<th scope="col">{% trans 'VCPU' %}</th>
<th scope="col">{% trans 'Memory' %}</th>
<th scope="col" data-sortable="false">{% trans 'Actions' %}</th>
</tr>
</thead>
<tbody class="searchable">
{% for instance in instances %}
<tr>
<td>
<a class="text-secondary" href="{% url 'instances:instance' instance.id %}">
{{ instance.name }}
</a>
</td>
<td>
<em>
{% if instance.userinstance_set.all.count > 0 %}
{{ instance.userinstance_set.all.0.user }}
{% if instance.userinstance_set.all.count > 1 %}
(+{{ instance.userinstance_set.all.count|add:"-1" }})
{% endif %}
{% endif %}
</em>
</td>
<td>
{% if instance.proxy.instance.info.0 == 1 %}<span
class="text-success">{% trans "Active" %}</span>{% endif %}
{% if instance.proxy.instance.info.0 == 5 %}<span
class="text-danger">{% trans "Off" %}</span>{% endif %}
{% if instance.proxy.instance.info.0 == 3 %}<span
class="text-warning">{% trans "Suspended" %}</span>{% endif %}
</td>
<td>{{ instance.proxy.instance.info.3 }}</td>
<td>{% widthratio instance.proxy.instance.info.1 1024 1 %} MiB</td>
<td class="text-nowrap">
{% include 'instance_actions.html' %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
{% endblock %}
{% block script %}
<script src="{% static "js/sortable.min.js" %}"></script>
<script>
function open_console(uuid) {
window.open("{% url 'console' %}?token=" + uuid, "", "width=850,height=685");
}
</script>
<script src="{% static 'js/filter-table.js' %}"></script>
{% endblock %}

View file

@ -83,6 +83,9 @@ class ComputesTestCase(TestCase):
response = self.client.get(reverse('storages', args=[1]))
self.assertEqual(response.status_code, 200)
def test_storage(self):
pass
def test_default_storage_volumes(self):
response = self.client.get(reverse('volumes', kwargs={'compute_id': 1, 'pool': 'default'}))
self.assertEqual(response.status_code, 200)
@ -115,9 +118,9 @@ class ComputesTestCase(TestCase):
response = self.client.get(reverse('secrets', args=[1]))
self.assertEqual(response.status_code, 200)
def test_create_instance_select_type(self):
response = self.client.get(reverse('create_instance_select_type', args=[1]))
self.assertEqual(response.status_code, 200)
# def test_create_instance_select_type(self):
# response = self.client.get(reverse('create_instance_select_type', args=[1]))
# self.assertEqual(response.status_code, 200)
# TODO: create_instance

View file

@ -1,14 +1,14 @@
from django.urls import path, include
from secrets.views import secrets
from . import views
from . import forms
from create.views import create_instance, create_instance_select_type
from instances.views import instances
from django.urls import include, path
# from instances.views import create_instance, create_instance_select_type
from interfaces.views import interface, interfaces
from networks.views import network, networks
from nwfilters.views import nwfilter, nwfilters
from secrets.views import secrets
from storages.views import get_volumes, storage, storages
from storages.views import create_volume, get_volumes, storage, storages
from . import forms, views
urlpatterns = [
path('', views.computes, name='computes'),
@ -23,10 +23,11 @@ urlpatterns = [
path('update/', views.compute_update, name='compute_update'),
path('delete/', views.compute_delete, name='compute_delete'),
path('statistics', views.compute_graph, name='compute_graph'),
path('instances/', instances, name='instances'),
path('instances/', views.instances, name='instances'),
path('storages/', storages, name='storages'),
path('storage/<str:pool>/volumes', get_volumes, name='volumes'),
path('storage/<str:pool>/volumes/', get_volumes, name='volumes'),
path('storage/<str:pool>/', storage, name='storage'),
path('storage/<str:pool>/create_volume/', create_volume, name='create_volume'),
path('networks/', networks, name='networks'),
path('network/<str:pool>/', network, name='network'),
path('interfaces/', interfaces, name='interfaces'),
@ -34,8 +35,8 @@ urlpatterns = [
path('nwfilters/', nwfilters, name='nwfilters'),
path('nwfilter/<str:nwfltr>/', nwfilter, name='nwfilter'),
path('secrets/', secrets, name='secrets'),
path('create/', create_instance_select_type, name='create_instance_select_type'),
path('create/archs/<str:arch>/machines/<str:machine>/', create_instance, name='create_instance'),
# path('create/', create_instance_select_type, name='create_instance_select_type'),
# path('create/archs/<str:arch>/machines/<str:machine>/', create_instance, name='create_instance'),
path('archs/<str:arch>/machines/', views.get_compute_machine_types, name='machines'),
path(
'archs/<str:arch>/machines/<str:machine>/disks/<str:disk>/buses/',

13
computes/utils.py Normal file
View file

@ -0,0 +1,13 @@
from instances.models import Instance
def refresh_instance_database(compute):
domains = compute.proxy.wvm.listAllDomains()
domain_names = [d.name() for d in domains]
# Delete instances that're not on host from DB
Instance.objects.filter(compute=compute).exclude(name__in=domain_names).delete()
# Create instances that're on host but not in DB
names = Instance.objects.filter(compute=compute).values_list('name', flat=True)
for domain in domains:
if domain.name() not in names:
Instance(compute=compute, name=domain.name(), uuid=domain.UUIDString()).save()

View file

@ -15,6 +15,8 @@ from instances.models import Instance
from vrtManager.connection import (CONN_SOCKET, CONN_SSH, CONN_TCP, CONN_TLS, connection_manager, wvmConnect)
from vrtManager.hostdetails import wvmHostDetails
from . import utils
@superuser_only
def computes(request):
@ -30,17 +32,9 @@ def computes(request):
@superuser_only
def overview(request, compute_id):
"""
:param request:
:param compute_id:
:return:
"""
error_messages = []
compute = get_object_or_404(Compute, pk=compute_id)
status = 'true' if connection_manager.host_is_up(compute.type, compute.hostname) is True else 'false'
try:
conn = wvmHostDetails(
compute.hostname,
compute.login,
@ -54,12 +48,20 @@ def overview(request, compute_id):
version = conn.get_version()
lib_version = conn.get_lib_version()
conn.close()
except libvirtError as lib_err:
error_messages.append(lib_err)
return render(request, 'overview.html', locals())
@superuser_only
def instances(request, compute_id):
compute = get_object_or_404(Compute, pk=compute_id)
utils.refresh_instance_database(compute)
instances = Instance.objects.filter(compute=compute).prefetch_related('userinstance_set')
return render(request, 'computes/instances.html', {'compute': compute, 'instances': instances})
@superuser_only
def compute_create(request, FormClass):
form = FormClass(request.POST or None)

View file

@ -8,7 +8,6 @@ import json
import guestfs
import re
PORT = 16510
ADDRESS = "0.0.0.0"
@ -20,7 +19,8 @@ class MyTCPServer(socketserver.ThreadingTCPServer):
class MyTCPServerHandler(socketserver.BaseRequestHandler):
def handle(self):
# recive data
data = json.loads(self.request.recv(1024).strip())
d = self.request.recv(1024).strip()
data = json.loads(d)
# GuestFS
gfs = guestfs.GuestFS(python_return_dict=True)
@ -51,8 +51,8 @@ class MyTCPServerHandler(socketserver.BaseRequestHandler):
pass
gfs.shutdown()
gfs.close()
except RuntimeError as err:
self.request.sendall(json.dumps({'return': 'error', 'message': err}))
except Exception as err:
self.request.sendall(bytes(json.dumps({'return': 'error', 'message': str(err)}).encode()))
server = MyTCPServer((ADDRESS, PORT), MyTCPServerHandler)

View file

@ -26,11 +26,13 @@ def console(request):
host = int(temptoken[0])
uuid = temptoken[1]
instance = Instance.objects.get(compute_id=host, uuid=uuid)
conn = wvmInstance(instance.compute.hostname,
conn = wvmInstance(
instance.compute.hostname,
instance.compute.login,
instance.compute.password,
instance.compute.type,
instance.name)
instance.name,
)
console_type = conn.get_console_type()
console_websocket_port = conn.get_console_websocket_port()
console_passwd = conn.get_console_passwd()

View file

View file

@ -1,24 +0,0 @@
# Generated by Django 2.2.10 on 2020-01-28 07:01
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Flavor',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('label', models.CharField(max_length=12)),
('memory', models.IntegerField()),
('vcpu', models.IntegerField()),
('disk', models.IntegerField()),
],
),
]

View file

@ -1,33 +0,0 @@
# Generated by Django 2.2.13 on 2020-06-15 06:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('create', '0002_addFlavors'),
]
operations = [
migrations.AlterField(
model_name='flavor',
name='disk',
field=models.IntegerField(verbose_name='disk'),
),
migrations.AlterField(
model_name='flavor',
name='label',
field=models.CharField(max_length=12, verbose_name='label'),
),
migrations.AlterField(
model_name='flavor',
name='memory',
field=models.IntegerField(verbose_name='memory'),
),
migrations.AlterField(
model_name='flavor',
name='vcpu',
field=models.IntegerField(verbose_name='vcpu'),
),
]

View file

@ -1,12 +0,0 @@
from django.db.models import Model, CharField, IntegerField
from django.utils.translation import ugettext_lazy as _
class Flavor(Model):
label = CharField(_('label'), max_length=12)
memory = IntegerField(_('memory'))
vcpu = IntegerField(_('vcpu'))
disk = IntegerField(_('disk'))
def __str__(self):
return self.name

View file

@ -1,61 +0,0 @@
{% load i18n %}
{% if request.user.is_superuser %}
<button href="#addFlavor" type="button" class="btn btn-success btn-header float-right" data-toggle="modal">
<span class="fa fa-plus" aria-hidden="true"></span>
</button>
<!-- Modal Flavor -->
<div class="modal fade" id="addFlavor" tabindex="-1" role="dialog" aria-labelledby="addFlavorLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{% trans "Add New Flavor" %}</h4>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<form method="post" role="form" aria-label="Flavor selection form">{% csrf_token %}
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans "Name" %}</label>
<div class="col-sm-6">
<input type="text" name="label" class="form-control" placeholder="{% trans "Micro" %}" maxlength="20"
required pattern="[a-zA-Z0-9]+">
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans "VCPU" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="vcpu" value="1" maxlength="1" required
pattern="[0-9]">
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans "RAM" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="memory" value="512" maxlength="5" required
pattern="[0-9]+">
</div>
<label class="col-sm-1 col-form-label">{% trans "MB" %}</label>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans "HDD" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="disk" value="10" maxlength="3" required
pattern="[0-9]+">
</div>
<label class="col-sm-1 col-form-label">{% trans "GB" %}</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
{% trans "Close" %}
</button>
<button type="submit" class="btn btn-primary" name="create_flavor">
{% trans "Add" %}
</button>
</div>
</form>
</div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog -->
</div> <!-- /.modal -->
{% endif %}

View file

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View file

@ -1,304 +0,0 @@
from django.contrib import messages
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse
from libvirt import libvirtError
from admin.decorators import superuser_only
from computes.models import Compute
from create.models import Flavor
from create.forms import FlavorAddForm, NewVMForm
from appsettings.models import AppSettings
from instances.models import Instance
from vrtManager.create import wvmCreate
from vrtManager import util
from logs.views import addlogmsg
from webvirtcloud.settings import QEMU_CONSOLE_LISTEN_ADDRESSES
@superuser_only
def create_instance_select_type(request, compute_id):
"""
:param request:
:param compute_id:
:return:
"""
conn = None
error_messages = list()
storages = list()
networks = list()
hypervisors = list()
meta_prealloc = False
compute = get_object_or_404(Compute, pk=compute_id)
appsettings = AppSettings.objects.all()
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 = appsettings.get(key="INSTANCE_MACHINE_DEFAULT_TYPE").value
default_arch = appsettings.get(key="INSTANCE_ARCH_DEFAULT_TYPE").value
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('instances:instance', args=[compute_id, name]))
except libvirtError as lib_err:
error_messages.append(lib_err)
except libvirtError as lib_err:
error_messages.append(lib_err)
return render(request, 'create_instance_w1.html', locals())
@superuser_only
def create_instance(request, compute_id, arch, machine):
"""
:param request:
:param compute_id:
:param arch:
:param machine:
:return:
"""
conn = None
error_messages = list()
storages = list()
networks = list()
hypervisors = list()
firmwares = list()
meta_prealloc = False
compute = get_object_or_404(Compute, pk=compute_id)
flavors = Flavor.objects.filter().order_by('id')
appsettings = AppSettings.objects.all()
try:
conn = wvmCreate(compute.hostname, compute.login, compute.password, compute.type)
default_firmware = appsettings.get(key="INSTANCE_FIRMWARE_DEFAULT_TYPE").value
default_cpu_mode = appsettings.get(key="INSTANCE_CPU_DEFAULT_MODE").value
instances = conn.get_instances()
videos = conn.get_video_models(arch, machine)
cache_modes = sorted(conn.get_cache_modes().items())
default_cache = appsettings.get(key="INSTANCE_VOLUME_DEFAULT_CACHE").value
default_io = appsettings.get(key="INSTANCE_VOLUME_DEFAULT_IO").value
default_zeroes = appsettings.get(key="INSTANCE_VOLUME_DEFAULT_DETECT_ZEROES").value
default_discard = appsettings.get(key="INSTANCE_VOLUME_DEFAULT_DISCARD").value
default_disk_format = appsettings.get(key="INSTANCE_VOLUME_DEFAULT_FORMAT").value
default_disk_owner_uid = int(appsettings.get(key="INSTANCE_VOLUME_DEFAULT_OWNER_UID").value)
default_disk_owner_gid = int(appsettings.get(key="INSTANCE_VOLUME_DEFAULT_OWNER_GID").value)
default_scsi_disk_model = appsettings.get(key="INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER").value
listener_addr = QEMU_CONSOLE_LISTEN_ADDRESSES
mac_auto = util.randomMAC()
disk_devices = conn.get_disk_device_types(arch, machine)
disk_buses = conn.get_disk_bus_types(arch, machine)
default_bus = appsettings.get(key="INSTANCE_VOLUME_DEFAULT_BUS").value
networks = sorted(conn.get_networks())
nwfilters = conn.get_nwfilters()
storages = sorted(conn.get_storages(only_actives=True))
default_graphics = appsettings.get(key="QEMU_CONSOLE_DEFAULT_TYPE").value
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:
error_messages.append(lib_err)
if conn:
if not storages:
msg = _("You haven't defined any storage pools")
error_messages.append(msg)
if not networks:
msg = _("You haven't defined any network pools")
error_messages.append(msg)
if request.method == 'POST':
if 'create_flavor' in request.POST:
form = FlavorAddForm(request.POST)
if form.is_valid():
data = form.cleaned_data
create_flavor = Flavor(label=data['label'], vcpu=data['vcpu'], memory=data['memory'], disk=data['disk'])
create_flavor.save()
return HttpResponseRedirect(request.get_full_path())
if 'delete_flavor' in request.POST:
flavor_id = request.POST.get('flavor', '')
delete_flavor = Flavor.objects.get(id=flavor_id)
delete_flavor.delete()
return HttpResponseRedirect(request.get_full_path())
if 'create' in request.POST:
firmware = dict()
volume_list = list()
is_disk_created = False
clone_path = ""
form = NewVMForm(request.POST)
if form.is_valid():
data = form.cleaned_data
if data['meta_prealloc']:
meta_prealloc = True
if instances:
if data['name'] in instances:
msg = _("A virtual machine with this name already exists")
error_messages.append(msg)
if Instance.objects.filter(name__exact=data['name']):
messages.warning(request, _("There is an instance with same name. Are you sure?"))
if not error_messages:
if data['hdd_size']:
if not data['mac']:
error_msg = _("No Virtual Machine MAC has been entered")
error_messages.append(error_msg)
else:
try:
path = conn.create_volume(
data['storage'],
data['name'],
data['hdd_size'],
default_disk_format,
meta_prealloc,
default_disk_owner_uid,
default_disk_owner_gid,
)
volume = dict()
volume['device'] = 'disk'
volume['path'] = path
volume['type'] = conn.get_volume_type(path)
volume['cache_mode'] = data['cache_mode']
volume['bus'] = default_bus
if volume['bus'] == 'scsi':
volume['scsi_model'] = default_scsi_disk_model
volume['discard_mode'] = default_discard
volume['detect_zeroes_mode'] = default_zeroes
volume['io_mode'] = default_io
volume_list.append(volume)
is_disk_created = True
except libvirtError as lib_err:
error_messages.append(lib_err)
elif data['template']:
templ_path = conn.get_volume_path(data['template'])
dest_vol = conn.get_volume_path(data["name"] + ".img", data['storage'])
if dest_vol:
error_msg = _("Image has already exist. Please check volumes or change instance name")
error_messages.append(error_msg)
else:
clone_path = conn.clone_from_template(
data['name'],
templ_path,
data['storage'],
meta_prealloc,
default_disk_owner_uid,
default_disk_owner_gid,
)
volume = dict()
volume['path'] = clone_path
volume['type'] = conn.get_volume_type(clone_path)
volume['device'] = 'disk'
volume['cache_mode'] = data['cache_mode']
volume['bus'] = default_bus
if volume['bus'] == 'scsi':
volume['scsi_model'] = default_scsi_disk_model
volume['discard_mode'] = default_discard
volume['detect_zeroes_mode'] = default_zeroes
volume['io_mode'] = default_io
volume_list.append(volume)
is_disk_created = True
else:
if not data['images']:
error_msg = _("First you need to create or select an image")
error_messages.append(error_msg)
else:
for idx, vol in enumerate(data['images'].split(',')):
try:
path = conn.get_volume_path(vol)
volume = dict()
volume['path'] = path
volume['type'] = conn.get_volume_type(path)
volume['device'] = request.POST.get('device' + str(idx), '')
volume['bus'] = request.POST.get('bus' + str(idx), '')
if volume['bus'] == 'scsi':
volume['scsi_model'] = default_scsi_disk_model
volume['cache_mode'] = data['cache_mode']
volume['discard_mode'] = default_discard
volume['detect_zeroes_mode'] = default_zeroes
volume['io_mode'] = default_io
volume_list.append(volume)
except libvirtError as lib_err:
error_messages.append(lib_err)
if data['cache_mode'] not in conn.get_cache_modes():
error_msg = _("Invalid cache mode")
error_messages.append(error_msg)
if 'UEFI' in data["firmware"]:
firmware["loader"] = data["firmware"].split(":")[1].strip()
firmware["secure"] = 'no'
firmware["readonly"] = 'yes'
firmware["type"] = 'pflash'
if 'secboot' in firmware["loader"] and machine != 'q35':
messages.warning(
request, "Changing machine type from '%s' to 'q35' "
"which is required for UEFI secure boot." % machine)
machine = 'q35'
firmware["secure"] = 'yes'
if not error_messages:
uuid = util.randomUUID()
try:
conn.create_instance(name=data['name'],
memory=data['memory'],
vcpu=data['vcpu'],
vcpu_mode=data['vcpu_mode'],
uuid=uuid,
arch=arch,
machine=machine,
firmware=firmware,
volumes=volume_list,
networks=data['networks'],
virtio=data['virtio'],
listen_addr=data["listener_addr"],
nwfilter=data["nwfilter"],
graphics=data["graphics"],
video=data["video"],
console_pass=data["console_pass"],
mac=data['mac'],
qemu_ga=data['qemu_ga'])
create_instance = Instance(compute_id=compute_id, name=data['name'], uuid=uuid)
create_instance.save()
msg = _("Instance is created")
messages.success(request, msg)
addlogmsg(request.user.username, create_instance.name, msg)
return HttpResponseRedirect(reverse('instances:instance', args=[compute_id, data['name']]))
except libvirtError as lib_err:
if data['hdd_size'] or len(volume_list) > 0:
if is_disk_created:
for vol in volume_list:
conn.delete_volume(vol['path'])
error_messages.append(lib_err)
conn.close()
return render(request, 'create_instance_w2.html', locals())

View file

@ -1,38 +1,40 @@
import re
from django import forms
from django.utils.translation import ugettext_lazy as _
from create.models import Flavor
from webvirtcloud.settings import QEMU_CONSOLE_LISTEN_ADDRESSES
from appsettings.models import AppSettings
from webvirtcloud.settings import QEMU_CONSOLE_LISTEN_ADDRESSES, QEMU_KEYMAPS
from .models import Flavor
class FlavorAddForm(forms.Form):
label = forms.CharField(label="Name",
error_messages={'required': _('No flavor name has been entered')},
max_length=64)
vcpu = forms.IntegerField(label="VCPU",
error_messages={'required': _('No VCPU has been entered')}, )
disk = forms.IntegerField(label="HDD",
error_messages={'required': _('No HDD image has been entered')}, )
memory = forms.IntegerField(label="RAM",
error_messages={'required': _('No RAM size has been entered')}, )
class FlavorForm(forms.ModelForm):
class Meta:
model = Flavor
fields = '__all__'
def clean_name(self):
label = self.cleaned_data['label']
have_symbol = re.match('^[a-zA-Z0-9._-]+$', label)
if not have_symbol:
raise forms.ValidationError(_('The flavor name must not contain any special characters'))
elif len(label) > 64:
raise forms.ValidationError(_('The flavor name must not exceed 20 characters'))
try:
Flavor.objects.get(label=label)
except Flavor.DoesNotExist:
return label
raise forms.ValidationError(_('Flavor name is already use'))
class ConsoleForm(forms.Form):
type = forms.ChoiceField()
listen_on = forms.ChoiceField()
generate_password = forms.BooleanField(required=False)
clear_password = forms.BooleanField(required=False)
password = forms.CharField(widget=forms.PasswordInput(render_value=True), required=False)
clear_keymap = forms.BooleanField(required=False)
keymap = forms.ChoiceField(required=False)
def __init__(self, *args, **kwargs):
super(ConsoleForm, self).__init__(*args, **kwargs)
type_choices = ((c, c) for c in AppSettings.objects.get(key="QEMU_CONSOLE_DEFAULT_TYPE").choices_as_list())
keymap_choices = [('auto', 'Auto')] + list((c, c) for c in QEMU_KEYMAPS)
self.fields['type'] = forms.ChoiceField(choices=type_choices)
self.fields['listen_on'] = forms.ChoiceField(choices=QEMU_CONSOLE_LISTEN_ADDRESSES)
self.fields['keymap'] = forms.ChoiceField(choices=keymap_choices)
class NewVMForm(forms.Form):
name = forms.CharField(error_messages={'required': _('No Virtual Machine name has been entered')},
max_length=64)
name = forms.CharField(error_messages={'required': _('No Virtual Machine name has been entered')}, max_length=64)
firmware = forms.CharField(max_length=50, required=False)
vcpu = forms.IntegerField(error_messages={'required': _('No VCPU has been entered')})
vcpu_mode = forms.CharField(max_length=20, required=False)

View file

@ -0,0 +1,23 @@
# Generated by Django 2.2.13 on 2020-06-18 08:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('instances', '0003_auto_20200615_0637'),
]
operations = [
migrations.AlterField(
model_name='instance',
name='name',
field=models.CharField(db_index=True, max_length=120, verbose_name='name'),
),
migrations.AlterField(
model_name='instance',
name='uuid',
field=models.CharField(db_index=True, max_length=36, verbose_name='uuid'),
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 2.2.13 on 2020-06-23 12:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('instances', '0004_auto_20200618_0817'),
]
operations = [
migrations.CreateModel(
name='Flavor',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('label', models.CharField(max_length=12, verbose_name='label')),
('memory', models.IntegerField(verbose_name='memory')),
('vcpu', models.IntegerField(verbose_name='vcpu')),
('disk', models.IntegerField(verbose_name='disk')),
],
),
]

View file

@ -3,8 +3,8 @@
from django.db import migrations
def add_favors(apps, schema_editor):
Flavor = apps.get_model("create", "Flavor")
def add_flavors(apps, schema_editor):
Flavor = apps.get_model("instances", "Flavor")
add_flavor = Flavor(label="micro", vcpu="1", memory="512", disk="20")
add_flavor.save()
add_flavor = Flavor(label="mini", vcpu="2", memory="1024", disk="30")
@ -19,12 +19,17 @@ def add_favors(apps, schema_editor):
add_flavor.save()
def del_flavors(apps, schema_editor):
Flavor = apps.get_model("instances", "Flavor")
Flavor.objects.all().delete()
class Migration(migrations.Migration):
dependencies = [
('create', '0001_initial'),
('instances', '0005_flavor'),
]
operations = [
migrations.RunPython(add_favors),
migrations.RunPython(add_flavors, del_flavors),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 2.2.13 on 2020-06-24 08:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('instances', '0006_addFlavors'),
]
operations = [
migrations.AlterField(
model_name='flavor',
name='label',
field=models.CharField(max_length=12, unique=True, verbose_name='label'),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 2.2.13 on 2020-07-08 09:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('instances', '0007_auto_20200624_0821'),
]
operations = [
migrations.AlterField(
model_name='instance',
name='created',
field=models.DateTimeField(auto_now_add=True, verbose_name='created'),
),
]

View file

@ -1,21 +1,211 @@
from django.db.models import (CASCADE, BooleanField, CharField, DateField, ForeignKey, Model)
from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from libvirt import VIR_DOMAIN_XML_SECURE
from computes.models import Compute
from vrtManager.instance import wvmInstance
class Instance(Model):
compute = ForeignKey(Compute, on_delete=CASCADE)
name = CharField(_('name'), max_length=120)
uuid = CharField(_('uuid'), max_length=36)
is_template = BooleanField(_('is template'), default=False)
created = DateField(_('created'), auto_now_add=True)
class Flavor(models.Model):
label = models.CharField(_('label'), max_length=12, unique=True)
memory = models.IntegerField(_('memory'))
vcpu = models.IntegerField(_('vcpu'))
disk = models.IntegerField(_('disk'))
def __str__(self):
return self.label
class InstanceManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related('compute')
class Instance(models.Model):
compute = models.ForeignKey(Compute, on_delete=models.CASCADE)
name = models.CharField(_('name'), max_length=120, db_index=True)
uuid = models.CharField(_('uuid'), max_length=36, db_index=True)
is_template = models.BooleanField(_('is template'), default=False)
created = models.DateTimeField(_('created'), auto_now_add=True)
objects = InstanceManager()
def __str__(self):
return f'{self.compute}/{self.name}'
@cached_property
def proxy(self):
return wvmInstance(
self.compute.hostname,
self.compute.login,
self.compute.password,
self.compute.type,
self.name,
)
class PermissionSet(Model):
@cached_property
def media(self):
return self.proxy.get_media_devices()
@cached_property
def media_iso(self):
return sorted(self.proxy.get_iso_media())
@cached_property
def disks(self):
return self.proxy.get_disk_devices()
@cached_property
def status(self):
return self.proxy.get_status()
@cached_property
def autostart(self):
return self.proxy.get_autostart()
@cached_property
def bootmenu(self):
return self.proxy.get_bootmenu()
@cached_property
def boot_order(self):
return self.proxy.get_bootorder()
@cached_property
def arch(self):
return self.proxy.get_arch()
@cached_property
def machine(self):
return self.proxy.get_machine_type()
@cached_property
def firmware(self):
return self.proxy.get_loader()
@cached_property
def nvram(self):
return self.proxy.get_nvram()
@cached_property
def vcpu(self):
return self.proxy.get_vcpu()
@cached_property
def vcpu_range(self):
return self.proxy.get_max_cpus()
@cached_property
def cur_vcpu(self):
return self.proxy.get_cur_vcpu()
@cached_property
def vcpus(self):
return self.proxy.get_vcpus()
@cached_property
def get_uuid(self):
return self.proxy.get_uuid()
@cached_property
def memory(self):
return self.proxy.get_memory()
@cached_property
def cur_memory(self):
return self.proxy.get_cur_memory()
@cached_property
def title(self):
return self.proxy.get_title()
@cached_property
def description(self):
return self.proxy.get_description()
@cached_property
def networks(self):
return self.proxy.get_net_devices()
@cached_property
def qos(self):
return self.proxy.get_all_qos()
@cached_property
def telnet_port(self):
return self.proxy.get_telnet_port()
@cached_property
def console_type(self):
return self.proxy.get_console_type()
@cached_property
def console_port(self):
return self.proxy.get_console_port()
@cached_property
def console_keymap(self):
return self.proxy.get_console_keymap()
@cached_property
def console_listen_address(self):
return self.proxy.get_console_listen_addr()
@cached_property
def guest_agent(self):
return False if self.proxy.get_guest_agent() is None else True
@cached_property
def guest_agent_ready(self):
return self.proxy.is_agent_ready()
@cached_property
def video_model(self):
return self.proxy.get_video_model()
@cached_property
def video_models(self):
return self.proxy.get_video_models(self.arch, self.machine)
@cached_property
def snapshots(self):
return sorted(self.proxy.get_snapshot(), reverse=True, key=lambda k: k['date'])
@cached_property
def inst_xml(self):
return self.proxy._XMLDesc(VIR_DOMAIN_XML_SECURE)
@cached_property
def has_managed_save_image(self):
return self.proxy.get_managed_save_image()
@cached_property
def console_passwd(self):
return self.proxy.get_console_passwd()
@cached_property
def cache_modes(self):
return sorted(self.proxy.get_cache_modes().items())
@cached_property
def io_modes(self):
return sorted(self.proxy.get_io_modes().items())
@cached_property
def discard_modes(self):
return sorted(self.proxy.get_discard_modes().items())
@cached_property
def detect_zeroes_modes(self):
return sorted(self.proxy.get_detect_zeroes_modes().items())
@cached_property
def formats(self):
return self.proxy.get_image_formats()
class PermissionSet(models.Model):
"""
Dummy model for holding set of permissions we need to be automatically added by Django
"""

View file

@ -13,7 +13,7 @@
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<form method="post" action="" role="form" aria-label="Add instance network form">{% csrf_token %}
<form action="{% url 'instances:add_network' instance.id %}" method="post" action="" role="form" aria-label="Add instance network form">{% csrf_token %}
<div class="form-group row">
<label class="col-sm-4 col-form-label">{% trans "MAC" %}</label>
<div class="col-sm-6">

View file

@ -13,7 +13,7 @@
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<form method="post" action="" role="form" aria-label="Add instance owner form">{% csrf_token %}
<form id="add-owner-form" method="post" action="{% url 'instances:add_owner' instance.id %}" role="form" aria-label="Add instance owner form">{% csrf_token %}
<div class="form-group row">
<label class="col-sm-4 col-form-label">{% trans "User" %}</label>
<div class="col-sm-6">
@ -24,12 +24,12 @@
</select>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans "Close" %}</button>
<button type="submit" class="btn btn-primary" name="add_owner">{% trans "Add" %}</button>
<button type="submit" class="btn btn-primary" form="add-owner-form">{% trans "Add" %}</button>
</div>
</form>
</div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog -->
</div> <!-- /.modal -->

View file

@ -23,7 +23,7 @@
</ul>
<div class="tab-content">
<div class="tab-pane active" id="NewDisk">
<form method="post" role="form" aria-label="Add new volume to disk form">{% csrf_token %}
<form action="{% url 'instances:add_new_vol' instance.id %}" method="post" role="form" aria-label="Add new volume to disk form">{% csrf_token %}
<div class="modal-body">
<p class="font-weight-bold">{% trans "Volume parameters" %}</p>
<div class="form-group row">
@ -46,7 +46,7 @@
<label class="col-sm-3 col-form-label">{% trans "Format" %}</label>
<div class="col-sm-6">
<select name="format" class="custom-select image-format">
{% for format in formats %}
{% for format in instance.formats %}
<option value="{{ format }}" {% if format == default_format %}selected{% endif %}>{% trans format %}</option>
{% endfor %}
</select>
@ -73,7 +73,7 @@
<label class="col-sm-3 col-form-label">{% trans "Cache" %}</label>
<div class="col-sm-6">
<select name="cache" class="custom-select image-format">
{% for mode, name in cache_modes %}
{% for mode, name in instance.cache_modes %}
<option value="{{ mode }}" {% if mode == default_cache %}selected{% endif %}>{% trans name %}</option>
{% endfor %}
</select>
@ -93,7 +93,7 @@
</div>
</div> <!-- /.modal-body -->
<div class="tab-pane" id="ExistingDisk">
<form method="post" role="form" aria-label="Add existing volume to instance form">{% csrf_token %}
<form action="{% url 'instances:add_existing_vol' instance.id %}" method="post" role="form" aria-label="Add existing volume to instance form">{% csrf_token %}
<div class="modal-body">
<p class="font-weight-bold">{% trans "Volume parameters" %}</p>
<div class="form-group row">
@ -103,7 +103,7 @@
<button id="select_storage" class="btn btn-secondary dropdown-toggle form-control" type="button" data-toggle="dropdown">{% trans 'Select Pool' %}...</button>
<div class="dropdown-menu">
{% for storage in storages_host %}
<a class="dropdown-item" href="#" onclick="get_volumes({{ compute_id }}, '{{ storage }}')">{{ storage }}</a>
<a class="dropdown-item" href="#" onclick="get_volumes({{ instance.compute.id }}, '{{ storage }}')">{{ storage }}</a>
{% endfor %}
</div>
<input id="selected_storage" name="selected_storage" hidden/>
@ -133,7 +133,7 @@
<label class="col-sm-3 col-form-label">{% trans "Cache" %}</label>
<div class="col-sm-6">
<select name="cache" class="custom-select image-format">
{% for mode, name in cache_modes %}
{% for mode, name in instance.cache_modes %}
<option value="{{ mode }}" {% if mode == default_cache %}selected{% endif %}>{% trans name %}</option>
{% endfor %}
</select>
@ -142,7 +142,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans "Close" %}</button>
<button type="submit" class="btn btn-success" name="add_existing_vol">{% trans "Add Volume" %}</button>
<button type="submit" class="btn btn-success">{% trans "Add Volume" %}</button>
</div>
</form>
</div>

View file

@ -1,131 +1,55 @@
{% extends "base.html" %}
{% load i18n %}
{% load icons %}
{% load staticfiles %}
{% block title %}{% trans "Instances" %}{% endblock %}
{% block style %}
<link rel="stylesheet" href="{% static "css/sortable-theme-bootstrap.css" %}" />
{% endblock %}
{% block content %}
<!-- Page Heading -->
<div class="row">
<div class="col-lg-12">
{% if request.user.is_superuser %}
{% block page_header %}{% trans "Instances" %}{% endblock page_header %}
{% block page_header_extra %}
{% if request.user.is_superuser %}
{% include 'create_inst_block.html' %}
{% endif %}
{% if all_host_vms or all_user_vms %}
<div class="float-right search">
{% endif %}
<div class="float-right search">
<input id="filter" class="form-control" type="text" placeholder="{% trans 'Search' %}">
</div>
{% endblock page_header_extra %}
{% block content %}
{% for compute in computes %}
{% if compute.status is not True %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{% trans 'Problem occurred with host' %} {{ compute.name }}: {{ compute.status }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endif %}
<h2 class="page-header">{% trans "Instances" %}</h2>
</div>
</div>
<!-- /.row -->
{% include 'errors_block.html' %}
{% endfor %}
<div class="col-lg-12">
{% if request.user.is_superuser %}
{% if not all_host_vms %}
<div class="col-lg-12">
<div class="alert alert-warning alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times</button>
<i class="fa fa-exclamation-triangle"></i> <strong>{% trans "Warning" %}:</strong> {% trans "You don't have any Instance" %}
</div>
</div>
{% if app_settings.VIEW_INSTANCES_LIST_STYLE == 'grouped' and request.user.is_superuser %}
{% include 'allinstances_index_grouped.html' %}
{% else %}
{% if view_style == "nongrouped" %}
{% include 'allinstances_index_nongrouped.html' %}
{% endif %}
{% if view_style == "grouped" %}
{% include 'allinstances_index_grouped.html' %}
{% endif %}
{% endif %}
{% else %}
{% if not all_user_vms %}
<div class="col-lg-12">
<div class="alert alert-warning alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<i class="fa fa-exclamation-triangle"></i> <strong>{% trans "Warning" %}:</strong> {% trans "You don't have any Instance" %}
</div>
</div>
{% else %}
<table class="table table-hover table-striped sortable-theme-bootstrap" data-sortable>
<thead>
<tr>
<th scope="col">{% trans 'Name' %}</th>
<th scope="col">{% trans 'Status' %}</th>
<th scope="col">{% trans 'VCPU' %}</th>
<th scope="col">{% trans 'Memory' %}</th>
<th scope="col" data-sortable="false" style="width: 165px;">{% trans 'Actions' %}</th>
</tr>
</thead>
<tbody class="searchable">
{% for inst, vm in all_user_vms.items %}
<tr>
<td><a href="{% url 'instances:instance' vm.compute_id vm.name %}">{{ vm.name }}</a><br><small><em>{{ vm.title }}</em></small></td>
<td>{% if vm.status == 1 %}
<span class="text-success">{% trans "Active" %}</span>
{% endif %}
{% if vm.status == 5 %}
<span class="text-danger">{% trans "Off" %}</span>
{% endif %}
{% if vm.status == 3 %}
<span class="text-warning">{% trans "Suspend" %}</span>
{% endif %}
</td>
<td>{{ vm.vcpu }}</td>
<td>{{ vm.memory }} {% trans "MB" %}</td>
<td>
{% include "instance_actions.html" %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endif %}
</div>
{% endblock %}
{% endblock content %}
{% block script %}
<script src="{% static "js/sortable.min.js" %}"></script>
<script>
<script src="{% static "js/sortable.min.js" %}"></script>
<script>
function open_console(uuid) {
window.open("{% url 'console' %}?token=" + uuid, "", "width=850,height=485");
}
</script>
<script>
function filter_table() {
var rex = new RegExp($(this).val(), 'i');
$('.searchable tr').hide();
$('.searchable tr').filter(function () {
return rex.test($(this).text());
}).show();
Cookies.set("instances_filter", $(this).val(), { expires: 1 });
}
$(document).ready(function () {
instances_filter_cookie = Cookies.get("instances_filter");
if (instances_filter_cookie) {
$('#filter').val(instances_filter_cookie);
$('#filter').each(filter_table);
}
(function ($) {
$('#filter').keyup(filter_table)
}(jQuery));
});
</script>
<script>
function goto_instance_clone(compute, instance) {
window.location = "/instances/" + compute + "/" + instance + "/#clone";
}
</script>
{% if request.user.is_superuser %}
</script>
<script src="{% static 'js/filter-table.js' %}"></script>
{% if request.user.is_superuser %}
<script>
function goto_compute() {
let compute = $("#compute_select").val();
{#window.location.href = "{% url 'create_instance' 1 %}".replace(1, compute);#}
window.location.href = "{% url 'create_instance_select_type' 1 %}".replace(1, compute);
window.location.href = "{% url 'instances:create_instance_select_type' 1 %}".replace(1, compute);
}
</script>
{% endif %}
{% endblock %}
{% endif %}
{% endblock script %}

View file

@ -1,4 +1,5 @@
{% load i18n %}
{% load icons %}
<table class="table table-hover sortable-theme-bootstrap" data-sortable>
<thead>
<tr style="border: 2px solid transparent; ">
@ -12,61 +13,65 @@
</tr>
</thead>
<tbody class="searchable">
{% for host, insts in all_host_vms.items %}
{% for compute in computes %}
{% if compute.status is True and compute.instance_set.count > 0 %}
<tr class="font-weight-bold active" style="border-bottom: 2px solid darkgray;border-top: 2px solid darkgray;">
<td>
<span id="collapse_host_instances_{{ host.1 }}" class="fa fa-chevron-up" onclick="hide_host_instances('{{ host.1 }}');"></span>
<span id="collapse_host_instances_{{ compute.id }}" class="fa fa-chevron-up" onclick="hide_host_instances('{{ compute.id }}');"></span>
</td>
<td>
<a class="text-secondary" href="{% url 'overview' host.0 %}">{{ host.1 }}</a>
<span id="inst_count_badge_{{ host.1 }}" class="badge badge-secondary d-none">{{ insts.items|length }}</span>
<a class="text-secondary" href="{% url 'overview' compute.id %}">{{ compute.name }}</a>
<span id="inst_count_badge_{{ compute.id }}" class="badge badge-secondary d-none">{{ compute.instance_set.count }}</span>
</td>
<td class="d-none d-sm-table-cell"></td>
<td>
{% if host.2 == 1 %}<span class="text-success">{% trans "Active" %}</span>{% endif %}
{% if host.2 == 2 %}<span class="text-warning">{% trans "Not Active" %}</span>{% endif %}
{% if host.2 == 3 %}<span class="text-danger">{% trans "Connection Failed" %}</span>{% endif %}
<span class="text-success">{% trans "Connected" %}</span>
</td>
<td class="d-none d-sm-table-cell text-center">{{ host.3 }}</td>
<td class="d-none d-sm-table-cell text-right">{{ host.4|filesizeformat }}</td>
<td class="d-none d-sm-table-cell text-center">{{ compute.cpu_count }}</td>
<td class="d-none d-sm-table-cell text-right">{{ compute.ram_size|filesizeformat }}</td>
<td>
<div class="progress">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ host.5 }}%"
aria-valuenow="{{ host.5 }}" aria-valuemin="0" aria-valuemax="100">{{ host.5 }}%
<div class="progress-bar bg-success" role="progressbar" style="width: {{ compute.ram_usage }}%"
aria-valuenow="{{ compute.ram_usage }}" aria-valuemin="0" aria-valuemax="100">{{ compute.ram_usage }}%
</div>
</div>
</td>
</tr>
{% for inst, vm in insts.items %}
<tr host="{{ host.1 }}">
{% for instance in compute.instance_set.all %}
<tr host="{{ compute.id }}">
<td class="text-right">{{ forloop.counter }} </td>
<td>&emsp;
<a class="text-secondary" href="{% url 'instances:instance' host.0 inst %}">{{ inst }}</a><br>
<small><em>{{ vm.title }}</em></small>
</td>
<td class="d-none d-sm-table-cell">
<span class="font-small font-italic">
{% if vm.userinstances.count > 0 %} {{ vm.userinstances.first_user.user.username }}
{% if vm.userinstances.count > 1 %} (+{{ vm.userinstances.count|add:"-1" }}){% endif %}
{% endif %}
</span>
<td>
<a class="text-secondary" href="{% url 'instances:instance' instance.id %}">{{ instance.name }}</a><br>
</td>
<td>
{% if vm.status == 1 %}<span class="text-success">{% trans "Active" %}</span>{% endif %}
{% if vm.status == 5 %}<span class="text-danger">{% trans "Off" %}</span>{% endif %}
{% if vm.status == 3 %}<span class="text-warning">{% trans "Suspend" %}</span>{% endif %}
<em>
{% if instance.userinstance_set.all.count > 0 %}
{{ instance.userinstance_set.all.0.user }}
{% if instance.userinstance_set.all.count > 1 %}
(+{{ instance.userinstance_set.all.count|add:"-1" }})
{% endif %}
{% endif %}
</em>
</td>
<td class="d-none d-sm-table-cell text-center">{{ vm.vcpu }}</td>
<td class="d-none d-sm-table-cell text-right">{{ vm.memory |filesizeformat }}</td>
<td>
{% if instance.proxy.instance.info.0 == 1 %}<span class="text-success">{% trans "Active" %}</span>{% endif %}
{% if instance.proxy.instance.info.0 == 5 %}<span class="text-danger">{% trans "Off" %}</span>{% endif %}
{% if instance.proxy.instance.info.0 == 3 %}
<span class="text-warning">{% trans "Suspended" %}</span>
{% endif %}
</td>
<td>{{ instance.proxy.instance.info.3 }}</td>
<td>{{ instance.cur_memory }} MB</td>
<td class="text-nowrap">
{% include 'instance_actions.html' %}
</td>
</tr>
{% endfor %}
{% endif %}
{% endfor %}
</tbody>
</table>
{% block script %}
<script>
function hide_all_host_instances() {

View file

@ -2,32 +2,54 @@
<table class="table table-hover sortable-theme-bootstrap" data-sortable>
<thead>
<tr>
<th scope="col">{% trans "Name" %}<br>{% trans "Description" %}</th>
<th scope="col">{% trans "Host" %}<br>{% trans "User"%}</th>
<th scope="col">{% trans "Status" %}</th>
<th scope="col">{% trans "VCPU" %}</th>
<th scope="col">{% trans "Memory" %}</th>
<th scope="col" style="width:200px;" data-sortable="false">{% trans "Actions" %}</th>
<th scope="col">{% trans 'Name' %}<br>{% trans 'Description' %}</th>
{% if request.user.is_superuser %}
<th scope="col">{% trans 'Host' %}<br>{% trans 'User' %}</th>
{% endif %}
<th scope="col">{% trans 'Status' %}</th>
<th scope="col">{% trans 'VCPU' %}</th>
<th scope="col">{% trans 'Memory' %}</th>
<th scope="col" data-sortable="false">{% trans 'Actions' %}</th>
</tr>
</thead>
<tbody class="searchable">
{% for host, inst in all_host_vms.items %}
{% for inst, vm in inst.items %}
{% for instance in instances %}
{% if instance.compute.status is True %}
<tr>
<td><a href="{% url 'instances:instance' host.0 inst %}">{{ inst }}</a><br><small><em>{{ info.title }}</em></small></td>
<td><a href="{% url 'overview' host.0 %}">{{ host.1 }}</a><br><small><em>{% if info.userinstances.count > 0 %}{{ info.userinstances.first_user.user.username }}{% if info.userinstances.count > 1 %} (+{{ info.userinstances.count|add:"-1" }}){% endif %}{% endif %}</em></small></td>
<td>
{% if vm.status == 1 %}<span class="text-success">{% trans "Active" %}</span>{% endif %}
{% if vm.status == 5 %}<span class="text-danger">{% trans "Off" %}</span>{% endif %}
{% if vm.status == 3 %}<span class="text-warning">{% trans "Suspend" %}</span>{% endif %}
<a class="text-secondary" href="{% url 'instances:instance' instance.id %}">
{{ instance.name }}
</a><br>
<small><em>{{ instance.title }}</em></small>
</td>
<td>{{ vm.vcpu }}</td>
<td>{{ vm.memory|filesizeformat }}</td>
{% if request.user.is_superuser %}
<td>
<a href="{% url 'overview' instance.compute.id %}">{{ instance.compute.name }}</a><br>
<small><em>
{% if instance.userinstance_set.all.count > 0 %}
{{ instance.userinstance_set.all.0.user }}
{% if instance.userinstance_set.all.count > 1 %}
(+{{ instance.userinstance_set.all.count|add:"-1" }})
{% endif %}
{% endif %}
</em></small>
</td>
{% endif %}
<td>
{% if instance.proxy.instance.info.0 == 1 %}<span
class="text-success">{% trans "Active" %}</span>{% endif %}
{% if instance.proxy.instance.info.0 == 5 %}<span
class="text-danger">{% trans "Off" %}</span>{% endif %}
{% if instance.proxy.instance.info.0 == 3 %}<span
class="text-warning">{% trans "Suspended" %}</span>{% endif %}
</td>
<td>{{ instance.proxy.instance.info.3 }}</td>
<td>{{ instance.cur_memory }} MB</td>
<td class="text-nowrap">
{% include "instance_actions.html" %}
{% include 'instance_actions.html' %}
</td>
</tr>
{% endfor %}
{% endif %}
{% endfor %}
</tbody>
</table>

View file

@ -17,7 +17,7 @@
<a class="nav-link" href="{% url 'instances' compute.id %}"><i class="fa fa-desktop"></i> {% trans "Instances" %}</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="{% url 'instances:instance' compute.id vname %}"><i class="fa fa-hdd-o"></i> {{ vname }}</a>
<a class="nav-link" href="{% url 'instances:instance' instance.id %}"><i class="fa fa-hdd-o"></i> {{ instance.name }}</a>
</li>
</ul>
</div>

View file

@ -0,0 +1,34 @@
{% load i18n %}
{% load bootstrap4 %}
{% if request.user.is_superuser %}
<button href="#addFlavor" type="button" class="btn btn-success btn-header float-right" data-toggle="modal">
<span class="fa fa-plus" aria-hidden="true"></span>
</button>
<!-- Modal Flavor -->
<div class="modal fade" id="addFlavor" tabindex="-1" role="dialog" aria-labelledby="addFlavorLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{% trans "Add New Flavor" %}</h4>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<form method="post" role="form" action="{% url 'instances:flavor_create' %}" id="flavor-create-form">
{% csrf_token %}
{% bootstrap_form flavor_form layout='horizontal' %}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
{% trans "Close" %}
</button>
<button form="flavor-create-form" type="submit" class="btn btn-primary">
{% trans "Add" %}
</button>
</div>
</div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog -->
</div> <!-- /.modal -->
{% endif %}

View file

@ -1,11 +1,10 @@
{% load i18n %}
{% if request.user.is_superuser %}
<a href="#AddInstance" type="button" class="btn btn-success btn-header float-right" data-toggle="modal">
<a href="#AddInstance" type="button" class="btn btn-success btn-header float-right" data-toggle="modal">
<span class="fa fa-plus" aria-hidden="true"></span>
</a>
</a>
<!-- Modal pool -->
<div class="modal fade" id="AddInstance" tabindex="-1" role="dialog" aria-labelledby="AddNetPoolLabel" aria-hidden="true">
<!-- Modal pool -->
<div class="modal fade" id="AddInstance" tabindex="-1" role="dialog" aria-labelledby="AddNetPoolLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
@ -13,13 +12,14 @@
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<form method="post" aria-label="Select compute for instance create form"> {% csrf_token %}
<form method="post" aria-label="Select compute for instance create form">
{% csrf_token %}
<div class="form-group row">
<label class="col-sm-4 col-form-label">{% trans "Compute" %}</label>
<div class="col-sm-6">
<select class="custom-select" id="compute_select">
<option>{% trans "Please select" %}</option>
{% for compute in computes_data %}
{% for compute in computes %}
<option {% if compute.status is not True %} class="font-italic text-muted" {% else %} value="{{ compute.id }}" {% endif %}>{{ compute.name }}</option>
{% empty %}
<option value="None">{% trans "None" %}</option>
@ -45,5 +45,4 @@
</div>
</div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog -->
</div> <!-- /.modal -->
{% endif %}
</div> <!-- /.modal -->

View file

@ -143,9 +143,9 @@
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;
//create_machine_url = "/computes/" + compute + "/create/archs/" + arch + "/machines/" + machine;
url = "{% url 'instances:create_instance' compute.id 'x86_64' 'pc' %}".replace(/x86_64/, arch).replace(/pc/, machine);
window.location.href = url;//create_machine_url;
}
</script>

View file

@ -1,6 +1,7 @@
{% extends "base.html" %}
{% load i18n %}
{% load staticfiles %}
{% load icons %}
{% block title %}{% trans "Create new instance" %}{% endblock %}
{% block style %}
<link href="{% static "css/bootstrap-multiselect.css" %}" rel="stylesheet">
@ -74,7 +75,7 @@
{% include 'create_flav_block.html' %}
<h3 class="page-header">{% trans "Create from flavor" %}</h3>
<div class="table-responsive">
<table class="table table-bordered table-hover">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">#</th>
@ -286,10 +287,10 @@
</a>
</td>
<td style="width:5px;">
<form action="" method="post" role="form" aria-label="Delete flavor form">{% csrf_token %}
<input type="hidden" name="flavor" value="{{ flavor.id }}">
<form action="{% url 'instances:flavor_delete' flavor.id %}" method="post" role="form" aria-label="Delete flavor form">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-secondary" name="delete_flavor" onclick="return confirm('{% trans "Are you sure?" %}')">
<span class="fa fa-trash"></span>
{% icon 'trash' %}
</button>
</form>
</td>
@ -872,7 +873,7 @@
<script>
function goto_compute() {
let compute = {{ compute.id }}
window.location.href = "{% url 'create_instance_select_type' 1 %}".replace(1, compute);
window.location.href = "{% url 'instances:create_instance_select_type' 1 %}".replace(1, compute);
}
</script>
{% endif %}

View file

@ -52,7 +52,7 @@
<div class="form-group row">
<label class="col-sm-4 col-form-label">{% trans 'Bus' %}</label>
<div class="col-sm-8">
<select class="custom-select" name="vol_bus" {% if status != 5 %} disabled {% endif %}>
<select class="custom-select" name="vol_bus" {% if instance.status != 5 %} disabled {% endif %}>
{% for bus in bus_host %}
<option value="{{ bus }}" {% if bus == disk.bus %}selected{% endif %}>{{ bus }}</option>
{% endfor %}
@ -78,7 +78,7 @@
<label class="col-sm-4 col-form-label">{% trans 'Cache mode' %}</label>
<div class="col-sm-8">
<select class="custom-select" name="vol_cache">
{% for key, val in cache_modes %}
{% for key, val in instance.cache_modes %}
<option value="{{ key }}" {% if key == disk.cache %}selected{% endif %}>{{ val }}</option>
{% endfor %}
</select>
@ -88,7 +88,7 @@
<label class="col-sm-4 col-form-label">{% trans 'IO mode' %}</label>
<div class="col-sm-8">
<select class="custom-select" name="vol_io_mode">
{% for key, val in io_modes %}
{% for key, val in instance.io_modes %}
<option value="{{ key }}" {% if key == disk.io %}selected{% endif %}>{{ val }}</option>
{% endfor %}
</select>
@ -98,7 +98,7 @@
<label class="col-sm-4 col-form-label">{% trans 'Discard mode' %}</label>
<div class="col-sm-8">
<select class="custom-select" name="vol_discard_mode">
{% for key, val in discard_modes %}
{% for key, val in instance.discard_modes %}
<option value="{{ key }}" {% if key == disk.discard %}selected{% endif %}>{{ val }}</option>
{% endfor %}
</select>
@ -108,7 +108,7 @@
<label class="col-sm-4 col-form-label">{% trans 'Detect zeroes' %}</label>
<div class="col-sm-8">
<select class="custom-select" name="vol_detect_zeroes">
{% for key, val in detect_zeroes_modes %}
{% for key, val in instance.detect_zeroes_modes %}
<option value="{{ key }}" {% if key == disk.detect_zeroes %}selected{% endif %}>{{ val }}</option>
{% endfor %}
</select>

File diff suppressed because it is too large Load diff

View file

@ -1,61 +1,44 @@
{% load i18n %}
{% load icons %}
<form action="" method="post" role="form" aria-label="Shortcut instance action form">{% csrf_token %}
<input type="hidden" name="name" value="{{ inst }}"/>
<input type="hidden" name="compute_id" value="{{ host.0 }}"/>
{% if vm.status == 5 %}
{% if vm.is_template %}
<button class="btn btn-sm btn-secondary" type="button" name="clone" title="{% trans "Clone" %}" onclick="goto_instance_clone({{ host.0 }}, '{{ inst }}');">
<span class="fa fa-clone"></span>
</button>
{% if instance.proxy.instance.info.0 == 5 %}
{% if instance.is_template %}
<a href="{% url 'instances:instance' instance.id %}#clone" class="btn btn-sm btn-secondary" title="{% trans "Clone" %}">
{% icon 'clone' %}
</a>
{% else %}
<button class="btn btn-sm btn-secondary" type="submit" name="poweron" title="{% trans "Power On" %}">
<span class="fa fa-play"></span>
</button>
<a class="btn btn-sm btn-secondary" href="{% url 'instances:poweron' instance.id %}" title="{% trans "Power On" %}">
{% icon 'play' %}
</a>
{% endif %}
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Suspend" %}" disabled>
<span class="fa fa-pause"></span>
</button>
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Power Off" %}" disabled>
<span class="fa fa-power-off"></span>
</button>
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Power Cycle" %}" disabled>
<span class="fa fa-refresh"></span>
</button>
<a class="btn btn-sm btn-secondary disabled" title="{% trans "Suspend" %}">{% icon 'pause' %}</a>
<a class="btn btn-sm btn-secondary disabled" title="{% trans "Power Off" %}">{% icon 'power-off' %}</a>
<a class="btn btn-sm btn-secondary disabled" title="{% trans "Power Cycle" %}">{% icon 'refresh' %}</a>
<button class="btn btn-sm btn-secondary disabled" title="{% trans "VNC Console" %}" disabled>
<span class="fa fa-eye"></span>
</button>
{% endif %}
{% if vm.status == 3 %}
<button class="btn btn-sm btn-secondary" type="submit" name="resume" title="{% trans "Resume" %}">
<span class="fa fa-play"></span>
</button>
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Suspend" %}" disabled>
<span class="fa fa-pause"></span>
</button>
<button class="btn btn-sm btn-secondary" type="submit" name="powerforce" title="{% trans "Force Off" %}" onclick="return confirm('Are you sure to force it down?')">
<span class="fa fa-power-off"></span>
</button>
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Power Cycle" %}" disabled>
<span class="fa fa-refresh"></span>
</button>
{% if instance.proxy.instance.info.0 == 3 %}
<a class="btn btn-sm btn-secondary" href="{% url 'instances:resume' instance.id %}" title="{% trans "Resume" %}">
{% icon 'play' %}
</a>
<a class="btn btn-sm btn-secondary disabled" title="{% trans "Suspend" %}">{% icon 'pause' %}</a>
<a class="btn btn-sm btn-secondary" href="{% url 'instances:force_off' instance.id %}" title="{% trans "Force Off" %}">
{% icon 'power-off' %}
</a>
<a class="btn btn-sm btn-secondary disabled" title="{% trans "Power Cycle" %}">{% icon 'refresh' %}</a>
<button class="btn btn-sm btn-secondary disabled" title="{% trans "VNC Console" %}" disabled>
<span class="fa fa-eye"></span>
</button>
{% endif %}
{% if vm.status == 1 %}
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Power On" %}" disabled>
<span class="fa fa-play"></span>
</button>
<button class="btn btn-sm btn-secondary" type="submit" name="suspend" title="{% trans "Suspend" %}">
<span class="fa fa-pause"></span>
</button>
<button class="btn btn-sm btn-secondary" type="submit" name="poweroff" title="{% trans "Power Off" %}" onclick="return confirm('Are you sure?')">
<span class="fa fa-power-off"></span>
</button>
<button class="btn btn-sm btn-secondary" type="submit" name="powercycle" title="{% trans "Power Cycle" %}" onclick="return confirm('Are you sure?')">
<span class="fa fa-refresh"></span>
</button>
<button class="btn btn-sm btn-secondary" type="button" onclick='open_console("{{ host.0 }}-{{ vm.uuid }}")' title="{% trans "Console" %}">
{% if instance.proxy.instance.info.0 == 1 %}
<a class="btn btn-sm btn-secondary disabled" title="{% trans "Power On" %}">{% icon 'play' %}</a>
<a class="btn btn-sm btn-secondary" href="{% url 'instances:suspend' instance.id %}"
title="{% trans "Suspend" %}">{% icon 'pause' %}</a>
<a class="btn btn-sm btn-secondary" href="{% url 'instances:poweroff' instance.id %}">{% icon 'power-off' %}</a>
<a class="btn btn-sm btn-secondary" href="{% url 'instances:powercycle' instance.id %}">{% icon 'refresh' %}</a>
<button class="btn btn-sm btn-secondary" type="button" onclick='open_console("{{ instance.compute.id }}-{{ instance.get_uuid }}")'
title="{% trans "Console" %}">
<span class="fa fa-eye"></span>
</button>
{% endif %}

View file

@ -1,135 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% load staticfiles %}
{% block title %}{% trans "Instances" %} - {{ compute.name }}{% endblock %}
{% block style %}
<link rel="stylesheet" href="{% static "css/sortable-theme-bootstrap.css" %}" />
{% endblock %}
{% block content %}
<!-- Page Heading -->
<div class="row">
<div class="col-lg-12">
{% if request.user.is_superuser %}
<a href="{% url 'create_instance_select_type' compute.id %}" type="button" class="btn btn-success btn-header float-right">
<span class="fa fa-plus" aria-hidden="true"></span>
</a>
{% endif %}
{% if all_host_vms or all_user_vms %}
<div class="float-right search">
<input id="filter" class="form-control" type="text" placeholder="{% trans 'Search' %}">
</div>
{% endif %}
<h2 class="page-header">{{ compute.name }}</h2>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb bg-light shadow-sm">
<li class="breadcrumb-item active">
<a href="{% url 'overview' compute.id %}"><i class="fa fa-dashboard"></i> {% trans "Overview" %}</a>
</li>
<li class="breadcrumb-item">
<span class="font-weight-bold"><i class="fa fa-server"></i> {% trans "Instances" %}</span>
</li>
<li class="breadcrumb-item">
<a href="{% url 'storages' compute.id %}"><i class="fa fa-hdd-o"></i> {% trans "Storages" %}</a>
</li>
<li class="breadcrumb-item">
<a href="{% url 'networks' compute.id %}"><i class="fa fa-sitemap"></i> {% trans "Networks" %}</a>
</li>
<li class="breadcrumb-item">
<a href="{% url 'interfaces' compute.id %}"><i class="fa fa-wifi"></i> {% trans "Interfaces" %}</a>
</li>
<li class="breadcrumb-item">
<a href="{% url 'nwfilters' compute.id %}"><i class="fa fa-filter"></i> {% trans "NWFilters" %}</a>
</li>
<li class="breadcrumb-item">
<a href="{% url 'secrets' compute.id %}"><i class="fa fa-key"></i> {% trans "Secrets" %}</a>
</li>
</ol>
</nav>
</div>
</div>
<!-- /.row -->
{% include 'errors_block.html' %}
{% include 'messages_block.html' %}
<div class="row">
{% if not all_host_vms %}
<div class="col-lg-12">
<div class="alert alert-warning alert-dismissable fade show">
<i class="fa fa-exclamation-triangle"></i> <strong>{% trans "Warning" %}:</strong> {% trans "Hypervisor doesn't have any Instances" %}
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
</div>
</div>
{% else %}
<div class="col-lg-12">
<table class="table table-hover sortable-theme-bootstrap" data-sortable>
<thead>
<tr>
<th scope="col">{% trans 'Name' %}<br>{% trans 'Description' %}</th>
<th scope="col" class="d-none d-md-table-cell">{% trans 'User' %}</th>
<th scope="col">{% trans 'Status' %}</th>
<th scope="col">{% trans 'VCPU' %}</th>
<th scope="col">{% trans 'Memory' %}</th>
<th scope="col" style="width:200px;" data-sortable="false">{% trans 'Actions' %}</th>
</tr>
</thead>
<tbody class="searchable">
{% for host, insts in all_host_vms.items %}
{% for inst, vm in insts.items %}
<tr>
<td><a class="text-secondary" href="{% url 'instances:instance' host.0 inst %}">{{ inst }}</a><br><small><em>{{ vm.title }}</em></small></td>
<td class="d-none d-md-table-cell"><small><em>{% if vm.userinstances.count > 0 %}{{ vm.userinstances.first_user.user.username }}{% if vm.userinstances.count > 1 %} (+{{ vm.userinstances.count|add:"-1" }}){% endif %}{% endif %}</em></small></td>
<td>
{% if vm.status == 1 %}<span class="text-success">{% trans "Active" %}</span>{% endif %}
{% if vm.status == 5 %}<span class="text-danger">{% trans "Off" %}</span>{% endif %}
{% if vm.status == 3 %}<span class="text-warning">{% trans "Suspend" %}</span>{% endif %}
</td>
<td>{{ vm.vcpu }}</td>
<td>{{ vm.memory|filesizeformat }}</td>
<td class="text-nowrap">
{% include 'instance_actions.html' %}
</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
{% endblock %}
{% block script %}
<script src="{% static "js/sortable.min.js" %}"></script>
<script>
function open_console(uuid) {
window.open("{% url 'console' %}?token=" + uuid, "", "width=850,height=685");
}
</script>
<script>
function filter_table() {
var rex = new RegExp($(this).val(), 'i');
$('.searchable tr').hide();
$('.searchable tr').filter(function () {
return rex.test($(this).text());
}).show();
Cookies.set("instances_filter", $(this).val(), { expires: 1 });
}
$(document).ready(function () {
instances_filter_cookie = Cookies.get("instances_filter");
if (instances_filter_cookie) {
$('#filter').val(instances_filter_cookie);
$('#filter').each(filter_table);
}
(function ($) {
$('#filter').keyup(filter_table)
}(jQuery));
});
</script>
<script>
function goto_instance_clone(compute, instance) {
window.location = "/instances/" + compute + "/" + instance + "/#clone";
}
</script>
{% endblock %}

View file

@ -0,0 +1,114 @@
{% load i18n %}
<div role="tabpanel" class="tab-pane" id="access" aria-label="Instance access options">
<div role="tabpanel">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link text-secondary active" href="#vnconsole" aria-controls="vnconsole" role="tab" data-toggle="tab">
{% trans "Console" %}
</a>
</li>
{% if app_settings.SHOW_ACCESS_ROOT_PASSWORD == 'True' %}
<li class="nav-item">
<a class="nav-link text-secondary" href="#rootpasswd" aria-controls="rootpasswd" role="tab" data-toggle="tab">
{% trans "Root Password" %}
</a>
</li>
{% endif %}
{% if app_settings.SHOW_ACCESS_SSH_KEYS == 'True' %}
<li class="nav-item">
<a class="nav-link text-secondary" href="#sshkeys" aria-controls="sshkeys" role="tab" data-toggle="tab">
{% trans "SSH Keys" %}
</a>
</li>
{% endif %}
{% if instance.status == 1 %}
<li class="nav-item">
<a class="nav-link text-secondary" href="#vdiconsole" aria-controls="vdiconsole" role="tab" data-toggle="tab">
{% trans "VDI" %}
</a>
</li>
{% endif %}
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="vnconsole">
<p>{% trans "This action opens a new window with a VNC connection to the console of the instance." %}</p>
{% if instance.status == 1 %}
<!-- Split button -->
<div class="btn-group float-right">
<button type="button" id="consoleBtnGroup" class="btn btn-lg btn-success" onclick="open_console('lite')">{% trans 'Console' %}</button>
<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="sr-only">{% trans 'Toggle Dropdown' %}</span>
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#" title="{% trans "Console port" %}: {{ instance.console_port }}" onclick="open_console('lite')">{% trans "Console" %} - {% trans "Lite" %}</a>
<a class="dropdown-item" href="#" title="{% trans "Console port" %}: {{ instance.console_port }}" onclick="open_console('full')">{% trans "Console" %} - {% trans "Full" %}</a>
</div>
</div>
{% else %}
<button class="btn btn-lg btn-success float-right disabled">{% trans "Console" %}</button>
{% endif %}
<div class="clearfix"></div>
</div>
{% if app_settings.SHOW_ACCESS_SSH_KEYS == 'True' %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="rootpasswd">
<p>{% trans "You need shut down your instance and enter a new root password." %}</p>
<form action="{% url 'instances:rootpasswd' instance.id %}" class="form-inline" method="post" role="form" aria-label="Add root password to instance form">
{% csrf_token %}
<div class="form-group row">
<div class="col-sm-12">
<input type="text" class="form-control-lg" name="passwd" placeholder="{% trans "Enter Password" %}" maxlength="24">
</div>
</div>
{% if instance.status == 5 %}
<input type="submit" class="btn btn-lg btn-success float-right" name="rootpasswd" value="{% trans "Reset Root Password" %}">
{% else %}
<button class="btn btn-lg btn-success float-right disabled">{% trans "Reset Root Password" %}</button>
{% endif %}
</form>
<div class="clearfix"></div>
</div>
{% endif %}
{% if app_settings.SHOW_ACCESS_SSH_KEYS == 'True' %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="sshkeys">
<p>{% trans "You need shut down your instance and choose your public key." %}</p>
<form action="{% url 'instances:add_public_key' instance.id %}" class="form-inline" method="post" role="form" aria-label="Add public key to instance form">
{% csrf_token %}
<div class="form-group row">
<div class="col-sm-12">
<select name="sshkeyid" class="form-control-lg keyselect">
{% if publickeys %}
{% for key in publickeys %}
<option value="{{ key.id }}">{{ key.keyname }}</option>
{% endfor %}
{% else %}
<option value="None">{% trans "None" %}</option>
{% endif %}
</select>
</div>
</div>
{% if instance.status == 5 %}
<input type="submit" class="btn btn-lg btn-success float-right" name="addpublickey" value="{% trans "Add Public Key" %}">
{% else %}
<button class="btn btn-lg btn-success float-right disabled">{% trans "Add Public Key" %}</button>
{% endif %}
</form>
<div class="clearfix"></div>
</div>
{% endif %}
{% if instance.status == 1 %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="vdiconsole">
<p>{% trans "This action opens a remote viewer with a connection to the console of the instance." %}</p>
<div class="input-group">
<input type="text" class="input-lg disabled form-control" disabled id="vdi_url_input"/>
<span class="input-group-append">
<a href="#" class="btn btn-success" id="vdi_url" >{% trans "VDI" %}</a>
</span>
</div>
<div class="clearfix"></div>
</div>
{% endif %}
</div>
</div>
</div>

View file

@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}
{% trans "Confirm Destroy" %}
{% endblock title %}
{% block page_header %}
{% trans "Destroy instance" %} {{ instance }}
{% endblock page_header %}
{% block content %}
{% if request.user.is_superuser or userinstance.is_delete %}
{% if instance.status == 3 %}
<div class="alert alert-danger">
{% trans "Instance is suspended, cannot destroy!" %}
</div>
{% else %}
<div class="alert alert-danger">
{% trans "This action is irreversible!" %}
</div>
<form action="{% url 'instances:destroy' instance.id %}" class="form" method="post" role="form" id="delete_form">
{% csrf_token %}
<div class="ml-3 form-row">
<div class="custom-control custom-switch">
<input class="custom-control-input" type="checkbox" name="delete_disk" value="true" checked id="delete_disk">
<label class="custom-control-label font-weight-bold" for="delete_disk">{% trans "Remove Instance's data" %}</label>
</div>
</div>
{% if instance.nvram %}
<div class="ml-3 form-row">
<div class="custom-control custom-switch">
<input class="custom-control-input" type="checkbox" name="delete_nvram" value="true" id="delete_nvram" checked>
<label class="custom-control-label font-weight-bold" for="delete_nvram">
{% trans "Remove Instance's NVRAM" %}
</label>
</div>
</div>
{% endif %}
<button type="submit" class="btn btn-lg btn-success float-right" name="delete">
{% trans "Destroy" %}
</button>
</form>
{% endif %}
{% else %}
<div class="alert alert-danger">
{% trans "You cannot destroy instance!" %}
</div>
{% endif %}
{% endblock content %}

View file

@ -0,0 +1,28 @@
{% load i18n %}
<div role="tabpanel" class="tab-pane" id="undefine">
<div role="tabpanel">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist" aria-label="Instance destroy menu">
<li class="nav-item">
<a class="nav-link active" href="#destroy" aria-controls="destroy" role="tab" data-toggle="tab">
{% trans "Destroy Instance" %}
</a>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="destroy">
{% if request.user.is_superuser or userinstance.is_delete %}
{% if instance.status == 3 %}
<a class="btn btn-lg btn-success disabled float-right">{% trans "Destroy" %}</a>
{% else %}
<a href="{% url 'instances:destroy' instance.id %}" class="btn btn-lg btn-success float-right">{% trans "Destroy" %}</a>
{% endif %}
{% else %}
<button class="btn btn-lg btn-success disabled float-right" name="delete">{% trans "Destroy" %}</button>
{% endif %}
<div class="clearfix"></div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,131 @@
{% load i18n %}
<div role="tabpanel" class="tab-pane active" id="power">
<div role="tabpanel">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist" aria-label="Instance power actions">
{% if instance.status == 1 %}
<li class="nav-item">
<a class="nav-link text-secondary active" href="#poweroff" aria-controls="poweroff" role="tab" data-toggle="tab">
{% trans "Power Off" %}
</a>
</li>
<li class="nav-item">
<a class="nav-link text-secondary" href="#powercycle" aria-controls="powercycle" role="tab" data-toggle="tab">
{% trans "Power Cycle" %}
</a>
</li>
<li class="nav-item">
<a class="nav-link text-secondary" href="#powerforce" aria-controls="powerforce" role="tab" data-toggle="tab">
{% trans "Force Off" %}
</a>
</li>
{% if request.user.is_superuser %}
<li class="nav-item">
<a class="nav-link text-secondary" href="#suspend" aria-controls="suspend" role="tab" data-toggle="tab">
{% trans "Suspend" %}
</a>
</li>
{% endif %}
{% endif %}
{% if instance.status == 3 %}
{% if request.user.is_superuser %}
<li class="nav-item">
<a class="nav-link text-secondary" href="#resume" aria-controls="resume" role="tab" data-toggle="tab">
{% trans "Resume" %}
</a>
</li>
<li class="nav-item">
<a class="nav-link text-secondary" href="#powerforce" aria-controls="powerforce" role="tab" data-toggle="tab">
{% trans "Force Off" %}
</a>
</li>
{% endif %}
{% endif %}
{% if instance.status == 5 %}
<li class="nav-item">
<a class="nav-link text-secondary active" href="#boot" aria-controls="boot" role="tab" data-toggle="tab">
{% trans "Power On" %}
</a>
</li>
{% endif %}
</ul>
<!-- Tab panes -->
<div class="tab-content">
{% if instance.status == 1 %}
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="poweroff">
<p>{% trans "This action sends an ACPI shutdown signal to the instance." %}</p>
<form action="{% url 'instances:poweroff' instance.id %}" method="post" role="form" aria-label0="Power off instance form">
{% csrf_token %}
<input type="submit" name="poweroff" class="btn btn-lg btn-success float-right" value="{% trans "Power Off" %}">
<div class="clearfix"></div>
</form>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="powercycle">
<p>{% trans "This action forcibly powers off and start the instance and may cause data corruption." %}</p>
<form action="{% url 'instances:powercycle' instance.id %}" method="post" role="form" aria-label="Power cycle instance form">{% csrf_token %}
<input type="submit" name="powercycle" class="btn btn-lg btn-success float-right" value="{% trans "Power Cycle" %}">
<div class="clearfix"></div>
</form>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="powerforce">
<p>{% trans "This action forcibly powers off the instance and may cause data corruption." %}</p>
<form action="{% url 'instances:force_off' instance.id %}" method="post" role="form" aria-label="Force to shotdown instance form">
{% csrf_token %}
<input type="submit" name="powerforce" class="btn btn-lg btn-success float-right" value="{% trans "Force Off" %}">
<div class="clearfix"></div>
</form>
</div>
{% if request.user.is_superuser %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="suspend">
<p>{% trans "This action suspends the instance." %}</p>
<form action="{% url 'instances:suspend' instance.id %}" method="post" role="form" aria-label="Suspend instance form">{% csrf_token %}
<input type="submit" name="suspend" class="btn btn-lg btn-success float-right" value="{% trans "Suspend" %}">
<div class="clearfix"></div>
</form>
</div>
{% endif %}
{% endif %}
{% if instance.status == 3 %}
{% if request.user.is_superuser %}
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="resume">
<p>{% trans "This action restore the instance after suspend." %}</p>
<form action="{% url 'instances:resume' instance.id %}" method="post" role="form" aria-label="Resume instance from suspension form">{% csrf_token %}
<input type="submit" name="resume" class="btn btn-lg btn-success float-right" value="{% trans "Resume" %}">
<div class="clearfix"></div>
</form>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="powerforce">
<p>{% trans "This action forcibly powers off the instance and may cause data corruption." %}</p>
<form action="{% url 'instances:force_off' instance.id %}" method="post" role="form" aria-label="Force to shutdown form">{% csrf_token %}
<input type="submit" name="powerforce" class="btn btn-lg btn-success float-right" value="{% trans "Force Off" %}">
<div class="clearfix"></div>
</form>
</div>
{% else %}
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="resume">
<p>{% trans "Administrator blocked your instance." %}</p>
<form action="{% url 'instances:resume' instance.id %}" method="post" role="form" aria-label="Resume instance form">{% csrf_token %}
<button class="btn btn-lg btn-success disabled float-right">{% trans "Resume" %}</button>
<div class="clearfix"></div>
</form>
</div>
{% endif %}
{% endif %}
{% if instance.status == 5 %}
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="boot">
<p>{% trans "Click on Power On button to start this instance." %}</p>
<form action="{% url 'instances:poweron' instance.id %}" method="post" role="form" aria-label="Start instance form">
{% csrf_token %}
{% if instance.is_template %}
<p>{% trans "Template instance cannot be started." %}</p>
<input type="submit" name="poweron" class="btn btn-lg btn-success float-right disabled" value="{% trans "Power On" %}">
{% else %}
<input type="submit" name="poweron" class="btn btn-lg btn-success float-right" value="{% trans "Power On" %}">
{% endif %}
<div class="clearfix"></div>
</form>
</div>
{% endif %}
</div>
</div>
</div>

View file

@ -0,0 +1,164 @@
{% load i18n %}
<div role="tabpanel" class="tab-pane" id="resize">
<div role="tabpanel">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist" aria-label="Instance resize options">
<li class="nav-item">
<a class="nav-link text-secondary active" href="#resizevm_cpu" aria-controls="resizevm_cpu" role="tab" data-toggle="tab">
{% trans "CPU" %}
</a>
</li>
<li class="nav-item">
<a class="nav-link text-secondary" href="#resizevm_mem" aria-controls="resizevm_mem" role="tab" data-toggle="tab">
{% trans "Memory" %}
</a>
</li>
<li class="nav-item">
<a class="nav-link text-secondary" href="#resizevm_disk" aria-controls="resizevm_disk" role="tab" data-toggle="tab">
{% trans "Disk" %}
</a>
</li>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="resizevm_cpu">
{% if request.user.is_superuser or request.user.is_staff or userinstance.is_change %}
{% if instance.status == 5 or not instance.vcpus %}
<form action="{% url 'instances:resizevm_cpu' instance.id %}" method="post" role="form" aria-label="Resize instance cpu form">{% csrf_token %}
<p class="font-weight-bold">{% trans "Logical host CPUs" %} : {{ vcpu_host }}</p>
<div class="form-group row">
<label class="col-sm-4 col-form-label"> {% trans "Current Allocation" %}</label>
<div class="col-sm-4">
<select name="cur_vcpu" class="custom-select">
{% for cpu in instance.vcpu_range %}
{% if instance.cur_vcpu %}
<option value="{{ cpu }}" {% if cpu == instance.cur_vcpu %}selected{% endif %}>{{ cpu }}</option>
{% else %}
<option value="{{ cpu }}" {% if cpu == instance.vcpu %}selected{% endif %}>{{ cpu }}</option>
{% endif %}
{% endfor %}
</select>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">{% trans "Maximum Allocation" %}</label>
<div class="col-sm-4">
<select name="vcpu" class="custom-select">
{% for cpu in instance.vcpu_range %}
<option value="{{ cpu }}" {% if cpu == instance.vcpu %}selected{% endif %}>{{ cpu }}</option>
{% endfor %}
</select>
</div>
</div>
{% if instance.status == 5 %}
<button type="submit" class="btn btn-lg btn-success float-right" name="resizevm_cpu">{% trans "Resize" %}</button>
{% else %}
<button class="btn btn-lg btn-success float-right disabled">{% trans "Resize" %}</button>
{% endif %}
</form>
<div class="clearfix"></div>
{% else %}
<p class="font-weight-bold">{% trans "Logical Instance Active/Maximum CPUs" %} : {{ instance.cur_vcpu }} / {{ instance.vcpu }} </p>
<div class="col-sm-3"></div>
<div class="col-sm-6">
{% for id, vcpu in instance.vcpus.items %}
<form action="{% url 'instances:set_vcpu' instance.id %}" method="post" role="form" aria-label="Resize instance cpu form">{% csrf_token %}
<div class="col-sm-3">
<input name="id" value="{{ id }}" hidden/>
{% if vcpu.enabled == 'yes' and vcpu.hotpluggable == "yes" %}
<button type="submit" class="btn btn-block btn-success" value="False" name="set_vcpu" title="{% trans "Disable" %}">{{ id }}</button>
{% elif vcpu.enabled == 'yes' and vcpu.hotpluggable == "no" %}
<button type="button" class="btn btn btn-block btn-info" title="{% trans "Constant" %}">{{ id }}</button>
{% else %}
<button type="submit" class="btn btn btn-block btn-secondary" value="True" name="set_vcpu" title="{% trans "Enable" %}">{{ id }}</button>
{% endif %}
</div>
</form>
{% endfor %}
</div>
<div class="col-sm-3"></div>
{% endif %}
{% else %}
{% trans "You don't have permission for resizing instance" %}
<button class="btn btn-lg btn-success float-right disabled">{% trans "Resize" %}</button>
{% endif %}
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="resizevm_mem">
{% if request.user.is_superuser or request.user.is_staff or userinstance.is_change %}
<form action="{% url 'instances:resize_memory' instance.id %}" method="post" role="form" aria-label="Resize instance memory form">
{% csrf_token %}
<p class="font-weight-bold">{% trans "Total host memory" %}: {{ memory_host|filesizeformat }}</p>
<div class="form-group row">
<label class="col-sm-4 col-form-label">{% trans "Current Allocation" %} ({% trans "MB" %})</label>
<div class="col-sm-4 js-custom__container">
<select name="cur_memory" class="custom-select js-custom__toggle">
{% for mem in memory_range %}
<option value="{{ mem }}" {% if mem == instance.cur_memory %}selected{% endif %}>{{ mem }}</option>
{% endfor %}
</select>
<input type="text" name="cur_memory_custom" class="custom-select js-custom__toggle" style="display: none" />
<small><input type="checkbox" class="js-custom__checkbox" /> {% trans "Custom value" %}</small>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">
{% trans "Maximum Allocation" %} ({% trans "MB" %})
</label>
<div class="col-sm-4 js-custom__container">
<select name="memory" class="form-control js-custom__toggle">
{% for mem in memory_range %}
<option value="{{ mem }}"
{% if mem == instance.memory %}selected{% endif %}>{{ mem }}</option>
{% endfor %}
</select>
<input type="text" name="memory_custom" class="form-control js-custom__toggle" style="display: none" />
<small><input type="checkbox" class="js-custom__checkbox" /> {% trans "Custom value" %}</small>
</div>
</div>
<button type="submit" class="btn btn-lg btn-success float-right" name="resizevm_mem">{% trans "Resize" %}</button>
</form>
{% else %}
{% trans "You don't have permission for resizing instance" %}
<button class="btn btn-lg btn-success float-right disabled">{% trans "Resize" %}</button>
{% endif %}
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="resizevm_disk">
{% if request.user.is_superuser or request.user.is_staff or userinstance.is_change %}
<form action="{% url 'instances:resize_disk' instance.id %}" method="post" role="form" aria-label="Resize instance disk form">
{% csrf_token %}
<p class="font-weight-bold">{% trans "Disk allocation (GB)" %}:</p>
{% for disk in instance.disks %}
<div class="form-group row">
<label class="col-sm-4 col-form-label">{% trans "Current Allocation" %} ({{ disk.dev }})</label>
{% if disk.storage is None %}
<div class="col-sm-4 js-custom__container">
<div class="alert alert-danger">
{% trans "Error getting disk info" %}
</div>
</div>
{% else %}
<div class="col-sm-4 js-custom__container">
<input type="number" name="disk_size_{{ disk.dev }}" class="form-control" value="{% widthratio disk.size 1073741824 1 %}" />
</div>
{% endif %}
</div>
{% endfor %}
{% if instance.status == 5 %}
<button type="submit" class="btn btn-lg btn-success float-right" name="resizevm_disk">{% trans "Resize" %}</button>
{% else %}
<button class="btn btn-lg btn-success float-right disabled">{% trans "Resize" %}</button>
{% endif %}
</form>
{% else %}
{% trans "You don't have permission for resizing instance" %}
<button class="btn btn-lg btn-success float-right disabled">{% trans "Resize" %}</button>
{% endif %}
<div class="clearfix"></div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,904 @@
{% load i18n %}
{% load bootstrap4 %}
{% load icons %}
<div role="tabpanel" class="tab-pane" id="settings">
<div role="tabpanel">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist" aria-label="Instance settings">
{% if request.user.is_superuser %}
<li class="nav-item ">
<a class="nav-link text-secondary active" href="#boot_opt" aria-controls="boot" role="tab" data-toggle="tab">
{% trans "Boot" %}
</a>
</li>
<li class="nav-item">
<a class="nav-link text-secondary" href="#disks" aria-controls="disks" role="tab" data-toggle="tab">
{% trans "Disk" %}
</a>
</li>
{% endif %}
{% if request.user.is_superuser or request.user.is_staff or userinstance.is_vnc %}
<li class="nav-item">
<a class="nav-link text-secondary" href="#vncsettings" aria-controls="vncsettings" role="tab" data-toggle="tab">
{% trans "Console" %}
</a>
</li>
{% endif %}
{% if request.user.is_superuser %}
<li class="nav-item">
<a class="nav-link text-secondary" href="#network" aria-controls="network" role="tab" data-toggle="tab">
{% trans "Network" %}
</a>
</li>
{% endif %}
{% if perms.instances.clone_instances %}
<li class="nav-item">
<a class="nav-link text-secondary" href="#clone" aria-controls="clone" role="tab" data-toggle="tab">
{% trans "Clone" %}
</a>
</li>
{% endif %}
{% if request.user.is_superuser %}
<li class="nav-item">
<a class="nav-link text-secondary" href="#migrate" aria-controls="migrate" role="tab" data-toggle="tab">
{% trans "Migrate" %}
</a>
</li>
<li class="nav-item">
<a class="nav-link text-secondary" href="#xmledit" aria-controls="xmledit" role="tab" data-toggle="tab">
{% trans "XML" %}
</a>
</li>
{% endif %}
{% if perms.instances.clone_instances %}
<li class="nav-item">
<a class="nav-link text-secondary" href="#options" aria-controls="options" role="tab" data-toggle="tab">
{% trans "Options" %}
</a>
</li>
{% endif %}
{% if request.user.is_superuser %}
<li class="nav-item">
<a class="nav-link text-secondary" href="#users" aria-controls="users" role="tab" data-toggle="tab">
{% trans "Users" %}
</a>
</li>
{% endif %}
</ul>
<!-- Tab panes -->
<div class="tab-content">
{% if request.user.is_superuser %}
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="boot_opt">
<p class="font-weight-bold">{% trans 'Autostart' %}</p>
<div class="form-group row">
<div class="col-sm-12 text-center">
<p>{% trans "Autostart your instance when host server is power on " %}
{% if instance.autostart == 0 %}
<a class="btn btn-success" href="{% url 'instances:set_autostart' instance.id %}">{% trans "Enable" %}</a>
{% else %}
<a class="btn btn-danger" href="{% url 'instances:unset_autostart' instance.id %}">{% trans "Disable" %}</a>
{% endif %}
</p>
</div>
</div>
<p class="font-weight-bold">{% trans 'Boot Order' %}</p>
<div class="form-group row">
<div class="col-sm-12 text-center">
{% if instance.status == 5 %}
<p>{% trans "Enable Boot Menu for your instance when it starts up " %}
{% if instance.bootmenu == 0 %}
<form action="{% url 'instances:set_bootmenu' instance.id %}" method="post" role="form" aria-label="Enable/disable instance boot order form">{% csrf_token %}
<input type="submit" class="btn btn-success" name="set_bootmenu" title="{% trans 'Show boot menu' %}" value="{% trans "Enable" %}">
</form>
{% else %}
<form action="{% url 'instances:unset_bootmenu' instance.id %}" method="post" role="form" aria-label="Enable/disable instance boot order form">{% csrf_token %}
<input type="submit" class="btn btn-danger" name="unset_bootmenu" title="{% trans 'Hide boot menu' %}" value="{% trans "Disable" %}">
</form>
{% endif %}
{% else %}
{% if instance.bootmenu == 0 %}
<p>**** {% trans "Please shutdown instance to modify boot menu" %} ****</p>
{% endif %}
{% endif %}
</div>
</div>
{% if instance.bootmenu == 1 %}
<div class="d-flex justify-content-center">
<div class="col-sm-6 bg-light rounded shadow-sm">
{% for idx, val in instance.boot_order.items %}
<label>{{ idx|add:1 }}:{{ val.target }}, </label>
{% endfor %}
</div>
</div>
<form action="{% url 'instances:set_bootorder' instance.id %}" method="post" role="form" aria-label="Boot order edit form">{% csrf_token %}
<input id="bootorder" name="bootorder" hidden>
<div class="d-flex justify-content-center">
<div id="b_order" class="multipleselect border-0">
{% for disk in instance.disks %}
<label><input type="checkbox" name="disk:{{ disk.dev }}" value="disk:{{ disk.dev }}" onclick="set_orderlist($('#bootorder'))" />{{ disk.dev }} - {{ disk.image }}</label>
{% endfor %}
{% for cd in instance.media %}
<label><input type="checkbox" name="cdrom:{{ cd.dev }}" value="cdrom:{{ cd.dev }}" onclick="set_orderlist($('#bootorder'))"/>{{ cd.dev }} - {{ cd.image }}</label>
{% endfor %}
{% for net in instance.networks %}
<label><input type="checkbox" name="network:{{ net.mac }}" value="network:{{ net.mac }}" onclick="set_orderlist($('#bootorder'))"/>NIC - {{ net.mac|slice:"9:" }}</label>
{% endfor %}
</div>
<div>
<div class="row mt-4">
<a href="#" id="boot_order_up" class="btn btn-light shadow-sm"><span class="fa fa-arrow-up" title="{% trans 'up: move selected devices' %}"></span></a>
</div>
<div class="row mt-2">
<a href="#" id="boot_order_down" class="btn btn-light shadow-sm"><span class="fa fa-arrow-down" title="{% trans 'down: move selected devices' %}"></span></a>
</div>
</div>
</div>
<div class="d-flex justify-content-center">
<div class="col-sm-6">
<input type="submit" class="btn btn-success btn-block" name="set_bootorder" value="{% trans "Apply" %}">
</div>
</div>
</form>
{% endif %}
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="disks">
<form action="{% url 'instances:add_cdrom' instance.id %}" method="post" role="form" aria-label="Add CD-ROM form">{% csrf_token %}
<p class="font-weight-bold">
{% trans "Instance Media" %}
<button class="btn btn-success float-right"
type="submit" type="button"
title="{% trans 'Add CD-ROM' %}"
{% if instance.status != 5 %} disabled {% endif %}>
<span class="fa fa-plus"></span>
</button>
</p>
</form>
<div class="clearfix"></div>
{% for cd in instance.media %}
<div class="row mt-2">
<a class="col-sm-3 col-form-label"
name="details"
title="{% trans "Details" %}"
tabindex="0"
data-trigger="focus"
data-toggle="popover"
data-html="true"
data-content="<strong>{% trans 'Bus' %}:</strong> {{ cd.bus }} <br/>
<strong>{% trans 'Device' %}:</strong> {{ cd.dev }}">
{% trans "CD-ROM" %} {{ forloop.counter }}
</a>
<div class="col-sm-9">
{% if not cd.image %}
<form action="{% url 'instances:mount_iso' instance.id %}" method="post">
{% csrf_token %}
<div class="input-group">
<select name="media" class="form-control">
{% if instance.media_iso %}
{% for iso in instance.media_iso %}
<option value="{{ iso }}">{{ iso }}</option>
{% endfor %}
{% else %}
<option value="none">{% trans "None" %}</option>
{% endif %}
</select>
<div class="input-group-append">
{% if instance.media_iso and allow_admin_or_not_template %}
<button type="submit" class="btn btn-sm btn-success float-left" name="mount_iso" value="{{ cd.dev }}">{% trans "Mount" %}</button>
{% else %}
<button class="btn btn-sm btn-success float-left disabled">{% trans "Mount" %}</button>
{% endif %}
{% if instance.status == 5 and allow_admin_or_not_template %}
<a href="{% url 'instances:detach_cdrom' instance.id cd.dev %}" class="btn btn-sm btn-danger float-right" title="{% trans "Detach CD-ROM (remove device)" %}">
{% icon 'remove' %}
</a>
{% endif %}
</div>
</div>
</form>
{% else %}
<form action="{% url 'instances:unmount_iso' instance.id %}" method="post">
{% csrf_token %}
<div class="input-group">
<input type="text" class="form-control" value="{{ cd.image }}" disabled/>
<div class="input-group-append">
<input type="hidden" name="path" value="{{ cd.path }}">
{% if allow_admin_or_not_template %}
<button type="submit" class="btn btn-sm btn-success float-left" value="{{ cd.dev }}" name="umount_iso">{% trans "Unmount" %}</button>
{% else %}
<button class="btn btn-sm btn-success float-left disabled" value="{{ cd.dev }}" name="umount_iso">{% trans "Unmount" %}</button>
{% endif %}
</div>
</div>
</form>
{% endif %}
</div>
</div>
{% empty %}
<div class="offset-3 col-sm-6">
<div class="bg-light rounded shadow-sm">{% trans 'There is not any CD-ROM device.' %}</div>
</div>
{% endfor %}
<div class="clearfix"></div>
<p class="font-weight-bold">
{% trans "Instance Volume" %}
{% include 'add_instance_volume.html' %}
</p>
<div class="col-12 col-sm-12">
<div class="table-responsive">
<table class="table table-hover mt-3">
<thead>
<tr>
<th scope="col">{% trans "Device" %}</th>
<th scope="col">{% trans "Used" %}</th>
<th scope="col">{% trans "Capacity" %}</th>
<th scope="col">{% trans "Storage" %}</th>
<th scope="col">{% trans "Source" %}</th>
<th scope="col">{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for disk in instance.disks %}
<tr>
<td>
<button type="submit" class="btn btn-sm btn-secondary"
name="details{{ forloop.counter0 }}"
title="{% trans "Details" %}"
tabindex="0"
data-trigger="focus"
data-toggle="popover"
data-html="true"
data-content="<strong>Bus:</strong> {{ disk.bus }} <br/>
<strong>Format:</strong> {{ disk.format }} <br/>
<strong>Cache:</strong> {{ disk.cache }} <br/>
<strong>Serial:</strong> {{ disk.serial }} <br/>
<strong>Readonly:</strong> {{ disk.readonly }} <br/>
<strong>Shareable:</strong> {{ disk.shareable }}</br>
<strong>IO Mode:</strong> {{ disk.io }} <br/>
<strong>Discard:</strong> {{ disk.discard }} <br/>
<strong>Detect Zeroes:</strong> {{ disk.detect_zeroes }}">
<i class="fa fa-info"></i>
</button>
{{ disk.dev }}
</td>
{% if disk.storage is None %}
<td colspan="4">
<div class="alert alert-danger">
{% trans "Error getting disk info" %}
</div>
</td>
{% else %}
<td>{{ disk.used | filesizeformat}}</td>
<td>{{ disk.size | filesizeformat }}</td>
<td>{{ disk.storage }}</td>
<td>{{ disk.path }}</td>
{% endif %}
<td class="text-nowrap">
<form class="d-inline" action="{% url 'instances:edit_volume' instance.id %}" method="post" role="form" aria-label="Edit instance volume form">
{% csrf_token %}
<input type="hidden" name="path" value="{{ disk.path }}">
<input type="hidden" name="dev" value="{{ disk.dev }}">
<input type="hidden" name="storage" value="{{ disk.storage }}">
<input type="hidden" name="name" value="{{ disk.image }}">
{% include 'edit_instance_volume.html' with id=forloop.counter0 %}
</form>
<form class="d-inline" action="{% url 'instances:detach_vol' instance.id %}" method="post">
{% csrf_token %}
<input type="hidden" name="path" value="{{ disk.path }}">
<input type="hidden" name="dev" value="{{ disk.dev }}">
<input type="hidden" name="storage" value="{{ disk.storage }}">
<input type="hidden" name="name" value="{{ disk.image }}">
{% if instance.status == 5 %}
<button type="submit" class="btn btn-sm btn-secondary" value="{{ disk.dev }}" title="{% trans "Detach" %}" onclick="return confirm('{% trans "Are you sure to detach volume?" %}')">
{% icon 'eject' %}
</button>
{% else %}
<button class="btn btn-sm btn-secondary disabled" value="{{ disk.dev }}" title="{% trans "Detach" %}" onclick="return confirm('{% trans "Are you sure? This may lead data corruption!" %}')">
{% icon 'eject' %}
</button>
{% endif %}
</form>
<form class="d-inline" action="{% url 'instances:delete_vol' instance.id %}" method="post">
{% csrf_token %}
<input type="hidden" name="path" value="{{ disk.path }}">
<input type="hidden" name="dev" value="{{ disk.dev }}">
<input type="hidden" name="storage" value="{{ disk.storage }}">
<input type="hidden" name="name" value="{{ disk.image }}">
{% if instance.status == 5 %}
<button type="submit" class="btn btn-sm btn-secondary" title="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure to delete volume?" %}')">
<i class="fa fa-trash"></i>
</button>
{% else %}
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure? This may lead data corruption!" %}')">
<i class="fa fa-trash"></i>
</button>
{% endif %}
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="network">
<p>
{% trans "Add a network device" %}
{% include 'add_instance_network_block.html' %}
</p>
<div class="row mt-3">
<div class="col-lg-12 mt-3">
<h5 class="font-weight-bold">{% trans "Network Devices" %}</h5>
<table class="table">
<thead>
<tr>
<th scope="col">{% trans 'Name' %}</th>
<th scope="col" class="d-none d-table-cell d-sm-table-cell" colspan="6">{% trans 'Info' %}</th>
<th scope="colgroup" class="d-none" colspan="2">{% trans 'Info' %}</th>
<th scope="colgroup" colspan="2">{% trans 'Actions' %}</th>
</tr>
</thead>
<tbody>
{% for network in instance.networks %}
<tr>
<td rowspan="2">eth{{ forloop.counter0 }}({{ network.target|default:"no target" }})
<form action="{% url 'instances:set_link_state' instance.id %}" method="post" aria-label="set instance link state form">
{% csrf_token %}
<input name="mac" value="{{ network.mac }}" hidden/>
<input name="set_link_state" value="{{ network.state }}" hidden/>
<input type="checkbox" {% if network.state == 'up' %} checked{% endif %} onclick='submit();' />
<strong>{% trans 'active' %}</strong>
</form>
</td>
<th class="d-none d-table-cell d-sm-table-cell">{% trans 'MAC' %}</th>
<td>{{ network.mac }}</td>
<th scope="row" class="d-none d-table-cell d-sm-table-cell">{% trans 'Filter' %}</th>
<td class="d-none d-table-cell">{{ network.filterref|default:"None" }}</td>
<th scope="row" class="d-none d-table-cell d-sm-table-cell">{% trans 'Source' %}</th>
<td>{{ network.nic }}</td>
<td>
<form action="{% url 'instances:change_network' instance.id %}" method="post" name="edit_network{{ forloop.counter0 }}" role="form">{% csrf_token %}
<button data-target="#editInstanceNetwork{{ forloop.counter0 }}" type="button" class="btn btn-sm btn-primary"
title="{% trans "Edit NIC" %}" data-toggle="modal">
<span class="fa fa-edit" aria-hidden="true"></span>
</button>
<div class="modal fade" id="editInstanceNetwork{{ forloop.counter0 }}" role="dialog" aria-labelledby="editInstanceNetworkLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{% trans "Edit Instance Network" %}</h5>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<div class="container">
<div class="form-group row">
<label class="col-form-label">{% trans "MAC" %}</label>
<div class="input-group">
<input class="form-control" type="text" value="{{ network.mac }}" readonly/>
<input class="form-control" type="text" name="net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
</div>
</div>
<div class="form-group row">
<label class="col-form-label">{% trans "Net Source" %}</label>
<div class="input-group">
<input class="form-control" type="text" value="{{ network.nic }}" readonly/>
<select class="form-control" name="net-source-{{ forloop.counter0 }}">
{% for c_net in networks_host %}
<option value="net:{{ c_net }}" {% if c_net == network.nic %} selected {% endif %}>{% trans 'Network' %} {{ c_net }}</option>
{% endfor %}
{% for c_iface in interfaces_host %}
<option value="iface:{{ c_iface }}" {% if c_iface == network.nic %} selected {% endif %}>{% trans 'Interface' %} {{ c_iface }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group row">
<label class="col-form-label">{% trans "NWFilter" %}</label>
<div class="input-group">
<input class="form-control" type="text" value="{{ network.filterref }}" readonly/>
<select class="form-control" name="net-nwfilter-{{ forloop.counter0 }}">
<option value="">{% trans "None" %}</option>
{% for c_filters in nwfilters_host %}
<option value="{{ c_filters }}" {% if c_filters == network.filterref %} selected {% endif %}>{{ c_filters }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group row">
<label class="col-form-label">{% trans "Model" %} </label>
<div class="input-group">
<input class="form-control" type="text" value="{{ network.model }}" readonly/>
<select class="form-control" name="net-model-{{ forloop.counter0 }}">
{% for model in net_models_host %}
<option value="{{ model }}" {% if model == network.model %} selected {% endif %}>{{ model }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-dismiss="modal">{% trans 'Close' %}</button>
<button class="btn btn-success" name="change_network" title="{% trans "Apply network changes" %}">{% trans "Apply" %}</button>
</div>
</div>
</div>
</div>
</form>
</td>
<td align="right">
<form action="{% url 'instances:delete_network' instance.id %}" method="post" name="delete_network" role="form">{% csrf_token %}
<button class="btn btn-sm btn-danger" value="{{ network.mac }}" name="delete_network" title="{% trans "Delete Device" %}"
onclick="return confirm('{% trans "Are you sure?" %}')">
<i class="fa fa-trash"></i>
</button>
</form>
</td>
</tr>
<tr>
<th scope="row">{% trans 'IPv4' %}</th>
<td>
{% for ipv4 in network.ipv4|default:"unknown" %}{{ ipv4 }}{% endfor %}
</td>
<th scope="row">{% trans 'IPv6' %}</th>
<td>
{% for ipv6 in network.ipv6|default:"unknown" %}{{ ipv6 }}{% endfor %}
</td>
<th scope="row">{% trans 'Model' %}</th>
<td>{{ network.model }}</td>
<th>{% trans 'QoS' %}</th>
<td class="d-flex justify-content-end">
<form action="{% url 'instances:set_qos' instance.id %}" method="post" name="add_qos{{ forloop.counter0 }}" role="form" aria-label="Add network qos form">
{% csrf_token %}
<input type="text" name="net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}" hidden/>
{% include 'add_network_qos.html' with id=forloop.counter0 %}
</form>
</td>
</tr>
<tr>
<td class="bg-primary" colspan="9"></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if instance.qos %}
<div class="row mt-3">
<div class="col-sm-10">
<p><strong>{% trans "QoS Configuration" %}</strong></p>
</div>
<div class="col-12 col-sm-12">
<table class="table table-hover">
<thead>
<tr class="d-flex">
<th scope="col" class="col-2">{% trans "MAC" %}/{% trans "Direction" %}</th>
<th scope="col" class="col-2">{% trans "Average" %}</th>
<th scope="col" class="col-3">{% trans "Peak" %}</th>
<th scope="col" class="col-3">{% trans "Burst" %}</th>
<th scope="col" class="col-2">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for q, attrs in instance.qos.items %}
{% for att in attrs %}
<tr class="d-flex">
<form action="{% url 'instances:set_qos' instance.id %}" method="post" role="form" aria-label="Instance QoS configuration form">
{% csrf_token %}
<td class="col-2"><label class="col-form-label">{{ q }} {{ att.direction | capfirst }}</label></td>
<td class="col-2"><input id="qos_average" class="form-control" name="qos_average"
value="{{ att.average|default:'' }}"/>
</td>
<td class="col-3"><input id="qos_peak" class="form-control" name="qos_peak"
value="{{ att.peak|default:'' }}"/>
</td>
<td class="col-3"><input id="qos_burst" class="form-control" name="qos_burst"
value="{{ att.burst|default:'' }}"/>
</td>
<td class="col-sm-2">
<input name="qos_direction" value="{{ att.direction }}" hidden/>
<input name="net-mac" value="{{ q }}" hidden/>
<button type="submit" class="btn btn-sm btn-primary"
name="set_qos" data-toggle="modal"
title="{% trans "Edit QoS" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
<i class="fa fa-save"></i>
</button>
</form>
<form action="{% url 'instances:unset_qos' instance.id %}" method="post" role="form" aria-label="Instance QoS configuration form">
{% csrf_token %}
<input name="qos_direction" value="{{ att.direction }}" hidden/>
<input name="net-mac" value="{{ q }}" hidden/>
<button type="submit" class="btn btn-sm btn-danger"
name="unset_qos"
title="{% trans "Delete QoS" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
<i class="fa fa-trash"></i>
</button>
</form>
</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="migrate">
<p>{% trans "For migration both host servers must have equal settings and OS type" %}</p>
<form action="{% url 'instances:migrate' instance.id %}" class="ml-3 form" method="post" role="form" aria-label="Migrate instance form">
{% csrf_token %}
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans "Original host" %}</label>
<div class="col-sm-6">
<label class="form-control" readonly="readonly">{{ compute.name }}</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans "Host migration" %}</label>
<div class="col-sm-6">
<select name="compute_id" class="custom-select">
{% if computes_count != 1 %}
{% for comp in computes %}
{% if comp.id != compute.id %}
<option value="{{ comp.id }}">{{ comp.name }}</option>
{% endif %}
{% endfor %}
{% endif %}
</select>
</div>
</div>
<div class="form-group row">
<div class="col-sm-6 offset-3">
<div class="custom-control custom-switch">
<input class="custom-control-input" type="checkbox" name="live_migrate" value="true" id="vm_live_migrate" {% if instance.status != 5 %}checked{% else %}disabled{% endif %}>
<label class="custom-control-label" for="vm_live_migrate">{% trans "Live migration" %}</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-6 offset-3">
<div class="custom-control custom-switch">
<input class="custom-control-input" type="checkbox" name="unsafe_migrate" value="true" id="vm_unsafe_migrate">
<label class="custom-control-label" for="vm_unsafe_migrate">{% trans "Unsafe migration" %}</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-6 offset-3">
<div class="custom-control custom-switch">
<input class="custom-control-input" type="checkbox" name="xml_delete" value="true" id="xml_delete" checked>
<label class="custom-control-label" for="xml_delete">{% trans "Delete original" %}</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-6 offset-3">
<div class="custom-control custom-switch">
<input class="custom-control-input" type="checkbox" name="offline_migrate" value="true" id="offline_migrate" {% if instance.status == 5 %}checked{% else %}disabled{% endif %}>
<label class="custom-control-label" for="offline_migrate">{% trans "Offline migration" %}</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-6 offset-3">
<div class="custom-control custom-switch">
<input class="custom-control-input" type="checkbox" name="postcopy" value="true" id="postcopy" {% if instance.status != 1 %}disabled{% endif %}>
<label class="custom-control-label" for="postcopy">{% trans "Post copy" %}</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-6 offset-3">
<div class="custom-control custom-switch">
<input class="custom-control-input" type="checkbox" name="autoconverge" value="true" id="autoconverge" {% if instance.status != 1 %}disabled{% endif %}>
<label class="custom-control-label" for="autoconverge" title="{% trans 'Forces CPU convergence during live migration' %}">{% trans "Auto converge" %}</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-6 offset-3">
<div class="custom-control custom-switch">
<input class="custom-control-input" type="checkbox" name="compress" value="true" id="compress" {% if instance.status != 1 %}disabled{% endif %}>
<label class="custom-control-label" for="compress" title="{% trans 'Compress instance memory for fast migration' %}">{% trans "Compressed" %}</label>
</div>
</div>
</div>
{% if computes_count != 1 %}
<button type="submit" class="btn btn-lg btn-success float-right" name="migrate" onclick="showPleaseWaitDialog();">{% trans "Migrate" %}</button>
{% else %}
<button class="btn btn-lg btn-success float-right disabled">{% trans "Migrate" %}</button>
{% endif %}
</form>
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="xmledit">
<p>{% trans "If you need to edit XML please Power Off the instance" %}</p>
<form action="{% url 'instances:change_xml' instance.id %}" method="post" role="form" aria-label="Edit instance XML form">
{% csrf_token %}
<div class="col-sm-12" id="xmlheight">
<textarea id="editor">{{ instance.inst_xml }}</textarea>
</div>
{% if instance.status == 5 %}
<input type="hidden" name="inst_xml">
<button type="submit" class="btn btn-lg btn-success float-right" name="change_xml">
{% trans "Change" %}
</button>
{% else %}
<button class="btn btn-lg btn-success float-right disabled">
{% trans "Change" %}
</button>
{% endif %}
</form>
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="users">
<div>
<p class="font-weight-bold">
{% trans "Instance owners" %}
{% include 'add_instance_owner_block.html' %}
</p>
</div>
<div class="table-responsive">
<table class="table table-striped sortable-theme-bootstrap mt-3" data-sortable>
<tbody class="searchable">
{% for userinstance in userinstances %}
<tr>
<td><a href="{% url 'account' userinstance.user.id %}">{{ userinstance.user }}</a></td>
<td style="width:30px;">
<a href="{% url 'user_instance_delete' userinstance.id %}?next={% url 'instances:instance' instance.id %}#users">
{% icon 'trash' %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="clearfix"></div>
</div>
{% endif %}
{% if request.user.is_superuser or request.user.is_staff or userinstance.is_vnc %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="vncsettings">
<form method="post" action="{% url 'instances:update_console' instance.id %}">
{% csrf_token %}
{% if instance.status != 5 %}
<div class="alert alert-warning">
{% trans "To change console settings, shutdown the instance." %}
</div>
{% endif %}
{% bootstrap_form console_form layout='horizontal' %}
<div class="float-right">
{% if instance.status != 5 %}
<button class="btn btn-success" name="set_console_type" disabled>{% trans "Update" %}</button>
{% else %}
<button type="submit" class="btn btn-success ">{% trans "Update" %}</button>
{% endif %}
</div>
</form>
<div class="clearfix"></div>
</div>
{% endif %}
{% if perms.instances.clone_instances %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="clone">
<p class="font-weight-bold">{% trans "Create a clone" %}</p>
<form class="form" action="{% url 'instances:clone' instance.id %}" method="post" role="form" aria-label="Create clone form">{% csrf_token %}
<div class="form-group row">
<label class="col-sm-3 col-form-label font-weight-normal">{% trans "Clone Name" %}</label>
{% if request.user.is_superuser %}
<div class="col-sm-6">
<div class="input-group">
<input id="clone_name" type="text" class="form-control" name="name" value="{{ instance.name }}-clone"/>
<div class="input-group-append">
<button type="button" class="btn btn-success" name="guess-clone-name"
onclick="guess_clone_name()">{% trans "Guess" %}</button>
</div>
</div>
</div>
{% elif app_settings.CLONE_INSTANCE_AUTO_NAME == 'True'%}
<div class="col-sm-4">
<input id="clone_instance_auto_name" type="text" class="form-control" name="auto_name" value="Automatic" disabled/>
</div>
{% else %}
<div class="col-sm-4">
<select id="select_clone_name" class="form-control" name="name" size="1">
{% for name in clone_free_names %}
<option value="{{ name }}">{{ name }}</option>
{% endfor %}
</select>
</div>
{% endif %}
</div>
{% if request.user.is_superuser %}
<label>{% trans "Network devices" %}:</label>
{% for network in instance.networks %}
<p>
<div class="form-group row">
<label class="col-sm-2 col-form-label offset-1">eth{{ forloop.counter0 }} ({{ network.nic }})</label>
<div class="col-sm-6">
<div class="input-group">
<input type="text" class="form-control" name="clone-net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
<div class="input-group-append">
<button type="button" class="btn btn-success" name="random-mac-{{ forloop.counter0 }}"
onclick="random_mac('clone-net-mac-{{ forloop.counter0 }}')">{% trans "Random" %}</button>
<button type="button" class="btn btn-success" name="guess-mac-{{ forloop.counter0 }}"
onclick="guess_mac_address('#clone_name', {{ forloop.counter0 }})">{% trans "Guess" %}</button>
</div>
</div>
</div>
</div>
</p>
{% endfor %}
{% else %}
{% for network in instance.networks %}
<input type="hidden" class="form-control" name="clone-net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
{% endfor %}
{% endif %}
{% if request.user.is_superuser %}
<label>{% trans "Storage devices" %}:</label>
{% for disk in instance.disks %}
<div class="form-group row">
<label class="col-sm-2 col-form-label offset-1">{{ disk.dev }} ({{ disk.storage }})</label>
<div class="col-sm-6">
<div class="input-group">
<input id="disk_name-{{ disk.dev }}" type="text" class="form-control" name="disk-{{ disk.dev }}" value="{{ disk.image }}"/>
{% if disk.format == 'qcow2' %}
<div class="input-group-append">
<span class="input-group-text" >{% trans 'Metadata' %}</span>
<div class="input-group-text">
<input type="checkbox" name="meta-{{ disk.dev }}" value="true">
</div>
</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
{% else %}
{% for disk in instance.disks %}
<input id="disk_name-{{ disk.dev }}" type="hidden" class="form-control" name="disk-{{ disk.dev }}" value="{{ disk.image }}"/>
{% endfor %}
{% endif %}
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans "Title" %}</label>
<div class="col-sm-6">
<input type="text" name="clone-title" class="form-control">
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans "Description" %}</label>
<div class="col-sm-6">
<textarea name="clone-description" class="form-control"></textarea>
</div>
</div>
{% if instance.status == 5 %}
<button type="submit" class="btn btn-lg btn-success float-right" name="clone" onclick="showPleaseWaitDialog();">{% trans "Clone" %}</button>
{% else %}
<button class="btn btn-lg btn-success float-right disabled" name="clone">{% trans "Clone" %}</button>
{% endif %}
</form>
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="options">
<p>{% trans "To set instance template name description, shutdown the instance." %}</p>
<form action="{% url 'instances:change_options' instance.id %}" method="post" role="form" aria-label="Set instance template name description form">{% csrf_token %}
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans "Title" %}</label>
<div class="col-sm-6">
<input type="text" name="title" class="form-control" value="{{ instance.title }}">
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans "Description" %}</label>
<div class="col-sm-6">
<textarea name="description" class="form-control">{{ instance.description }}</textarea>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans "Is template" %}</label>
<div class="col-sm-6">
<input type="checkbox"
name="is_template"
value="True"
id="is_template"
{% if instance.is_template %}checked{% endif %}
{% if not request.user.is_superuser and not request.user.is_staff %}disabled{% endif %}>
</div>
</div>
<div class="form-group row">
<div class="offset-3 col-sm-6">
{% if instance.status == 5 %}
<button type="submit" class="btn btn-block btn-success" name="change_options">{% trans "Change" %}</button>
{% else %}
<button class="btn btn-block btn-success disabled" name="change_options">{% trans "Change" %}</button>
{% endif %}
</div>
</div>
</form>
<p class="font-weight-bold">{% trans "To set instance video model, shutdown the instance." %}</p>
<form action="{% url 'instances:set_video_model' instance.id %}" method="post" role="form" aria-label="Set instance video model form">{% csrf_token %}
<div class="form-group row">
<label for="video_model_select" class="col-sm-3 col-form-label">{% trans "Primary Video Model" %}</label>
<div class="col-sm-6">
<div class="input-group">
<select id="video_model_select" class="custom-select" name="video_model">
<option class="font-weight-bold" value="">{% trans "please choose" %}</option>
{% for vmodel in instance.video_models %}
<option value="{{ vmodel }}"{% if vmodel == instance.video_model %} selected{% endif %}>{{ vmodel }}</option>
{% endfor %}
</select>
<span class="input-group-btn">
{% if instance.status == 5 %}
<button type="submit" class="btn btn-success" name="set_video_model">{% trans "Set" %}</button>
{% else %}
<button class="btn btn-success disabled" name="set_video_model">{% trans "Set" %}</button>
{% endif %}
</span>
</div>
</div>
</div>
</form>
<p class="font-weight-bold">{% trans "To set instance vCPUs hotpluggable" %}</p>
<form action="{% url 'instances:set_vcpu_hotplug' instance.id %}" method="post" role="form" aria-label="Set instance vCPUs hotpluggable form">{% csrf_token %}
<div class="form-group row">
<label for="vcpu_hotplug" class="col-sm-3 col-form-label">{% trans "vCPU Hot Plug" %}</label>
<div class="col-sm-6">
<div class="input-group">
<select id="vcpu_hotplug" class="form-control" name="vcpu_hotplug">
<option value="True" {% if instance.vcpus %} selected {% endif %}>{% trans 'Enabled' %}</option>
<option value="False" {% if not instance.vcpus %} selected {% endif %}>{% trans 'Disabled' %}</option>
</select>
<span class="input-group-btn">
{% if instance.status == 5 %}
<button type="submit" class="btn btn-success" name="set_vcpu_hotplug">{% trans "Set" %}</button>
{% else %}
<button class="btn btn-success" name="set_vcpu_hotplug" disabled>{% trans "Set" %}</button>
{% endif %}
</span>
</div>
</div>
</div>
</form>
<p class="font-weight-bold">{% trans "To Enable/Disable Qemu Guest Agent. Status" %}:
{% if instance.status == 1 %}
{% if instance.guest_agent_ready %}
<label class="badge badge-success">{% trans 'Connected' %}</label>
{% else %}
<label class="badge badge-danger">{% trans 'Disconnected' %}</label>
{% endif %}</p>
{% else %}
<label class="badge badge-default">{% trans 'Unknown' %}</label>
{% endif %}
<form action="{% url 'instances:set_guest_agent' instance.id %}" method="post" role="form" aria-label="Enable/Disable Qemu Guest Agent form">{% csrf_token %}
<div class="form-group row">
<label for="guest_agent" class="col-sm-3 col-form-label">{% trans "Qemu Guest Agent" %}</label>
<div class="col-sm-6">
<div class="input-group">
<select id="guest_agent" class="custom-select" name="guest_agent">
<option value="True" {% if instance.guest_agent %} selected {% endif %}>{% trans 'Enabled' %}</option>
<option value="False" {% if not instance.guest_agent %} selected {% endif %}>{% trans 'Disabled' %}</option>
</select>
<span class="input-group-btn">
<button type="submit" class="btn btn-success" name="set_guest_agent">{% trans "Set" %}</button>
</span>
</div>
</div>
</div>
</form>
<div class="clearfix"></div>
</div>
{% endif %}
</div>
</div>
</div>

View file

@ -0,0 +1,91 @@
{% load i18n %}
{% load icons %}
<div role="tabpanel" class="tab-pane" id="snapshots">
<div role="tabpanel">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist" aria-label="Instance snapshot menu">
<li class="nav-item">
<a class="nav-link text-secondary active" href="#takesnapshot" aria-controls="takesnapshot" role="tab" data-toggle="tab">
{% trans "Take Snapshot" %}
</a>
</li>
<li class="nav-item">
<a class="nav-link text-secondary" href="#managesnapshot" aria-controls="managesnapshot" role="tab" data-toggle="tab">
{% trans "Manage Snapshots" %}
</a>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="takesnapshot">
{% if instance.status == 5 %}
<p>{% trans "This may take more than an hour, depending on how much content is on your droplet and how large the disk is." %}</p>
<form action="{% url 'instances:snapshot' instance.id %}" class="form-inline" method="post" role="form" aria-label="Create snapshot form">
{% csrf_token %}
<div class="form-group row">
<div class="col-sm-12">
<input type="text" class="form-control form-control-lg" name="name" placeholder="{% trans "Enter Snapshot Name" %}" maxlength="14">
</div>
</div>
{% if instance.status == 5 %}
<input type="submit" class="btn btn-lg btn-success float-right" name="snapshot" value="{% trans "Take Snapshot" %}">
{% else %}
<button class="btn btn-lg btn-success float-right disabled">{% trans "Take Snapshot" %}</button>
{% endif %}
</form>
<div class="clearfix"></div>
{% else %}
<p>{% trans "To take a snapshot please Power Off the instance." %}</p>
{% endif %}
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="managesnapshot">
{% if instance.snapshots %}
<p>{% trans "Choose a snapshot for restore/delete" %}</p>
<div class="table-responsive">
<table class="table">
<thead>
<th scope="col">{% trans "Name" %}</th>
<th scope="col">{% trans "Date" %}</th>
<th scope="colgroup" colspan="2">{% trans "Action" %}</th>
</thead>
<tbody>
{% for snap in instance.snapshots %}
<tr>
<td><strong>{{ snap.name }}</strong></td>
<td>{{ snap.date|date:"M d H:i:s" }}</td>
<td style="width:30px;">
<form action="{% url 'instances:revert_snapshot' instance.id %}" method="post" role="form" aria-label="Restore snapshot form">
{% csrf_token %}
<input type="hidden" name="name" value="{{ snap.name }}">
{% if instance.status == 5 %}
<button type="submit" class="btn btn-sm btn-secondary" name="revert_snapshot" title="{% trans 'Revert to this Snapshot' %}" onclick="return confirm('Are you sure?')">
<span class="fa fa-download"></span>
</button>
{% else %}
<button type="button" class="btn btn-sm btn-secondary disabled"
title="{% trans "To restore snapshots you need Power Off the instance." %}">
<span class="fa fa-download"></span>
</button>
{% endif %}
</form>
</td>
<td style="width:30px;">
<form action="{% url 'instances:delete_snapshot' instance.id %}" method="post" role="form" aria-label="Delete snapshot form">{% csrf_token %}
<input type="hidden" name="name" value="{{ snap.name }}">
<button type="submit" class="btn btn-sm btn-danger" title="{% trans 'Delete Snapshot' %}" onclick="return confirm('{% trans "Are you sure?" %}')">
{% icon 'trash' %}
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p>{% trans "You do not have any snapshots" %}</p>
{% endif %}
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,116 @@
{% load i18n %}
<div role="tabpanel" class="tab-pane" id="graphics">
<div role="tabpanel">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist" aria-label="Instance graphs and logs menu">
<li class="nav-item">
<a class="nav-link text-secondary active" href="#graphs" id="graphtab" aria-controls="graphs" role="tab" data-toggle="tab" aria-controls="graphs" aria-selected="true">
{% trans "Real Time" %}
</a>
</li>
<li class="nav-item">
<a class="nav-link text-secondary" href="#logs" id="logtab" aria-controls="logs" role="tab" data-toggle="tab" aria-controls="logs" onclick='update_logs_table("{{ instance.name }}");'>
{% trans "Logs" %}
</a>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="graphs">
<div class="mb-1 card border-success">
<div class="card-header">
<h5 class="card-title"><i class="fa fa-long-arrow-right"></i>
{% trans "CPU Usage" %}</h5>
</div>
<div class="card-body">
<div class="flot-chart">
<div class="flot-chart-content" id="flot-moving-line-chart">
<canvas id="cpuChart" width="735" height="160"></canvas>
</div>
</div>
</div>
</div>
<div class="mb-1 card border-danger">
<div class="card-header">
<h5 class="card-title"><i class="fa fa-long-arrow-right"></i>
{% trans "Memory Usage" %}</h5>
</div>
<div class="card-body">
<div class="flot-chart">
<div class="flot-chart-content" id="flot-moving-line-chart">
<canvas id="memChart" width="735" height="160"></canvas>
</div>
</div>
</div>
</div>
{% for net in instance.networks %}
<div class="mb-1 card border-info">
<div class="card-header">
<h5 class="card-title"><i class="fa fa-long-arrow-right"></i>
{% trans "Bandwidth Device" %}: eth{{ forloop.counter0 }}</h5>
</div>
<div class="card-body">
<div class="flot-chart">
<div class="flot-chart-content" id="flot-moving-line-chart">
<canvas id="netEth{{ forloop.counter0 }}Chart" width="735" height="160"></canvas>
</div>
</div>
</div>
</div>
{% endfor %}
{% for disk in instance.disks %}
<div class="mb-1 card border-warning">
<div class="card-header">
<h5 class="card-title"><i class="fa fa-long-arrow-right"></i>
{% trans "Disk I/O device" %}: {{ disk.dev }}</h5>
</div>
<div class="card-body">
<div class="flot-chart">
<div class="flot-chart-content" id="flot-moving-line-chart">
<canvas id="blk{{ disk.dev }}Chart" width="735" height="160"></canvas>
</div>
</div>
</div>
</div>
{% endfor %}
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="logs">
<div class="table-responsive">
<table class="table table-striped sortable-theme-bootstrap" id="logs_table" data-sortable>
<thead>
<tr>
<th scope="col">{% trans "Date" %}</th>
<th scope="col">{% trans "User" %}</th>
<th scope="col">{% trans "Message" %}</th>
</tr>
</thead>
<tbody class="searchable">
<tr>
<td colspan="3"><i>{% trans 'None' %}...</i></td>
</tr>
</tbody>
</table>
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<script>
function update_logs_table(vname) {
// TODO
logurl = "{% url 'vm_logs' 1 %}".replace(1, vname);
$.getJSON(logurl, function(data) {
var logs = "";
$.each(data, function(id) {
row = data[id];
// console.log(row);
logs += '<tr><td style="width:150px">'+row['date']+'</td>';
logs += '<td>'+row['user']+'</td>';
logs += '<td>'+row['message']+'</td></tr>';
});
$("#logs_table > tbody").html(logs);
});
}
</script>

View file

@ -1,3 +1,478 @@
import tempfile
from django.shortcuts import reverse
from django.test import TestCase
# Create your tests here.
from computes.models import Compute
from .models import Instance
class InstancesTestCase(TestCase):
def setUp(self):
self.client.login(username='admin', password='admin')
Compute(
name='local',
hostname='localhost',
login='',
password='',
details='local',
type=4,
).save()
def test_index(self):
response = self.client.get(reverse('instances:index'))
# with open('index.html', 'wb') as f:
# f.write(response.content)
self.assertEqual(response.status_code, 200)
def test_create_select_type(self):
response = self.client.get(reverse('instances:create_instance_select_type', args=[1]))
self.assertEqual(response.status_code, 200)
def test_instance(self):
compute = Compute.objects.get(pk=1)
# create volume
response = self.client.post(
reverse('create_volume', args=[compute.id, 'default']),
{
'name': 'test',
'format': 'qcow2',
'size': '1',
'meta_prealloc': False,
},
)
self.assertRedirects(response, reverse('storage', args=[compute.id, 'default']))
# create instance
response = self.client.get(reverse('instances:create_instance', args=[compute.id, 'x86_64', 'q35']))
self.assertEqual(response.status_code, 200)
response = self.client.post(
reverse('instances:create_instance', args=[compute.id, 'x86_64', 'q35']),
{
'name': 'test',
'firmware': 'BIOS',
'vcpu': 1,
'vcpu_mode': 'host-model',
'memory': 512,
'device0': 'disk',
'bus0': 'virtio',
'images': 'test.qcow2',
'storage-control': 'default',
'image-control': 'test.qcow2',
'networks': 'default',
'network-control': 'default',
'cache_mode': 'directsync',
'nwfilter': '',
'graphics': 'spice',
'video': 'vga',
'listener_addr': '0.0.0.0',
'console_pass': '',
'qemu_ga': False,
'virtio': True,
'create': True,
},
)
self.assertEqual(response.status_code, 302)
instance: Instance = Instance.objects.get(pk=1)
self.assertEqual(instance.name, 'test')
# get instance page
response = self.client.get(reverse('instances:instance', args=[instance.id]))
self.assertEqual(response.status_code, 200)
# resize cpu
self.assertEqual(instance.vcpu, 1)
self.assertEqual(instance.cur_vcpu, 1)
response = self.client.post(reverse('instances:resizevm_cpu', args=[instance.id]), {'vcpu': 4, 'cur_vcpu': 2})
self.assertRedirects(response, reverse('instances:instance', args=[instance.id]) + '#resize')
# reset cached properties
del instance.vcpu
del instance.cur_vcpu
self.assertEqual(instance.vcpu, 4)
self.assertEqual(instance.cur_vcpu, 2)
# resize memory
self.assertEqual(instance.memory, 512)
self.assertEqual(instance.cur_memory, 512)
response = self.client.post(reverse('instances:resize_memory', args=[instance.id]), {
'memory': 2048,
'cur_memory': 1024
})
self.assertRedirects(response, reverse('instances:instance', args=[instance.id]) + '#resize')
del instance.memory
del instance.cur_memory
self.assertEqual(instance.memory, 2048)
self.assertEqual(instance.cur_memory, 1024)
# resize disk
self.assertEqual(instance.disks[0]['size'], 1024**3)
response = self.client.post(reverse('instances:resize_disk', args=[instance.id]), {
'disk_size_vda': '2.0 GB',
})
self.assertRedirects(response, reverse('instances:instance', args=[instance.id]) + '#resize')
del instance.disks
self.assertEqual(instance.disks[0]['size'], 2 * 1024**3)
# add new volume
self.assertEqual(len(instance.disks), 1)
response = self.client.post(
reverse('instances:add_new_vol', args=[instance.id]),
{
'storage': 'default',
'name': 'test2',
'size': 1,
},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.disks
self.assertEqual(len(instance.disks), 2)
# delete volume from instance
response = self.client.post(
reverse('instances:delete_vol', args=[instance.id]),
{
'storage': 'default',
'dev': 'vdb',
'name': 'test2.qcow2',
},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.disks
self.assertEqual(len(instance.disks), 1)
# detach volume
response = self.client.post(
reverse('instances:detach_vol', args=[instance.id]),
{
'dev': 'vda',
},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.disks
self.assertEqual(len(instance.disks), 0)
# add existing volume
response = self.client.post(
reverse('instances:add_existing_vol', args=[instance.id]),
{
'selected_storage': 'default',
'vols': 'test.qcow2',
},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.disks
self.assertEqual(len(instance.disks), 1)
# edit volume
response = self.client.post(
reverse('instances:edit_volume', args=[instance.id]),
{
'vol_path': '/var/lib/libvirt/images/test.qcow2',
# 'vol_shareable': False,
# 'vol_readonly': False,
'vol_bus': 'virtio',
'vol_bus_old': 'virtio',
'vol_format': 'qcow2',
'dev': 'vda',
'edit_volume': True
},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
# add media device
self.assertEqual(len(instance.media), 1)
response = self.client.post(
reverse('instances:add_cdrom', args=[instance.id]),
{
'bus': 'sata',
},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.media
self.assertEqual(len(instance.media), 2)
# create dummy iso
# with tempfile.NamedTemporaryFile() as f:
# f.write(b'\x00' * 1024**2)
# response = self.client.post(
# reverse('storage', args=[instance.compute.id, 'default']),
# {
# 'iso_upload': True,
# 'file': f
# },
# )
# remove media device
response = self.client.post(
reverse('instances:detach_cdrom', args=[instance.id, 'sda']),
{},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.media
self.assertEqual(len(instance.media), 1)
# snapshots
self.assertEqual(len(instance.snapshots), 0)
response = self.client.post(
reverse('instances:snapshot', args=[instance.id]),
{
'name': 'test',
},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.snapshots
self.assertEqual(len(instance.snapshots), 1)
response = self.client.post(
reverse('instances:revert_snapshot', args=[instance.id]),
{
'name': 'test',
},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
response = self.client.post(
reverse('instances:delete_snapshot', args=[instance.id]),
{
'name': 'test',
},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.snapshots
self.assertEqual(len(instance.snapshots), 0)
# autostart
self.assertEqual(instance.autostart, 0)
response = self.client.post(
reverse('instances:set_autostart', args=[instance.id]),
{},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.autostart
self.assertEqual(instance.autostart, 1)
response = self.client.post(
reverse('instances:unset_autostart', args=[instance.id]),
{},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.autostart
self.assertEqual(instance.autostart, 0)
# bootmenu
self.assertEqual(instance.bootmenu, True)
response = self.client.post(
reverse('instances:unset_bootmenu', args=[instance.id]),
{},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.bootmenu
self.assertEqual(instance.bootmenu, False)
response = self.client.post(
reverse('instances:set_bootmenu', args=[instance.id]),
{},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.bootmenu
self.assertEqual(instance.bootmenu, True)
# guest agent
self.assertEqual(instance.guest_agent, False)
response = self.client.post(
reverse('instances:set_guest_agent', args=[instance.id]),
{'guest_agent': True},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.guest_agent
self.assertEqual(instance.guest_agent, True)
response = self.client.post(
reverse('instances:set_guest_agent', args=[instance.id]),
{'guest_agent': False},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.guest_agent
self.assertEqual(instance.guest_agent, False)
# video model
self.assertEqual(instance.video_model, 'vga')
response = self.client.post(
reverse('instances:set_video_model', args=[instance.id]),
{'video_model': 'virtio'},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
del instance.video_model
self.assertEqual(instance.video_model, 'virtio')
# console
response = self.client.post(
reverse('instances:update_console', args=[instance.id]),
{
'type': 'spice',
'listen_on': '0.0.0.0',
'password': '',
'keymap': 'auto'
},
HTTP_REFERER=reverse('index'),
)
self.assertEqual(response.status_code, 302)
# poweron
self.assertEqual(instance.status, 5)
response = self.client.get(reverse('instances:poweron', args=[instance.id]), HTTP_REFERER=reverse('index'))
self.assertEqual(response.status_code, 302)
del instance.status
self.assertEqual(instance.status, 1)
# status
response = self.client.get(reverse('instances:status', args=[instance.id]))
self.assertEqual(response.status_code, 200)
# stats
response = self.client.get(reverse('instances:stats', args=[instance.id]))
self.assertEqual(response.status_code, 200)
# guess_mac_address
response = self.client.get(reverse('instances:guess_mac_address', args=[instance.name]))
self.assertEqual(response.status_code, 200)
# random_mac_address
response = self.client.get(reverse('instances:random_mac_address'))
self.assertEqual(response.status_code, 200)
# random_mac_address
response = self.client.get(reverse('instances:guess_clone_name'))
self.assertEqual(response.status_code, 200)
# guess_mac_address
response = self.client.get(reverse('instances:check_instance', args=[instance.name]))
self.assertEqual(response.status_code, 200)
# sshkeys
response = self.client.get(reverse('instances:sshkeys', args=[instance.name]))
self.assertEqual(response.status_code, 200)
# suspend
response = self.client.get(reverse('instances:suspend', args=[instance.id]), HTTP_REFERER=reverse('index'))
self.assertEqual(response.status_code, 302)
del instance.status
self.assertEqual(instance.status, 3)
# resume
response = self.client.get(reverse('instances:resume', args=[instance.id]), HTTP_REFERER=reverse('index'))
self.assertEqual(response.status_code, 302)
del instance.status
self.assertEqual(instance.status, 1)
# poweroff
response = self.client.get(reverse('instances:poweroff', args=[instance.id]), HTTP_REFERER=reverse('index'))
self.assertEqual(response.status_code, 302)
# as no OS is installed ACPI won't work
del instance.status
self.assertEqual(instance.status, 1)
# powercycle
response = self.client.get(reverse('instances:powercycle', args=[instance.id]), HTTP_REFERER=reverse('index'))
self.assertEqual(response.status_code, 302)
self.assertEqual(instance.status, 1)
# force_off
response = self.client.get(reverse('instances:force_off', args=[instance.id]), HTTP_REFERER=reverse('index'))
self.assertEqual(response.status_code, 302)
del instance.status
self.assertEqual(instance.status, 5)
# delete started instance with disks
instance.proxy.start()
del instance.status
self.assertEqual(instance.status, 1)
response = self.client.post(
reverse('instances:destroy', args=[instance.id]),
{'delete_disk': True},
HTTP_REFERER=reverse('index'),
)
self.assertRedirects(response, reverse('instances', args=[compute.id]))
# # create volume
# response = self.client.post(
# reverse('create_volume', args=[compute.id, 'default']),
# {
# 'name': 'test3',
# 'format': 'qcow2',
# 'size': '1',
# 'meta_prealloc': False,
# },
# )
# self.assertRedirects(response, reverse('storage', args=[compute.id, 'default']))
# # delete volume
# response = self.client.post(
# reverse('instances:delete_vol', args=[instance.id]),
# {
# 'storage': 'default',
# 'dev': 'vdb',
# 'name': 'test3.qcow2',
# },
# HTTP_REFERER=reverse('index'),
# )
# self.assertEqual(response.status_code, 302)

View file

@ -5,10 +5,62 @@ from . import views
app_name = 'instances'
urlpatterns = [
path('', views.allinstances, name='index'),
path('<int:compute_id>/<vname>/', views.instance, name='instance'),
path('statistics/<int:compute_id>/<vname>/', views.inst_graph, name='inst_graph'),
path('status/<int:compute_id>/<vname>/', views.inst_status, name='inst_status'),
path('', views.index, name='index'),
path('flavor/create/', views.flavor_create, name='flavor_create'),
path('flavor/<int:pk>/update/', views.flavor_update, name='flavor_update'),
path('flavor/<int:pk>/delete/', views.flavor_delete, name='flavor_delete'),
path('<int:pk>/', views.instance, name='instance'),
path('<int:pk>/poweron/', views.poweron, name='poweron'),
path('<int:pk>/powercycle/', views.powercycle, name='powercycle'),
path('<int:pk>/poweroff/', views.poweroff, name='poweroff'),
path('<int:pk>/suspend/', views.suspend, name='suspend'),
path('<int:pk>/resume/', views.resume, name='resume'),
path('<int:pk>/force_off/', views.force_off, name='force_off'),
path('<int:pk>/destroy/', views.destroy, name='destroy'),
path('<int:pk>/migrate/', views.migrate, name='migrate'),
path('<int:pk>/status/', views.status, name='status'),
path('<int:pk>/stats/', views.stats, name='stats'),
path('<int:pk>/rootpasswd/', views.set_root_pass, name='rootpasswd'),
path('<int:pk>/add_public_key/', views.add_public_key, name='add_public_key'),
path('<int:pk>/resizevm_cpu/', views.resizevm_cpu, name='resizevm_cpu'),
path('<int:pk>/resize_memory/', views.resize_memory, name='resize_memory'),
path('<int:pk>/resize_disk/', views.resize_disk, name='resize_disk'),
path('<int:pk>/add_new_vol/', views.add_new_vol, name='add_new_vol'),
path('<int:pk>/delete_vol/', views.delete_vol, name='delete_vol'),
path('<int:pk>/add_owner/', views.add_owner, name='add_owner'),
path('<int:pk>/add_existing_vol/', views.add_existing_vol, name='add_existing_vol'),
path('<int:pk>/edit_volume/', views.edit_volume, name='edit_volume'),
path('<int:pk>/detach_vol/', views.detach_vol, name='detach_vol'),
path('<int:pk>/add_cdrom/', views.add_cdrom, name='add_cdrom'),
path('<int:pk>/detach_cdrom/<str:dev>/', views.detach_cdrom, name='detach_cdrom'),
path('<int:pk>/unmount_iso/', views.unmount_iso, name='unmount_iso'),
path('<int:pk>/mount_iso/', views.mount_iso, name='mount_iso'),
path('<int:pk>/snapshot/', views.snapshot, name='snapshot'),
path('<int:pk>/delete_snapshot/', views.delete_snapshot, name='delete_snapshot'),
path('<int:pk>/revert_snapshot/', views.revert_snapshot, name='revert_snapshot'),
path('<int:pk>/set_vcpu/', views.set_vcpu, name='set_vcpu'),
path('<int:pk>/set_vcpu_hotplug/', views.set_vcpu_hotplug, name='set_vcpu_hotplug'),
path('<int:pk>/set_autostart/', views.set_autostart, name='set_autostart'),
path('<int:pk>/unset_autostart/', views.unset_autostart, name='unset_autostart'),
path('<int:pk>/set_bootmenu/', views.set_bootmenu, name='set_bootmenu'),
path('<int:pk>/unset_bootmenu/', views.unset_bootmenu, name='unset_bootmenu'),
path('<int:pk>/set_bootorder/', views.set_bootorder, name='set_bootorder'),
path('<int:pk>/change_xml/', views.change_xml, name='change_xml'),
path('<int:pk>/set_guest_agent/', views.set_guest_agent, name='set_guest_agent'),
path('<int:pk>/set_video_model/', views.set_video_model, name='set_video_model'),
path('<int:pk>/change_network/', views.change_network, name='change_network'),
path('<int:pk>/add_network/', views.add_network, name='add_network'),
path('<int:pk>/delete_network/', views.delete_network, name='delete_network'),
path('<int:pk>/set_link_state/', views.set_link_state, name='set_link_state'),
path('<int:pk>/set_qos/', views.set_qos, name='set_qos'),
path('<int:pk>/unset_qos/', views.unset_qos, name='unset_qos'),
path('<int:pk>/del_owner/', views.del_owner, name='del_owner'), # no links to this one???
path('<int:pk>/clone/', views.clone, name='clone'),
path('<int:pk>/update_console/', views.update_console, name='update_console'),
path('<int:pk>/change_options/', views.change_options, name='change_options'),
path('<int:pk>/getvvfile/', views.getvvfile, name='getvvfile'), # no links to this one???
path('create/<int:compute_id>/', views.create_instance_select_type, name='create_instance_select_type'),
path('create/<int:compute_id>/<str:arch>/<str:machine>/', views.create_instance, name='create_instance'),
path('guess_mac_address/<vname>/', views.guess_mac_address, name='guess_mac_address'),
path('guess_clone_name/', views.guess_clone_name, name='guess_clone_name'),
path('random_mac_address/', views.random_mac_address, name='random_mac_address'),

361
instances/utils.py Normal file
View file

@ -0,0 +1,361 @@
import os
import random
import string
from collections import OrderedDict
from django.conf import settings
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from accounts.models import UserInstance
from appsettings.settings import app_settings
from logs.views import addlogmsg
from vrtManager.connection import connection_manager
from vrtManager.hostdetails import wvmHostDetails
from vrtManager.instance import wvmInstance, wvmInstances
from .models import Instance
def filesizefstr(size_str):
if size_str == '':
return 0
size_str = size_str.upper().replace("B", "")
if size_str[-1] == 'K':
return int(float(size_str[:-1])) << 10
elif size_str[-1] == 'M':
return int(float(size_str[:-1])) << 20
elif size_str[-1] == 'G':
return int(float(size_str[:-1])) << 30
elif size_str[-1] == 'T':
return int(float(size_str[:-1])) << 40
elif size_str[-1] == 'P':
return int(float(size_str[:-1])) << 50
else:
return int(float(size_str))
def get_clone_free_names(size=10):
prefix = app_settings.CLONE_INSTANCE_DEFAULT_PREFIX
free_names = []
existing_names = [i.name for i in Instance.objects.filter(name__startswith=prefix)]
index = 1
while len(free_names) < size:
new_name = prefix + str(index)
if new_name not in existing_names:
free_names.append(new_name)
index += 1
return free_names
def check_user_quota(user, instance, cpu, memory, disk_size):
ua = user.userattributes
msg = ""
if user.is_superuser:
return msg
quota_debug = app_settings.QUOTA_DEBUG
user_instances = UserInstance.objects.filter(user=user, instance__is_template=False)
instance += user_instances.count()
for usr_inst in user_instances:
if connection_manager.host_is_up(
usr_inst.instance.compute.type,
usr_inst.instance.compute.hostname,
):
conn = wvmInstance(
usr_inst.instance.compute.hostname,
usr_inst.instance.compute.login,
usr_inst.instance.compute.password,
usr_inst.instance.compute.type,
usr_inst.instance.name,
)
cpu += int(conn.get_vcpu())
memory += int(conn.get_memory())
for disk in conn.get_disk_devices():
if disk['size']:
disk_size += int(disk['size']) >> 30
if ua.max_instances > 0 and instance > ua.max_instances:
msg = "instance"
if quota_debug:
msg += f" ({instance} > {ua.max_instances})"
if ua.max_cpus > 0 and cpu > ua.max_cpus:
msg = "cpu"
if quota_debug:
msg += f" ({cpu} > {ua.max_cpus})"
if ua.max_memory > 0 and memory > ua.max_memory:
msg = "memory"
if quota_debug:
msg += f" ({memory} > {ua.max_memory})"
if ua.max_disk_size > 0 and disk_size > ua.max_disk_size:
msg = "disk"
if quota_debug:
msg += f" ({disk_size} > {ua.max_disk_size})"
return msg
def get_new_disk_dev(media, disks, bus):
existing_disk_devs = []
existing_media_devs = []
if bus == "virtio":
dev_base = "vd"
elif bus == "ide":
dev_base = "hd"
elif bus == "fdc":
dev_base = "fd"
else:
dev_base = "sd"
if disks:
existing_disk_devs = [disk['dev'] for disk in disks]
# cd-rom bus could be virtio/sata, because of that we should check it also
if media:
existing_media_devs = [m['dev'] for m in media]
for al in string.ascii_lowercase:
dev = dev_base + al
if dev not in existing_disk_devs and dev not in existing_media_devs:
return dev
raise Exception(_('None available device name'))
def get_network_tuple(network_source_str):
network_source_pack = network_source_str.split(":", 1)
if len(network_source_pack) > 1:
return network_source_pack[1], network_source_pack[0]
else:
return network_source_pack[0], 'net'
def migrate_instance(
new_compute,
instance,
user,
live=False,
unsafe=False,
xml_del=False,
offline=False,
autoconverge=False,
compress=False,
postcopy=False,
):
status = connection_manager.host_is_up(new_compute.type, new_compute.hostname)
if not status:
return
if new_compute == instance.compute:
return
try:
conn_migrate = wvmInstances(
new_compute.hostname,
new_compute.login,
new_compute.password,
new_compute.type,
)
autostart = instance.autostart
conn_migrate.moveto(
instance.proxy,
instance.name,
live,
unsafe,
xml_del,
offline,
autoconverge,
compress,
postcopy,
)
conn_new = wvmInstance(
new_compute.hostname,
new_compute.login,
new_compute.password,
new_compute.type,
instance.name,
)
if autostart:
conn_new.set_autostart(1)
finally:
conn_migrate.close()
conn_new.close()
instance.compute = new_compute
instance.save()
def get_hosts_status(computes):
"""
Function return all hosts all vds on host
"""
compute_data = []
for compute in computes:
compute_data.append({
'id': compute.id,
'name': compute.name,
'hostname': compute.hostname,
'status': connection_manager.host_is_up(compute.type, compute.hostname),
'type': compute.type,
'login': compute.login,
'password': compute.password,
'details': compute.details,
})
return compute_data
def get_userinstances_info(instance):
info = {}
uis = UserInstance.objects.filter(instance=instance)
info['count'] = uis.count()
if info['count'] > 0:
info['first_user'] = uis[0]
else:
info['first_user'] = None
return info
def refr(compute):
if compute.status is True:
domains = compute.proxy.wvm.listAllDomains()
domain_names = [d.name() for d in domains]
# Delete instances that're not on host
Instance.objects.filter(compute=compute).exclude(name__in=domain_names).delete()
# Create instances that're not in DB
names = Instance.objects.filter(compute=compute).values_list('name', flat=True)
for domain in domains:
if domain.name() not in names:
Instance(compute=compute, name=domain.name(), uuid=domain.UUIDString()).save()
def refresh_instance_database(comp, inst_name, info, all_host_vms, user):
# Multiple Instance Name Check
instances = Instance.objects.filter(name=inst_name)
if instances.count() > 1:
for i in instances:
user_instances_count = UserInstance.objects.filter(instance=i).count()
if user_instances_count == 0:
addlogmsg(user.username, i.name, _("Deleting due to multiple(Instance Name) records."))
i.delete()
# Multiple UUID Check
instances = Instance.objects.filter(uuid=info['uuid'])
if instances.count() > 1:
for i in instances:
if i.name != inst_name:
addlogmsg(user.username, i.name, _("Deleting due to multiple(UUID) records."))
i.delete()
try:
inst_on_db = Instance.objects.get(compute_id=comp["id"], name=inst_name)
if inst_on_db.uuid != info['uuid']:
inst_on_db.save()
all_host_vms[comp["id"], comp["name"], comp["status"], comp["cpu"], comp["mem_size"],
comp["mem_perc"]][inst_name]['is_template'] = inst_on_db.is_template
all_host_vms[comp["id"], comp["name"], comp["status"], comp["cpu"], comp["mem_size"],
comp["mem_perc"]][inst_name]['userinstances'] = get_userinstances_info(inst_on_db)
except Instance.DoesNotExist:
inst_on_db = Instance(compute_id=comp["id"], name=inst_name, uuid=info['uuid'])
inst_on_db.save()
def get_user_instances(user):
all_user_vms = {}
user_instances = UserInstance.objects.filter(user=user)
for usr_inst in user_instances:
if connection_manager.host_is_up(usr_inst.instance.compute.type, usr_inst.instance.compute.hostname):
conn = wvmHostDetails(
usr_inst.instance.compute.hostname,
usr_inst.instance.compute.login,
usr_inst.instance.compute.password,
usr_inst.instance.compute.type,
)
all_user_vms[usr_inst] = conn.get_user_instances(usr_inst.instance.name)
all_user_vms[usr_inst].update({'compute_id': usr_inst.instance.compute.id})
return all_user_vms
def get_host_instances(compute):
all_host_vms = OrderedDict()
# if compute.status:
comp_node_info = compute.proxy.get_node_info()
comp_mem = compute.proxy.get_memory_usage()
comp_instances = compute.proxy.get_host_instances(True)
# if comp_instances:
comp_info = {
"id": compute.id,
"name": compute.name,
"status": compute.status,
"cpu": comp_node_info[3],
"mem_size": comp_node_info[2],
"mem_perc": comp_mem['percent'],
}
# refr(compute)
all_host_vms[comp_info["id"], comp_info["name"], comp_info["status"], comp_info["cpu"], comp_info["mem_size"],
comp_info["mem_perc"]] = comp_instances
for vm, info in comp_instances.items():
# TODO: Delete this function completely
refresh_instance_database(comp_info, vm, info, all_host_vms, User.objects.get(pk=1))
# else:
# raise libvirtError(_(f"Problem occurred with host: {compute.name} - {status}"))
return all_host_vms
def get_dhcp_mac_address(vname):
dhcp_file = settings.BASE_DIR + '/dhcpd.conf'
mac = ''
if os.path.isfile(dhcp_file):
with open(dhcp_file, 'r') as f:
name_found = False
for line in f:
if "host %s." % vname in line:
name_found = True
if name_found and "hardware ethernet" in line:
mac = line.split(' ')[-1].strip().strip(';')
break
return mac
def get_random_mac_address():
mac = '52:54:00:%02x:%02x:%02x' % (
random.randint(0x00, 0xff),
random.randint(0x00, 0xff),
random.randint(0x00, 0xff),
)
return mac
def get_clone_disk_name(disk, prefix, clone_name=''):
if not disk['image']:
return None
if disk['image'].startswith(prefix) and clone_name:
suffix = disk['image'][len(prefix):]
image = f"{clone_name}{suffix}"
elif "." in disk['image'] and len(disk['image'].rsplit(".", 1)[1]) <= 7:
name, suffix = disk['image'].rsplit(".", 1)
image = f"{name}-clone.{suffix}"
else:
image = f"{disk['image']}-clone"
return image
# TODO: this function is not used
def _get_clone_disks(disks, vname=''):
clone_disks = []
for disk in disks:
new_image = get_clone_disk_name(disk, vname)
if not new_image:
continue
new_disk = {
'dev': disk['dev'],
'storage': disk['storage'],
'image': new_image,
'format': disk['format'],
}
clone_disks.append(new_disk)
return clone_disks

File diff suppressed because it is too large Load diff

View file

@ -4,8 +4,14 @@ function filter_table() {
$('.searchable tr').filter(function () {
return rex.test($(this).text());
}).show();
Cookies.set("instances_filter", $(this).val(), { expires: 1 });
}
$(document).ready(function () {
instances_filter_cookie = Cookies.get("instances_filter");
if (instances_filter_cookie) {
$('#filter').val(instances_filter_cookie);
$('#filter').each(filter_table);
}
(function ($) {
$('#filter').keyup(filter_table)
}(jQuery));

View file

@ -4,12 +4,9 @@ from django.utils.translation import ugettext_lazy as _
class AddStgPool(forms.Form):
name = forms.CharField(error_messages={'required': _('No pool name has been entered')},
max_length=20)
name = forms.CharField(error_messages={'required': _('No pool name has been entered')}, max_length=20)
stg_type = forms.CharField(max_length=10)
target = forms.CharField(error_messages={'required': _('No path has been entered')},
max_length=100,
required=False)
target = forms.CharField(error_messages={'required': _('No path has been entered')}, max_length=100, required=False)
source = forms.CharField(max_length=100, required=False)
ceph_user = forms.CharField(required=False)
ceph_host = forms.CharField(required=False)
@ -51,11 +48,9 @@ class AddStgPool(forms.Form):
return source
class AddImage(forms.Form):
class CreateVolumeForm(forms.Form):
name = forms.CharField(max_length=120)
format = forms.ChoiceField(required=True, choices=(('qcow2', 'qcow2 (recommended)'),
('qcow', 'qcow'),
('raw', 'raw')))
format = forms.ChoiceField(required=True, choices=(('qcow2', 'qcow2 (recommended)'), ('qcow', 'qcow'), ('raw', 'raw')))
size = forms.IntegerField()
meta_prealloc = forms.BooleanField(required=False)
@ -64,8 +59,6 @@ class AddImage(forms.Form):
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
if not have_symbol:
raise forms.ValidationError(_('The image name must not contain any special characters'))
elif len(name) > 120:
raise forms.ValidationError(_('The image name must not exceed 120 characters'))
return name
@ -73,9 +66,7 @@ class CloneImage(forms.Form):
name = forms.CharField(max_length=120)
image = forms.CharField(max_length=120)
convert = forms.BooleanField(required=False)
format = forms.ChoiceField(required=False, choices=(('qcow2', 'qcow2 (recommended)'),
('qcow', 'qcow'),
('raw', 'raw')))
format = forms.ChoiceField(required=False, choices=(('qcow2', 'qcow2 (recommended)'), ('qcow', 'qcow'), ('raw', 'raw')))
meta_prealloc = forms.BooleanField(required=False)
def clean_name(self):

View file

@ -1,4 +1,5 @@
{% load i18n %}
{% load bootstrap4 %}
{% if request.user.is_superuser %}
{% if state != 0 %}
{% if pool == "iso" %}
@ -46,42 +47,15 @@
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<form method="post" role="form" aria-label="Create volume to storage form">{% csrf_token %}
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans "Name" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="name" placeholder="{% trans "Name" %}" required pattern="[a-zA-Z0-9\.\-_]+">
</div>
</div>
<div class="form-group row" id="image_format">
<label class="col-sm-3 col-form-label">{% trans "Format" %}</label>
<div class="col-sm-6">
<select name="format" class="form-control image-format">
<option value="qcow2">{% trans "qcow2" %}</option>
<option value="qcow">{% trans "qcow" %}</option>
<option value="raw">{% trans "raw" %}</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans "Size" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="size" value="10" maxlength="3" required pattern="[0-9]+">
</div>
<label class="col-sm-1 col-form-label">{% trans "GB" %}</label>
</div>
<div class="form-group row meta-prealloc">
<label class="col-sm-3 col-form-label">{% trans "Metadata" %}</label>
<div class="col-sm-6">
<input type="checkbox" name="meta_prealloc" value="true">
</div>
</div>
<form action="{% url 'create_volume' compute_id pool %}" id="create-volume" method="post" role="form" aria-label="Create volume to storage form">
{% csrf_token %}
{% bootstrap_form form layout='horizontal' %}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans "Close" %}</button>
<button type="submit" class="btn btn-primary" name="add_volume">{% trans "Create" %}</button>
<button type="submit" form="create-volume" class="btn btn-primary">{% trans "Create" %}</button>
</div>
</form>
</div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog -->
</div> <!-- /.modal -->

View file

@ -40,7 +40,6 @@
<!-- /.row -->
{% include 'errors_block.html' %}
{% include 'messages_block.html' %}
<dl class="ml-3 row">
<dt class="col-6">{% trans "Pool name" %}</dt>

View file

@ -1,3 +1,6 @@
from django.test import TestCase
# Create your tests here.
class StoragesTestCase(TestCase):
def setUp(self):
pass

View file

@ -1,15 +1,17 @@
import json
from django.contrib import messages
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import render, get_object_or_404
from django.utils.translation import ugettext_lazy as _
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from libvirt import libvirtError
from admin.decorators import superuser_only
from computes.models import Compute
from appsettings.models import AppSettings
from storages.forms import AddStgPool, AddImage, CloneImage
from appsettings.settings import app_settings
from computes.models import Compute
from storages.forms import AddStgPool, CloneImage, CreateVolumeForm
from vrtManager.storage import wvmStorage, wvmStorages
@ -79,11 +81,10 @@ def storage(request, compute_id, pool):
destination.write(chunk)
destination.close()
error_messages = []
compute = get_object_or_404(Compute, pk=compute_id)
meta_prealloc = False
form = CreateVolumeForm()
try:
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, pool)
storages = conn.get_storages()
@ -104,72 +105,34 @@ def storage(request, compute_id, pool):
volumes = conn.update_volumes()
else:
volumes = None
except libvirtError as lib_err:
error_messages.append(lib_err)
if request.method == 'POST':
if 'start' in request.POST:
try:
conn.start()
return HttpResponseRedirect(request.get_full_path())
except libvirtError as lib_err:
error_messages.append(lib_err)
if 'stop' in request.POST:
try:
conn.stop()
return HttpResponseRedirect(request.get_full_path())
except libvirtError as lib_err:
error_messages.append(lib_err)
if 'delete' in request.POST:
try:
conn.delete()
return HttpResponseRedirect(reverse('storages', args=[compute_id]))
except libvirtError as lib_err:
error_messages.append(lib_err)
if 'set_autostart' in request.POST:
try:
conn.set_autostart(1)
return HttpResponseRedirect(request.get_full_path())
except libvirtError as lib_err:
error_messages.append(lib_err)
if 'unset_autostart' in request.POST:
try:
conn.set_autostart(0)
return HttpResponseRedirect(request.get_full_path())
except libvirtError as lib_err:
error_messages.append(lib_err)
if 'add_volume' in request.POST:
form = AddImage(request.POST)
if form.is_valid():
data = form.cleaned_data
if data['meta_prealloc'] and data['format'] == 'qcow2':
meta_prealloc = True
try:
disk_owner = AppSettings.objects.filter(key__startswith="INSTANCE_VOLUME_DEFAULT_OWNER")
disk_owner_uid = int(disk_owner.get(key="INSTANCE_VOLUME_DEFAULT_OWNER_UID").value)
disk_owner_gid = int(disk_owner.get(key="INSTANCE_VOLUME_DEFAULT_OWNER_GID").value)
name = conn.create_volume(data['name'], data['size'], data['format'], meta_prealloc, disk_owner_uid, disk_owner_gid)
messages.success(request, _(f"Image file {name} is created successfully"))
return HttpResponseRedirect(request.get_full_path())
except libvirtError as lib_err:
error_messages.append(lib_err)
else:
for msg_err in form.errors.values():
error_messages.append(msg_err.as_text())
if 'del_volume' in request.POST:
volname = request.POST.get('volname', '')
try:
vol = conn.get_volume(volname)
vol.delete(0)
messages.success(request, _(f"Volume: {volname} is deleted."))
return HttpResponseRedirect(request.get_full_path())
except libvirtError as lib_err:
error_messages.append(lib_err)
return redirect(reverse('storage', args=[compute.id, pool]))
# return HttpResponseRedirect(request.get_full_path())
if 'iso_upload' in request.POST:
if str(request.FILES['file']) in conn.update_volumes():
error_msg = _("ISO image already exist")
error_messages.append(error_msg)
messages.error(request, error_msg)
else:
handle_uploaded_file(path, request.FILES['file'])
messages.success(request, _(f"ISO: {request.FILES['file']} is uploaded."))
@ -182,8 +145,7 @@ def storage(request, compute_id, pool):
meta_prealloc = 0
if img_name in conn.update_volumes():
msg = _("Name of volume already in use")
error_messages.append(msg)
if not error_messages:
messages.error(request, msg)
if data['convert']:
format = data['format']
if data['meta_prealloc'] and data['format'] == 'qcow2':
@ -195,16 +157,50 @@ def storage(request, compute_id, pool):
messages.success(request, _(f"{data['image']} image cloned as {name} successfully"))
return HttpResponseRedirect(request.get_full_path())
except libvirtError as lib_err:
error_messages.append(lib_err)
messages.error(request, lib_err)
else:
for msg_err in form.errors.values():
error_messages.append(msg_err.as_text())
messages.error(request, msg_err.as_text())
conn.close()
return render(request, 'storage.html', locals())
@superuser_only
def create_volume(request, compute_id, pool):
compute = get_object_or_404(Compute, pk=compute_id)
meta_prealloc = False
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, pool)
storages = conn.get_storages()
form = CreateVolumeForm(request.POST or None)
if form.is_valid():
data = form.cleaned_data
if data['meta_prealloc'] and data['format'] == 'qcow2':
meta_prealloc = True
disk_owner_uid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_UID)
disk_owner_gid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_GID)
name = conn.create_volume(
data['name'],
data['size'],
data['format'],
meta_prealloc,
disk_owner_uid,
disk_owner_gid,
)
messages.success(request, _(f"Image file {name} is created successfully"))
else:
for msg_err in form.errors.values():
messages.error(request, msg_err.as_text())
return redirect(reverse('storage', args=[compute.id, pool]))
def get_volumes(request, compute_id, pool):
"""
:param request:

View file

@ -37,11 +37,21 @@
{% include 'navbar.html' %}
<div role="main" class="container">
<div class"row">
<div class="col-sm-12">
<div class="float-right">
{% block page_header_extra %}
{% endblock page_header_extra %}
</div>
<h3 class="page-header">
{% block page_header %}
{% endblock page_header %}
</h3>
</div>
</div>
{% bootstrap_messages %}
{% block content %}{% endblock %}
</div> <!-- /container -->
</div>
<!-- Bootstrap core JavaScript
================================================== -->

View file

@ -5,20 +5,18 @@
{% block title %}{{ title }}{% endblock %}
{% block page_header %}{{ title }}{% endblock page_header %}
{% block content %}
<div class="row">
<div class="col-lg-12">
<h2 class="page-header">{{ title }}</h2>
</div>
</div>
{% bootstrap_messages %}
<div class="row">
<div class="thumbnail col-sm-10 offset-1">
<div class="card">
<div class="card-body">
<form id="create-update" action="" method="post">
{% csrf_token %}
{% bootstrap_form form layout='horizontal' %}
</form>
<div class="form-group float-right">
</div>
<div class="card-footer">
<div class="form-group mb-0 float-right">
<a class="btn btn-primary" href="javascript:history.back()">{% icon 'times' %} {% trans "Cancel" %}</a>
<button type="submit" form="create-update" class="btn btn-success">
{% icon 'check' %} {% trans "Save" %}

View file

@ -7,7 +7,6 @@ from vrtManager.rwlock import ReadWriteLock
from django.conf import settings
from libvirt import libvirtError
CONN_SOCKET = 4
CONN_TLS = 3
CONN_SSH = 2
@ -19,7 +18,6 @@ TCP_PORT = 16509
class wvmEventLoop(threading.Thread):
""" Event Loop Class"""
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
# register the default event implementation
# of libvirt, as we do not have an existing
@ -48,7 +46,6 @@ class wvmConnection(object):
class representing a single connection stored in the Connection Manager
# to-do: may also need some locking to ensure to not connect simultaniously in 2 threads
"""
def __init__(self, host, login, passwd, conn):
"""
Sets all class attributes and tries to open the connection
@ -208,7 +205,7 @@ class wvmConnection(object):
except:
pass
def __unicode__(self):
def __str__(self):
if self.type == CONN_TCP:
type_str = 'tcp'
elif self.type == CONN_SSH:
@ -332,7 +329,7 @@ class wvmConnectionManager(object):
connection_manager = wvmConnectionManager(
settings.LIBVIRT_KEEPALIVE_INTERVAL if hasattr(settings, 'LIBVIRT_KEEPALIVE_INTERVAL') else 5,
settings.LIBVIRT_KEEPALIVE_COUNT if hasattr(settings, 'LIBVIRT_KEEPALIVE_COUNT') else 5
settings.LIBVIRT_KEEPALIVE_COUNT if hasattr(settings, 'LIBVIRT_KEEPALIVE_COUNT') else 5,
)
@ -374,9 +371,11 @@ class wvmConnect(object):
result["machines"] = []
for m in arch_el.xpath("machine"):
result["machines"].append({"machine": m.text,
result["machines"].append({
"machine": m.text,
"max_cpu": m.get("maxCpus"),
"canonical": m.get("canonical")})
"canonical": m.get("canonical")
})
guest_el = arch_el.getparent()
for f in guest_el.xpath("features"):
@ -385,6 +384,7 @@ class wvmConnect(object):
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):
@ -560,6 +560,7 @@ class wvmConnect(object):
arch_name = arch.xpath('@name')[0]
result[arch_name] = domain_types
return result
return util.get_xml_path(self.get_cap_xml(), func=hypervisors)
def get_hypervisors_machines(self):
@ -573,6 +574,7 @@ class wvmConnect(object):
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):
@ -607,6 +609,7 @@ class wvmConnect(object):
arch_name = arch.xpath('@name')[0]
result[arch_name] = emulator
return result
return util.get_xml_path(self.get_cap_xml(), func=emulators)
def get_os_loaders(self, arch='x86_64', machine='pc'):
@ -617,6 +620,7 @@ class wvmConnect(object):
"""
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_os_loader_enums(self, arch, machine):
@ -632,6 +636,7 @@ class wvmConnect(object):
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):
@ -642,6 +647,7 @@ class wvmConnect(object):
"""
def get_bus_list(ctx):
return [v.text for v in ctx.xpath("/domainCapabilities/devices/disk/enum[@name='bus']/value")]
# return [ 'ide', 'scsi', 'usb', 'virtio' ]
return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_bus_list)
@ -653,6 +659,7 @@ class wvmConnect(object):
"""
def get_device_list(ctx):
return [v.text for v in ctx.xpath("/domainCapabilities/devices/disk/enum[@name='diskDevice']/value")]
# return [ 'disk', 'cdrom', 'floppy', 'lun' ]
return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_device_list)
@ -664,6 +671,7 @@ class wvmConnect(object):
"""
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):
@ -674,6 +682,7 @@ class wvmConnect(object):
"""
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):
@ -688,6 +697,7 @@ class wvmConnect(object):
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):
@ -698,6 +708,7 @@ class wvmConnect(object):
"""
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):
@ -708,6 +719,7 @@ class wvmConnect(object):
"""
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):
@ -718,6 +730,7 @@ class wvmConnect(object):
"""
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_network_models(self):
@ -751,6 +764,7 @@ class wvmConnect(object):
for values in video_enum:
result.append(values.text)
return result
return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=get_video_list)
def get_iface(self, name):
@ -829,7 +843,7 @@ class wvmConnect(object):
mem = util.get_xpath(doc, "/domain/currentMemory")
mem = int(mem) / 1024
if raw_mem_size:
mem = int(mem) * (1024*1024)
mem = int(mem) * (1024 * 1024)
cur_vcpu = util.get_xpath(doc, "/domain/vcpu/@current")
if cur_vcpu:
vcpu = cur_vcpu
@ -840,6 +854,7 @@ class wvmConnect(object):
description = util.get_xpath(doc, "/domain/description")
description = description if description else ''
return mem, vcpu, title, description
for name in self.get_instances():
dom = self.get_instance(name)
xml = dom.XMLDesc(0)
@ -871,6 +886,7 @@ class wvmConnect(object):
description = util.get_xpath(ctx, "/domain/description")
description = description if description else ''
return mem, vcpu, title, description
(mem, vcpu, title, description) = util.get_xml_path(xml, func=get_info)
return {
'name': dom.name(),
@ -929,8 +945,7 @@ class wvmConnect(object):
"""
Return True if libvirt advertises support for proper UEFI setup
"""
return ("readonly" in loader_enums and
"yes" in loader_enums.get("readonly"))
return ("readonly" in loader_enums and "yes" in loader_enums.get("readonly"))
def is_supports_virtio(self, arch, machine):
if not self.is_qemu():

View file

@ -206,6 +206,8 @@ class wvmInstance(wvmConnect):
cur_vcpu = util.get_xml_path(self._XMLDesc(0), "/domain/vcpu/@current")
if cur_vcpu:
return int(cur_vcpu)
else:
return self.get_vcpu()
def get_arch(self):
return util.get_xml_path(self._XMLDesc(0), "/domain/os/type/@arch")
@ -251,7 +253,6 @@ class wvmInstance(wvmConnect):
return title if title else ''
def get_filterrefs(self):
def filterrefs(ctx):
result = []
for net in ctx.xpath('/domain/devices/interface'):
@ -295,8 +296,7 @@ class wvmInstance(wvmConnect):
for addr in addrs["addrs"]:
if addr["type"] == 0:
ipv4.append(addr["addr"])
elif (addr["type"] == 1 and
not str(addr["addr"]).startswith("fe80")):
elif (addr["type"] == 1 and not str(addr["addr"]).startswith("fe80")):
ipv6.append(addr["addr"] + "/" + str(addr["prefix"]))
return ipv4, ipv6
@ -335,14 +335,12 @@ class wvmInstance(wvmConnect):
return
if self.is_agent_ready():
self._ip_cache["qemuga"] = self._get_interface_addresses(
VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT)
self._ip_cache["qemuga"] = self._get_interface_addresses(VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT)
arp_flag = 3 # libvirt."VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP"
self._ip_cache["arp"] = self._get_interface_addresses(arp_flag)
def get_net_devices(self):
def networks(ctx):
result = []
inbound = outbound = []
@ -370,7 +368,8 @@ class wvmInstance(wvmConnect):
ipv4, ipv6 = self.get_interface_addresses(mac_inst)
except libvirtError:
ipv4, ipv6 = None, None
result.append({'mac': mac_inst,
result.append({
'mac': mac_inst,
'nic': nic_inst,
'target': target_inst,
'state': link_state,
@ -400,7 +399,13 @@ class wvmInstance(wvmConnect):
try:
dev = disk.xpath('target/@dev')[0]
bus = disk.xpath('target/@bus')[0]
src_file = disk.xpath('source/@file|source/@dev|source/@name|source/@volume')[0]
try:
src_file = disk.xpath('source/@file|source/@dev|source/@name')[0]
except Exception as e:
v = disk.xpath('source/@volume')[0]
s_name = disk.xpath('source/@pool')[0]
s = self.wvm.storagePoolLookupByName(s_name)
src_file = s.storageVolLookupByName(v).path()
try:
disk_format = disk.xpath('driver/@type')[0]
except:
@ -436,14 +441,26 @@ class wvmInstance(wvmConnect):
storage = stg.name()
except libvirtError:
volume = src_file
except:
pass
except Exception as e:
print(f'Exception: {e}')
finally:
result.append(
{'dev': dev, 'bus': bus, 'image': volume, 'storage': storage, 'path': src_file,
'format': disk_format, 'size': disk_size, 'used': used_size,
'cache': disk_cache, 'io': disk_io, 'discard': disk_discard, 'detect_zeroes': disk_zeroes,
'readonly': readonly, 'shareable': shareable, 'serial': serial})
result.append({
'dev': dev,
'bus': bus,
'image': volume,
'storage': storage,
'path': src_file,
'format': disk_format,
'size': disk_size,
'used': used_size,
'cache': disk_cache,
'io': disk_io,
'discard': disk_discard,
'detect_zeroes': disk_zeroes,
'readonly': readonly,
'shareable': shareable,
'serial': serial
})
return result
return util.get_xml_path(self._XMLDesc(0), func=disks)
@ -544,7 +561,7 @@ class wvmInstance(wvmConnect):
elif dev_type == 'usb':
pass
boot_order[int(idx)-1] = {"type": dev_type, "dev": dev_device, "target": dev_target}
boot_order[int(idx) - 1] = {"type": dev_type, "dev": dev_device, "target": dev_target}
return boot_order
@ -642,10 +659,21 @@ class wvmInstance(wvmConnect):
xmldom = ElementTree.tostring(tree).decode()
self._defineXML(xmldom)
def attach_disk(self, target_dev, source, target_bus='ide', disk_type='file',
disk_device='disk', driver_name='qemu', driver_type='raw',
readonly=False, shareable=False, serial=None,
cache_mode=None, io_mode=None, discard_mode=None, detect_zeroes_mode=None):
def attach_disk(self,
target_dev,
source,
target_bus='ide',
disk_type='file',
disk_device='disk',
driver_name='qemu',
driver_type='raw',
readonly=False,
shareable=False,
serial=None,
cache_mode=None,
io_mode=None,
discard_mode=None,
detect_zeroes_mode=None):
additionals = ''
if cache_mode is not None and cache_mode != 'default' and disk_device != 'cdrom':
@ -691,7 +719,8 @@ class wvmInstance(wvmConnect):
if self.get_status() == 5:
self.instance.detachDeviceFlags(xml_disk, VIR_DOMAIN_AFFECT_CONFIG)
def edit_disk(self, target_dev, source, readonly, shareable, target_bus, serial, format, cache_mode, io_mode, discard_mode, detect_zeroes_mode):
def edit_disk(self, target_dev, source, readonly, shareable, target_bus, serial, format, cache_mode, io_mode, discard_mode,
detect_zeroes_mode):
tree = etree.fromstring(self._XMLDesc(0))
disk_el = tree.xpath("./devices/disk/target[@dev='{}']".format(target_dev))[0].getparent()
old_disk_type = disk_el.get('type')
@ -735,7 +764,7 @@ class wvmInstance(wvmConnect):
time.sleep(1)
cpu_use_now = self.instance.info()[4]
diff_usage = cpu_use_now - cpu_use_ago
cpu_usage['cpu'] = 100 * diff_usage / (1 * nbcore * 10 ** 9)
cpu_usage['cpu'] = 100 * diff_usage / (1 * nbcore * 10**9)
else:
cpu_usage['cpu'] = 0
return cpu_usage
@ -928,8 +957,7 @@ class wvmInstance(wvmConnect):
def get_console_websocket_port(self):
console_type = self.get_console_type()
websocket_port = util.get_xml_path(self._XMLDesc(0),
"/domain/devices/graphics[@type='%s']/@websocket" % console_type)
websocket_port = util.get_xml_path(self._XMLDesc(0), "/domain/devices/graphics[@type='%s']/@websocket" % console_type)
return websocket_port
def get_console_passwd(self):
@ -1124,7 +1152,7 @@ class wvmInstance(wvmConnect):
return mac
# if mac does not contain ":", try to split into tuples and join with ":"
n = 2
mac_tuples = [mac[i:i+n] for i in range(0, len(mac), n)]
mac_tuples = [mac[i:i + n] for i in range(0, len(mac), n)]
return ':'.join(mac_tuples)
def clone_instance(self, clone_data):
@ -1388,7 +1416,7 @@ class wvmInstance(wvmConnect):
tree.remove(option)
else:
if option is None:
option = etree.SubElement(tree , o)
option = etree.SubElement(tree, o)
option.text = option_value
def set_options(self, options):
@ -1421,7 +1449,13 @@ class wvmInstance(wvmConnect):
in_peak = in_qos.get('peak')
in_burst = in_qos.get('burst')
in_floor = in_qos.get('floor')
bound_list.append({'direction': 'inbound', 'average': in_av, 'peak': in_peak, 'floor': in_floor, 'burst': in_burst})
bound_list.append({
'direction': 'inbound',
'average': in_av,
'peak': in_peak,
'floor': in_floor,
'burst': in_burst
})
out_qos = band.find('outbound')
if out_qos is not None:

View file

@ -0,0 +1,24 @@
import json
from django.contrib import messages
from django.http import HttpResponse
from django.shortcuts import render
from django.utils.translation import ugettext_lazy as _
from libvirt import libvirtError
class ExceptionMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
if isinstance(exception, libvirtError):
messages.error(
request,
_('libvirt Error - %(exception)s') % {'exception': exception},
)
return render(request, '500.html', status=500)
# TODO: check connecting to host via VPN

View file

@ -28,7 +28,6 @@ INSTALLED_APPS = [
'appsettings',
'computes',
'console',
'create',
'datasource',
'networks',
'instances',
@ -51,6 +50,7 @@ MIDDLEWARE = [
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'appsettings.middleware.AppSettingsMiddleware',
'webvirtcloud.middleware.ExceptionMiddleware',
]
ROOT_URLCONF = 'webvirtcloud.urls'