mirror of
https://github.com/retspen/webvirtcloud
synced 2024-11-01 03:54:15 +00:00
Merge pull request #88 from honza801/master
multiple enhancements and fixes
This commit is contained in:
commit
531ea1e652
48 changed files with 1188 additions and 197 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -4,3 +4,6 @@ venv
|
|||
.DS_*
|
||||
*.pyc
|
||||
db.sqlite3
|
||||
console/cert.pem
|
||||
tags
|
||||
dhcpd.*
|
||||
|
|
|
@ -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
7
accounts/backends.py
Normal 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
|
||||
|
|
@ -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']
|
||||
|
|
26
accounts/migrations/0004_userattributes.py
Normal file
26
accounts/migrations/0004_userattributes.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -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),
|
||||
),
|
||||
]
|
19
accounts/migrations/0006_userattributes_max_disk_size.py
Normal file
19
accounts/migrations/0006_userattributes_max_disk_size.py
Normal 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),
|
||||
),
|
||||
]
|
34
accounts/migrations/0007_auto_20160426_0635.py
Normal file
34
accounts/migrations/0007_auto_20160426_0635.py
Normal 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),
|
||||
),
|
||||
]
|
15
accounts/migrations/0008_merge.py
Normal file
15
accounts/migrations/0008_merge.py
Normal 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 = [
|
||||
]
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
@ -99,4 +141,4 @@
|
|||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -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>
|
||||
|
@ -35,4 +35,4 @@
|
|||
</div> <!-- /.modal-content -->
|
||||
</div> <!-- /.modal-dialog -->
|
||||
</div> <!-- /.modal -->
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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']
|
||||
|
|
18
computes/migrations/0002_compute_details.py
Normal file
18
computes/migrations/0002_compute_details.py
Normal 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),
|
||||
),
|
||||
]
|
|
@ -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):
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
@ -156,4 +164,4 @@
|
|||
</div> <!-- /.modal-content -->
|
||||
</div> <!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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', '')
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'))
|
||||
|
||||
|
|
19
instances/migrations/0002_instance_is_template.py
Normal file
19
instances/migrations/0002_instance_is_template.py
Normal 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),
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
|
|
@ -35,16 +35,16 @@
|
|||
{% 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>
|
||||
</div> <!-- /.modal-content -->
|
||||
</div> <!-- /.modal-dialog -->
|
||||
</div> <!-- /.modal -->
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
@ -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:
|
||||
|
@ -187,6 +200,71 @@ def instance(request, compute_id, vname):
|
|||
{'dev': disk['dev'], 'storage': disk['storage'],
|
||||
'image': image, 'format': disk['format']})
|
||||
return clone_disk
|
||||
|
||||
def filesizefstr(size_str):
|
||||
if size_str == '':
|
||||
return 0
|
||||
size_str = size_str.encode('ascii', 'ignore').upper().translate(None, " B")
|
||||
if 'K' == size_str[-1]:
|
||||
return long(float(size_str[:-1]))<<10
|
||||
elif 'M' == size_str[-1]:
|
||||
return long(float(size_str[:-1]))<<20
|
||||
elif 'G' == size_str[-1]:
|
||||
return long(float(size_str[:-1]))<<30
|
||||
elif 'T' == size_str[-1]:
|
||||
return long(float(size_str[:-1]))<<40
|
||||
elif 'P' == size_str[-1]:
|
||||
return long(float(size_str[:-1]))<<50
|
||||
else:
|
||||
return long(float(size_str))
|
||||
|
||||
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,
|
||||
|
@ -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));
|
||||
|
|
|
@ -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'))
|
||||
|
||||
|
|
12
logs/templates/paging.html
Normal file
12
logs/templates/paging.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<center>
|
||||
{% if page > 1 %}
|
||||
<a href="{% url 'showlogspage' page|add:"-1" %}">←</a>
|
||||
{% else %}
|
||||
|
||||
{% endif %}
|
||||
{% if has_next_page %}
|
||||
<a href="{% url 'showlogspage' page|add:"1" %}">→</a>
|
||||
{% else %}
|
||||
|
||||
{% endif %}
|
||||
</center>
|
|
@ -22,31 +22,33 @@
|
|||
</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>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
7
logs/urls.py
Normal file
7
logs/urls.py
Normal 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'),
|
||||
]
|
|
@ -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())
|
||||
|
|
|
@ -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'))
|
||||
|
||||
|
|
|
@ -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'))
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ body {
|
|||
}
|
||||
|
||||
.container {
|
||||
max-width: 768px;
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
|
@ -132,4 +132,4 @@ p {
|
|||
.keyselect {
|
||||
display: inline;
|
||||
min-width: 250px;
|
||||
}
|
||||
}
|
||||
|
|
151
static/js/js.cookie.js
Normal file
151
static/js/js.cookie.js
Normal 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 () {});
|
||||
}));
|
|
@ -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'))
|
||||
|
||||
|
|
|
@ -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>
|
||||
</html>
|
||||
|
|
25
templates/pleasewaitdialog.html
Normal file
25
templates/pleasewaitdialog.html
Normal 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>
|
|
@ -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"""
|
||||
|
|
|
@ -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'/>
|
||||
|
|
|
@ -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':
|
||||
|
@ -649,5 +675,65 @@ class wvmInstance(wvmConnect):
|
|||
</volume>""" % (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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)),
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue