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

Merge pull request #88 from honza801/master

multiple enhancements and fixes
This commit is contained in:
Anatoliy Guskov 2017-03-09 15:34:21 +02:00 committed by GitHub
commit 531ea1e652
48 changed files with 1188 additions and 197 deletions

3
.gitignore vendored
View file

@ -4,3 +4,6 @@ venv
.DS_*
*.pyc
db.sqlite3
console/cert.pem
tags
dhcpd.*

View file

@ -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

7
accounts/backends.py Normal file
View file

@ -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

View file

@ -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']

View file

@ -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)),
],
),
]

View file

@ -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),
),
]

View file

@ -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),
),
]

View file

@ -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),
),
]

View file

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0004_userinstance_is_vnc'),
('accounts', '0007_auto_20160426_0635'),
]
operations = [
]

View file

@ -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

View file

@ -71,6 +71,48 @@
<input type="password" name="user_pass" class="form-control" value="">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Is staff" %}</label>
<div class="col-sm-2">
<input type="checkbox" name="user_is_staff" {% if user.is_staff %}checked{% endif %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Is superuser" %}</label>
<div class="col-sm-2">
<input type="checkbox" name="user_is_superuser" {% if user.is_superuser %}checked{% endif %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Can clone instances" %}</label>
<div class="col-sm-2">
<input type="checkbox" name="userattributes_can_clone_instances" {% if user.userattributes.can_clone_instances %}checked{% endif %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max instances" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_instances" class="form-control" value="{{ user.userattributes.max_instances }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max cpus" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_cpus" class="form-control" value="{{ user.userattributes.max_cpus }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max memory (MB)" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_memory" class="form-control" value="{{ user.userattributes.max_memory }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max disk size (GB)" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_disk_size" class="form-control" value="{{ user.userattributes.max_disk_size }}">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="pull-left btn btn-danger" name="delete">

View file

@ -23,7 +23,7 @@
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Password" %}</label>
<div class="col-sm-6">
<input type="password" class="form-control" name="password" placeholder="*******" required>
<input type="password" class="form-control" name="password" placeholder="*******" {% if not allow_empty_password %}required{% endif %}>
</div>
</div>
</div>

View file

@ -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())

View file

@ -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']

View file

@ -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),
),
]

View file

@ -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):

View file

@ -45,6 +45,11 @@
{% else %}
<p>{% trans "Not Connected" %}</p>
{% endif %}
{% if compute.details %}
<p>{% trans compute.details %}</p>
{% else %}
<p>{% trans "No details available" %}</p>
{% endif %}
</div>
</div>

View file

@ -141,6 +141,14 @@
<input type="text" name="name" class="form-control" placeholder="Label Name" maxlength="20" required pattern="[a-z0-9\.\-_]+">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Details" %}</label>
<div class="col-sm-6">
<input type="text" name="details" class="form-control" placeholder="{% trans "Details" %}">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">

View file

@ -40,6 +40,7 @@
<p>{% trans "Logical CPUs" %}</p>
<p>{% trans "Processor" %}</p>
<p>{% trans "Connection" %}</p>
<p>{% trans "Details" %}</p>
</div>
<div class="col-xs-8 col-sm-7">
<p>{{ hostname }}</p>
@ -49,6 +50,7 @@
<p>{{ logical_cpu }}</p>
<p>{{ model_cpu }}</p>
<p>{{ uri_conn }}</p>
<p>{{ compute.details }}</p>
</div>
</div>
<div class="row">

View file

@ -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,12 +35,13 @@ 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':
@ -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 = {}

View file

@ -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', '')

View file

@ -239,7 +239,7 @@
</div>
{% else %}
<div class="col-lg-12">
<h3 class="page-header">{% trans "Create from flover" %}</h3>
<h3 class="page-header">{% trans "Create from flavor" %}</h3>
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead>

View file

@ -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'))

View file

@ -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),
),
]

View file

@ -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

View file

@ -35,12 +35,12 @@
{% trans "Close" %}
</button>
{% if computes %}
<button type="submit" class="btn btn-primary" name="chose" onclick='goto_compute()'>
{% trans "Chose" %}
<button type="submit" class="btn btn-primary" name="choose" onclick='goto_compute()'>
{% trans "Choose" %}
</button>
{% else %}
<button class="btn btn-primary disabled">
{% trans "Chose" %}
{% trans "Choose" %}
</button>
{% endif %}
</div>

View file

