From 164c9a9145832f16a008b58847026cd603d8baa5 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Fri, 13 Nov 2015 09:13:36 +0000 Subject: [PATCH 01/76] order Compute objects by name --- computes/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/computes/views.py b/computes/views.py index 39af4d0..63c17f8 100644 --- a/computes/views.py +++ b/computes/views.py @@ -41,7 +41,7 @@ def computes(request): return compute_data error_messages = [] - computes = Compute.objects.filter() + computes = Compute.objects.filter().order_by('name') computes_info = get_hosts_status(computes) if request.method == 'POST': From 8f2f95e128027beb649a1735bafa5188866fa6b4 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Fri, 13 Nov 2015 09:15:38 +0000 Subject: [PATCH 02/76] cloning instance with lvm disk also clones the disk and sets the correct target in XML configuration --- vrtManager/instance.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 4218aa4..ceec977 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -8,6 +8,7 @@ from vrtManager import util from xml.etree import ElementTree from datetime import datetime from vrtManager.connection import wvmConnect +from vrtManager.storage import wvmStorage from webvirtcloud.settings import QEMU_CONSOLE_TYPES @@ -598,6 +599,14 @@ class wvmInstance(wvmConnect): def get_managed_save_image(self): return self.instance.hasManagedSaveImage(0) + def get_wvmStorage(self, pool): + storage = wvmStorage(self.host, + self.login, + self.passwd, + self.conn, + pool) + return storage + def clone_instance(self, clone_data): clone_dev_path = [] @@ -649,5 +658,19 @@ class wvmInstance(wvmConnect): """ % (target_file, vol_format) stg = vol.storagePoolLookupByVolume() stg.createXMLFrom(vol_clone_xml, vol, meta_prealloc) - + + source_dev = elm.get('dev') + if source_dev: + clone_path = os.path.join(os.path.dirname(source_dev), target_file) + elm.set('dev', clone_path) + + vol = self.get_volume_by_path(source_dev) + stg = vol.storagePoolLookupByVolume() + + vol_name = util.get_xml_path(vol.XMLDesc(0), "/volume/name") + pool_name = util.get_xml_path(stg.XMLDesc(0), "/pool/name") + + storage = self.get_wvmStorage(pool_name) + storage.clone_volume(vol_name, target_file) + self._defineXML(ElementTree.tostring(tree)) From 04f3a76c05aa9cf2656859d32dbb483d97fc5981 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Tue, 24 Nov 2015 08:53:13 +0000 Subject: [PATCH 03/76] resize disk image option added --- instances/templates/instance.html | 9 +++++++++ instances/views.py | 25 ++++++++++++++++++++++++- vrtManager/instance.py | 7 ++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index c3baecc..fb2075c 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -355,6 +355,15 @@ {% trans "Custom value" %} +

{% trans "Disk allocation (B):" %}

+ {% for disk in disks %} +
+ +
+ +
+
+ {% endfor %} {% ifequal status 5 %} {% else %} diff --git a/instances/views.py b/instances/views.py index 29a1a1b..a68f52c 100644 --- a/instances/views.py +++ b/instances/views.py @@ -186,6 +186,23 @@ def instance(request, compute_id, vname): {'dev': disk['dev'], 'storage': disk['storage'], 'image': image, 'format': disk['format']}) return clone_disk + + def filesizefstr(size_str): + if size_str == '': + return 0 + size_str = size_str.encode('ascii', 'ignore').upper().translate(None, " B") + if 'K' == size_str[-1]: + return long(float(size_str[:-1]))<<10 + elif 'M' == size_str[-1]: + return long(float(size_str[:-1]))<<20 + elif 'G' == size_str[-1]: + return long(float(size_str[:-1]))<<30 + elif 'T' == size_str[-1]: + return long(float(size_str[:-1]))<<40 + elif 'P' == size_str[-1]: + return long(float(size_str[:-1]))<<50 + else: + return long(float(size_str)) try: conn = wvmInstance(compute.hostname, @@ -340,7 +357,13 @@ def instance(request, compute_id, vname): cur_memory_custom = request.POST.get('cur_memory_custom', '') if cur_memory_custom: cur_memory = cur_memory_custom - conn.resize(cur_memory, memory, cur_vcpu, vcpu) + disks_new = [] + for disk in disks: + input_disk_size = filesizefstr(request.POST.get('disk_size_' + disk['dev'], '')) + if input_disk_size > disk['size']+(64<<20): + disk['size_new'] = input_disk_size + disks_new.append(disk) + conn.resize(cur_memory, memory, cur_vcpu, vcpu, disks_new) msg = _("Resize") addlogmsg(request.user.username, instance.name, msg) return HttpResponseRedirect(request.get_full_path() + '#resize') diff --git a/vrtManager/instance.py b/vrtManager/instance.py index ceec977..fe003b1 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -524,7 +524,7 @@ class wvmInstance(wvmConnect): return util.get_xml_path(self._XMLDesc(VIR_DOMAIN_XML_SECURE), "/domain/devices/graphics/@keymap") or '' - def resize(self, cur_memory, memory, cur_vcpu, vcpu): + def resize(self, cur_memory, memory, cur_vcpu, vcpu, disks=[]): """ Function change ram and cpu on vds. """ @@ -542,6 +542,11 @@ class wvmInstance(wvmConnect): set_vcpu.text = vcpu set_vcpu.set('current', cur_vcpu) + for disk in disks: + source_dev = disk['path'] + vol = self.get_volume_by_path(source_dev) + vol.resize(disk['size_new']) + new_xml = ElementTree.tostring(tree) self._defineXML(new_xml) From 35369edf28ef560ca71da00f574561b08175cbde Mon Sep 17 00:00:00 2001 From: Anatoliy Guskov Date: Fri, 27 Nov 2015 09:38:04 +0200 Subject: [PATCH 04/76] Fixed Checking KVM --- vrtManager/util.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vrtManager/util.py b/vrtManager/util.py index b152e92..939e5b7 100644 --- a/vrtManager/util.py +++ b/vrtManager/util.py @@ -1,12 +1,11 @@ -import re import random import libxml2 import libvirt def is_kvm_available(xml): - capabilites = re.search('kvm', xml) - if capabilites: + kvm_domains = get_xml_path(xml, "//domain/@type='kvm'") + if kvm_domains > 0: return True else: return False From 6dbd6e558cc357c27db7c4376e427b2a280f474d Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Mon, 21 Dec 2015 10:28:03 +0000 Subject: [PATCH 05/76] pip installing packages need gcc and pkg-config on debian/ubuntu --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d140ff..29d266d 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ WebVirtCloud is a virtualization web interface for admins and users. It can dele ### Install WebVirtCloud panel (Ubuntu) ```bash -sudo apt-get -y install git python-virtualenv python-dev libxml2-dev libvirt-dev zlib1g-dev nginx supervisor libsasl2-modules +sudo apt-get -y install git python-virtualenv python-dev libxml2-dev libvirt-dev zlib1g-dev nginx supervisor libsasl2-modules gcc pkg-config git clone https://github.com/retspen/webvirtcloud cd webvirtcloud sudo cp conf/supervisor/webvirtcloud.conf /etc/supervisor/conf.d From decd5ab4a65a74ff8d6504ef770070b519597f8c Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Tue, 22 Dec 2015 09:48:54 +0100 Subject: [PATCH 06/76] add remoteuser auth middleware classes --- webvirtcloud/settings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/webvirtcloud/settings.py b/webvirtcloud/settings.py index a163fa3..d8e8906 100644 --- a/webvirtcloud/settings.py +++ b/webvirtcloud/settings.py @@ -38,11 +38,16 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.RemoteUserMiddleware', 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) +AUTHENTICATION_BACKENDS = ( + 'django.contrib.auth.backends.RemoteUserBackend', +) + ROOT_URLCONF = 'webvirtcloud.urls' WSGI_APPLICATION = 'webvirtcloud.wsgi.application' From f8c08cb71916b3a3ea8938df49895db75d51988a Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Tue, 22 Dec 2015 15:04:23 +0100 Subject: [PATCH 07/76] wsgi.py customized for use mod_wsgi with apache module --- README.md | 6 ++++++ webvirtcloud/wsgi.py | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 29d266d..f1b3767 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,12 @@ webvirtcloud RUNNING pid 24185, uptime 2:59:14 ``` +#### Apache mod_wsgi configuration +``` +WSGIDaemonProcess webvirtcloud threads=2 maximum-requests=1000 display-name=webvirtcloud +WSGIScriptAlias / /srv/webvirtcloud/webvirtcloud/wsgi.py +``` + #### Install final required packages for libvirtd and others on Host Server ```bash wget -O - https://clck.ru/9V9fH | sudo sh diff --git a/webvirtcloud/wsgi.py b/webvirtcloud/wsgi.py index 35ceb9b..a9bf44c 100644 --- a/webvirtcloud/wsgi.py +++ b/webvirtcloud/wsgi.py @@ -7,7 +7,10 @@ For more information on this file, see https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ """ -import os +execfile('/srv/webvirtcloud/venv/bin/activate_this.py', dict(__file__='/srv/webvirtcloud/venv/bin/activate_this.py')) + +import os, sys +sys.path.append('/srv/webvirtcloud') os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webvirtcloud.settings") from django.core.wsgi import get_wsgi_application From ae4fdcec92a275baa62173a67f8691f399a91a93 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Tue, 22 Dec 2015 15:06:19 +0100 Subject: [PATCH 08/76] added class MyRemoteUserBackend(RemoteUserBackend) giving all authenticated users superuser flag --- accounts/backends.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 accounts/backends.py diff --git a/accounts/backends.py b/accounts/backends.py new file mode 100644 index 0000000..77aa509 --- /dev/null +++ b/accounts/backends.py @@ -0,0 +1,7 @@ +from django.contrib.auth.backends import RemoteUserBackend + +class MyRemoteUserBackend(RemoteUserBackend): + def configure_user(self, user): + user.is_superuser = True + return user + From dac974ddabd4317296d4ec66f8ac2843c67265bb Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Tue, 22 Dec 2015 15:09:02 +0100 Subject: [PATCH 09/76] request.user.is_authenticated() substitued for @login_required decorator settings.LOGIN_URL = /accounts/login --- accounts/views.py | 13 ++++--------- computes/views.py | 13 ++++--------- console/views.py | 5 ++--- create/views.py | 5 ++--- instances/views.py | 23 +++++++---------------- interfaces/views.py | 9 +++------ networks/views.py | 9 +++------ secrets/views.py | 5 ++--- storages/views.py | 9 +++------ webvirtcloud/settings.py | 3 +++ 10 files changed, 33 insertions(+), 61 deletions(-) diff --git a/accounts/views.py b/accounts/views.py index d2893bb..899a67c 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -3,18 +3,18 @@ from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User +from django.contrib.auth.decorators import login_required from accounts.models import UserInstance, UserSSHKey from instances.models import Instance from accounts.forms import UserAddForm +@login_required def profile(request): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) error_messages = [] user = User.objects.get(id=request.user.id) @@ -63,16 +63,13 @@ def profile(request): return HttpResponseRedirect(request.get_full_path()) return render(request, 'profile.html', locals()) - +@login_required def accounts(request): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) @@ -123,15 +120,13 @@ def accounts(request): return render(request, 'accounts.html', locals()) +@login_required def account(request, user_id): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) diff --git a/computes/views.py b/computes/views.py index 63c17f8..e293107 100644 --- a/computes/views.py +++ b/computes/views.py @@ -3,6 +3,7 @@ import json from django.http import HttpResponse, HttpResponseRedirect from django.core.urlresolvers import reverse from django.shortcuts import render, get_object_or_404 +from django.contrib.auth.decorators import login_required from computes.models import Compute from instances.models import Instance from accounts.models import UserInstance @@ -12,15 +13,13 @@ from vrtManager.connection import CONN_SSH, CONN_TCP, CONN_TLS, CONN_SOCKET, con from libvirt import libvirtError +@login_required def computes(request): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) @@ -130,15 +129,13 @@ def computes(request): return render(request, 'computes.html', locals()) +@login_required def overview(request, compute_id): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) @@ -160,15 +157,13 @@ def overview(request, compute_id): return render(request, 'overview.html', locals()) +@login_required def compute_graph(request, compute_id): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('login')) - points = 5 datasets = {} cookies = {} diff --git a/console/views.py b/console/views.py index a123064..4651c87 100644 --- a/console/views.py +++ b/console/views.py @@ -2,6 +2,7 @@ import re from django.shortcuts import render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse +from django.contrib.auth.decorators import login_required from instances.models import Instance from vrtManager.instance import wvmInstance from webvirtcloud.settings import WS_PORT @@ -9,15 +10,13 @@ from webvirtcloud.settings import WS_PUBLIC_HOST from libvirt import libvirtError +@login_required def console(request): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('login')) - if request.method == 'GET': token = request.GET.get('token', '') diff --git a/create/views.py b/create/views.py index 5b03f8c..3f97606 100644 --- a/create/views.py +++ b/create/views.py @@ -2,6 +2,7 @@ from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse +from django.contrib.auth.decorators import login_required from computes.models import Compute from create.models import Flavor from create.forms import FlavorAddForm, NewVMForm @@ -11,15 +12,13 @@ from vrtManager import util from libvirt import libvirtError +@login_required def create_instance(request, compute_id): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) diff --git a/instances/views.py b/instances/views.py index a68f52c..f6f8007 100644 --- a/instances/views.py +++ b/instances/views.py @@ -9,6 +9,7 @@ from django.http import HttpResponse, HttpResponseRedirect from django.core.urlresolvers import reverse from django.shortcuts import render, get_object_or_404 from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.decorators import login_required from computes.models import Compute from instances.models import Instance from accounts.models import UserInstance, UserSSHKey @@ -20,27 +21,23 @@ from webvirtcloud.settings import QEMU_KEYMAPS, QEMU_CONSOLE_TYPES from logs.views import addlogmsg +@login_required def index(request): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('login')) - else: - return HttpResponseRedirect(reverse('instances')) + return HttpResponseRedirect(reverse('instances')) +@login_required def instances(request): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - error_messages = [] all_host_vms = {} all_user_vms = {} @@ -144,15 +141,13 @@ def instances(request): return render(request, 'instances.html', locals()) +@login_required def instance(request, compute_id, vname): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - error_messages = [] messages = [] compute = get_object_or_404(Compute, pk=compute_id) @@ -517,15 +512,13 @@ def instance(request, compute_id, vname): return render(request, 'instance.html', locals()) +@login_required def inst_status(request, compute_id, vname): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('login')) - compute = get_object_or_404(Compute, pk=compute_id) response = HttpResponse() response['Content-Type'] = "text/javascript" @@ -544,15 +537,13 @@ def inst_status(request, compute_id, vname): return response +@login_required def inst_graph(request, compute_id, vname): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('login')) - datasets = {} json_blk = [] datasets_blk = {} diff --git a/interfaces/views.py b/interfaces/views.py index a889b27..921d0b8 100644 --- a/interfaces/views.py +++ b/interfaces/views.py @@ -1,21 +1,20 @@ from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse +from django.contrib.auth.decorators import login_required from computes.models import Compute from interfaces.forms import AddInterface from vrtManager.interface import wvmInterface, wvmInterfaces from libvirt import libvirtError +@login_required def interfaces(request, compute_id): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) @@ -57,15 +56,13 @@ def interfaces(request, compute_id): return render(request, 'interfaces.html', locals()) +@login_required def interface(request, compute_id, iface): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) diff --git a/networks/views.py b/networks/views.py index 01347db..24d6bc6 100644 --- a/networks/views.py +++ b/networks/views.py @@ -2,6 +2,7 @@ from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse +from django.contrib.auth.decorators import login_required from computes.models import Compute from networks.forms import AddNetPool from vrtManager.network import wvmNetwork, wvmNetworks @@ -9,15 +10,13 @@ from vrtManager.network import network_size from libvirt import libvirtError +@login_required def networks(request, compute_id): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) @@ -60,15 +59,13 @@ def networks(request, compute_id): return render(request, 'networks.html', locals()) +@login_required def network(request, compute_id, pool): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) diff --git a/secrets/views.py b/secrets/views.py index 5e6bf0e..90e5e36 100644 --- a/secrets/views.py +++ b/secrets/views.py @@ -1,21 +1,20 @@ from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse +from django.contrib.auth.decorators import login_required from computes.models import Compute from secrets.forms import AddSecret from vrtManager.secrets import wvmSecrets from libvirt import libvirtError +@login_required def secrets(request, compute_id): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) diff --git a/storages/views.py b/storages/views.py index 499d8b3..d3f1965 100644 --- a/storages/views.py +++ b/storages/views.py @@ -2,21 +2,20 @@ from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse +from django.contrib.auth.decorators import login_required from computes.models import Compute from storages.forms import AddStgPool, AddImage, CloneImage from vrtManager.storage import wvmStorage, wvmStorages from libvirt import libvirtError +@login_required def storages(request, compute_id): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) @@ -68,15 +67,13 @@ def storages(request, compute_id): return render(request, 'storages.html', locals()) +@login_required def storage(request, compute_id, pool): """ :param request: :return: """ - if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse('index')) - if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) diff --git a/webvirtcloud/settings.py b/webvirtcloud/settings.py index d8e8906..610f757 100644 --- a/webvirtcloud/settings.py +++ b/webvirtcloud/settings.py @@ -46,8 +46,11 @@ MIDDLEWARE_CLASSES = ( AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.RemoteUserBackend', + #'accounts.backends.MyRemoteUserBackend', ) +LOGIN_URL = '/accounts/login' + ROOT_URLCONF = 'webvirtcloud.urls' WSGI_APPLICATION = 'webvirtcloud.wsgi.application' From 0eb60b1aa49c28273a7a412883d6c7a278227d75 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Mon, 28 Dec 2015 13:53:40 +0100 Subject: [PATCH 10/76] ignore novnc certificate (console/cert.pem) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7d921d2..10f2658 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ venv .DS_* *.pyc db.sqlite3 +console/cert.pem From 1499af1eef07e060433ee82bc95fd545dfdfd29f Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Mon, 28 Dec 2015 13:57:17 +0100 Subject: [PATCH 11/76] upgrade websockify 0.6.0 to 0.7.0 older version contains bug that closes novnc connection after 30 seconds. this commit puts the right version into conf/requirements.txt used by pip install --- conf/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/requirements.txt b/conf/requirements.txt index 676876f..63f4677 100644 --- a/conf/requirements.txt +++ b/conf/requirements.txt @@ -1,5 +1,5 @@ Django==1.8.2 -websockify==0.6.0 +websockify==0.7.0 gunicorn==19.3.0 libvirt-python==1.2.16 http://github.com/retspen/retspen.github.io/raw/master/libxml2-python-2.9.1.tar.gz From 50ddda98f2e02187e167816b8482b89945d52cd8 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Thu, 14 Jan 2016 16:59:50 +0100 Subject: [PATCH 12/76] settings tab for changing devices/interface/source/bridge added TODO: handle multiple interfaces --- instances/templates/instance.html | 34 ++++++++++++++++++++++++++++++- instances/views.py | 12 +++++++++++ vrtManager/instance.py | 13 ++++++++++++ webvirtcloud/settings.py | 2 +- 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index fb2075c..c9ebc58 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -491,6 +491,11 @@ {% trans "VNC" %} +
  • + + {% trans "Network" %} + +
  • {% trans "Clone" %} @@ -652,6 +657,33 @@
    +
    +

    {% trans "Assign network device to bridge" %}

    +
    {% csrf_token %} +

    {% trans "Network devices" %}

    + {% for network in networks %} +
    + +
    + +
    +
    + +
    +
    + +
    +
    + {% endfor %} + {% ifequal status 5 %} + + {% else %} + + {% endifequal %} +
    +
    +

    {% trans "Create a clone" %}

    {% csrf_token %} @@ -1117,7 +1149,7 @@ } }); } - if (~$.inArray(hash, ['#media', '#clone', '#autostart', '#xmledit', '#vncsettings', '#migrate'])) { + if (~$.inArray(hash, ['#media', '#network', '#clone', '#autostart', '#xmledit', '#vncsettings', '#migrate'])) { var btnsect = $('#navbtn>li>a'); $(btnsect).each(function () { if ($(this).attr('href') === '#settings') { diff --git a/instances/views.py b/instances/views.py index f6f8007..371d6fe 100644 --- a/instances/views.py +++ b/instances/views.py @@ -503,6 +503,18 @@ def instance(request, compute_id, vname): addlogmsg(request.user.username, instance.name, msg) return HttpResponseRedirect(reverse('instance', args=[compute_id, clone_data['name']])) + if 'change_network' in request.POST: + network_data = {} + + for post in request.POST: + if 'net-' in post: + network_data[post] = request.POST.get(post, '') + + conn.change_network(network_data) + msg = _("Edit network") + addlogmsg(request.user.username, instance.name, msg) + return HttpResponseRedirect(request.get_full_path() + '#network') + conn.close() except libvirtError as lib_err: diff --git a/vrtManager/instance.py b/vrtManager/instance.py index fe003b1..f47747c 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -679,3 +679,16 @@ class wvmInstance(wvmConnect): storage.clone_volume(vol_name, target_file) self._defineXML(ElementTree.tostring(tree)) + + def change_network(self, network_data): + xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE) + tree = ElementTree.fromstring(xml) + + for interface in tree.findall('devices/interface'): + if interface.get('type') == 'bridge': + source = interface.find('source') + source.set('bridge', network_data['net-source-0']) + + new_xml = ElementTree.tostring(tree) + self._defineXML(new_xml) + diff --git a/webvirtcloud/settings.py b/webvirtcloud/settings.py index 610f757..4395315 100644 --- a/webvirtcloud/settings.py +++ b/webvirtcloud/settings.py @@ -8,7 +8,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__)) SECRET_KEY = '4y(f4rfqc6f2!i8_vfuu)kav6tdv5#sc=n%o451dm+th0&3uci' -DEBUG = False +DEBUG = True TEMPLATE_DEBUG = DEBUG From 6151792d9bc289aa16448256f5e9db702cd881d1 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Fri, 15 Jan 2016 10:12:03 +0100 Subject: [PATCH 13/76] allow multiple owners of single instance. this is controlled by settings.ALLOW_INSTANCE_MULTIPLE_OWNER --- accounts/views.py | 23 +++++++++++++++-------- webvirtcloud/settings.py | 4 +++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/accounts/views.py b/accounts/views.py index 899a67c..2e0cc2b 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -7,6 +7,7 @@ from django.contrib.auth.decorators import login_required from accounts.models import UserInstance, UserSSHKey from instances.models import Instance from accounts.forms import UserAddForm +from django.conf import settings @login_required @@ -135,6 +136,11 @@ def account(request, user_id): user_insts = UserInstance.objects.filter(user_id=user_id) instances = Instance.objects.all() + def add_user_inst(request, inst_id, user_id): + add_user_inst = UserInstance(instance_id=int(inst_id), user_id=user_id) + add_user_inst.save() + return HttpResponseRedirect(request.get_full_path()) + if user.username == request.user.username: return HttpResponseRedirect(reverse('profile')) @@ -155,13 +161,14 @@ def account(request, user_id): return HttpResponseRedirect(request.get_full_path()) if 'add' in request.POST: inst_id = request.POST.get('inst_id', '') - try: - check_inst = UserInstance.objects.get(instance_id=int(inst_id)) - msg = _("Instance already added") - error_messages.append(msg) - except UserInstance.DoesNotExist: - add_user_inst = UserInstance(instance_id=int(inst_id), user_id=user_id) - add_user_inst.save() - return HttpResponseRedirect(request.get_full_path()) + if settings.ALLOW_INSTANCE_MULTIPLE_OWNER: + return add_user_inst(request, inst_id, user_id) + else: + try: + check_inst = UserInstance.objects.get(instance_id=int(inst_id)) + msg = _("Instance already added") + error_messages.append(msg) + except UserInstance.DoesNotExist: + return add_user_inst(request, inst_id, user_id) return render(request, 'account.html', locals()) diff --git a/webvirtcloud/settings.py b/webvirtcloud/settings.py index 4395315..471659f 100644 --- a/webvirtcloud/settings.py +++ b/webvirtcloud/settings.py @@ -8,7 +8,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__)) SECRET_KEY = '4y(f4rfqc6f2!i8_vfuu)kav6tdv5#sc=n%o451dm+th0&3uci' -DEBUG = True +DEBUG = False TEMPLATE_DEBUG = DEBUG @@ -111,3 +111,5 @@ QEMU_KEYMAPS = ['ar', 'da', 'de', 'de-ch', 'en-gb', 'en-us', 'es', 'et', 'fi', # keepalive interval and count for libvirt connections LIBVIRT_KEEPALIVE_INTERVAL = 5 LIBVIRT_KEEPALIVE_COUNT = 5 + +ALLOW_INSTANCE_MULTIPLE_OWNER = True From ca328a7527e954706dad531576274096e2f6688d Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Fri, 15 Jan 2016 15:09:23 +0100 Subject: [PATCH 14/76] instances/templates/instance.html checkbox delete_disk default checked --- instances/templates/instance.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index c9ebc58..0ae50f4 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -880,7 +880,7 @@ {% csrf_token %}
    From d036d582fdcd944dea6780be6f8ae97ccb0b29df Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Tue, 19 Jan 2016 15:39:56 +0100 Subject: [PATCH 15/76] .gitignore: tags --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 10f2658..ab2416e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ venv *.pyc db.sqlite3 console/cert.pem +tags From 8deb844c35533d605a2c6ac2432a04e41397ca22 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Tue, 19 Jan 2016 15:43:56 +0100 Subject: [PATCH 16/76] adding new instance to user checks existing instances assigned to user --- accounts/views.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/accounts/views.py b/accounts/views.py index 2e0cc2b..a364865 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -136,11 +136,6 @@ def account(request, user_id): user_insts = UserInstance.objects.filter(user_id=user_id) instances = Instance.objects.all() - def add_user_inst(request, inst_id, user_id): - add_user_inst = UserInstance(instance_id=int(inst_id), user_id=user_id) - add_user_inst.save() - return HttpResponseRedirect(request.get_full_path()) - if user.username == request.user.username: return HttpResponseRedirect(reverse('profile')) @@ -161,14 +156,18 @@ def account(request, user_id): return HttpResponseRedirect(request.get_full_path()) if 'add' in request.POST: inst_id = request.POST.get('inst_id', '') + if settings.ALLOW_INSTANCE_MULTIPLE_OWNER: - return add_user_inst(request, inst_id, user_id) + check_inst = UserInstance.objects.filter(instance_id=int(inst_id), user_id=int(user_id)) else: - try: - check_inst = UserInstance.objects.get(instance_id=int(inst_id)) - msg = _("Instance already added") - error_messages.append(msg) - except UserInstance.DoesNotExist: - return add_user_inst(request, inst_id, user_id) + check_inst = UserInstance.objects.filter(instance_id=int(inst_id)) + + if check_inst: + msg = _("Instance already added") + error_messages.append(msg) + else: + add_user_inst = UserInstance(instance_id=int(inst_id), user_id=int(user_id)) + add_user_inst.save() + return HttpResponseRedirect(request.get_full_path()) return render(request, 'account.html', locals()) From d4fa71ff25b804bda332521f248c56b4fa36131e Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Wed, 20 Jan 2016 14:35:23 +0100 Subject: [PATCH 17/76] default true: live migration and delete original --- instances/templates/instance.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index 0ae50f4..8d6393b 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -755,7 +755,7 @@
    - +
    @@ -767,7 +767,7 @@
    - +
    {% if computes_count != 1 %} From 4ab85613601672d7d03299f63922cd2dbe649745 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Wed, 20 Jan 2016 14:49:49 +0100 Subject: [PATCH 18/76] correct value=true of checked checkboxes --- instances/templates/instance.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index 8d6393b..c33082e 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -755,7 +755,7 @@
    - +
  • +
  • + + {% trans "Template" %} + +
  • {% endif %} @@ -684,6 +694,23 @@
    +
    +

    {% trans "Is this instance template?" %}

    +
    {% csrf_token %} +
    + +
    + +
    +
    + {% ifequal status 5 %} + + {% else %} + + {% endifequal %} +
    +
    +

    {% trans "Create a clone" %}

    {% csrf_token %} @@ -1149,7 +1176,7 @@ } }); } - if (~$.inArray(hash, ['#media', '#network', '#clone', '#autostart', '#xmledit', '#vncsettings', '#migrate'])) { + if (~$.inArray(hash, ['#media', '#network', '#clone', '#autostart', '#xmledit', '#vncsettings', '#migrate', '#template'])) { var btnsect = $('#navbtn>li>a'); $(btnsect).each(function () { if ($(this).attr('href') === '#settings') { diff --git a/instances/templates/instances.html b/instances/templates/instances.html index 144a5cd..b279d40 100644 --- a/instances/templates/instances.html +++ b/instances/templates/instances.html @@ -69,7 +69,7 @@ {% ifequal info.status 5 %} -
    @@ -737,8 +737,8 @@ {% for disk in clone_disks %}
    -
    - +
    +
    {% ifequal disk.format 'qcow2' %} @@ -964,6 +964,28 @@ } } + + diff --git a/instances/views.py b/instances/views.py index 92c59fb..314148f 100644 --- a/instances/views.py +++ b/instances/views.py @@ -511,7 +511,7 @@ def instance(request, compute_id, vname): network_data = {} for post in request.POST: - if 'net-' in post: + if post.startswith('net-'): network_data[post] = request.POST.get(post, '') conn.change_network(network_data) diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 20728db..c1009eb 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -632,7 +632,7 @@ class wvmInstance(wvmConnect): for num, net in enumerate(tree.findall('devices/interface')): elm = net.find('mac') - mac_address = self.fix_mac(clone_data['net-' + str(num)]) + mac_address = self.fix_mac(clone_data['clone-net-mac-' + str(num)]) elm.set('address', mac_address) for disk in tree.findall('devices/disk'): @@ -693,10 +693,12 @@ class wvmInstance(wvmConnect): xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE) tree = ElementTree.fromstring(xml) - for interface in tree.findall('devices/interface'): + for num, interface in enumerate(tree.findall('devices/interface')): if interface.get('type') == 'bridge': + source = interface.find('mac') + source.set('address', network_data['net-mac-' + str(num)]) source = interface.find('source') - source.set('bridge', network_data['net-source-0']) + source.set('bridge', network_data['net-source-' + str(num)]) new_xml = ElementTree.tostring(tree) self._defineXML(new_xml) From a1d5edebe2f054e95124193f93135e311a0e99db Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Thu, 11 Feb 2016 12:56:36 +0100 Subject: [PATCH 26/76] guess_mac_address reads /srv/webvirtcloud/dhcpd.conf --- instances/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instances/views.py b/instances/views.py index 314148f..57caf46 100644 --- a/instances/views.py +++ b/instances/views.py @@ -669,7 +669,7 @@ def inst_graph(request, compute_id, vname): @login_required def guess_mac_address(request, vname): - dhcp_file = '/srv/webvirtcloud/dhcpd.hype.ipv4.conf' + dhcp_file = '/srv/webvirtcloud/dhcpd.conf' data = { 'vname': vname, 'mac': '52:54:00:' } with open(dhcp_file, 'r') as f: name_found = False From 2958a21ad1d0390525b2a2d6d648cbe3a2d0f15f Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Thu, 11 Feb 2016 14:37:26 +0100 Subject: [PATCH 27/76] instance/check_instance service endpoint added. checks for existing instance (returns json) --- instances/urls.py | 2 ++ instances/views.py | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/instances/urls.py b/instances/urls.py index c1b2841..5df6755 100644 --- a/instances/urls.py +++ b/instances/urls.py @@ -10,4 +10,6 @@ urlpatterns = [ views.inst_status, name='inst_status'), url(r'^guess_mac_address/(?P[\w\-\.]+)/$', views.guess_mac_address, name='guess_mac_address'), + url(r'^check_instance/(?P[\w\-\.]+)/$', + views.check_instance, name='check_instance'), ] diff --git a/instances/views.py b/instances/views.py index 57caf46..1f1f9c1 100644 --- a/instances/views.py +++ b/instances/views.py @@ -680,3 +680,11 @@ def guess_mac_address(request, vname): data['mac'] = line.split(' ')[-1].strip().strip(';') break return HttpResponse(json.dumps(data)); + +@login_required +def check_instance(request, vname): + check_instance = Instance.objects.filter(name=vname) + data = { 'vname': vname, 'exists': False } + if check_instance: + data['exists'] = True + return HttpResponse(json.dumps(data)); From 16510dee5906027585e355e369dea061d86e8c4f Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Thu, 11 Feb 2016 14:46:42 +0100 Subject: [PATCH 28/76] instance/clone block new instance creating if instance with same name found --- instances/views.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/instances/views.py b/instances/views.py index 1f1f9c1..361c1c2 100644 --- a/instances/views.py +++ b/instances/views.py @@ -498,14 +498,19 @@ def instance(request, compute_id, vname): clone_data = {} clone_data['name'] = request.POST.get('name', '') - for post in request.POST: - if 'disk' or 'meta' in post: - clone_data[post] = request.POST.get(post, '') + check_instance = Instance.objects.filter(name=clone_data['name']) + if check_instance: + msg = _("Instance '%s' already exists!" % clone_data['name']) + error_messages.append(msg) + else: + for post in request.POST: + if 'disk' or 'meta' in post: + clone_data[post] = request.POST.get(post, '') - conn.clone_instance(clone_data) - msg = _("Clone") - addlogmsg(request.user.username, instance.name, msg) - return HttpResponseRedirect(reverse('instance', args=[compute_id, clone_data['name']])) + conn.clone_instance(clone_data) + msg = _("Clone") + addlogmsg(request.user.username, instance.name, msg) + return HttpResponseRedirect(reverse('instance', args=[compute_id, clone_data['name']])) if 'change_network' in request.POST: network_data = {} From 510e0e6ee586629a7fae2c9375e648dcd0dcfa4a Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Thu, 11 Feb 2016 14:48:41 +0100 Subject: [PATCH 29/76] guess_mac checks for existing dhcp_file --- instances/views.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/instances/views.py b/instances/views.py index 361c1c2..f69f356 100644 --- a/instances/views.py +++ b/instances/views.py @@ -1,3 +1,4 @@ +import os import time import json import socket @@ -676,14 +677,15 @@ def inst_graph(request, compute_id, vname): def guess_mac_address(request, vname): dhcp_file = '/srv/webvirtcloud/dhcpd.conf' data = { 'vname': vname, 'mac': '52:54:00:' } - 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: - data['mac'] = line.split(' ')[-1].strip().strip(';') - break + 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: + data['mac'] = line.split(' ')[-1].strip().strip(';') + break return HttpResponse(json.dumps(data)); @login_required From 2ceb456a4f009ad0026658f9350bbcaf05f0b69e Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Tue, 23 Feb 2016 14:43:32 +0100 Subject: [PATCH 30/76] section #template renamed to #options, added title and description fields --- instances/templates/instance.html | 25 ++++++++++++++++++------- instances/views.py | 14 +++++++++++--- vrtManager/instance.py | 28 +++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index 948fd7d..4eeb659 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -517,8 +517,8 @@
  • - - {% trans "Template" %} + + {% trans "Options" %}
  • {% endif %} @@ -690,9 +690,20 @@
    -
    -

    {% trans "Is this instance template?" %}

    +
    {% csrf_token %} +
    + +
    + +
    +
    +
    + +
    + +
    +
    @@ -700,9 +711,9 @@
    {% ifequal status 5 %} - + {% else %} - + {% endifequal %}
    @@ -1207,7 +1218,7 @@ } }); } - if (~$.inArray(hash, ['#media', '#network', '#clone', '#autostart', '#xmledit', '#vncsettings', '#migrate', '#template'])) { + if (~$.inArray(hash, ['#media', '#network', '#clone', '#autostart', '#xmledit', '#vncsettings', '#migrate', '#options'])) { var btnsect = $('#navbtn>li>a'); $(btnsect).each(function () { if ($(this).attr('href') === '#settings') { diff --git a/instances/views.py b/instances/views.py index f69f356..5938f29 100644 --- a/instances/views.py +++ b/instances/views.py @@ -218,6 +218,7 @@ def instance(request, compute_id, vname): uuid = conn.get_uuid() memory = conn.get_memory() cur_memory = conn.get_cur_memory() + title = conn.get_title() description = conn.get_description() disks = conn.get_disk_device() media = conn.get_media_device() @@ -525,12 +526,19 @@ def instance(request, compute_id, vname): addlogmsg(request.user.username, instance.name, msg) return HttpResponseRedirect(request.get_full_path() + '#network') - if 'change_template' in request.POST: + if 'change_options' in request.POST: instance.is_template = request.POST.get('is_template', False) instance.save() - msg = _("Edit template %s" % instance.is_template) + + options = {} + for post in request.POST: + if post in ['title', 'description']: + options[post] = request.POST.get(post, '') + conn.set_options(options) + + msg = _("Edit options") addlogmsg(request.user.username, instance.name, msg) - return HttpResponseRedirect(request.get_full_path() + '#template') + return HttpResponseRedirect(request.get_full_path() + '#options') conn.close() diff --git a/vrtManager/instance.py b/vrtManager/instance.py index c1009eb..3df2483 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -185,8 +185,13 @@ class wvmInstance(wvmConnect): mem = util.get_xml_path(self._XMLDesc(0), "/domain/currentMemory") return int(mem) / 1024 + def get_title(self): + title = util.get_xml_path(self._XMLDesc(0), "/domain/title") + return title if title else '' + def get_description(self): - return util.get_xml_path(self._XMLDesc(0), "/domain/description") + description = util.get_xml_path(self._XMLDesc(0), "/domain/description") + return description if description else '' def get_max_memory(self): return self.wvm.getInfo()[1] * 1048576 @@ -703,3 +708,24 @@ class wvmInstance(wvmConnect): new_xml = ElementTree.tostring(tree) self._defineXML(new_xml) + def set_options(self, options): + """ + Function change description, title + """ + xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE) + tree = ElementTree.fromstring(xml) + + for o in ['title', 'description']: + option = tree.find(o) + option_value = str(options[o]).strip() + if not option_value: + if not option is None: + tree.remove(option) + else: + if option is None: + option = ElementTree.SubElement(tree, o) + option.text = option_value + + new_xml = ElementTree.tostring(tree) + self._defineXML(new_xml) + From e1b4fdf5dedfa67aaff8e4def9feb3af0af2fd58 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Tue, 23 Feb 2016 15:03:33 +0100 Subject: [PATCH 31/76] instances display virtual title under name --- instances/templates/instances.html | 2 +- vrtManager/connection.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/instances/templates/instances.html b/instances/templates/instances.html index b279d40..5e5c761 100644 --- a/instances/templates/instances.html +++ b/instances/templates/instances.html @@ -51,7 +51,7 @@ {% for host, inst in all_host_vms.items %} {% for vm, info in inst.items %} - {{ vm }} + {{ vm }}
    {{ info.title }} {{ host.1 }} {% ifequal info.status 1 %} {% trans "Active" %} diff --git a/vrtManager/connection.py b/vrtManager/connection.py index fd9fb80..ee2f7e7 100644 --- a/vrtManager/connection.py +++ b/vrtManager/connection.py @@ -442,7 +442,16 @@ class wvmConnect(object): vcpu = cur_vcpu else: vcpu = util.get_xml_path(dom.XMLDesc(0), "/domain/vcpu") - vname[dom.name()] = {'status': dom.info()[0], 'uuid': dom.UUIDString(), 'vcpu': vcpu, 'memory': mem} + title = util.get_xml_path(dom.XMLDesc(0), "/domain/title") + description = util.get_xml_path(dom.XMLDesc(0), "/domain/description") + vname[dom.name()] = { + 'status': dom.info()[0], + 'uuid': dom.UUIDString(), + 'vcpu': vcpu, + 'memory': mem, + 'title': title if title else '', + 'description': description if description else '', + } return vname def get_user_instances(self, name): From 679f84f1175e56e85f63cda34ed2d79767fbba5c Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Wed, 24 Feb 2016 12:44:14 +0100 Subject: [PATCH 32/76] template/instance: correct placement of options tab panel div, should be after xmledit div --- instances/templates/instance.html | 56 +++++++++++++++---------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index 4eeb659..ca2fafd 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -690,34 +690,6 @@
    -
    -
    {% csrf_token %} -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    - {% ifequal status 5 %} - - {% else %} - - {% endifequal %} -
    -
    -

    {% trans "Create a clone" %}

    {% csrf_token %} @@ -833,6 +805,34 @@
    +
    +
    {% csrf_token %} +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + {% ifequal status 5 %} + + {% else %} + + {% endifequal %} +
    +
    +
    {% endif %}
    From 6c4a3a93e3bd89f691c4fb13f7ee5db1a4ac4b41 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Wed, 24 Feb 2016 12:50:43 +0100 Subject: [PATCH 33/76] instance/clone view: generation of clone_data algorithm enhanced if disk or meta in post: results always true, so removed. this pushes all POST data to conn.clone_instance() --- instances/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/instances/views.py b/instances/views.py index 5938f29..1d2c623 100644 --- a/instances/views.py +++ b/instances/views.py @@ -506,8 +506,7 @@ def instance(request, compute_id, vname): error_messages.append(msg) else: for post in request.POST: - if 'disk' or 'meta' in post: - clone_data[post] = request.POST.get(post, '') + clone_data[post] = request.POST.get(post, '') conn.clone_instance(clone_data) msg = _("Clone") From 2aa81ccecb4add481c6e9aff21f2a4194d1d9c3b Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Wed, 24 Feb 2016 13:08:43 +0100 Subject: [PATCH 34/76] instance/clone adds title and description input fields --- instances/templates/instance.html | 12 ++++++++++++ vrtManager/instance.py | 27 ++++++++++++++++++--------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index ca2fafd..3633b2e 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -729,6 +729,18 @@ {% endifequal %} {% endfor %} +
    + +
    + +
    +
    +
    + +
    + +
    +
    {% ifequal status 5 %} {% else %} diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 3df2483..5e2ec68 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -691,7 +691,13 @@ class wvmInstance(wvmConnect): storage = self.get_wvmStorage(pool_name) storage.clone_volume(vol_name, target_file) - + + options = { + 'title': clone_data.get('clone-title', ''), + 'description': clone_data.get('clone-description', ''), + } + self._set_options(tree, options) + self._defineXML(ElementTree.tostring(tree)) def change_network(self, network_data): @@ -708,16 +714,10 @@ class wvmInstance(wvmConnect): new_xml = ElementTree.tostring(tree) self._defineXML(new_xml) - def set_options(self, options): - """ - Function change description, title - """ - xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE) - tree = ElementTree.fromstring(xml) - + def _set_options(self, tree, options): for o in ['title', 'description']: option = tree.find(o) - option_value = str(options[o]).strip() + option_value = str(options.get(o, '')).strip() if not option_value: if not option is None: tree.remove(option) @@ -726,6 +726,15 @@ class wvmInstance(wvmConnect): option = ElementTree.SubElement(tree, o) option.text = option_value + def set_options(self, options): + """ + Function change description, title + """ + xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE) + tree = ElementTree.fromstring(xml) + + self._set_options(tree, options) + new_xml = ElementTree.tostring(tree) self._defineXML(new_xml) From fc56e66555ca7c365381ad2a39ec83df6f45a39f Mon Sep 17 00:00:00 2001 From: andrem Date: Fri, 18 Mar 2016 12:07:42 -0300 Subject: [PATCH 35/76] add migration for new column details for hypervisor. --- computes/migrations/0002_compute_details.py | 18 ++++++++++++++++++ computes/models.py | 1 + 2 files changed, 19 insertions(+) create mode 100644 computes/migrations/0002_compute_details.py diff --git a/computes/migrations/0002_compute_details.py b/computes/migrations/0002_compute_details.py new file mode 100644 index 0000000..1e0fdf5 --- /dev/null +++ b/computes/migrations/0002_compute_details.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + +class Migration(migrations.Migration): + + dependencies = [ + ('computes', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='Compute', + name='details', + field=models.CharField(max_length=50, null=True, blank=True), + ), + ] diff --git a/computes/models.py b/computes/models.py index 6ee7de8..df9bf02 100644 --- a/computes/models.py +++ b/computes/models.py @@ -6,6 +6,7 @@ class Compute(models.Model): hostname = models.CharField(max_length=20) login = models.CharField(max_length=20) password = models.CharField(max_length=14, blank=True, null=True) + details = models.CharField(max_length=50, null=True, blank=True) type = models.IntegerField() def __unicode__(self): From b6350e134e2c6ecf185de8af9398facf1f6c99a6 Mon Sep 17 00:00:00 2001 From: andrem Date: Fri, 18 Mar 2016 15:21:44 -0300 Subject: [PATCH 36/76] add details for local socket --- computes/forms.py | 2 ++ computes/templates/computes.html | 5 +++++ computes/templates/create_comp_block.html | 10 +++++++++- computes/templates/overview.html | 2 ++ computes/views.py | 7 +++++-- webvirtcloud/settings.py | 2 +- 6 files changed, 24 insertions(+), 4 deletions(-) diff --git a/computes/forms.py b/computes/forms.py index a626106..7dfcbe6 100644 --- a/computes/forms.py +++ b/computes/forms.py @@ -149,6 +149,8 @@ class ComputeEditHostForm(forms.Form): class ComputeAddSocketForm(forms.Form): name = forms.CharField(error_messages={'required': _('No hostname has been entered')}, max_length=20) + details = forms.CharField(error_messages={'required': _('No details has been entred')}, + max_length=50) def clean_name(self): name = self.cleaned_data['name'] diff --git a/computes/templates/computes.html b/computes/templates/computes.html index 7c1c28f..2ffc6f4 100644 --- a/computes/templates/computes.html +++ b/computes/templates/computes.html @@ -45,6 +45,11 @@ {% else %}

    {% trans "Not Connected" %}

    {% endif %} + {% if compute.details %} +

    {% trans compute.details %}

    + {% else %} +

    {% trans "No details available" %}

    + {% endif %} diff --git a/computes/templates/create_comp_block.html b/computes/templates/create_comp_block.html index 57e327a..9e9a965 100644 --- a/computes/templates/create_comp_block.html +++ b/computes/templates/create_comp_block.html @@ -141,6 +141,14 @@ + +
    + +
    + +
    +
    + -{% endif %} \ No newline at end of file +{% endif %} diff --git a/computes/templates/overview.html b/computes/templates/overview.html index 09d5d83..8bfad6e 100644 --- a/computes/templates/overview.html +++ b/computes/templates/overview.html @@ -40,6 +40,7 @@

    {% trans "Logical CPUs" %}

    {% trans "Processor" %}

    {% trans "Connection" %}

    +

    {% trans "Details" %}

    {{ hostname }}

    @@ -49,6 +50,7 @@

    {{ logical_cpu }}

    {{ model_cpu }}

    {{ uri_conn }}

    +

    {{ compute.details }}

    diff --git a/computes/views.py b/computes/views.py index 39af4d0..30d81b3 100644 --- a/computes/views.py +++ b/computes/views.py @@ -36,14 +36,15 @@ def computes(request): 'status': connection_manager.host_is_up(compute.type, compute.hostname), 'type': compute.type, 'login': compute.login, - 'password': compute.password + 'password': compute.password, + 'details': compute.details }) return compute_data error_messages = [] computes = Compute.objects.filter() computes_info = get_hosts_status(computes) - + if request.method == 'POST': if 'host_del' in request.POST: compute_id = request.POST.get('host_id', '') @@ -104,6 +105,7 @@ def computes(request): if form.is_valid(): data = form.cleaned_data new_socket_host = Compute(name=data['name'], + details=data['details'], hostname='localhost', type=CONN_SOCKET, login='', @@ -122,6 +124,7 @@ def computes(request): compute_edit.hostname = data['hostname'] compute_edit.login = data['login'] compute_edit.password = data['password'] + compute.edit_details = data['details'] compute_edit.save() return HttpResponseRedirect(request.get_full_path()) else: diff --git a/webvirtcloud/settings.py b/webvirtcloud/settings.py index a163fa3..d318ea9 100644 --- a/webvirtcloud/settings.py +++ b/webvirtcloud/settings.py @@ -8,7 +8,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__)) SECRET_KEY = '4y(f4rfqc6f2!i8_vfuu)kav6tdv5#sc=n%o451dm+th0&3uci' -DEBUG = False +DEBUG = True TEMPLATE_DEBUG = DEBUG From 9832e662a880807d49c59c4ca75cd48095190a6e Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Mon, 21 Mar 2016 10:24:09 +0100 Subject: [PATCH 37/76] account create_user_instance form sorts instances by name --- accounts/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/views.py b/accounts/views.py index 30f6904..5b6c4b2 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -138,7 +138,7 @@ def account(request, user_id): error_messages = [] user = User.objects.get(id=user_id) user_insts = UserInstance.objects.filter(user_id=user_id) - instances = Instance.objects.all() + instances = Instance.objects.all().order_by('name') if user.username == request.user.username: return HttpResponseRedirect(reverse('profile')) From c2b3938f2a24ee1a1fb32ee3e58d25afcf292765 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Tue, 22 Mar 2016 10:31:49 +0100 Subject: [PATCH 38/76] add instances/migrations/0002_instance_is_template.py --- .../migrations/0002_instance_is_template.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 instances/migrations/0002_instance_is_template.py diff --git a/instances/migrations/0002_instance_is_template.py b/instances/migrations/0002_instance_is_template.py new file mode 100644 index 0000000..cbf2cdd --- /dev/null +++ b/instances/migrations/0002_instance_is_template.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('instances', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='instance', + name='is_template', + field=models.BooleanField(default=False), + ), + ] From 78dcb10a57d4ba53e46a1f64f7d427baa31395ae Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Tue, 22 Mar 2016 10:32:16 +0100 Subject: [PATCH 39/76] .gitignore dhcpd.* --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ab2416e..b3ebced 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ venv db.sqlite3 console/cert.pem tags +dhcpd.* From de5cb19913e7a7d0571cd6f3fd55f68a96d3b2f0 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Wed, 23 Mar 2016 08:53:29 +0100 Subject: [PATCH 40/76] class UserAddForm: remove is_staff, is_superuser --- accounts/forms.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/accounts/forms.py b/accounts/forms.py index ac29f81..55d5c29 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -9,8 +9,6 @@ class UserAddForm(forms.Form): error_messages={'required': _('No User name has been entered')}, max_length=20) password = forms.CharField(required=True, error_messages={'required': _('No password has been entered')},) - is_staff = forms.BooleanField(required=True) - is_superuser = forms.BooleanField(required=True) def clean_name(self): name = self.cleaned_data['name'] From 317c2a85ae70a7e08c9d77c568369d4d0c892d18 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Wed, 23 Mar 2016 09:00:42 +0100 Subject: [PATCH 41/76] user can now clone instances, admin can specify user quotas (instances,cpus,memory) user can only select predefined instance names, mac and disk names are selected automatically --- accounts/migrations/0004_userattributes.py | 26 +++++ ...0005_userattributes_can_clone_instances.py | 19 ++++ accounts/models.py | 10 ++ accounts/templates/accounts.html | 24 +++++ accounts/views.py | 20 +++- instances/templates/instance.html | 100 ++++++++++++------ instances/views.py | 72 ++++++++++--- vrtManager/instance.py | 3 +- webvirtcloud/settings.py | 1 + 9 files changed, 221 insertions(+), 54 deletions(-) create mode 100644 accounts/migrations/0004_userattributes.py create mode 100644 accounts/migrations/0005_userattributes_can_clone_instances.py diff --git a/accounts/migrations/0004_userattributes.py b/accounts/migrations/0004_userattributes.py new file mode 100644 index 0000000..fb32539 --- /dev/null +++ b/accounts/migrations/0004_userattributes.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('accounts', '0003_usersshkey'), + ] + + operations = [ + migrations.CreateModel( + name='UserAttributes', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('max_instances', models.IntegerField(default=0)), + ('max_cpus', models.IntegerField(default=0)), + ('max_memory', models.IntegerField(default=0)), + ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/accounts/migrations/0005_userattributes_can_clone_instances.py b/accounts/migrations/0005_userattributes_can_clone_instances.py new file mode 100644 index 0000000..4539657 --- /dev/null +++ b/accounts/migrations/0005_userattributes_can_clone_instances.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0004_userattributes'), + ] + + operations = [ + migrations.AddField( + model_name='userattributes', + name='can_clone_instances', + field=models.BooleanField(default=False), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 15cedee..16f5f6e 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -20,3 +20,13 @@ class UserSSHKey(models.Model): def __unicode__(self): return self.keyname + +class UserAttributes(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + can_clone_instances = models.BooleanField(default=False) + max_instances = models.IntegerField(default=0) + max_cpus = models.IntegerField(default=0) + max_memory = models.IntegerField(default=0) + + def __unicode__(self): + return self.user.username diff --git a/accounts/templates/accounts.html b/accounts/templates/accounts.html index cc03668..41d5448 100644 --- a/accounts/templates/accounts.html +++ b/accounts/templates/accounts.html @@ -83,6 +83,30 @@
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + {% endif %} + {% if request.user.is_superuser or request.user.userattributes.can_clone_instances %}

    {% trans "Create a clone" %}

    {% csrf_token %}
    + {% if request.user.is_superuser %} + {% else %} + + {% endif %}
    -

    {% trans "Network devices" %}

    - {% for network in networks %} -
    - -
    - -
    -
    - - -
    -
    - {% endfor %} -

    {% trans "Storage devices" %}

    - {% for disk in clone_disks %} -
    - -
    - -
    - {% ifequal disk.format 'qcow2' %} - -
    - + {% if request.user.is_superuser %} +

    {% trans "Network devices" %}

    + {% for network in networks %} +
    + +
    +
    - {% endifequal %} -
    - {% endfor %} +
    + + +
    +
    + {% endfor %} + {% else %} + {% for network in networks %} + + {% endfor %} + {% endif %} + {% if request.user.is_superuser %} +

    {% trans "Storage devices" %}

    + {% for disk in clone_disks %} +
    + +
    + +
    + {% ifequal disk.format 'qcow2' %} + +
    + +
    + {% endifequal %} +
    + {% endfor %} + {% else %} + {% for disk in clone_disks %} + + {% endfor %} + {% endif %}
    @@ -749,6 +775,8 @@
    + {% endif %} + {% if request.user.is_superuser %}

    {% trans "For migration both host servers must have equal settings and OS type" %}

    {% csrf_token %} @@ -986,8 +1014,8 @@ } + {% if request.user.is_superuser %} From 380cc2d09b0042dd30db74a64698111eb7f522c9 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Wed, 27 Apr 2016 15:32:40 +0200 Subject: [PATCH 58/76] wvmInstance._set_options options.get(o) returns unicode, so cannot be str()ed --- vrtManager/instance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrtManager/instance.py b/vrtManager/instance.py index e792e13..3c287e0 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -718,7 +718,7 @@ class wvmInstance(wvmConnect): def _set_options(self, tree, options): for o in ['title', 'description']: option = tree.find(o) - option_value = str(options.get(o, '')).strip() + option_value = options.get(o, '').strip() if not option_value: if not option is None: tree.remove(option) From e45c712d67da5725b2a038d739bc0614c6473ca3 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Thu, 28 Apr 2016 12:50:11 +0200 Subject: [PATCH 59/76] add instance/options/users tab. lists all owners of the instance --- instances/templates/instance.html | 14 +++++++++++++- instances/views.py | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index b274f03..77f5791 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -529,6 +529,11 @@ {% trans "Options" %} +
  • + + {% trans "Users" %} + +
  • {% endif %} @@ -877,6 +882,13 @@
    +
    +

    {% trans "Instance owners" %}

    + {% for userinstance in userinstances %} +

    {{ userinstance.user }}

    + {% endfor %} +
    +
    {% endif %}
    @@ -1272,7 +1284,7 @@ } }); } - if (~$.inArray(hash, ['#media', '#network', '#clone', '#autostart', '#xmledit', '#vncsettings', '#migrate', '#options'])) { + if (~$.inArray(hash, ['#media', '#network', '#clone', '#autostart', '#xmledit', '#vncsettings', '#migrate', '#options', '#users'])) { var btnsect = $('#navbtn>li>a'); $(btnsect).each(function () { if ($(this).attr('href') === '#settings') { diff --git a/instances/views.py b/instances/views.py index 5fcce4a..4b00ec9 100644 --- a/instances/views.py +++ b/instances/views.py @@ -302,6 +302,8 @@ def instance(request, compute_id, vname): instance = Instance(compute_id=compute_id, name=vname, uuid=uuid) instance.save() + userinstances = UserInstance.objects.filter(instance=instance).order_by('user__username') + if request.method == 'POST': if 'poweron' in request.POST: conn.start() From e966e6c030d5b96a6785e1c480098fa885947340 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Mon, 2 May 2016 12:23:18 +0200 Subject: [PATCH 60/76] add user information per instance on the instances list wider .container (900px) --- instances/templates/instances.html | 6 +++--- instances/views.py | 11 +++++++++++ static/css/webvirtcloud.css | 4 ++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/instances/templates/instances.html b/instances/templates/instances.html index df08f66..05e97f5 100644 --- a/instances/templates/instances.html +++ b/instances/templates/instances.html @@ -39,8 +39,8 @@ - - + + @@ -52,7 +52,7 @@ {% for vm, info in inst.items %} - + +
    NameHostName
    Description
    Host
    User
    Status VCPU Memory
    {{ vm }}
    {{ info.title }}
    {{ host.1 }}{{ host.1 }}
    {% if info.userinstances.count > 0 %}{{ info.userinstances.first_user.user.username }}{% if info.userinstances.count > 1 %} (+{{ info.userinstances.count|add:"-1" }}){% endif %}{% endif %}
    {% ifequal info.status 1 %} {% trans "Active" %} {% endifequal %} diff --git a/instances/views.py b/instances/views.py index 4b00ec9..19dc7dc 100644 --- a/instances/views.py +++ b/instances/views.py @@ -45,6 +45,16 @@ def instances(request): all_user_vms = {} computes = Compute.objects.all() + def get_userinstances_info(instance): + info = {} + uis = UserInstance.objects.filter(instance=instance) + info['count'] = len(uis) + if len(uis) > 0: + info['first_user'] = uis[0] + else: + info['first_user'] = None + return info + if not request.user.is_superuser: user_instances = UserInstance.objects.filter(user_id=request.user.id) for usr_inst in user_instances: @@ -69,6 +79,7 @@ def instances(request): if check_uuid.uuid != info['uuid']: check_uuid.save() all_host_vms[comp.id, comp.name][vm]['is_template'] = check_uuid.is_template + all_host_vms[comp.id, comp.name][vm]['userinstances'] = get_userinstances_info(check_uuid) except Instance.DoesNotExist: check_uuid = Instance(compute_id=comp.id, name=vm, uuid=info['uuid']) check_uuid.save() diff --git a/static/css/webvirtcloud.css b/static/css/webvirtcloud.css index d7382f1..542e8b9 100644 --- a/static/css/webvirtcloud.css +++ b/static/css/webvirtcloud.css @@ -12,7 +12,7 @@ body { } .container { - max-width: 768px; + max-width: 900px; } .page-header { @@ -132,4 +132,4 @@ p { .keyselect { display: inline; min-width: 250px; -} \ No newline at end of file +} From 99bd6930b94ea7e719100f384ee47ddd63c971cf Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Fri, 6 May 2016 10:55:24 +0200 Subject: [PATCH 61/76] add js.cookie.js to base.html --- static/js/js.cookie.js | 151 +++++++++++++++++++++++++++++++++++++++++ templates/base.html | 4 +- 2 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 static/js/js.cookie.js diff --git a/static/js/js.cookie.js b/static/js/js.cookie.js new file mode 100644 index 0000000..d4232b9 --- /dev/null +++ b/static/js/js.cookie.js @@ -0,0 +1,151 @@ +/*! + * JavaScript Cookie v2.1.1 + * https://github.com/js-cookie/js-cookie + * + * Copyright 2006, 2015 Klaus Hartl & Fagner Brack + * Released under the MIT license + */ +;(function (factory) { + if (typeof define === 'function' && define.amd) { + define(factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + var OldCookies = window.Cookies; + var api = window.Cookies = factory(); + api.noConflict = function () { + window.Cookies = OldCookies; + return api; + }; + } +}(function () { + function extend () { + var i = 0; + var result = {}; + for (; i < arguments.length; i++) { + var attributes = arguments[ i ]; + for (var key in attributes) { + result[key] = attributes[key]; + } + } + return result; + } + + function init (converter) { + function api (key, value, attributes) { + var result; + if (typeof document === 'undefined') { + return; + } + + // Write + + if (arguments.length > 1) { + attributes = extend({ + path: '/' + }, api.defaults, attributes); + + if (typeof attributes.expires === 'number') { + var expires = new Date(); + expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); + attributes.expires = expires; + } + + try { + result = JSON.stringify(value); + if (/^[\{\[]/.test(result)) { + value = result; + } + } catch (e) {} + + if (!converter.write) { + value = encodeURIComponent(String(value)) + .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); + } else { + value = converter.write(value, key); + } + + key = encodeURIComponent(String(key)); + key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); + key = key.replace(/[\(\)]/g, escape); + + return (document.cookie = [ + key, '=', value, + attributes.expires && '; expires=' + attributes.expires.toUTCString(), // use expires attribute, max-age is not supported by IE + attributes.path && '; path=' + attributes.path, + attributes.domain && '; domain=' + attributes.domain, + attributes.secure ? '; secure' : '' + ].join('')); + } + + // Read + + if (!key) { + result = {}; + } + + // To prevent the for loop in the first place assign an empty array + // in case there are no cookies at all. Also prevents odd result when + // calling "get()" + var cookies = document.cookie ? document.cookie.split('; ') : []; + var rdecode = /(%[0-9A-Z]{2})+/g; + var i = 0; + + for (; i < cookies.length; i++) { + var parts = cookies[i].split('='); + var name = parts[0].replace(rdecode, decodeURIComponent); + var cookie = parts.slice(1).join('='); + + if (cookie.charAt(0) === '"') { + cookie = cookie.slice(1, -1); + } + + try { + cookie = converter.read ? + converter.read(cookie, name) : converter(cookie, name) || + cookie.replace(rdecode, decodeURIComponent); + + if (this.json) { + try { + cookie = JSON.parse(cookie); + } catch (e) {} + } + + if (key === name) { + result = cookie; + break; + } + + if (!key) { + result[name] = cookie; + } + } catch (e) {} + } + + return result; + } + + api.set = api; + api.get = function (key) { + return api(key); + }; + api.getJSON = function () { + return api.apply({ + json: true + }, [].slice.call(arguments)); + }; + api.defaults = {}; + + api.remove = function (key, attributes) { + api(key, '', extend(attributes, { + expires: -1 + })); + }; + + api.withConverter = init; + + return api; + } + + return init(function () {}); +})); diff --git a/templates/base.html b/templates/base.html index e9f4505..cbdd75a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -48,6 +48,8 @@ + + {% block script %}{% endblock %} - \ No newline at end of file + From c4ae3d2982d46d81818c2ded2f1eebd7e1db2de8 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Fri, 6 May 2016 10:56:50 +0200 Subject: [PATCH 62/76] instances filter stores filter value into cookie and auto-fills/applies filter on document ready --- instances/templates/instances.html | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/instances/templates/instances.html b/instances/templates/instances.html index 05e97f5..5b6f87d 100644 --- a/instances/templates/instances.html +++ b/instances/templates/instances.html @@ -239,15 +239,22 @@ } From 9327cf36c49eeee0b67db1bd6167670de4a17f65 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Fri, 6 May 2016 14:39:52 +0200 Subject: [PATCH 63/76] accounts view sorts users by username --- accounts/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/views.py b/accounts/views.py index 2669d94..371758b 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -83,7 +83,7 @@ def accounts(request): return HttpResponseRedirect(reverse('index')) error_messages = [] - users = User.objects.all() + users = User.objects.all().order_by('username') create_missing_userattributes(users) if request.method == 'POST': From 572f7b12cd8d9c08a2d00de3b06d9d7b926bd166 Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Fri, 6 May 2016 14:40:24 +0200 Subject: [PATCH 64/76] instances_filter cookie expires after 1 day --- instances/templates/instances.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instances/templates/instances.html b/instances/templates/instances.html index 5b6f87d..8cee448 100644 --- a/instances/templates/instances.html +++ b/instances/templates/instances.html @@ -245,7 +245,7 @@ $('.searchable tr').filter(function () { return rex.test($(this).text()); }).show(); - Cookies.set("instances_filter", $(this).val()); + Cookies.set("instances_filter", $(this).val(), { expires: 1 }); } $(document).ready(function () { instances_filter_cookie = Cookies.get("instances_filter"); From 0b80b030fecbab81ff46499bb0c158bf13d1976c Mon Sep 17 00:00:00 2001 From: Jan Krcmar Date: Thu, 12 May 2016 10:06:16 +0200 Subject: [PATCH 65/76] add refresh button into instance view --- instances/templates/instance.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index 77f5791..f1bef69 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -19,6 +19,9 @@ {% trans "Suspend" %} {% endifequal %} + +
    From f4845984148e34d41fbd06e9b08c964f4d39a938 Mon Sep 17 00:00:00 2001 From: "Ing. Jan KRCMAR" Date: Fri, 27 May 2016 14:13:24 +0200 Subject: [PATCH 66/76] add guess button for cloned instance name. this reads dhcp conf and uses settings.CLONE_INSTANCE_DEFAULT_PREFIX. --- instances/templates/instance.html | 21 ++++++++++++++++++--- instances/urls.py | 2 ++ instances/views.py | 15 +++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/instances/templates/instance.html b/instances/templates/instance.html index f1bef69..b18836a 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -713,17 +713,23 @@
    {% csrf_token %}
    + {% if request.user.is_superuser %}
    - {% if request.user.is_superuser %} - {% else %} +
    +
    + +
    + {% else %} +
    - {% endif %}
    + {% endif %}
    {% if request.user.is_superuser %}

    {% trans "Network devices" %}

    @@ -1040,6 +1046,15 @@ }); } + diff --git a/instances/views.py b/instances/views.py index 76d29ec..028891c 100644 --- a/instances/views.py +++ b/instances/views.py @@ -790,7 +790,8 @@ def guess_clone_name(request): for line in f: line = line.strip() if "host %s" % prefix in line: - hostname = line.split(' ')[1] + fqdn = line.split(' ')[1] + hostname = fqdn.split('.')[0] if hostname.startswith(prefix) and hostname not in instance_names: return HttpResponse(json.dumps({'name': hostname})) return HttpResponse(json.dumps({})); From 9e294ade3c275d5035df33bbaca25082a339c9a3 Mon Sep 17 00:00:00 2001 From: QDaniel Date: Wed, 13 Jul 2016 12:49:14 +0200 Subject: [PATCH 70/76] Security Bug: No external Connections --- vrtManager/create.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vrtManager/create.py b/vrtManager/create.py index 59496c2..6497755 100644 --- a/vrtManager/create.py +++ b/vrtManager/create.py @@ -227,9 +227,7 @@ class wvmCreate(wvmConnect): xml += """ - - - +