diff --git a/.gitignore b/.gitignore index 7d921d2..b3ebced 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ venv .DS_* *.pyc db.sqlite3 +console/cert.pem +tags +dhcpd.* diff --git a/README.md b/README.md index 1d140ff..f1b3767 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 @@ -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/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 + diff --git a/accounts/forms.py b/accounts/forms.py index 55d5c29..4127ac4 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -2,13 +2,14 @@ import re from django import forms from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User +from django.conf import settings class UserAddForm(forms.Form): name = forms.CharField(label="Name", 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')},) + password = forms.CharField(required=not settings.ALLOW_EMPTY_PASSWORD, error_messages={'required': _('No password has been entered')},) def clean_name(self): name = self.cleaned_data['name'] 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/migrations/0006_userattributes_max_disk_size.py b/accounts/migrations/0006_userattributes_max_disk_size.py new file mode 100644 index 0000000..3d21f5f --- /dev/null +++ b/accounts/migrations/0006_userattributes_max_disk_size.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', '0005_userattributes_can_clone_instances'), + ] + + operations = [ + migrations.AddField( + model_name='userattributes', + name='max_disk_size', + field=models.IntegerField(default=0), + ), + ] diff --git a/accounts/migrations/0007_auto_20160426_0635.py b/accounts/migrations/0007_auto_20160426_0635.py new file mode 100644 index 0000000..2f92aba --- /dev/null +++ b/accounts/migrations/0007_auto_20160426_0635.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0006_userattributes_max_disk_size'), + ] + + operations = [ + migrations.AlterField( + model_name='userattributes', + name='max_cpus', + field=models.IntegerField(default=1), + ), + migrations.AlterField( + model_name='userattributes', + name='max_disk_size', + field=models.IntegerField(default=20), + ), + migrations.AlterField( + model_name='userattributes', + name='max_instances', + field=models.IntegerField(default=1), + ), + migrations.AlterField( + model_name='userattributes', + name='max_memory', + field=models.IntegerField(default=2048), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 20efc6f..06fefee 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -21,3 +21,14 @@ 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=1) + max_cpus = models.IntegerField(default=1) + max_memory = models.IntegerField(default=2048) + max_disk_size = models.IntegerField(default=20) + + def __unicode__(self): + return self.user.username diff --git a/accounts/templates/accounts.html b/accounts/templates/accounts.html index 9ebff0b..4fdfc8a 100644 --- a/accounts/templates/accounts.html +++ b/accounts/templates/accounts.html @@ -71,6 +71,48 @@ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/accounts/templates/create_user_block.html b/accounts/templates/create_user_block.html index 7752f56..f4b4679 100644 --- a/accounts/templates/create_user_block.html +++ b/accounts/templates/create_user_block.html @@ -23,7 +23,7 @@
- +
@@ -35,4 +35,4 @@ -{% endif %} \ No newline at end of file +{% endif %} diff --git a/accounts/views.py b/accounts/views.py index 3b6a63e..155ecc8 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -3,18 +3,19 @@ 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 accounts.models import UserInstance, UserSSHKey +from django.contrib.auth.decorators import login_required +from accounts.models import * from instances.models import Instance from accounts.forms import UserAddForm +from django.conf import settings +@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,21 +64,28 @@ 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')) + def create_missing_userattributes(users): + for user in users: + try: + userattributes = user.userattributes + except UserAttributes.DoesNotExist: + userattributes = UserAttributes(user=user) + userattributes.save() if not request.user.is_superuser: return HttpResponseRedirect(reverse('index')) error_messages = [] - users = User.objects.filter(is_staff=False, is_superuser=False) + users = User.objects.all().order_by('username') + create_missing_userattributes(users) + allow_empty_password = settings.ALLOW_EMPTY_PASSWORD if request.method == 'POST': if 'create' in request.POST: @@ -96,7 +104,17 @@ def accounts(request): user_pass = request.POST.get('user_pass', '') user_edit = User.objects.get(id=user_id) user_edit.set_password(user_pass) + user_edit.is_staff = request.POST.get('user_is_staff', False) + user_edit.is_superuser = request.POST.get('user_is_superuser', False) user_edit.save() + + userattributes = user_edit.userattributes + userattributes.can_clone_instances = request.POST.get('userattributes_can_clone_instances', False) + userattributes.max_instances = request.POST.get('userattributes_max_instances', 0) + userattributes.max_cpus = request.POST.get('userattributes_max_cpus', 0) + userattributes.max_memory = request.POST.get('userattributes_max_memory', 0) + userattributes.max_disk_size = request.POST.get('userattributes_max_disk_size', 0) + userattributes.save() return HttpResponseRedirect(request.get_full_path()) if 'block' in request.POST: user_id = request.POST.get('user_id', '') @@ -123,22 +141,20 @@ 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')) 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')) @@ -162,12 +178,17 @@ 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)) + + if settings.ALLOW_INSTANCE_MULTIPLE_OWNER: + check_inst = UserInstance.objects.filter(instance_id=int(inst_id), user_id=int(user_id)) + else: + check_inst = UserInstance.objects.filter(instance_id=int(inst_id)) + + if check_inst: msg = _("Instance already added") error_messages.append(msg) - except UserInstance.DoesNotExist: - add_user_inst = UserInstance(instance_id=int(inst_id), user_id=user_id) + 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()) 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/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): 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..13fb70d 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')) @@ -36,14 +35,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 = Compute.objects.filter().order_by('name') 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 +104,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 +123,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: @@ -130,15 +132,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 +160,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/templates/create_instance.html b/create/templates/create_instance.html index 9df7326..d2be478 100644 --- a/create/templates/create_instance.html +++ b/create/templates/create_instance.html @@ -239,7 +239,7 @@
{% else %}
- +
diff --git a/create/views.py b/create/views.py index c3e1139..6c223da 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/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), + ), + ] diff --git a/instances/models.py b/instances/models.py index 4345cc1..bf2a1c4 100644 --- a/instances/models.py +++ b/instances/models.py @@ -6,6 +6,7 @@ class Instance(models.Model): compute = models.ForeignKey(Compute) name = models.CharField(max_length=20) uuid = models.CharField(max_length=36) + is_template = models.BooleanField(default=False) def __unicode__(self): return self.name diff --git a/instances/templates/create_inst_block.html b/instances/templates/create_inst_block.html index 36ec37d..264aa75 100644 --- a/instances/templates/create_inst_block.html +++ b/instances/templates/create_inst_block.html @@ -35,16 +35,16 @@ {% trans "Close" %} {% if computes %} - {% else %} {% endif %} -{% endif %} \ No newline at end of file +{% endif %} diff --git a/instances/templates/instance.html b/instances/templates/instance.html index 36e7afa..e7ca557 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -2,6 +2,7 @@ {% load i18n %} {% block title %}{% trans "Instance" %} - {{ vname }}{% endblock %} {% block content %} + {% include 'pleasewaitdialog.html' %}
@@ -18,6 +19,9 @@ {% trans "Suspend" %} {% endifequal %} +
+ +
@@ -39,6 +43,9 @@ {% endfor %}
+ {% if user_quota_msg %} + {{ user_quota_msg|capfirst }} quota reached. + {% endif %}
@@ -204,7 +211,12 @@