@ -2,6 +2,7 @@
{% load i18n %}
{% block title %}{% trans "Instance" %} - {{ vname }}{% endblock %}
{% block content %}
{% include 'pleasewaitdialog.html' %}
<!-- Page Heading -->
<div class="row">
<table>
@ -18,6 +19,9 @@
<span class="label label-warning">{% trans "Suspend" %}</span>
{% endifequal %}
</td>
<td>
<a href="{% url 'instance' compute.id vname %}" type="button" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-refresh"></span></a>
</td>
</tr>
</table>
<table width="65%">
@ -39,6 +43,9 @@
{% endfor %}
</tr>
</table>
{% if user_quota_msg %}
<span class="label label-warning">{{ user_quota_msg|capfirst }} quota reached.</span>
{% endif %}
<hr>
</div>
@ -204,7 +211,12 @@
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="boot">
<p>{% trans "Click on Boot button to start this instance." %}</p>
<form action="" method="post" role="form">{% csrf_token %}
<input type="submit" name="poweron" class="btn btn-lg btn-success pull-right" value="{% trans "Power On" %}">
{% if instance.is_template %}
<p>{% trans "Template instance cannot be started." %}</p>
<input type="submit" name="poweron" class="btn btn-lg btn-success pull-right disabled" value="{% trans "Power On" %}">
{% else %}
<input type="submit" name="poweron" class="btn btn-lg btn-success pull-right" value="{% trans "Power On" %}">
{% endif %}
<div class="clearfix"></div>
</form>
</div>
@ -355,6 +367,15 @@
<small><input type="checkbox" class="js-custom__checkbox" /> {% trans "Custom value" %}</small>
</div>
</div>
<p style="font-weight:bold;">{% trans "Disk allocation (B):" %}</p>
{% for disk in disks %}
<div class="form-group">
<label class="col-sm-4 control-label" style="font-weight:normal;">{% trans "Current allocation" %} ({{ disk.dev }})</label>
<div class="col-sm-4 js-custom__container">
<input type="text" name="disk_size_{{ disk.dev }}" class="form-control" value="{{ disk.size|filesizeformat }}" />
</div>
</div>
{% endfor %}
{% ifequal status 5 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="resize">{% trans "Resize" %}</button>
{% else %}
@ -486,11 +507,20 @@
</li>
{% endif %}
{% if request.user.is_superuser %}
<li role="presentation">
<a href="#network" aria-controls="network" role="tab" data-toggle="tab">
{% trans "Network" %}
</a>
</li>
{% endif %}
{% if request.user.is_superuser or request.user.userattributes.can_clone_instances %}
<li role="presentation">
<a href="#clone" aria-controls="clone" role="tab" data-toggle="tab">
{% trans "Clone" %}
</a>
</li>
{% endif %}
{% if request.user.is_superuser %}
<li role="presentation">
<a href="#migrate" aria-controls="migrate" role="tab" data-toggle="tab">
{% trans "Migrate" %}
@ -501,6 +531,16 @@
{% trans "XML" %}
</a>
</li>
<li role="presentation">
<a href="#options" aria-controls="options" role="tab" data-toggle="tab">
{% trans "Options" %}
</a>
</li>
<li role="presentation">
<a href="#users" aria-controls="users" role="tab" data-toggle="tab">
{% trans "Users" %}
</a>
</li>
{% endif %}
</ul>
<!-- Tab panes -->
@ -651,51 +691,118 @@
</div>
{% endif %}
{% if request.user.is_superuser %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="network">
<p>{% trans "Assign network device to bridge" %}</p>
<form class="form-horizontal" action="" method="post" role="form">{% csrf_token %}
<p style="font-weight:bold;">{% trans "Network devices" %}</p>
{% for network in networks %}
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">eth{{ forloop.counter0 }}</label>
<div class="col-sm-4">
<input type="text" class="form-control" name="net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
</div>
<div class="col-sm-3">
<input type="text" class="form-control" name="net-source-{{ forloop.counter0 }}" value="{{ network.nic }}"/>
</div>
</div>
{% endfor %}
{% ifequal status 5 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="change_network">{% trans "Change" %}</button>
{% else %}
<button class="btn btn-lg btn-success pull-right disabled" name="change_network">{% trans "Change" %}</button>
{% endifequal %}
</form>
<div class="clearfix"></div>
</div>
{% endif %}
{% if request.user.is_superuser or request.user.userattributes.can_clone_instances %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="clone">
<p style="font-weight:bold;">{% trans "Create a clone" %}</p>
<form class="form-horizontal" action="" method="post" role="form">{% csrf_token %}
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Clone Name" %}</label>
<div class="col-sm-3">
<input type="text" class="form-control" name="name" value="{{ vname }}-clone"/>
{% if request.user.is_superuser %}
<div class="col-sm-4">
<input id="clone_name" type="text" class="form-control" name="name" value="{{ vname }}-clone"/>
</div>
<div class="col-sm-4">
<button type="button" class="btn btn-sm btn-success pull-left" name="guess-clone-name"
onclick="guess_clone_name()" style="margin-top: 2px;">{% trans "Guess" %}</button>
</div>
{% else %}
<div class="col-sm-4">
<select id="select_clone_name" class="form-control" name="name" size="1"/>
{% for name in clone_free_names %}
<option value="{{ name }}">{{ name }}</option>
{% endfor %}
</select>
</div>
{% endif %}
</div>
{% if request.user.is_superuser %}
<p style="font-weight:bold;">{% trans "Network devices" %}</p>
{% for network in networks %}
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">eth{{ forloop.counter0 }} ({{ network.nic }})</label>
<div class="col-sm-4">
<input type="text" class="form-control" name="clone-net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
</div>
<div class="col-sm-4">
<button type="button" class="btn btn-sm btn-success pull-left" name="random-mac-{{ forloop.counter0 }}"
onclick="random_mac({{ forloop.counter0 }})" style="margin-top: 2px;">{% trans "Random" %}</button>
<button type="button" class="btn btn-sm btn-success pull-left" name="guess-mac-{{ forloop.counter0 }}"
onclick="guess_mac_address('#clone_name', {{ forloop.counter0 }})" style="margin-top: 2px;">{% trans "Guess" %}</button>
</div>
</div>
{% endfor %}
{% else %}
{% for network in networks %}
<input type="hidden" class="form-control" name="clone-net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
{% endfor %}
{% endif %}
{% if request.user.is_superuser %}
<p style="font-weight:bold;">{% trans "Storage devices" %}</p>
{% for disk in clone_disks %}
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{{ disk.dev }} ({{ disk.storage }})</label>
<div class="col-sm-4">
<input id="disk_name-{{ disk.dev }}" type="text" class="form-control" name="disk-{{ disk.dev }}" value="{{ disk.image }}"/>
</div>
{% ifequal disk.format 'qcow2' %}
<label class="col-sm-2 control-label" style="font-weight:normal;margin-left:-35px;">Metadata</label>
<div class="col-sm-1">
<input type="checkbox" name="meta-{{ disk.dev }}" value="true" style="margin-top: 10px;">
</div>
{% endifequal %}
</div>
{% endfor %}
{% else %}
{% for disk in clone_disks %}
<input id="disk_name-{{ disk.dev }}" type="hidden" class="form-control" name="disk-{{ disk.dev }}" value="{{ disk.image }}"/>
{% endfor %}
{% endif %}
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Title" %}</label>
<div class="col-sm-6">
<input type="text" name="clone-title" class="form-control">
</div>
</div>
<p style="font-weight:bold;">{% trans "Network devices" %}</p>
{% for network in networks %}
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">eth{{ forloop.counter0 }} ({{ network.nic }})</label>
<div class="col-sm-3">
<input type="text" class="form-control" name="net-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
</div>
<div class="col-sm-1">
<button type="button" class="btn btn-sm btn-success pull-left" name="random-mac-{{ forloop.counter0 }}"
onclick="random_mac({{ forloop.counter0 }})" style="margin-top: 2px;">{% trans "Random" %}</button>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Description" %}</label>
<div class="col-sm-6">
<textarea name="clone-description" class="form-control"></textarea>
</div>
{% endfor %}
<p style="font-weight:bold;">{% trans "Storage devices" %}</p>
{% for disk in clone_disks %}
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{{ disk.dev }} ({{ disk.storage }})</label>
<div class="col-sm-3">
<input type="text" class="form-control" name="disk-{{ disk.dev }}" value="{{ disk.image }}"/>
</div>
{% ifequal disk.format 'qcow2' %}
<label class="col-sm-2 control-label" style="font-weight:normal;margin-left:-35px;">Metadata</label>
<div class="col-sm-1">
<input type="checkbox" name="meta-{{ disk.dev }}" value="true" style="margin-top: 10px;">
</div>
{% endifequal %}
</div>
{% endfor %}
</div>
{% ifequal status 5 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="clone">{% trans "Clone" %}</button>
<button type="submit" class="btn btn-lg btn-success pull-right" name="clone" onclick="showPleaseWaitDialog();">{% trans "Clone" %}</button>
{% else %}
<button class="btn btn-lg btn-success pull-right disabled" name="clone">{% trans "Clone" %}</button>
{% endifequal %}
</form>
<div class="clearfix"></div>
</div>
{% endif %}
{% if request.user.is_superuser %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="migrate">
<p>{% trans "For migration both host servers must have equal settings and OS type" %}</p>
<form class="form-horizontal" method="post" role="form">{% csrf_token %}
@ -722,7 +829,7 @@
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Live migration" %}</label>
<div class="col-sm-6">
<input type="checkbox" name="live_migrate" value="true" id="vm_live_migrate">
<input type="checkbox" name="live_migrate" value="true" id="vm_live_migrate" checked>
</div>
</div>
<div class="form-group">
@ -734,11 +841,17 @@
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Delete original" %}</label>
<div class="col-sm-6">
<input type="checkbox" name="xml_delete" value="true" id="xml_delete">
<input type="checkbox" name="xml_delete" value="true" id="xml_delete" checked>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Offline migration" %}</label>
<div class="col-sm-6">
<input type="checkbox" name="offline_migrate" value="true" id="offline_migrate">
</div>
</div>
{% if computes_count != 1 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="migrate">{% trans "Migrate" %}</button>
<button type="submit" class="btn btn-lg btn-success pull-right" name="migrate" onclick="showPleaseWaitDialog();">{% trans "Migrate" %}</button>
{% else %}
<button class="btn btn-lg btn-success pull-right disabled">{% trans "Migrate" %}</button>
{% endif %}
@ -764,6 +877,41 @@
</form>
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="options">
<form class="form-horizontal" action="" method="post" role="form">{% csrf_token %}
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Title" %}</label>
<div class="col-sm-6">
<input type="text" name="title" class="form-control" value="{{ title }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Description" %}</label>
<div class="col-sm-6">
<textarea name="description" class="form-control">{{ description }}</textarea>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Is template" %}</label>
<div class="col-sm-6">
<input type="checkbox" name="is_template" value="true" id="is_template" {% if instance.is_template %}checked{% endif %}>
</div>
</div>
{% ifequal status 5 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="change_options">{% trans "Change" %}</button>
{% else %}
<button class="btn btn-lg btn-success pull-right disabled" name="change_options">{% trans "Change" %}</button>
{% endifequal %}
</form>
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="users">
<p style="font-weight:bold;">{% trans "Instance owners" %}</p>
{% for userinstance in userinstances %}
<p><a href="{% url 'account' userinstance.user.id %}">{{ userinstance.user }}</a></p>
{% endfor %}
<div class="clearfix"></div>
</div>
{% endif %}
</div>
</div>
@ -847,7 +995,7 @@
<form class="form-group" method="post" role="form">{% csrf_token %}
<div class="checkbox" style="margin-left: 8px;">
<label>
<input type="checkbox" name="delete_disk" value="true">
<input type="checkbox" name="delete_disk" value="true" checked>
<strong>{% trans "Remove Instance's data" %}</strong>
</label>
</div>
@ -892,7 +1040,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);
};
</script>
<script>
@ -904,6 +1052,46 @@
}
}
</script>
<script>
function guess_mac_address(src_elem, net) {
new_vname = $(src_elem).val();
$.getJSON('/instance/guess_mac_address/' + new_vname + '/', function(data) {
$('input[name="clone-net-mac-'+net+'"]').val(data['mac']);
});
}
</script>
<script>
function guess_clone_name() {
$.getJSON('/instance/guess_clone_name/', function(data) {
guessed_name = data['name'].split(".")[0];
$('#clone_name').val(guessed_name);
update_clone_disk_name(guessed_name);
guess_mac_address('#clone_name', 0);
});
}
</script>
<script>
function update_clone_disk_name(new_vname) {
vname = '{{ vname }}-clone';
{% for disk in clone_disks %}
disk_name = '{{ disk.image }}';
disk_minus = disk_name.split('-');
disk_minus_suffix = disk_minus[disk_minus.length-1];
disk_minus.pop();
disk_minus_name = disk_minus.join('-');
disk_dot = disk_name.split('.')
disk_dot_suffix = disk_dot[disk_dot.length-1];
if (disk_name.lastIndexOf('-') > -1 && disk_minus_name == vname) {
image = new_vname + "-" + disk_minus_suffix;
} else if (disk_name.lastIndexOf('.') > -1 && disk_dot_suffix.length <= 7) {
image = new_vname + "." + disk_dot_suffix
} else {
image = new_vname + '-clone';
}
$('#disk_name-{{ disk.dev }}').val(image);
{% endfor %}
}
</script>
<script>
$(document).on('change', '#console_passwd_gen', function () {
if ($(this).prop('checked')) {
@ -928,6 +1116,9 @@
$('#console_keymap_selection').show();
}
});
$('#clone_name').on('input', function () {
update_clone_disk_name($(this).val());
});
$(document).ready(function () {
// set current console keymap or fall back to default
var keymap = "{{ console_keymap }}"
@ -942,6 +1133,16 @@
$("#console_select_type option[value='" + console_type + "']").prop('selected', true);
}
});
{% if not request.user.is_superuser %}
$('#select_clone_name').on('change', function () {
update_clone_disk_name($(this).val());
guess_mac_address('#select_clone_name', 0);
});
$(document).ready(function () {
update_clone_disk_name($('#select_clone_name').val());
guess_mac_address('#select_clone_name', 0);
});
{% endif %}
</script>
<script>
$(function () {
@ -1116,7 +1317,7 @@
}
});
}
if (~$.inArray(hash, ['#media', '#clone', '#autostart', '#xmledit', '#vncsettings', '#migrate'])) {
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') {

View file

@ -39,8 +39,8 @@
<table class="table table-hover table-striped sortable-theme-bootstrap" data-sortable>
<thead>
<tr>
<th>Name</th>
<th>Host</th>
<th>Name<br>Description</th>
<th>Host<br>User</th>
<th>Status</th>
<th>VCPU</th>
<th>Memory</th>
@ -51,8 +51,8 @@
{% for host, inst in all_host_vms.items %}
{% for vm, info in inst.items %}
<tr>
<td><a href="{% url 'instance' host.0 vm %}">{{ vm }}</a></td>
<td><a href="{% url 'overview' host.0 %}">{{ host.1 }}</a></td>
<td><a href="{% url 'instance' host.0 vm %}">{{ vm }}</a><br><small><em>{{ info.title }}</em></small></td>
<td><a href="{% url 'overview' host.0 %}">{{ host.1 }}</a><br><small><em>{% if info.userinstances.count > 0 %}{{ info.userinstances.first_user.user.username }}{% if info.userinstances.count > 1 %} (+{{ info.userinstances.count|add:"-1" }}){% endif %}{% endif %}</em></small></td>
<td>{% ifequal info.status 1 %}
<span class="text-success">{% trans "Active" %}</span>
{% endifequal %}
@ -69,9 +69,15 @@
<input type="hidden" name="name" value="{{ vm }}"/>
<input type="hidden" name="compute_id" value="{{ host.0 }}"/>
{% ifequal info.status 5 %}
<button class="btn btn-sm btn-default" type="submit" name="poweron" title="{% trans "Power On" %}">
<span class="glyphicon glyphicon-play"></span>
</button>
{% if info.is_template %}
<button class="btn btn-sm btn-default" type="button" name="clone" title="{% trans "Clone" %}" onclick="goto_instance_clone({{ host.0 }}, '{{ vm }}');">
<span class="glyphicon glyphicon-duplicate"></span>
</button>
{% else %}
<button class="btn btn-sm btn-default" type="submit" name="poweron" title="{% trans "Power On" %}">
<span class="glyphicon glyphicon-play"></span>
</button>
{% endif %}
<button class="btn btn-sm btn-default disabled" title="{% trans "Suspend" %}">
<span class="glyphicon glyphicon-pause"></span>
</button>
@ -149,7 +155,7 @@
<tbody class="searchable">
{% for inst, vm in all_user_vms.items %}
<tr>
<td><a href="{% url 'instance' vm.compute_id vm.name %}">{{ vm.name }}</a></td>
<td><a href="{% url 'instance' vm.compute_id vm.name %}">{{ vm.name }}</a><br><small><em>{{ vm.title }}</em></small></td>
<td>{% ifequal vm.status 1 %}
<span class="text-success">{% trans "Active" %}</span>
{% endifequal %}
@ -166,9 +172,15 @@
<input type="hidden" name="name" value="{{ vm.name }}"/>
<input type="hidden" name="compute_id" value="{{ vm.compute_id }}"/>
{% ifequal vm.status 5 %}
<button class="btn btn-sm btn-default" type="submit" name="poweron" title="Power On">
<span class="glyphicon glyphicon-play"></span>
</button>
{% if inst.instance.is_template %}
<button class="btn btn-sm btn-default" type="button" name="clone" title="{% trans "Clone" %}" onclick="goto_instance_clone({{ vm.compute_id }}, '{{ vm.name }}');">
<span class="glyphicon glyphicon-duplicate"></span>
</button>
{% else %}
<button class="btn btn-sm btn-default" type="submit" name="poweron" title="{% trans "Power On" %}">
<span class="glyphicon glyphicon-play"></span>
</button>
{% endif %}
<button class="btn btn-sm btn-default disabled" title="{% trans "Power Off" %}">
<span class="glyphicon glyphicon-off"></span>
</button>
@ -227,18 +239,30 @@
}
</script>
<script>
function filter_table() {
var rex = new RegExp($(this).val(), 'i');
$('.searchable tr').hide();
$('.searchable tr').filter(function () {
return rex.test($(this).text());
}).show();
Cookies.set("instances_filter", $(this).val(), { expires: 1 });
}
$(document).ready(function () {
instances_filter_cookie = Cookies.get("instances_filter");
if (instances_filter_cookie) {
$('#filter').val(instances_filter_cookie);
$('#filter').each(filter_table);
}
(function ($) {
$('#filter').keyup(function () {
var rex = new RegExp($(this).val(), 'i');
$('.searchable tr').hide();
$('.searchable tr').filter(function () {
return rex.test($(this).text());
}).show();
})
$('#filter').keyup(filter_table)
}(jQuery));
});
</script>
<script>
function goto_instance_clone(compute, instance) {
window.location = "/instance/" + compute + "/" + instance + "/#clone";
}
</script>
{% if request.user.is_superuser %}
<script>
function goto_compute() {

View file

@ -8,4 +8,10 @@ urlpatterns = [
views.inst_graph, name='inst_graph'),
url(r'^status/(?P<compute_id>[0-9]+)/(?P<vname>[\w\-\.]+)/$',
views.inst_status, name='inst_status'),
url(r'^guess_mac_address/(?P<vname>[\w\-\.]+)/$',
views.guess_mac_address, name='guess_mac_address'),
url(r'^guess_clone_name/$',
views.guess_clone_name, name='guess_clone_name'),
url(r'^check_instance/(?P<vname>[\w\-\.]+)/$',
views.check_instance, name='check_instance'),
]

