1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2024-11-01 03:54:15 +00:00

Fixed conflict

This commit is contained in:
retspen 2018-05-07 12:40:19 +03:00
commit 5bba739e6b
25 changed files with 734 additions and 198 deletions

5
.gitignore vendored
View file

@ -3,7 +3,8 @@ venv
.idea .idea
.DS_* .DS_*
*.pyc *.pyc
db.sqlite3 db.sqlite3*
console/cert.pem console/cert.pem*
tags tags
dhcpd.* dhcpd.*
webvirtcloud/settings.py

View file

@ -19,12 +19,23 @@ sudo service supervisor restart
WebVirtCloud is a virtualization web interface for admins and users. It can delegate Virtual Machine's to users. A noVNC viewer presents a full graphical console to the guest domain. KVM is currently the only hypervisor supported. WebVirtCloud is a virtualization web interface for admins and users. It can delegate Virtual Machine's to users. A noVNC viewer presents a full graphical console to the guest domain. KVM is currently the only hypervisor supported.
### Generate secret key
You should generate SECRET_KEY after cloning repo. Then put it into webvirtcloud/settings.py.
```python
import random, string
haystack = string.ascii_letters + string.digits + string.punctuation
print(''.join([random.SystemRandom().choice(haystack) for _ in range(50)]))
```
### Install WebVirtCloud panel (Ubuntu) ### Install WebVirtCloud panel (Ubuntu)
```bash ```bash
sudo apt-get -y install git python-virtualenv python-dev libxml2-dev libvirt-dev zlib1g-dev nginx supervisor libsasl2-modules gcc pkg-config 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 git clone https://github.com/retspen/webvirtcloud
cd webvirtcloud cd webvirtcloud
cp webvirtcloud/settings.py.template webvirtcloud/settings.py
# now put secret key to webvirtcloud/settings.py
sudo cp conf/supervisor/webvirtcloud.conf /etc/supervisor/conf.d sudo cp conf/supervisor/webvirtcloud.conf /etc/supervisor/conf.d
sudo cp conf/nginx/webvirtcloud.conf /etc/nginx/conf.d sudo cp conf/nginx/webvirtcloud.conf /etc/nginx/conf.d
cd .. cd ..
@ -63,6 +74,8 @@ sudo yum -y install python-virtualenv python-devel libvirt-devel glibc gcc nginx
```bash ```bash
sudo mkdir /srv && cd /srv sudo mkdir /srv && cd /srv
sudo git clone https://github.com/retspen/webvirtcloud && cd webvirtcloud sudo git clone https://github.com/retspen/webvirtcloud && cd webvirtcloud
cp webvirtcloud/settings.py.template webvirtcloud/settings.py
# now put secret key to webvirtcloud/settings.py
``` ```
#### Start installation webvirtcloud #### Start installation webvirtcloud

View file

@ -1,7 +1,13 @@
from django.contrib.auth.backends import RemoteUserBackend from django.contrib.auth.backends import RemoteUserBackend
from accounts.models import UserInstance, UserAttributes
from instances.models import Instance
class MyRemoteUserBackend(RemoteUserBackend): class MyRemoteUserBackend(RemoteUserBackend):
#create_unknown_user = True
def configure_user(self, user): def configure_user(self, user):
user.is_superuser = True #user.is_superuser = True
UserAttributes.configure_user(user)
return user return user

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0008_merge'),
]
operations = [
migrations.AlterField(
model_name='userattributes',
name='can_clone_instances',
field=models.BooleanField(default=True),
),
]

View file

@ -1,5 +1,6 @@
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings
from instances.models import Instance from instances.models import Instance
@ -24,11 +25,33 @@ class UserSSHKey(models.Model):
class UserAttributes(models.Model): class UserAttributes(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE) user = models.OneToOneField(User, on_delete=models.CASCADE)
can_clone_instances = models.BooleanField(default=False) can_clone_instances = models.BooleanField(default=True)
max_instances = models.IntegerField(default=1) max_instances = models.IntegerField(default=1)
max_cpus = models.IntegerField(default=1) max_cpus = models.IntegerField(default=1)
max_memory = models.IntegerField(default=2048) max_memory = models.IntegerField(default=2048)
max_disk_size = models.IntegerField(default=20) max_disk_size = models.IntegerField(default=20)
@staticmethod
def create_missing_userattributes(user):
try:
userattributes = user.userattributes
except UserAttributes.DoesNotExist:
userattributes = UserAttributes(user=user)
userattributes.save()
@staticmethod
def add_default_instances(user):
existing_instances = UserInstance.objects.filter(user=user)
if not existing_instances:
for instance_name in settings.NEW_USER_DEFAULT_INSTANCES:
instance = Instance.objects.get(name=instance_name)
user_instance = UserInstance(user=user, instance=instance)
user_instance.save()
@staticmethod
def configure_user(user):
UserAttributes.create_missing_userattributes(user)
UserAttributes.add_default_instances(user)
def __unicode__(self): def __unicode__(self):
return self.user.username return self.user.username

View file