{% trans "Click on Boot button to start this instance." %}

{% csrf_token %} - + {% if instance.is_template %} +

{% trans "Template instance cannot be started." %}

+ + {% else %} + + {% endif %}
@@ -355,6 +367,15 @@ {% trans "Custom value" %}
+

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

+ {% for disk in disks %} +
+ +
+ +
+
+ {% endfor %} {% ifequal status 5 %} {% else %} @@ -486,11 +507,20 @@ {% endif %} {% if request.user.is_superuser %} +
  • + + {% trans "Network" %} + +
  • + {% endif %} + {% if request.user.is_superuser or request.user.userattributes.can_clone_instances %}
  • {% trans "Clone" %}
  • + {% endif %} + {% if request.user.is_superuser %}
  • {% trans "Migrate" %} @@ -501,6 +531,16 @@ {% trans "XML" %}
  • +
  • + + {% trans "Options" %} + +
  • +
  • + + {% trans "Users" %} + +
  • {% endif %} @@ -651,51 +691,118 @@ {% endif %} {% if request.user.is_superuser %} +
    +

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

    +
    {% csrf_token %} +

    {% trans "Network devices" %}

    + {% for network in networks %} +
    + +
    + +
    +
    + +
    +
    + {% endfor %} + {% ifequal status 5 %} + + {% else %} + + {% endifequal %} +
    +
    +
    + {% 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 %} +
    + {% if request.user.is_superuser %} +

    {% trans "Network devices" %}

    + {% for network in networks %} +
    + +
    + +
    +
    + + +
    +
    + {% 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 %} +
    + +
    +
    -

    {% trans "Network devices" %}

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

    {% trans "Storage devices" %}

    - {% for disk in clone_disks %} -
    - -
    - -
    - {% ifequal disk.format 'qcow2' %} - -
    - -
    - {% endifequal %} -
    - {% endfor %} +
    {% ifequal status 5 %} - + {% else %} {% endifequal %}
    + {% endif %} + {% if request.user.is_superuser %}

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

    {% csrf_token %} @@ -722,7 +829,7 @@
    - +
    @@ -734,11 +841,11 @@
    - +
    {% if computes_count != 1 %} - + {% else %} {% endif %} @@ -764,6 +871,41 @@
    +
    +
    {% csrf_token %} +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + {% ifequal status 5 %} + + {% else %} + + {% endifequal %} +
    +
    +
    +
    +

    {% trans "Instance owners" %}

    + {% for userinstance in userinstances %} +

    {{ userinstance.user }}

    + {% endfor %} +
    +
    {% endif %}
    @@ -847,7 +989,7 @@
    {% csrf_token %}
    @@ -892,7 +1034,7 @@ macAddress+=hexDigits.charAt(Math.round(Math.random()*16)); if (i != 2) macAddress+=":"; } - $('input[name="net-'+net+'"]').val(macAddress); + $('input[name="clone-net-mac-'+net+'"]').val(macAddress); }; + + + + {% if request.user.is_superuser %} + + {% block script %}{% endblock %} - \ No newline at end of file + diff --git a/templates/pleasewaitdialog.html b/templates/pleasewaitdialog.html new file mode 100644 index 0000000..49c893b --- /dev/null +++ b/templates/pleasewaitdialog.html @@ -0,0 +1,25 @@ +{% load i18n %} + + + diff --git a/vrtManager/connection.py b/vrtManager/connection.py index fd9fb80..c62c6e4 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): @@ -454,7 +463,17 @@ class wvmConnect(object): vcpu = cur_vcpu else: vcpu = util.get_xml_path(dom.XMLDesc(0), "/domain/vcpu") - return {'name': 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") + return { + 'name': dom.name(), + 'status': dom.info()[0], + 'uuid': dom.UUIDString(), + 'vcpu': vcpu, + 'memory': mem, + 'title': title if title else '', + 'description': description if description else '', + } def close(self): """Close connection""" diff --git a/vrtManager/create.py b/vrtManager/create.py index 1d6d9e7..15f1cd8 100644 --- a/vrtManager/create.py +++ b/vrtManager/create.py @@ -220,7 +220,8 @@ class wvmCreate(wvmConnect): xml += """""" if mac: xml += """""" % mac - xml += """""" % net + xml += """ + """ % net if virtio: xml += """""" xml += """""" diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 0f3b258..3c287e0 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 @@ -184,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 @@ -523,7 +529,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. """ @@ -541,6 +547,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) @@ -598,6 +609,22 @@ 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 fix_mac(self, mac): + if ":" in mac: + 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)] + return ':'.join(mac_tuples) + def clone_instance(self, clone_data): clone_dev_path = [] @@ -610,7 +637,8 @@ class wvmInstance(wvmConnect): for num, net in enumerate(tree.findall('devices/interface')): elm = net.find('mac') - elm.set('address', 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'): if disk.get('device') == 'disk': @@ -649,5 +677,65 @@ 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) + options = { + 'title': clone_data.get('clone-title', ''), + 'description': clone_data.get('clone-description', ''), + } + self._set_options(tree, options) self._defineXML(ElementTree.tostring(tree)) + + return self.get_instance(clone_data['name']).UUIDString() + + def change_network(self, network_data): + xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE) + tree = ElementTree.fromstring(xml) + + 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-' + str(num)]) + + new_xml = ElementTree.tostring(tree) + self._defineXML(new_xml) + + def _set_options(self, tree, options): + for o in ['title', 'description']: + option = tree.find(o) + option_value = options.get(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 + + 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) + diff --git a/webvirtcloud/settings.py b/webvirtcloud/settings.py index a163fa3..0c31433 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 @@ -38,11 +38,19 @@ 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', + #'accounts.backends.MyRemoteUserBackend', +) + +LOGIN_URL = '/accounts/login' + ROOT_URLCONF = 'webvirtcloud.urls' WSGI_APPLICATION = 'webvirtcloud.wsgi.application' @@ -103,3 +111,9 @@ 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 +CLONE_INSTANCE_DEFAULT_PREFIX = 'ourea' +LOGS_PER_PAGE = 100 +QUOTA_DEBUG = True +ALLOW_EMPTY_PASSWORD = True diff --git a/webvirtcloud/urls.py b/webvirtcloud/urls.py index 0eff1a5..8cfa9d9 100644 --- a/webvirtcloud/urls.py +++ b/webvirtcloud/urls.py @@ -8,6 +8,7 @@ urlpatterns = patterns('', url(r'^instance/', include('instances.urls')), url(r'^accounts/', include('accounts.urls')), url(r'^computes/', include('computes.urls')), + url(r'^logs/', include('logs.urls')), url(r'^compute/(?P[0-9]+)/storages/$', 'storages.views.storages', name='storages'), @@ -27,6 +28,5 @@ urlpatterns = patterns('', 'create.views.create_instance', name='create_instance'), url(r'^console/$', 'console.views.console', name='console'), - url(r'^logs/$', 'logs.views.showlogs', name='showlogs'), # (r'^admin/', include(admin.site.urls)), ) 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