View file

@ -1,7 +1,9 @@
import os
import time
import json
import socket
import crypt
import re
from string import letters, digits
from random import choice
from bisect import insort
@ -9,6 +11,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
@ -19,34 +22,41 @@ from vrtManager.util import randomPasswd
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE
from webvirtcloud.settings import QEMU_KEYMAPS, QEMU_CONSOLE_TYPES
from logs.views import addlogmsg
from django.conf import settings
@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 = {}
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:
@ -70,6 +80,8 @@ def instances(request):
check_uuid = Instance.objects.get(compute_id=comp.id, name=vm)
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()
@ -145,15 +157,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)
@ -173,12 +183,15 @@ def instance(request, compute_id, vname):
if not userinstace:
return HttpResponseRedirect(reverse('index'))
def show_clone_disk(disks):
def show_clone_disk(disks, vname=''):
clone_disk = []
for disk in disks:
if disk['image'] is None:
continue
if disk['image'].count(".") and len(disk['image'].rsplit(".", 1)[1]) <= 7:
if disk['image'].count("-") and disk['image'].rsplit("-", 1)[0] == vname:
name, suffix = disk['image'].rsplit("-", 1)
image = name + "-clone" + "-" + suffix
elif disk['image'].count(".") and len(disk['image'].rsplit(".", 1)[1]) <= 7:
name, suffix = disk['image'].rsplit(".", 1)
image = name + "-clone" + "." + suffix
else:
@ -188,6 +201,71 @@ def instance(request, compute_id, vname):
'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))
def get_clone_free_names(size=10):
prefix = settings.CLONE_INSTANCE_DEFAULT_PREFIX
free_names = []
existing_names = [i.name for i in Instance.objects.filter(name__startswith=prefix)]
index = 1
while len(free_names) < size:
new_name = prefix + str(index)
if new_name not in existing_names:
free_names.append(new_name)
index += 1
return free_names
def check_user_quota(instance, cpu, memory, disk_size):
user_instances = UserInstance.objects.filter(user_id=request.user.id, instance__is_template=False)
instance += len(user_instances)
for usr_inst in user_instances:
if connection_manager.host_is_up(usr_inst.instance.compute.type,
usr_inst.instance.compute.hostname):
conn = wvmInstance(usr_inst.instance.compute,
usr_inst.instance.compute.login,
usr_inst.instance.compute.password,
usr_inst.instance.compute.type,
usr_inst.instance.name)
cpu += int(conn.get_vcpu())
memory += int(conn.get_memory())
for disk in conn.get_disk_device():
disk_size += int(disk['size'])>>30
ua = request.user.userattributes
msg = ""
if ua.max_instances > 0 and instance > ua.max_instances:
msg = "instance"
if settings.QUOTA_DEBUG:
msg += " (%s > %s)" % (instance, ua.max_instances)
if ua.max_cpus > 0 and cpu > ua.max_cpus:
msg = "cpu"
if settings.QUOTA_DEBUG:
msg += " (%s > %s)" % (cpu, ua.max_cpus)
if ua.max_memory > 0 and memory > ua.max_memory:
msg = "memory"
if settings.QUOTA_DEBUG:
msg += " (%s > %s)" % (memory, ua.max_memory)
if ua.max_disk_size > 0 and disk_size > ua.max_disk_size:
msg = "disk"
if settings.QUOTA_DEBUG:
msg += " (%s > %s)" % (disk_size, ua.max_disk_size)
return msg
try:
conn = wvmInstance(compute.hostname,
compute.login,
@ -202,6 +280,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()
@ -222,8 +301,10 @@ def instance(request, compute_id, vname):
snapshots = sorted(conn.get_snapshot(), reverse=True)
inst_xml = conn._XMLDesc(VIR_DOMAIN_XML_SECURE)
has_managed_save_image = conn.get_managed_save_image()
clone_disks = show_clone_disk(disks)
clone_disks = show_clone_disk(disks, vname)
console_passwd = conn.get_console_passwd()
clone_free_names = get_clone_free_names()
user_quota_msg = check_user_quota(0, 0, 0, 0)
try:
instance = Instance.objects.get(compute_id=compute_id, name=vname)
@ -234,6 +315,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()
@ -264,6 +347,8 @@ def instance(request, compute_id, vname):
if conn.get_status() == 1:
conn.force_shutdown()
if request.POST.get('delete_disk', ''):
for snap in snapshots:
conn.snapshot_delete(snap['name'])
conn.delete_disk()
conn.delete()
@ -271,15 +356,11 @@ def instance(request, compute_id, vname):
instance_name = instance.name
instance.delete()
if not request.user.is_superuser:
del_userinstance = UserInstance.objects.get(id=userinstace.id)
try:
del_userinstance = UserInstance.objects.filter(instance__compute_id=compute_id, instance__name=vname)
del_userinstance.delete()
else:
try:
del_userinstance = UserInstance.objects.filter(instance__compute_id=compute_id, instance__name=vname)
del_userinstance.delete()
except UserInstance.DoesNotExist:
pass
except UserInstance.DoesNotExist:
pass
msg = _("Destroy")
addlogmsg(request.user.username, instance_name, msg)
@ -331,20 +412,37 @@ def instance(request, compute_id, vname):
error_messages.append(msg)
if 'resize' in request.POST and (request.user.is_superuser or userinstace.is_change):
vcpu = request.POST.get('vcpu', '')
cur_vcpu = request.POST.get('cur_vcpu', '')
memory = request.POST.get('memory', '')
memory_custom = request.POST.get('memory_custom', '')
if memory_custom:
memory = memory_custom
cur_memory = request.POST.get('cur_memory', '')
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)
msg = _("Resize")
addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#resize')
new_vcpu = request.POST.get('vcpu', '')
new_cur_vcpu = request.POST.get('cur_vcpu', '')
new_memory = request.POST.get('memory', '')
new_memory_custom = request.POST.get('memory_custom', '')
if new_memory_custom:
new_memory = new_memory_custom
new_cur_memory = request.POST.get('cur_memory', '')
new_cur_memory_custom = request.POST.get('cur_memory_custom', '')
if new_cur_memory_custom:
new_cur_memory = new_cur_memory_custom
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)
disk_sum = sum([disk['size']>>30 for disk in disks_new])
disk_new_sum = sum([disk['size_new']>>30 for disk in disks_new])
quota_msg = check_user_quota(0, int(new_vcpu)-vcpu, int(new_memory)-memory, disk_new_sum-disk_sum)
if not request.user.is_superuser and quota_msg:
msg = _("User %s quota reached, cannot resize '%s'!" % (quota_msg, instance.name))
error_messages.append(msg)
else:
cur_memory = new_cur_memory
memory = new_memory
cur_vcpu = new_cur_vcpu
vcpu = new_vcpu
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')
if 'umount_iso' in request.POST:
image = request.POST.get('path', '')
@ -463,30 +561,85 @@ def instance(request, compute_id, vname):
live = request.POST.get('live_migrate', False)
unsafe = request.POST.get('unsafe_migrate', False)
xml_del = request.POST.get('xml_delete', False)
offline = request.POST.get('offline_migrate', False)
new_compute = Compute.objects.get(id=compute_id)
conn_migrate = wvmInstances(new_compute.hostname,
new_compute.login,
new_compute.password,
new_compute.type)
conn_migrate.moveto(conn, vname, live, unsafe, xml_del)
conn_migrate.define_move(vname)
conn_migrate.moveto(conn, vname, live, unsafe, xml_del, offline)
instance.compute = new_compute
instance.save()
conn_migrate.close()
msg = _("Migrate")
if autostart:
conn_new = wvmInstance(new_compute.hostname,
new_compute.login,
new_compute.password,
new_compute.type,
vname)
conn_new.set_autostart(1)
conn_new.close()
msg = _("Migrate to %s" % new_compute.hostname)
addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(reverse('instance', args=[compute_id, vname]))
if 'change_network' in request.POST:
network_data = {}
for post in request.POST:
if post.startswith('net-'):
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')
if 'change_options' in request.POST:
instance.is_template = request.POST.get('is_template', False)
instance.save()
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() + '#options')
if request.user.is_superuser or request.user.userattributes.can_clone_instances:
if 'clone' in request.POST:
clone_data = {}
clone_data['name'] = request.POST.get('name', '')
for post in request.POST:
if 'disk' or 'meta' in post:
disk_sum = sum([disk['size']>>30 for disk in disks])
quota_msg = check_user_quota(1, vcpu, memory, disk_sum)
check_instance = Instance.objects.filter(name=clone_data['name'])
if not request.user.is_superuser and quota_msg:
msg = _("User %s quota reached, cannot create '%s'!" % (quota_msg, clone_data['name']))
error_messages.append(msg)
elif check_instance:
msg = _("Instance '%s' already exists!" % clone_data['name'])
error_messages.append(msg)
elif not re.match(r'^[a-zA-Z0-9-]+$', clone_data['name']):
msg = _("Instance name '%s' contains invalid characters!" % clone_data['name'])
error_messages.append(msg)
else:
for post in request.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']]))
new_uuid = conn.clone_instance(clone_data)
new_instance = Instance(compute_id=compute_id, name=clone_data['name'], uuid=new_uuid)
new_instance.save()
userinstance = UserInstance(instance_id=new_instance.id, user_id=request.user.id, is_delete=True)
userinstance.save()
msg = _("Clone of '%s'" % instance.name)
addlogmsg(request.user.username, new_instance.name, msg)
return HttpResponseRedirect(reverse('instance', args=[compute_id, clone_data['name']]))
conn.close()
@ -497,15 +650,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"
@ -524,15 +675,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 = {}
@ -632,3 +781,42 @@ def inst_graph(request, compute_id, vname):
response.write(data)
return response
@login_required
def guess_mac_address(request, vname):
dhcp_file = '/srv/webvirtcloud/dhcpd.conf'
data = { 'vname': vname, 'mac': '52:54:00:' }
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
def guess_clone_name(request):
dhcp_file = '/srv/webvirtcloud/dhcpd.conf'
prefix = settings.CLONE_INSTANCE_DEFAULT_PREFIX
if os.path.isfile(dhcp_file):
instance_names = [i.name for i in Instance.objects.filter(name__startswith=prefix)]
with open(dhcp_file, 'r') as f:
for line in f:
line = line.strip()
if "host %s" % prefix in line:
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({}));
@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));

View file

@ -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'))

View file

@ -0,0 +1,12 @@
<center>
{% if page > 1 %}
<a href="{% url 'showlogspage' page|add:"-1" %}">&larr;</a>
{% else %}
&nbsp;
{% endif %}
{% if has_next_page %}
<a href="{% url 'showlogspage' page|add:"1" %}">&rarr;</a>
{% else %}
&nbsp;
{% endif %}
</center>

View file

@ -22,30 +22,32 @@
</div>
</div>
{% else %}
{% include "paging.html" %}
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>{% trans "Date" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Instance" %}</th>
<th>{% trans "Message" %}</th>
<th>{% trans "Date" %}</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ log.id }}</td>
<td style="width:130px;">{{ log.date|date:"M d H:i:s" }}</td>
<td>{{ log.user }}</a></td>
<td>{{ log.instance }}</a></td>
<td>{{ log.message }}</td>
<td style="width:130px;">{{ log.date|date:"M d H:i:s" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include "paging.html" %}
{% endif %}
</div>
</div>

7
logs/urls.py Normal file
View file

@ -0,0 +1,7 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.showlogs, name='showlogs'),
url(r'^(?P<page>[0-9]+)/$', views.showlogs, name='showlogspage'),
]

View file

@ -2,6 +2,7 @@ from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from logs.models import Logs
from django.conf import settings
def addlogmsg(user, instance, message):
@ -13,7 +14,7 @@ def addlogmsg(user, instance, message):
add_log_msg.save()
def showlogs(request):
def showlogs(request, page=1):
"""
:param request:
:return:
@ -25,6 +26,11 @@ def showlogs(request):
if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index'))
logs = Logs.objects.all()
page = int(page)
limit_from = (page-1)*settings.LOGS_PER_PAGE
limit_to = page*settings.LOGS_PER_PAGE
logs = Logs.objects.all().order_by('-date')[limit_from:limit_to+1]
has_next_page = logs.count() > settings.LOGS_PER_PAGE
# TODO: remove last element from queryset, but do not affect database
return render(request, 'showlogs.html', locals())

View file

@ -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'))

View file

@ -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'))

View file

@ -12,7 +12,7 @@ body {
}
.container {
max-width: 768px;
max-width: 900px;
}
.page-header {

151
static/js/js.cookie.js Normal file
View file

@ -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 () {});
}));

View file

@ -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'))

View file

@ -48,6 +48,8 @@
<script src="{% static "js/jquery.js" %}"></script>
<!-- Bootstrap Core JavaScript -->
<script src="{% static "js/bootstrap.min.js" %}"></script>
<!-- JavaScript Cookie -->
<script src="{% static "js/js.cookie.js" %}"></script>
{% block script %}{% endblock %}
</body>
</html>

View file

@ -0,0 +1,25 @@
{% load i18n %}
<!-- Please wait dialog -->
<div class="modal fade" id="pleaseWaitDialog" tabindex="-1" role="dialog" aria-labelledby="pleaseWaitDialogLabel" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{% trans "Processing" %}...</h4>
</div>
<div class="modal-body">
<div class="progress">
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="10" aria-valuemin="0" aria-valuemax="10" style="width:100%">
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function showPleaseWaitDialog() {
$('#pleaseWaitDialog').modal();
}
function hidePleaseWaitDialog() {
$('#pleaseWaitDialog').modal('hide');
}
</script>

View file

@ -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"""