@ -13,6 +13,31 @@
{% include 'errors_block.html' %} {% include 'errors_block.html' %}
{% if request.user.is_superuser and publickeys %}
<div class="row">
<div class="col-lg-12">
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>{% trans "Key name" %}</th>
<th>{% trans "Public key" %}</th>
</tr>
</thead>
<tbody>
{% for publickey in publickeys %}
<tr>
<td>{{ publickey.keyname }}</td>
<td title="{{ publickey.keypublic }}">{{ publickey.keypublic|truncatechars:64 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
{% if not user_insts %} {% if not user_insts %}
@ -112,4 +137,4 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -41,6 +41,7 @@
</div> </div>
</div> </div>
</form> </form>
{% if show_profile_edit_password %}
<h3 class="page-header">{% trans "Edit Password" %}</h3> <h3 class="page-header">{% trans "Edit Password" %}</h3>
<form class="form-horizontal" method="post" action="" role="form">{% csrf_token %} <form class="form-horizontal" method="post" action="" role="form">{% csrf_token %}
<div class="form-group"> <div class="form-group">
@ -67,6 +68,7 @@
</div> </div>
</div> </div>
</form> </form>
{% endif %}
<h3 class="page-header">{% trans "SSH Keys" %}</h3> <h3 class="page-header">{% trans "SSH Keys" %}</h3>
{% if publickeys %} {% if publickeys %}
<div class="col-lg-12"> <div class="col-lg-12">
@ -93,23 +95,23 @@
{% endif %} {% endif %}
<form class="form-horizontal" method="post" action="" role="form">{% csrf_token %} <form class="form-horizontal" method="post" action="" role="form">{% csrf_token %}
<div class="form-group bridge_name_form_group_dhcp"> <div class="form-group bridge_name_form_group_dhcp">
<label class="col-sm-2 control-label">{% trans "Retry" %}</label> <label class="col-sm-2 control-label">{% trans "Key name" %}</label>
<div class="col-sm-4"> <div class="col-sm-4">
<input type="text" class="form-control" name="keyname" placeholder="{% trans "Enter Name" %}"> <input type="text" class="form-control" name="keyname" placeholder="{% trans "Enter Name" %}">
</div> </div>
</div> </div>
<div class="form-group bridge_name_form_group_dhcp"> <div class="form-group bridge_name_form_group_dhcp">
<label class="col-sm-2 control-label">{% trans "Retry" %}</label> <label class="col-sm-2 control-label">{% trans "Public key" %}</label>
<div class="col-sm-8"> <div class="col-sm-8">
<textarea name="keypublic" class="form-control" rows="6" placeholder="{% trans "Enter Public Key" %}"></textarea> <textarea name="keypublic" class="form-control" rows="6" placeholder="{% trans "Enter Public Key" %}"></textarea>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">{% trans "Create" %}</button> <button type="submit" class="btn btn-primary">{% trans "Add" %}</button>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -7,6 +7,9 @@ register = template.Library()
@register.simple_tag @register.simple_tag
def ssh_to_fingerprint(line): def ssh_to_fingerprint(line):
key = base64.b64decode(line.strip().split()[1].encode('ascii')) try:
fp_plain = hashlib.md5(key).hexdigest() key = base64.b64decode(line.strip().split()[1].encode('ascii'))
return ':'.join(a + b for a, b in zip(fp_plain[::2], fp_plain[1::2])) fp_plain = hashlib.md5(key).hexdigest()
return ':'.join(a + b for a, b in zip(fp_plain[::2], fp_plain[1::2]))
except Exception:
return 'Invalid key'

View file

@ -20,6 +20,7 @@ def profile(request):
error_messages = [] error_messages = []
user = User.objects.get(id=request.user.id) user = User.objects.get(id=request.user.id)
publickeys = UserSSHKey.objects.filter(user_id=request.user.id) publickeys = UserSSHKey.objects.filter(user_id=request.user.id)
show_profile_edit_password = settings.SHOW_PROFILE_EDIT_PASSWORD
if request.method == 'POST': if request.method == 'POST':
if 'username' in request.POST: if 'username' in request.POST:
@ -70,21 +71,11 @@ def accounts(request):
:param request: :param request:
:return: :return:
""" """
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: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
error_messages = [] error_messages = []
users = User.objects.all().order_by('username') users = User.objects.all().order_by('username')
create_missing_userattributes(users)
allow_empty_password = settings.ALLOW_EMPTY_PASSWORD allow_empty_password = settings.ALLOW_EMPTY_PASSWORD
if request.method == 'POST': if request.method == 'POST':
@ -98,6 +89,7 @@ def accounts(request):
if not error_messages: if not error_messages:
new_user = User.objects.create_user(data['name'], None, data['password']) new_user = User.objects.create_user(data['name'], None, data['password'])
new_user.save() new_user.save()
UserAttributes.configure_user(new_user)
return HttpResponseRedirect(request.get_full_path()) return HttpResponseRedirect(request.get_full_path())
if 'edit' in request.POST: if 'edit' in request.POST:
user_id = request.POST.get('user_id', '') user_id = request.POST.get('user_id', '')
@ -155,9 +147,7 @@ def account(request, user_id):
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
user_insts = UserInstance.objects.filter(user_id=user_id) user_insts = UserInstance.objects.filter(user_id=user_id)
instances = Instance.objects.all().order_by('name') instances = Instance.objects.all().order_by('name')
publickeys = UserSSHKey.objects.filter(user_id=user_id)
if user.username == request.user.username:
return HttpResponseRedirect(reverse('profile'))
if request.method == 'POST': if request.method == 'POST':
if 'delete' in request.POST: if 'delete' in request.POST:

View file

@ -1,11 +1,12 @@
{% load staticfiles %}
{% load i18n %} {% load i18n %}
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="shortcut icon" href="{{ STATIC_URL }}img/favicon.ico"> <link rel="shortcut icon" href="{% static "img/favicon.ico" %}">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ STATIC_URL }}css/bootstrap.min.css"> <link rel="stylesheet" href="{% static "css/bootstrap.min.css" %}">
<link href="{{ STATIC_URL }}css/webvirtcloud.css" rel="stylesheet"> <link href="{% static "css/webvirtcloud.css" %}" rel="stylesheet">
<style> <style>
body { body {
@ -92,8 +93,8 @@
<div id='main_container' class="container"> <div id='main_container' class="container">
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div> </div>
<script src="{{ STATIC_URL }}js/jquery.js"></script> <script src="{% static "js/jquery.js" %}"></script>
<script src="{{ STATIC_URL }}js/bootstrap.min.js"></script> <script src="{% static "js/bootstrap.min.js" %}"></script>
<script> <script>
function log_message(msg,type) { function log_message(msg,type) {

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import datetime
class Migration(migrations.Migration):
dependencies = [
('instances', '0002_instance_is_template'),
]
operations = [
migrations.AddField(
model_name='instance',
name='created',
field=models.DateField(default=datetime.datetime(2017, 10, 26, 8, 5, 55, 797326), auto_now_add=True),
preserve_default=False,
),
]

View file

@ -7,6 +7,7 @@ class Instance(models.Model):
name = models.CharField(max_length=20) name = models.CharField(max_length=20)
uuid = models.CharField(max_length=36) uuid = models.CharField(max_length=36)
is_template = models.BooleanField(default=False) is_template = models.BooleanField(default=False)
created = models.DateField(auto_now_add=True)
def __unicode__(self): def __unicode__(self):
return self.name return self.name

View file

@ -0,0 +1,36 @@
{% load i18n %}
{% if request.user.is_superuser %}
<a href="#addInstanceOwner" type="button" class="btn btn-success pull-right" data-toggle="modal">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
</a>
<!-- Modal pool -->
<div class="modal fade" id="addInstanceOwner" tabindex="-1" role="dialog" aria-labelledby="addInstanceOwnerLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{% trans "Add Instance Owner" %}</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" method="post" action="" role="form">{% csrf_token %}
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "User" %}</label>
<div class="col-sm-6">
<select class="form-control" name="user_id">
{% for user in users %}
<option value="{{ user.id }}">{{ user.username }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button>
<button type="submit" class="btn btn-primary" name="add_owner">{% trans "Add" %}</button>
</div>
</form>
</div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog -->
</div> <!-- /.modal -->
{% endif %}

View file

@ -1,48 +1,42 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load staticfiles %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Instance" %} - {{ vname }}{% endblock %} {% block title %}{% trans "Instance" %} - {{ vname }}{% endblock %}
{% block content %} {% block content %}
{% include 'pleasewaitdialog.html' %} {% include 'pleasewaitdialog.html' %}
<!-- Page Heading --> <!-- Page Heading -->
<div class="row"> <div class="row">
<table> <div>
<tr> <h3>
<td><h3>{{ vname }}</h3></td> {{ vname }}{% if title %}&nbsp;({{ title }}){% endif %}
<td> </h3>
{% ifequal status 5 %} </div>
<span class="label label-danger">{% trans "Off" %}</span> <div>
{% endifequal %} <div>
{% ifequal status 1 %} {% ifequal status 5 %}
<span class="label label-success">{% trans "Active" %}</span> <span class="label label-danger">{% trans "Off" %}</span>
{% endifequal %} {% endifequal %}
{% ifequal status 3 %} {% ifequal status 1 %}
<span class="label label-warning">{% trans "Suspend" %}</span> <span class="label label-success">{% trans "Active" %}</span>
{% endifequal %} {% endifequal %}
</td> {% ifequal status 3 %}
<td> <span class="label label-warning">{% trans "Suspend" %}</span>
<a href="{% url 'instance' compute.id vname %}" type="button" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-refresh"></span></a> {% endifequal %}
</td> |
</tr> {% if cur_vcpu %}
</table> {{ cur_vcpu }} {% trans "Vcpu" %}
<table width="65%"> {% else %}
<tr> {{ vcpu }} {% trans "Vcpu" %}
<td> {% endif %}
{% if cur_vcpu %} |
<h4>{{ cur_vcpu }} {% trans "Vcpu" %}</h4> {{ cur_memory }} {% trans "MB" %} {% trans "Ram" %}
{% else %} |
<h4>{{ vcpu }} {% trans "Vcpu" %}</h4>
{% endif %}
</td>
<td>
<h4>{{ cur_memory }} {% trans "MB" %} {% trans "Ram" %}</h4>
</td>
{% for disk in disks %} {% for disk in disks %}
<td> {{ disk.size|filesizeformat }} {% trans "Disk" %} |
<h4>{{ disk.size|filesizeformat }} {% trans "Disk" %}</h4>
</td>
{% endfor %} {% endfor %}
</tr> <a href="{% url 'instance' compute.id vname %}" type="button" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-refresh"></span></a>
</table> </div>
</div>
{% if user_quota_msg %} {% if user_quota_msg %}
<span class="label label-warning">{{ user_quota_msg|capfirst }} quota reached.</span> <span class="label label-warning">{{ user_quota_msg|capfirst }} quota reached.</span>
{% endif %} {% endif %}
@ -90,8 +84,8 @@
</li> </li>
<li role="presentation"> <li role="presentation">
<a href="#graphics" id="chartgraphs" class="action-button" aria-controls="graphics" role="tab" data-toggle="tab"> <a href="#graphics" id="chartgraphs" class="action-button" aria-controls="graphics" role="tab" data-toggle="tab">
<span id="action-block" class="glyphicon glyphicon-signal" aria-hidden="true"></span> <span id="action-block" class="glyphicon glyphicon-stats" aria-hidden="true"></span>
{% trans "Graphs" %} {% trans "Stats" %}
</a> </a>
</li> </li>
<li role="presentation"> <li role="presentation">
@ -233,16 +227,20 @@
{% trans "Console" %} {% trans "Console" %}
</a> </a>
</li> </li>
{% if show_access_root_password %}
<li role="presentation"> <li role="presentation">
<a href="#rootpasswd" aria-controls="rootpasswd" role="tab" data-toggle="tab"> <a href="#rootpasswd" aria-controls="rootpasswd" role="tab" data-toggle="tab">
{% trans "Root Password" %} {% trans "Root Password" %}
</a> </a>
</li> </li>
{% endif %}
{% if show_access_ssh_keys %}
<li role="presentation"> <li role="presentation">
<a href="#sshkeys" aria-controls="sshkeys" role="tab" data-toggle="tab"> <a href="#sshkeys" aria-controls="sshkeys" role="tab" data-toggle="tab">
{% trans "SSH Keys" %} {% trans "SSH Keys" %}
</a> </a>
</li> </li>
{% endif %}
</ul> </ul>
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content"> <div class="tab-content">
@ -255,6 +253,7 @@
{% endifequal %} {% endifequal %}
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
{% if show_access_root_password %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="rootpasswd"> <div role="tabpanel" class="tab-pane tab-pane-bordered" id="rootpasswd">
<p>{% trans "You need shut down your instance and enter a new root password." %}</p> <p>{% trans "You need shut down your instance and enter a new root password." %}</p>
<form class="form-inline" method="post" role="form">{% csrf_token %} <form class="form-inline" method="post" role="form">{% csrf_token %}
@ -271,6 +270,8 @@
</form> </form>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
{% endif %}
{% if show_access_ssh_keys %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="sshkeys"> <div role="tabpanel" class="tab-pane tab-pane-bordered" id="sshkeys">
<p>{% trans "You need shut down your instance and choose your public key." %}</p> <p>{% trans "You need shut down your instance and choose your public key." %}</p>
<form class="form-inline" method="post" role="form">{% csrf_token %} <form class="form-inline" method="post" role="form">{% csrf_token %}
@ -295,6 +296,7 @@
</form> </form>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -307,11 +309,16 @@
{% trans "Resize Instance" %} {% trans "Resize Instance" %}
</a> </a>
</li> </li>
<li role="presentation">
<a href="#addvolume" aria-controls="addvolume" role="tab" data-toggle="tab">
{% trans "Add New Volume" %}
</a>
</li>
</ul> </ul>
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content"> <div class="tab-content">
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="resizevm"> <div role="tabpanel" class="tab-pane tab-pane-bordered active" id="resizevm">
{% if request.user.is_superuser or userinstace.is_change %} {% if request.user.is_superuser or request.user.is_staff or userinstace.is_change %}
<form class="form-horizontal" method="post" role="form">{% csrf_token %} <form class="form-horizontal" method="post" role="form">{% csrf_token %}
<p style="font-weight:bold;">{% trans "Logical host CPUs:" %} {{ vcpu_host }}</p> <p style="font-weight:bold;">{% trans "Logical host CPUs:" %} {{ vcpu_host }}</p>
<div class="form-group"> <div class="form-group">
@ -388,6 +395,88 @@
{% endif %} {% endif %}
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="addvolume">
{% if request.user.is_superuser or userinstace.is_change %}
<form class="form-horizontal" method="post" role="form">{% csrf_token %}
<p style="font-weight:bold;">{% trans "Volume parameters" %}</p>
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Storage" %}</label>
<div class="col-sm-4">
<select name="storage" class="form-control image-format">
{% for storage in storages %}
<option value="{{ storage }}">{{ storage }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Name" %}</label>
<div class="col-sm-4">
<input type="text" class="form-control" name="name" placeholder="{% trans "Name" %}" required pattern="[a-zA-Z0-9\.\-_]+">
</div>
<div class="col-sm-2">
<select name="extension" class="form-control image-format">
{% for format in formats %}
<option value="{{ format }}" {% if format == default_format %}selected{% endif %}>{% trans format %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Format" %}</label>
<div class="col-sm-4">
<select name="format" class="form-control image-format">
{% for format in formats %}
<option value="{{ format }}" {% if format == default_format %}selected{% endif %}>{% trans format %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Size" %}</label>
<div class="col-sm-4">
<input type="text" class="form-control" name="size" value="10" maxlength="3" required pattern="[0-9]+">
</div>
<label class="col-sm-1 control-label">{% trans "GB" %}</label>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Bus" %}</label>
<div class="col-sm-4">
<select name="bus" class="form-control image-format">
{% for bus in busses %}
<option value="{{ bus }}" {% if bus == default_bus %}selected{% endif %}>{% trans bus %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Cache" %}</label>
<div class="col-sm-4">
<select name="cache" class="form-control image-format">
{% for mode, name in cache_modes %}
<option value="{{ mode }}" {% if mode == default_cache %}selected{% endif %}>{% trans name %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group meta-prealloc">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Metadata" %}</label>
<div class="col-sm-4">
<input type="checkbox" name="meta_prealloc" value="true">
</div>
</div>
{% ifequal status 5 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="addvolume">{% trans "Add volume" %}</button>
{% else %}
<button class="btn btn-lg btn-success pull-right disabled">{% trans "Add volume" %}</button>
{% endifequal %}
</form>
{% else %}
{% trans "You don't have permission for resizing instance" %}
<button class="btn btn-lg btn-success pull-right disabled">{% trans "Add volume" %}</button>
{% endif %}
<div class="clearfix"></div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -531,11 +620,15 @@
{% trans "XML" %} {% trans "XML" %}
</a> </a>
</li> </li>
{% endif %}
{% if request.user.is_superuser or request.user.userattributes.can_clone_instances %}
<li role="presentation"> <li role="presentation">
<a href="#options" aria-controls="options" role="tab" data-toggle="tab"> <a href="#options" aria-controls="options" role="tab" data-toggle="tab">
{% trans "Options" %} {% trans "Options" %}
</a> </a>
</li> </li>
{% endif %}
{% if request.user.is_superuser %}
<li role="presentation"> <li role="presentation">
<a href="#users" aria-controls="users" role="tab" data-toggle="tab"> <a href="#users" aria-controls="users" role="tab" data-toggle="tab">
{% trans "Users" %} {% trans "Users" %}
@ -546,8 +639,8 @@
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content"> <div class="tab-content">
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="media"> <div role="tabpanel" class="tab-pane tab-pane-bordered active" id="media">
<form class="form-horizontal" action="" method="post" role="form">{% csrf_token %} {% for cd in media %}
{% for cd in media %} <form class="form-horizontal" action="" method="post" role="form">{% csrf_token %}
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label">{% trans "CDROM" %} {{ forloop.counter }}</label> <label class="col-sm-2 control-label">{% trans "CDROM" %} {{ forloop.counter }}</label>
{% if not cd.image %} {% if not cd.image %}
@ -579,8 +672,8 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% endfor %} </form>
</form> {% endfor %}
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
@ -829,7 +922,7 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Live migration" %}</label> <label class="col-sm-3 control-label">{% trans "Live migration" %}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="checkbox" name="live_migrate" value="true" id="vm_live_migrate" checked> <input type="checkbox" name="live_migrate" value="true" id="vm_live_migrate" {% ifequal status 1 %}checked{% endifequal %}>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -847,7 +940,7 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Offline migration" %}</label> <label class="col-sm-3 control-label">{% trans "Offline migration" %}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="checkbox" name="offline_migrate" value="true" id="offline_migrate"> <input type="checkbox" name="offline_migrate" value="true" id="offline_migrate" {% ifequal status 5 %}checked{% endifequal %}>
</div> </div>
</div> </div>
{% if computes_count != 1 %} {% if computes_count != 1 %}
@ -877,6 +970,8 @@
</form> </form>
<div class="clearfix"></div> <div class="clearfix"></div>
</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="options"> <div role="tabpanel" class="tab-pane tab-pane-bordered" id="options">
<form class="form-horizontal" action="" method="post" role="form">{% csrf_token %} <form class="form-horizontal" action="" method="post" role="form">{% csrf_token %}
<div class="form-group"> <div class="form-group">
@ -894,7 +989,7 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Is template" %}</label> <label class="col-sm-3 control-label">{% trans "Is template" %}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="checkbox" name="is_template" value="true" id="is_template" {% if instance.is_template %}checked{% endif %}> <input type="checkbox" name="is_template" value="true" id="is_template" {% if instance.is_template %}checked{% endif %} {% if not request.user.is_superuser %}disabled{% endif %}>
</div> </div>
</div> </div>
{% ifequal status 5 %} {% ifequal status 5 %}
@ -905,11 +1000,34 @@
</form> </form>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
{% endif %}
{% if request.user.is_superuser %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="users"> <div role="tabpanel" class="tab-pane tab-pane-bordered" id="users">
<p style="font-weight:bold;">{% trans "Instance owners" %}</p> <div>
{% for userinstance in userinstances %} <p style="font-weight:bold;">
<p><a href="{% url 'account' userinstance.user.id %}">{{ userinstance.user }}</a></p> {% trans "Instance owners" %}
{% endfor %} {% include 'add_instance_owner_block.html' %}
</p>
</div>
<div class="table-responsive">
<table class="table table-striped sortable-theme-bootstrap" data-sortable>
<tbody class="searchable">
{% for userinstance in userinstances %}
<tr>
<td><a href="{% url 'account' userinstance.user.id %}">{{ userinstance.user }}</a></td>
<td style="width:30px;">
<form action="" method="post" style="height:10px" role="form">{% csrf_token %}
<input type="hidden" name="userinstance" value="{{ userinstance.pk }}">
<button type="submit" class="btn btn-sm btn-default" name="del_owner" title="{% trans "Delete" %}">
<i class="fa fa-trash"></i>
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
{% endif %} {% endif %}
@ -925,6 +1043,11 @@
{% trans "Real Time" %} {% trans "Real Time" %}
</a> </a>
</li> </li>
<li role="presentation">
<a href="#logs" aria-controls="logs" role="tab" data-toggle="tab" onclick='update_logs_table("{{ vname }}");'>
{% trans "Logs" %}
</a>
</li>
</ul> </ul>
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content"> <div class="tab-content">
@ -971,6 +1094,23 @@
{% endfor %} {% endfor %}
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="logs">
<div class="table-responsive">
<table class="table table-striped sortable-theme-bootstrap" id="logs_table" data-sortable>
<thead>
<tr>
<th>{% trans "Date" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Message" %}</th>
</tr>
</thead>
<tbody class="searchable">
<tr><td colspan="3"><i>None ...</i></td></tr>
</tbody>
</table>
</div>
<div class="clearfix"></div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -1016,7 +1156,7 @@
</div> </div>
{% endblock %} {% endblock %}
{% block script %} {% block script %}
<script src="{{ STATIC_URL }}/js/ace.js" type="text/javascript" charset="utf-8"></script> <script src="{% static "js/ace.js" %}" type="text/javascript" charset="utf-8"></script>
<script> <script>
var editor = ace.edit("editor"); var editor = ace.edit("editor");
editor.getSession().setMode("ace/mode/xml"); editor.getSession().setMode("ace/mode/xml");
@ -1153,7 +1293,7 @@
}); });
}); });
</script> </script>
<script src="{{ STATIC_URL }}js/Chart.min.js"></script> <script src="{% static "js/Chart.min.js" %}"></script>
<script> <script>
$('#chartgraphs').on('shown.bs.tab', function (event) { $('#chartgraphs').on('shown.bs.tab', function (event) {
var cpuLineData = { var cpuLineData = {
@ -1290,13 +1430,14 @@
}); });
</script> </script>
<script> <script>
backgroundJobRunning = false;
window.setInterval(function get_status() { window.setInterval(function get_status() {
var status = {{ status }}; var status = {{ status }};
$.getJSON('{% url 'inst_status' compute_id vname %}', function (data) { $.getJSON('{% url 'inst_status' compute_id vname %}', function (data) {
if (data['status'] != status) { if (data['status'] != status && !backgroundJobRunning) {
window.location.reload() window.location.reload()
} }
}) });
}, 5000); }, 5000);
</script> </script>
<script> <script>
@ -1348,4 +1489,19 @@
}); });
} }
</script> </script>
<script>
function update_logs_table(vname) {
$.getJSON('/logs/vm_logs/'+vname+'/', function(data) {
var logs = "";
$.each(data, function(id) {
row = data[id];
console.log(row);
logs += '<tr><td style="width:150px">'+row['date']+'</td>';
logs += '<td>'+row['user']+'</td>';
logs += '<td>'+row['message']+'</td></tr>';
});
$("#logs_table > tbody").html(logs);
});
}
</script>
{% endblock %} {% endblock %}

View file

@ -43,7 +43,7 @@
<th>Host<br>User</th> <th>Host<br>User</th>
<th>Status</th> <th>Status</th>
<th>VCPU</th> <th>VCPU</th>
<th>Memory</th> <th>Memory<br>({% trans "MB" %})</th>
<th data-sortable="false" style="width:205px;">Actions</th> <th data-sortable="false" style="width:205px;">Actions</th>
</tr> </tr>
</thead> </thead>
@ -64,7 +64,7 @@
{% endifequal %} {% endifequal %}
</td> </td>
<td>{{ info.vcpu }}</td> <td>{{ info.vcpu }}</td>
<td>{{ info.memory }} {% trans "MB" %}</td> <td>{{ info.memory }}</td>
<td><form action="" method="post" role="form">{% csrf_token %} <td><form action="" method="post" role="form">{% csrf_token %}
<input type="hidden" name="name" value="{{ vm }}"/> <input type="hidden" name="name" value="{{ vm }}"/>
<input type="hidden" name="compute_id" value="{{ host.0 }}"/> <input type="hidden" name="compute_id" value="{{ host.0 }}"/>

View file

@ -14,4 +14,6 @@ urlpatterns = [
views.guess_clone_name, name='guess_clone_name'), views.guess_clone_name, name='guess_clone_name'),
url(r'^check_instance/(?P<vname>[\w\-\.]+)/$', url(r'^check_instance/(?P<vname>[\w\-\.]+)/$',
views.check_instance, name='check_instance'), views.check_instance, name='check_instance'),
url(r'^sshkeys/(?P<vname>[\w\-\.]+)/$',
views.sshkeys, name='sshkeys'),
] ]

View file

@ -4,7 +4,7 @@ import json
import socket import socket
import crypt import crypt
import re import re
from string import letters, digits import string
from random import choice from random import choice
from bisect import insort from bisect import insort
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
@ -14,10 +14,12 @@ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from computes.models import Compute from computes.models import Compute
from instances.models import Instance from instances.models import Instance
from django.contrib.auth.models import User
from accounts.models import UserInstance, UserSSHKey from accounts.models import UserInstance, UserSSHKey
from vrtManager.hostdetails import wvmHostDetails from vrtManager.hostdetails import wvmHostDetails
from vrtManager.instance import wvmInstance, wvmInstances from vrtManager.instance import wvmInstance, wvmInstances
from vrtManager.connection import connection_manager from vrtManager.connection import connection_manager
from vrtManager.create import wvmCreate
from vrtManager.util import randomPasswd from vrtManager.util import randomPasswd
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE
from webvirtcloud.settings import QEMU_KEYMAPS, QEMU_CONSOLE_TYPES from webvirtcloud.settings import QEMU_KEYMAPS, QEMU_CONSOLE_TYPES
@ -50,13 +52,32 @@ def instances(request):
def get_userinstances_info(instance): def get_userinstances_info(instance):
info = {} info = {}
uis = UserInstance.objects.filter(instance=instance) uis = UserInstance.objects.filter(instance=instance)
info['count'] = len(uis) info['count'] = uis.count()
if len(uis) > 0: if info['count'] > 0:
info['first_user'] = uis[0] info['first_user'] = uis[0]
else: else:
info['first_user'] = None info['first_user'] = None
return info return info
def refresh_instance_database(comp, vm, info):
instances = Instance.objects.filter(name=vm)
if instances.count() > 1:
for i in instances:
user_instances_count = UserInstance.objects.filter(instance=i).count()
if user_instances_count == 0:
addlogmsg(request.user.username, i.name, _("Deleting due to multiple records."))
i.delete()
try:
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()
if not request.user.is_superuser: if not request.user.is_superuser:
user_instances = UserInstance.objects.filter(user_id=request.user.id) user_instances = UserInstance.objects.filter(user_id=request.user.id)
for usr_inst in user_instances: for usr_inst in user_instances:
@ -73,18 +94,12 @@ def instances(request):
if connection_manager.host_is_up(comp.type, comp.hostname): if connection_manager.host_is_up(comp.type, comp.hostname):
try: try:
conn = wvmHostDetails(comp, comp.login, comp.password, comp.type) conn = wvmHostDetails(comp, comp.login, comp.password, comp.type)
if conn.get_host_instances(): host_instances = conn.get_host_instances()
all_host_vms[comp.id, comp.name] = conn.get_host_instances() if host_instances:
for vm, info in conn.get_host_instances().items(): all_host_vms[comp.id, comp.name] = host_instances
try: for vm, info in host_instances.items():
check_uuid = Instance.objects.get(compute_id=comp.id, name=vm) refresh_instance_database(comp, vm, info)
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()
conn.close() conn.close()
except libvirtError as lib_err: except libvirtError as lib_err:
error_messages.append(lib_err) error_messages.append(lib_err)
@ -167,8 +182,9 @@ def instance(request, compute_id, vname):
error_messages = [] error_messages = []
messages = [] messages = []
compute = get_object_or_404(Compute, pk=compute_id) compute = get_object_or_404(Compute, pk=compute_id)
computes = Compute.objects.all() computes = Compute.objects.all().order_by('name')
computes_count = len(computes) computes_count = computes.count()
users = User.objects.all().order_by('username')
publickeys = UserSSHKey.objects.filter(user_id=request.user.id) publickeys = UserSSHKey.objects.filter(user_id=request.user.id)
keymaps = QEMU_KEYMAPS keymaps = QEMU_KEYMAPS
console_types = QEMU_CONSOLE_TYPES console_types = QEMU_CONSOLE_TYPES
@ -232,7 +248,7 @@ def instance(request, compute_id, vname):
def check_user_quota(instance, cpu, memory, disk_size): def check_user_quota(instance, cpu, memory, disk_size):
user_instances = UserInstance.objects.filter(user_id=request.user.id, instance__is_template=False) user_instances = UserInstance.objects.filter(user_id=request.user.id, instance__is_template=False)
instance += len(user_instances) instance += user_instances.count()
for usr_inst in user_instances: for usr_inst in user_instances:
if connection_manager.host_is_up(usr_inst.instance.compute.type, if connection_manager.host_is_up(usr_inst.instance.compute.type,
usr_inst.instance.compute.hostname): usr_inst.instance.compute.hostname):
@ -244,7 +260,8 @@ def instance(request, compute_id, vname):
cpu += int(conn.get_vcpu()) cpu += int(conn.get_vcpu())
memory += int(conn.get_memory()) memory += int(conn.get_memory())
for disk in conn.get_disk_device(): for disk in conn.get_disk_device():
disk_size += int(disk['size'])>>30 if disk['size']:
disk_size += int(disk['size'])>>30
ua = request.user.userattributes ua = request.user.userattributes
msg = "" msg = ""
@ -266,13 +283,25 @@ def instance(request, compute_id, vname):
msg += " (%s > %s)" % (disk_size, ua.max_disk_size) msg += " (%s > %s)" % (disk_size, ua.max_disk_size)
return msg return msg
def get_new_disk_dev(disks, bus):
if bus == "virtio":
dev_base = "vd"
else:
dev_base = "sd"
existing_devs = [ disk['dev'] for disk in disks ]
for l in string.lowercase:
dev = dev_base + l
if dev not in existing_devs:
return dev
raise Exception(_('None available device name'))
try: try:
conn = wvmInstance(compute.hostname, conn = wvmInstance(compute.hostname,
compute.login, compute.login,
compute.password, compute.password,
compute.type, compute.type,
vname) vname)
status = conn.get_status() status = conn.get_status()
autostart = conn.get_autostart() autostart = conn.get_autostart()
vcpu = conn.get_vcpu() vcpu = conn.get_vcpu()
@ -285,7 +314,10 @@ def instance(request, compute_id, vname):
disks = conn.get_disk_device() disks = conn.get_disk_device()
media = conn.get_media_device() media = conn.get_media_device()
networks = conn.get_net_device() networks = conn.get_net_device()
media_iso = sorted(conn.get_iso_media()) if len(media) != 0:
media_iso = sorted(conn.get_iso_media())
else:
media_iso = []
vcpu_range = conn.get_max_cpus() vcpu_range = conn.get_max_cpus()
memory_range = [256, 512, 768, 1024, 2048, 4096, 6144, 8192, 16384] memory_range = [256, 512, 768, 1024, 2048, 4096, 6144, 8192, 16384]
if memory not in memory_range: if memory not in memory_range:
@ -305,6 +337,16 @@ def instance(request, compute_id, vname):
console_passwd = conn.get_console_passwd() console_passwd = conn.get_console_passwd()
clone_free_names = get_clone_free_names() clone_free_names = get_clone_free_names()
user_quota_msg = check_user_quota(0, 0, 0, 0) user_quota_msg = check_user_quota(0, 0, 0, 0)
storages = sorted(conn.get_storages())
cache_modes = sorted(conn.get_cache_modes().items())
default_cache = settings.INSTANCE_VOLUME_DEFAULT_CACHE
default_format = settings.INSTANCE_VOLUME_DEFAULT_FORMAT
formats = conn.get_image_formats()
default_bus = settings.INSTANCE_VOLUME_DEFAULT_BUS
busses = conn.get_busses()
default_bus = settings.INSTANCE_VOLUME_DEFAULT_BUS
show_access_root_password = settings.SHOW_ACCESS_ROOT_PASSWORD
show_access_ssh_keys = settings.SHOW_ACCESS_SSH_KEYS
try: try:
instance = Instance.objects.get(compute_id=compute_id, name=vname) instance = Instance.objects.get(compute_id=compute_id, name=vname)
@ -411,7 +453,7 @@ def instance(request, compute_id, vname):
msg = _("Please shutdow down your instance and then try again") msg = _("Please shutdow down your instance and then try again")
error_messages.append(msg) error_messages.append(msg)
if 'resize' in request.POST and (request.user.is_superuser or userinstace.is_change): if 'resize' in request.POST and (request.user.is_superuser or request.user.is_staff or userinstace.is_change):
new_vcpu = request.POST.get('vcpu', '') new_vcpu = request.POST.get('vcpu', '')
new_cur_vcpu = request.POST.get('cur_vcpu', '') new_cur_vcpu = request.POST.get('cur_vcpu', '')
new_memory = request.POST.get('memory', '') new_memory = request.POST.get('memory', '')
@ -444,6 +486,27 @@ def instance(request, compute_id, vname):
addlogmsg(request.user.username, instance.name, msg) addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#resize') return HttpResponseRedirect(request.get_full_path() + '#resize')
if 'addvolume' in request.POST and (request.user.is_superuser or userinstace.is_change):
connCreate = wvmCreate(compute.hostname,
compute.login,
compute.password,
compute.type)
storage = request.POST.get('storage', '')
name = request.POST.get('name', '')
extension = request.POST.get('extension', '')
format = request.POST.get('format', '')
size = request.POST.get('size', 0)
meta_prealloc = request.POST.get('meta_prealloc', False)
bus = request.POST.get('bus', '')
cache = request.POST.get('cache', '')
target = get_new_disk_dev(disks, bus)
path = connCreate.create_volume(storage, name, size, format, meta_prealloc, extension)
conn.attach_disk(path, target, subdriver=format, cache=cache, targetbus=bus)
msg = _('Attach new disk')
addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#resize')
if 'umount_iso' in request.POST: if 'umount_iso' in request.POST:
image = request.POST.get('path', '') image = request.POST.get('path', '')
dev = request.POST.get('umount_iso', '') dev = request.POST.get('umount_iso', '')
@ -595,6 +658,68 @@ def instance(request, compute_id, vname):
addlogmsg(request.user.username, instance.name, msg) addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#network') return HttpResponseRedirect(request.get_full_path() + '#network')
if 'add_owner' in request.POST:
user_id = int(request.POST.get('user_id', ''))
if settings.ALLOW_INSTANCE_MULTIPLE_OWNER:
check_inst = UserInstance.objects.filter(instance=instance, user_id=user_id)
else:
check_inst = UserInstance.objects.filter(instance=instance)
if check_inst:
msg = _("Owner already added")
error_messages.append(msg)
else:
add_user_inst = UserInstance(instance=instance, user_id=user_id)
add_user_inst.save()
msg = _("Added owner %d" % user_id)
addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#users')
if 'del_owner' in request.POST:
userinstance_id = int(request.POST.get('userinstance', ''))
userinstance = UserInstance.objects.get(pk=userinstance_id)
userinstance.delete()
msg = _("Deleted owner %d" % userinstance_id)
addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#users')
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', '')
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'])
for post in request.POST:
clone_data[post] = request.POST.get(post, '').strip()
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)
elif not re.match(r'^([0-9A-F]{2})(\:?[0-9A-F]{2}){5}$', clone_data['clone-net-mac-0'], re.IGNORECASE):
msg = _("Instance mac '%s' invalid format!" % clone_data['clone-net-mac-0'])
error_messages.append(msg)
else:
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']]))
if 'change_options' in request.POST: if 'change_options' in request.POST:
instance.is_template = request.POST.get('is_template', False) instance.is_template = request.POST.get('is_template', False)
instance.save() instance.save()
@ -609,38 +734,6 @@ def instance(request, compute_id, vname):
addlogmsg(request.user.username, instance.name, msg) addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#options') 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', '')
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, '')
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() conn.close()
except libvirtError as lib_err: except libvirtError as lib_err:
@ -795,7 +888,7 @@ def guess_mac_address(request, vname):
if name_found and "hardware ethernet" in line: if name_found and "hardware ethernet" in line:
data['mac'] = line.split(' ')[-1].strip().strip(';') data['mac'] = line.split(' ')[-1].strip().strip(';')
break break
return HttpResponse(json.dumps(data)); return HttpResponse(json.dumps(data))
@login_required @login_required
def guess_clone_name(request): def guess_clone_name(request):
@ -811,7 +904,7 @@ def guess_clone_name(request):
hostname = fqdn.split('.')[0] hostname = fqdn.split('.')[0]
if hostname.startswith(prefix) and hostname not in instance_names: if hostname.startswith(prefix) and hostname not in instance_names:
return HttpResponse(json.dumps({'name': hostname})) return HttpResponse(json.dumps({'name': hostname}))
return HttpResponse(json.dumps({})); return HttpResponse(json.dumps({}))
@login_required @login_required
def check_instance(request, vname): def check_instance(request, vname):
@ -819,4 +912,62 @@ def check_instance(request, vname):
data = { 'vname': vname, 'exists': False } data = { 'vname': vname, 'exists': False }
if check_instance: if check_instance:
data['exists'] = True data['exists'] = True
return HttpResponse(json.dumps(data)); return HttpResponse(json.dumps(data))
def sshkeys(request, vname):
"""
:param request:
:param vm:
:return:
"""
instance_keys = []
userinstances = UserInstance.objects.filter(instance__name=vname)
for ui in userinstances:
keys = UserSSHKey.objects.filter(user=ui.user)
for k in keys:
instance_keys.append(k.keypublic)
if request.GET.get('plain', ''):
response = '\n'.join(instance_keys)
response += '\n'
else:
response = json.dumps(instance_keys)
return HttpResponse(response)
def delete_instance(instance, delete_disk=False):
compute = instance.compute
instance_name = instance.name
try:
conn = wvmInstance(compute.hostname,
compute.login,
compute.password,
compute.type,
instance.name)
del_userinstance = UserInstance.objects.filter(instance=instance)
if del_userinstance:
print("Deleting UserInstances")
print(del_userinstance)
del_userinstance.delete()
if conn.get_status() == 1:
print("Forcing shutdown")
conn.force_shutdown()
if delete_disk:
snapshots = sorted(conn.get_snapshot(), reverse=True)
for snap in snapshots:
print("Deleting snapshot {}".format(snap['name']))
conn.snapshot_delete(snap['name'])
print("Deleting disks")
conn.delete_disk()
conn.delete()
instance.delete()
print("Instance {} on compute {} sucessfully deleted".format(instance_name, compute.hostname))
except libvirtError as lib_err:
print("Error removing instance {} on compute {}".format(instance_name, compute.hostname))
raise lib_err