View file

@ -220,16 +220,15 @@ class wvmCreate(wvmConnect):
xml += """<interface type='network'>"""
if mac:
xml += """<mac address='%s'/>""" % mac
xml += """<source network='%s'/>""" % net
xml += """<source network='%s'/>
<filterref filter='clean-traffic'/>""" % net
if virtio:
xml += """<model type='virtio'/>"""
xml += """</interface>"""
xml += """ <input type='mouse' bus='ps2'/>
<input type='tablet' bus='usb'/>
<graphics type='%s' port='-1' autoport='yes' listen='0.0.0.0' passwd='%s'>
<listen type='address' address='0.0.0.0'/>
</graphics>
<graphics type='%s' port='-1' autoport='yes' passwd='%s' listen='127.0.0.1'/>
<console type='pty'/>
<video>
<model type='cirrus'/>

View file

@ -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
@ -66,20 +67,18 @@ class wvmInstances(wvmConnect):
dom = self.get_instance(name)
dom.resume()
def moveto(self, conn, name, live, unsafe, undefine):
def moveto(self, conn, name, live, unsafe, undefine, offline):
flags = 0
if live and conn.get_status() == 1:
flags |= VIR_MIGRATE_LIVE
if unsafe and conn.get_status() == 1:
flags |= VIR_MIGRATE_UNSAFE
dom = conn.get_instance(name)
dom.migrate(self.wvm, flags, name, None, 0)
xml = dom.XMLDesc(VIR_DOMAIN_XML_SECURE)
if not offline:
dom.migrate(self.wvm, flags, None, None, 0)
if undefine:
dom.undefine()
def define_move(self, name):
dom = self.get_instance(name)
xml = dom.XMLDesc(VIR_DOMAIN_XML_SECURE)
self.wvm.defineXML(xml)
def graphics_type(self, name):
@ -184,8 +183,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 +527,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 +545,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 +607,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 +635,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':
@ -650,4 +676,64 @@ class wvmInstance(wvmConnect):
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)

View file

@ -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

View file

@ -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<compute_id>[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)),
)

View file

@ -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