View file

@ -4,4 +4,5 @@ from . import views
urlpatterns = [ urlpatterns = [
url(r'^$', views.showlogs, name='showlogs'), url(r'^$', views.showlogs, name='showlogs'),
url(r'^(?P<page>[0-9]+)/$', views.showlogs, name='showlogspage'), url(r'^(?P<page>[0-9]+)/$', views.showlogs, name='showlogspage'),
url(r'^vm_logs/(?P<vname>[\w\-\.]+)/$', views.vm_logs, name='vm_logs'),
] ]

View file

@ -1,8 +1,11 @@
from django.shortcuts import render from django.shortcuts import render
from django.http import HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from instances.models import Instance
from logs.models import Logs from logs.models import Logs
from django.conf import settings from django.conf import settings
import json
def addlogmsg(user, instance, message): def addlogmsg(user, instance, message):
@ -14,15 +17,13 @@ def addlogmsg(user, instance, message):
add_log_msg.save() add_log_msg.save()
@login_required
def showlogs(request, page=1): def showlogs(request, page=1):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
@ -34,3 +35,27 @@ def showlogs(request, page=1):
# TODO: remove last element from queryset, but do not affect database # TODO: remove last element from queryset, but do not affect database
return render(request, 'showlogs.html', locals()) return render(request, 'showlogs.html', locals())
@login_required
def vm_logs(request, vname):
"""
:param request:
:param vm:
:return:
"""
if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index'))
vm = Instance.objects.get(name=vname)
logs_ = Logs.objects.filter(instance=vm.name, date__gte=vm.created).order_by('-date')
logs = []
for l in logs_:
log = {}
log['user'] = l.user
log['instance'] = l.instance
log['message'] = l.message
log['date'] = l.date.strftime('%x %X')
logs.append(log)
return HttpResponse(json.dumps(logs))

View file

@ -18,8 +18,10 @@
<script> <script>
function showPleaseWaitDialog() { function showPleaseWaitDialog() {
$('#pleaseWaitDialog').modal(); $('#pleaseWaitDialog').modal();
backgroundJobRunning = true;
} }
function hidePleaseWaitDialog() { function hidePleaseWaitDialog() {
$('#pleaseWaitDialog').modal('hide'); $('#pleaseWaitDialog').modal('hide');
backgroundJobRunning = false;
} }
</script> </script>

View file

@ -380,6 +380,25 @@ class wvmConnect(object):
interface.append(inface) interface.append(inface)
return interface return interface
def get_cache_modes(self):
"""Get cache available modes"""
return {
'default': 'Default',
'none': 'Disabled',
'writethrough': 'Write through',
'writeback': 'Write back',
'directsync': 'Direct sync', # since libvirt 0.9.5
'unsafe': 'Unsafe', # since libvirt 0.9.7
}
def get_busses(self):
"""Get available busses"""
return [ 'ide', 'scsi', 'usb', 'virtio' ]
def get_image_formats(self):
"""Get available image formats"""
return [ 'raw', 'qcow', 'qcow2' ]
def get_iface(self, name): def get_iface(self, name):
return self.wvm.interfaceLookupByName(name) return self.wvm.interfaceLookupByName(name)
@ -424,55 +443,71 @@ class wvmConnect(object):
def get_net_device(self): def get_net_device(self):
netdevice = [] netdevice = []
def get_info(ctx):
dev_type = util.get_xpath(ctx, '/device/capability/@type')
interface = util.get_xpath(ctx, '/device/capability/interface')
return (dev_type, interface)
for dev in self.wvm.listAllDevices(0): for dev in self.wvm.listAllDevices(0):
xml = dev.XMLDesc(0) xml = dev.XMLDesc(0)
dev_type = util.get_xml_path(xml, '/device/capability/@type') (dev_type, interface) = util.get_xml_path(xml, func=get_info)
if dev_type == 'net': if dev_type == 'net':
netdevice.append(util.get_xml_path(xml, '/device/capability/interface')) netdevice.append(interface)
return netdevice return netdevice
def get_host_instances(self): def get_host_instances(self):
vname = {} vname = {}
for name in self.get_instances(): def get_info(ctx):
dom = self.get_instance(name) mem = util.get_xpath(ctx, "/domain/currentMemory")
mem = util.get_xml_path(dom.XMLDesc(0), "/domain/currentMemory")
mem = int(mem) / 1024 mem = int(mem) / 1024
cur_vcpu = util.get_xml_path(dom.XMLDesc(0), "/domain/vcpu/@current") cur_vcpu = util.get_xpath(ctx, "/domain/vcpu/@current")
if cur_vcpu: if cur_vcpu:
vcpu = cur_vcpu vcpu = cur_vcpu
else: else:
vcpu = util.get_xml_path(dom.XMLDesc(0), "/domain/vcpu") vcpu = util.get_xpath(ctx, "/domain/vcpu")
title = util.get_xml_path(dom.XMLDesc(0), "/domain/title") title = util.get_xpath(ctx, "/domain/title")
description = util.get_xml_path(dom.XMLDesc(0), "/domain/description") title = title if title else ''
description = util.get_xpath(ctx, "/domain/description")
description = description if description else ''
return (mem, vcpu, title, description)
for name in self.get_instances():
dom = self.get_instance(name)
xml = dom.XMLDesc(0)
(mem, vcpu, title, description) = util.get_xml_path(xml, func=get_info)
vname[dom.name()] = { vname[dom.name()] = {
'status': dom.info()[0], 'status': dom.info()[0],
'uuid': dom.UUIDString(), 'uuid': dom.UUIDString(),
'vcpu': vcpu, 'vcpu': vcpu,
'memory': mem, 'memory': mem,
'title': title if title else '', 'title': title,
'description': description if description else '', 'description': description,
} }
return vname return vname
def get_user_instances(self, name): def get_user_instances(self, name):
dom = self.get_instance(name) dom = self.get_instance(name)
mem = util.get_xml_path(dom.XMLDesc(0), "/domain/currentMemory") xml = dom.XMLDesc(0)
mem = int(mem) / 1024 def get_info(ctx):
cur_vcpu = util.get_xml_path(dom.XMLDesc(0), "/domain/vcpu/@current") mem = util.get_xpath(ctx, "/domain/currentMemory")
if cur_vcpu: mem = int(mem) / 1024
vcpu = cur_vcpu cur_vcpu = util.get_xpath(ctx, "/domain/vcpu/@current")
else: if cur_vcpu:
vcpu = util.get_xml_path(dom.XMLDesc(0), "/domain/vcpu") vcpu = cur_vcpu
title = util.get_xml_path(dom.XMLDesc(0), "/domain/title") else:
description = util.get_xml_path(dom.XMLDesc(0), "/domain/description") vcpu = util.get_xpath(ctx, "/domain/vcpu")
title = util.get_xpath(ctx, "/domain/title")
title = title if title else ''
description = util.get_xpath(ctx, "/domain/description")
description = description if description else ''
return (mem, vcpu, title, description)
(mem, vcpu, title, description) = util.get_xml_path(xml, func=get_info)
return { return {
'name': dom.name(), 'name': dom.name(),
'status': dom.info()[0], 'status': dom.info()[0],
'uuid': dom.UUIDString(), 'uuid': dom.UUIDString(),
'vcpu': vcpu, 'vcpu': vcpu,
'memory': mem, 'memory': mem,
'title': title if title else '', 'title': title,
'description': description if description else '', 'description': description,
} }
def close(self): def close(self):

View file

@ -48,23 +48,12 @@ class wvmCreate(wvmConnect):
"""Get guest capabilities""" """Get guest capabilities"""
return util.get_xml_path(self.get_cap_xml(), "/capabilities/host/cpu/arch") return util.get_xml_path(self.get_cap_xml(), "/capabilities/host/cpu/arch")
def get_cache_modes(self): def create_volume(self, storage, name, size, format='qcow2', metadata=False, image_extension='img'):
"""Get cache available modes"""
return {
'default': 'Default',
'none': 'Disabled',
'writethrough': 'Write through',
'writeback': 'Write back',
'directsync': 'Direct sync', # since libvirt 0.9.5
'unsafe': 'Unsafe', # since libvirt 0.9.7
}
def create_volume(self, storage, name, size, format='qcow2', metadata=False):
size = int(size) * 1073741824 size = int(size) * 1073741824
stg = self.get_storage(storage) stg = self.get_storage(storage)
storage_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type") storage_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type")
if storage_type == 'dir': if storage_type == 'dir':
name += '.img' name += '.' + image_extension
alloc = 0 alloc = 0
else: else:
alloc = size alloc = size

View file

@ -340,6 +340,22 @@ class wvmInstance(wvmConnect):
xmldom = ElementTree.tostring(tree) xmldom = ElementTree.tostring(tree)
self._defineXML(xmldom) self._defineXML(xmldom)
def attach_disk(self, source, target, sourcetype='file', type='disk', driver='qemu', subdriver='raw', cache='none', targetbus='ide'):
tree = ElementTree.fromstring(self._XMLDesc(0))
xml_disk = """
<disk type='%s' device='%s'>
<driver name='%s' type='%s' cache='%s'/>
<source file='%s'/>
<target dev='%s' bus='%s'/>
</disk>
""" % (sourcetype, type, driver, subdriver, cache, source, target, targetbus)
if self.get_status() == 5:
devices = tree.find('devices')
elm_disk = ElementTree.fromstring(xml_disk)
devices.append(elm_disk)
xmldom = ElementTree.tostring(tree)
self._defineXML(xmldom)
def cpu_usage(self): def cpu_usage(self):
cpu_usage = {} cpu_usage = {}
if self.get_status() == 1: if self.get_status() == 1:

View file

@ -94,13 +94,7 @@ def get_xml_path(xml, path=None, func=None):
ctx = doc.xpathNewContext() ctx = doc.xpathNewContext()
if path: if path:
ret = ctx.xpathEval(path) result = get_xpath(ctx, path)
if ret is not None:
if type(ret) == list:
if len(ret) >= 1:
result = ret[0].content
else:
result = ret
elif func: elif func:
result = func(ctx) result = func(ctx)
@ -115,6 +109,19 @@ def get_xml_path(xml, path=None, func=None):
return result return result
def get_xpath(ctx, path):
result = None
ret = ctx.xpathEval(path)
if ret is not None:
if type(ret) == list:
if len(ret) >= 1:
result = ret[0].content
else:
result = ret
return result
def pretty_mem(val): def pretty_mem(val):
val = int(val) val = int(val)
if val > (10 * 1024 * 1024): if val > (10 * 1024 * 1024):

View file

@ -6,7 +6,7 @@ Django settings for webvirtcloud project.
import os import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) BASE_DIR = os.path.dirname(os.path.dirname(__file__))
SECRET_KEY = '4y(f4rfqc6f2!i8_vfuu)kav6tdv5#sc=n%o451dm+th0&3uci' SECRET_KEY = ''
DEBUG = True DEBUG = True
@ -44,10 +44,10 @@ MIDDLEWARE_CLASSES = (
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
) )
#AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
# 'django.contrib.auth.backends.RemoteUserBackend', #'django.contrib.auth.backends.RemoteUserBackend',
# #'accounts.backends.MyRemoteUserBackend', #'accounts.backends.MyRemoteUserBackend',
#) )
LOGIN_URL = '/accounts/login' LOGIN_URL = '/accounts/login'
@ -78,9 +78,13 @@ STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"), os.path.join(BASE_DIR, "static"),
) )
TEMPLATE_DIRS = ( TEMPLATES = [
os.path.join(BASE_DIR, 'templates'), {
) 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [ os.path.join(BASE_DIR, 'templates'), ],
'APP_DIRS': True,
}
]
## WebVirtCloud settings ## WebVirtCloud settings
@ -113,7 +117,14 @@ LIBVIRT_KEEPALIVE_INTERVAL = 5
LIBVIRT_KEEPALIVE_COUNT = 5 LIBVIRT_KEEPALIVE_COUNT = 5
ALLOW_INSTANCE_MULTIPLE_OWNER = True ALLOW_INSTANCE_MULTIPLE_OWNER = True
CLONE_INSTANCE_DEFAULT_PREFIX = 'ourea' NEW_USER_DEFAULT_INSTANCES = []
CLONE_INSTANCE_DEFAULT_PREFIX = 'instance'
LOGS_PER_PAGE = 100 LOGS_PER_PAGE = 100
QUOTA_DEBUG = True QUOTA_DEBUG = True
ALLOW_EMPTY_PASSWORD = True ALLOW_EMPTY_PASSWORD = True
SHOW_ACCESS_ROOT_PASSWORD = False
SHOW_ACCESS_SSH_KEYS = False
SHOW_PROFILE_EDIT_PASSWORD = False
INSTANCE_VOLUME_DEFAULT_FORMAT = 'qcow2'
INSTANCE_VOLUME_DEFAULT_BUS = 'virtio'
INSTANCE_VOLUME_DEFAULT_CACHE = 'directsync'