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

Merge pull request #1 from retspen/master

add
This commit is contained in:
aiminick 2018-05-14 02:02:53 +08:00 committed by GitHub
commit 4013b95d14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 734 additions and 198 deletions

5
.gitignore vendored
View file

@ -3,7 +3,8 @@ venv
.idea
.DS_*
*.pyc
db.sqlite3
console/cert.pem
db.sqlite3*
console/cert.pem*
tags
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.
### 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)
```bash
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
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/nginx/webvirtcloud.conf /etc/nginx/conf.d
cd ..
@ -63,6 +74,8 @@ sudo yum -y install python-virtualenv python-devel libvirt-devel glibc gcc nginx
```bash
sudo mkdir /srv && cd /srv
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

View file

@ -1,7 +1,13 @@
from django.contrib.auth.backends import RemoteUserBackend
from accounts.models import UserInstance, UserAttributes
from instances.models import Instance
class MyRemoteUserBackend(RemoteUserBackend):
#create_unknown_user = True
def configure_user(self, user):
user.is_superuser = True
#user.is_superuser = True
UserAttributes.configure_user(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.contrib.auth.models import User
from django.conf import settings
from instances.models import Instance
@ -24,11 +25,33 @@ class UserSSHKey(models.Model):
class UserAttributes(models.Model):
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_cpus = models.IntegerField(default=1)
max_memory = models.IntegerField(default=2048)
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):
return self.user.username

View file

@ -13,6 +13,31 @@
{% 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="col-lg-12">
{% if not user_insts %}
@ -112,4 +137,4 @@
{% endif %}
</div>
</div>
{% endblock %}
{% endblock %}

View file

@ -41,6 +41,7 @@
</div>
</div>
</form>
{% if show_profile_edit_password %}
<h3 class="page-header">{% trans "Edit Password" %}</h3>
<form class="form-horizontal" method="post" action="" role="form">{% csrf_token %}
<div class="form-group">
@ -67,6 +68,7 @@
</div>
</div>
</form>
{% endif %}
<h3 class="page-header">{% trans "SSH Keys" %}</h3>
{% if publickeys %}
<div class="col-lg-12">
@ -93,23 +95,23 @@
{% endif %}
<form class="form-horizontal" method="post" action="" role="form">{% csrf_token %}
<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">
<input type="text" class="form-control" name="keyname" placeholder="{% trans "Enter Name" %}">
</div>
</div>
<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">
<textarea name="keypublic" class="form-control" rows="6" placeholder="{% trans "Enter Public Key" %}"></textarea>
</div>
</div>
<div class="form-group">
<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>
</form>
</div>
</div>
{% endblock %}
{% endblock %}

View file

@ -7,6 +7,9 @@ register = template.Library()
@register.simple_tag
def ssh_to_fingerprint(line):
key = base64.b64decode(line.strip().split()[1].encode('ascii'))
fp_plain = hashlib.md5(key).hexdigest()
return ':'.join(a + b for a, b in zip(fp_plain[::2], fp_plain[1::2]))
try:
key = base64.b64decode(line.strip().split()[1].encode('ascii'))
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 = []
user = User.objects.get(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 'username' in request.POST:
@ -70,21 +71,11 @@ def accounts(request):
:param request:
: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:
return HttpResponseRedirect(reverse('index'))
error_messages = []
users = User.objects.all().order_by('username')
create_missing_userattributes(users)
allow_empty_password = settings.ALLOW_EMPTY_PASSWORD
if request.method == 'POST':
@ -98,6 +89,7 @@ def accounts(request):
if not error_messages:
new_user = User.objects.create_user(data['name'], None, data['password'])
new_user.save()
UserAttributes.configure_user(new_user)
return HttpResponseRedirect(request.get_full_path())
if 'edit' in request.POST:
user_id = request.POST.get('user_id', '')
@ -155,9 +147,7 @@ def account(request, user_id):
user = User.objects.get(id=user_id)
user_insts = UserInstance.objects.filter(user_id=user_id)
instances = Instance.objects.all().order_by('name')
if user.username == request.user.username:
return HttpResponseRedirect(reverse('profile'))
publickeys = UserSSHKey.objects.filter(user_id=user_id)
if request.method == 'POST':
if 'delete' in request.POST:

View file

@ -1,11 +1,12 @@
{% load staticfiles %}
{% load i18n %}
<html>
<head>
<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">
<link rel="stylesheet" href="{{ STATIC_URL }}css/bootstrap.min.css">
<link href="{{ STATIC_URL }}css/webvirtcloud.css" rel="stylesheet">
<link rel="stylesheet" href="{% static "css/bootstrap.min.css" %}">
<link href="{% static "css/webvirtcloud.css" %}" rel="stylesheet">
<style>
body {
@ -92,8 +93,8 @@
<div id='main_container' class="container">
{% block content %}{% endblock %}
</div>
<script src="{{ STATIC_URL }}js/jquery.js"></script>
<script src="{{ STATIC_URL }}js/bootstrap.min.js"></script>
<script src="{% static "js/jquery.js" %}"></script>
<script src="{% static "js/bootstrap.min.js" %}"></script>
<script>
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)
uuid = models.CharField(max_length=36)
is_template = models.BooleanField(default=False)
created = models.DateField(auto_now_add=True)
def __unicode__(self):
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" %}
{% load staticfiles %}
{% load i18n %}
{% block title %}{% trans "Instance" %} - {{ vname }}{% endblock %}
{% block content %}
{% include 'pleasewaitdialog.html' %}
<!-- Page Heading -->
<div class="row">
<table>
<tr>
<td><h3>{{ vname }}</h3></td>
<td>
{% ifequal status 5 %}
<span class="label label-danger">{% trans "Off" %}</span>
{% endifequal %}
{% ifequal status 1 %}
<span class="label label-success">{% trans "Active" %}</span>
{% endifequal %}
{% ifequal status 3 %}
<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%">
<tr>
<td>
{% if cur_vcpu %}
<h4>{{ cur_vcpu }} {% trans "Vcpu" %}</h4>
{% else %}
<h4>{{ vcpu }} {% trans "Vcpu" %}</h4>
{% endif %}
</td>
<td>
<h4>{{ cur_memory }} {% trans "MB" %} {% trans "Ram" %}</h4>
</td>
<div>
<h3>
{{ vname }}{% if title %}&nbsp;({{ title }}){% endif %}
</h3>
</div>
<div>
<div>
{% ifequal status 5 %}
<span class="label label-danger">{% trans "Off" %}</span>
{% endifequal %}
{% ifequal status 1 %}
<span class="label label-success">{% trans "Active" %}</span>
{% endifequal %}
{% ifequal status 3 %}
<span class="label label-warning">{% trans "Suspend" %}</span>
{% endifequal %}
|
{% if cur_vcpu %}
{{ cur_vcpu }} {% trans "Vcpu" %}
{% else %}
{{ vcpu }} {% trans "Vcpu" %}
{% endif %}
|
{{ cur_memory }} {% trans "MB" %} {% trans "Ram" %}
|
{% for disk in disks %}
<td>
<h4>{{ disk.size|filesizeformat }} {% trans "Disk" %}</h4>
</td>
{{ disk.size|filesizeformat }} {% trans "Disk" %} |
{% endfor %}
</tr>
</table>
<a href="{% url 'instance' compute.id vname %}" type="button" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-refresh"></span></a>
</div>
</div>
{% if user_quota_msg %}
<span class="label label-warning">{{ user_quota_msg|capfirst }} quota reached.</span>
{% endif %}
@ -90,8 +84,8 @@
</li>
<li role="presentation">
<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>
{% trans "Graphs" %}
<span id="action-block" class="glyphicon glyphicon-stats" aria-hidden="true"></span>
{% trans "Stats" %}
</a>
</li>
<li role="presentation">
@ -233,16 +227,20 @@
{% trans "Console" %}
</a>
</li>
{% if show_access_root_password %}
<li role="presentation">
<a href="#rootpasswd" aria-controls="rootpasswd" role="tab" data-toggle="tab">
{% trans "Root Password" %}
</a>
</li>
{% endif %}
{% if show_access_ssh_keys %}
<li role="presentation">
<a href="#sshkeys" aria-controls="sshkeys" role="tab" data-toggle="tab">
{% trans "SSH Keys" %}
</a>
</li>
{% endif %}
</ul>
<!-- Tab panes -->
<div class="tab-content">
@ -255,6 +253,7 @@
{% endifequal %}
<div class="clearfix"></div>
</div>
{% if show_access_root_password %}
<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>
<form class="form-inline" method="post" role="form">{% csrf_token %}
@ -271,6 +270,8 @@
</form>
<div class="clearfix"></div>
</div>
{% endif %}
{% if show_access_ssh_keys %}
<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>
<form class="form-inline" method="post" role="form">{% csrf_token %}
@ -295,6 +296,7 @@
</form>
<div class="clearfix"></div>
</div>
{% endif %}
</div>
</div>
</div>
@ -307,11 +309,16 @@
{% trans "Resize Instance" %}
</a>
</li>
<li role="presentation">
<a href="#addvolume" aria-controls="addvolume" role="tab" data-toggle="tab">
{% trans "Add New Volume" %}
</a>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<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 %}
<p style="font-weight:bold;">{% trans "Logical host CPUs:" %} {{ vcpu_host }}</p>
<div class="form-group">
@ -388,6 +395,88 @@
{% endif %}
<div class="clearfix"></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>
@ -531,11 +620,15 @@
{% trans "XML" %}
</a>
</li>
{% endif %}
{% if request.user.is_superuser or request.user.userattributes.can_clone_instances %}
<li role="presentation">
<a href="#options" aria-controls="options" role="tab" data-toggle="tab">
{% trans "Options" %}
</a>
</li>
{% endif %}
{% if request.user.is_superuser %}
<li role="presentation">
<a href="#users" aria-controls="users" role="tab" data-toggle="tab">
{% trans "Users" %}
@ -546,8 +639,8 @@
<!-- Tab panes -->
<div class="tab-content">
<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">
<label class="col-sm-2 control-label">{% trans "CDROM" %} {{ forloop.counter }}</label>
{% if not cd.image %}
@ -579,8 +672,8 @@
</div>
{% endif %}
</div>
{% endfor %}
</form>
</form>
{% endfor %}
<div class="clearfix"></div>
</div>
{% if request.user.is_superuser %}
@ -829,7 +922,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" checked>
<input type="checkbox" name="live_migrate" value="true" id="vm_live_migrate" {% ifequal status 1 %}checked{% endifequal %}>
</div>
</div>
<div class="form-group">
@ -847,7 +940,7 @@
<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">
<input type="checkbox" name="offline_migrate" value="true" id="offline_migrate" {% ifequal status 5 %}checked{% endifequal %}>
</div>
</div>
{% if computes_count != 1 %}
@ -877,6 +970,8 @@
</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="options">
<form class="form-horizontal" action="" method="post" role="form">{% csrf_token %}
<div class="form-group">
@ -894,7 +989,7 @@
<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 %}>
<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>
{% ifequal status 5 %}
@ -905,11 +1000,34 @@
</form>
<div class="clearfix"></div>
</div>
{% endif %}
{% if request.user.is_superuser %}
<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>
<p style="font-weight:bold;">
{% trans "Instance owners" %}
{% 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>
{% endif %}
@ -925,6 +1043,11 @@
{% trans "Real Time" %}
</a>
</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>
<!-- Tab panes -->
<div class="tab-content">
@ -971,6 +1094,23 @@
{% endfor %}
<div class="clearfix"></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>
@ -1016,7 +1156,7 @@
</div>
{% endblock %}
{% 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>
var editor = ace.edit("editor");
editor.getSession().setMode("ace/mode/xml");
@ -1153,7 +1293,7 @@
});
});
</script>
<script src="{{ STATIC_URL }}js/Chart.min.js"></script>
<script src="{% static "js/Chart.min.js" %}"></script>
<script>
$('#chartgraphs').on('shown.bs.tab', function (event) {
var cpuLineData = {
@ -1290,13 +1430,14 @@
});
</script>
<script>
backgroundJobRunning = false;
window.setInterval(function get_status() {
var status = {{ status }};
$.getJSON('{% url 'inst_status' compute_id vname %}', function (data) {
if (data['status'] != status) {
if (data['status'] != status && !backgroundJobRunning) {
window.location.reload()
}
})
});
}, 5000);
</script>
<script>
@ -1348,4 +1489,19 @@
});
}
</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 %}

View file

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

View file

@ -14,4 +14,6 @@ urlpatterns = [
views.guess_clone_name, name='guess_clone_name'),
url(r'^check_instance/(?P<vname>[\w\-\.]+)/$',
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 crypt
import re
from string import letters, digits
import string
from random import choice
from bisect import insort
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 computes.models import Compute
from instances.models import Instance
from django.contrib.auth.models import User
from accounts.models import UserInstance, UserSSHKey
from vrtManager.hostdetails import wvmHostDetails
from vrtManager.instance import wvmInstance, wvmInstances
from vrtManager.connection import connection_manager
from vrtManager.create import wvmCreate
from vrtManager.util import randomPasswd
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE
from webvirtcloud.settings import QEMU_KEYMAPS, QEMU_CONSOLE_TYPES
@ -50,13 +52,32 @@ def instances(request):
def get_userinstances_info(instance):
info = {}
uis = UserInstance.objects.filter(instance=instance)
info['count'] = len(uis)
if len(uis) > 0:
info['count'] = uis.count()
if info['count'] > 0:
info['first_user'] = uis[0]
else:
info['first_user'] = None
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:
user_instances = UserInstance.objects.filter(user_id=request.user.id)
for usr_inst in user_instances:
@ -73,18 +94,12 @@ def instances(request):
if connection_manager.host_is_up(comp.type, comp.hostname):
try:
conn = wvmHostDetails(comp, comp.login, comp.password, comp.type)
if conn.get_host_instances():
all_host_vms[comp.id, comp.name] = conn.get_host_instances()
for vm, info in conn.get_host_instances().items():
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()
host_instances = conn.get_host_instances()
if host_instances:
all_host_vms[comp.id, comp.name] = host_instances
for vm, info in host_instances.items():
refresh_instance_database(comp, vm, info)
conn.close()
except libvirtError as lib_err:
error_messages.append(lib_err)
@ -167,8 +182,9 @@ def instance(request, compute_id, vname):
error_messages = []
messages = []
compute = get_object_or_404(Compute, pk=compute_id)
computes = Compute.objects.all()
computes_count = len(computes)
computes = Compute.objects.all().order_by('name')
computes_count = computes.count()
users = User.objects.all().order_by('username')
publickeys = UserSSHKey.objects.filter(user_id=request.user.id)
keymaps = QEMU_KEYMAPS
console_types = QEMU_CONSOLE_TYPES
@ -232,7 +248,7 @@ def instance(request, compute_id, vname):
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)
instance += user_instances.count()
for usr_inst in user_instances:
if connection_manager.host_is_up(usr_inst.instance.compute.type,
usr_inst.instance.compute.hostname):
@ -244,7 +260,8 @@ def instance(request, compute_id, vname):
cpu += int(conn.get_vcpu())
memory += int(conn.get_memory())
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
msg = ""
@ -266,13 +283,25 @@ def instance(request, compute_id, vname):
msg += " (%s > %s)" % (disk_size, ua.max_disk_size)
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:
conn = wvmInstance(compute.hostname,
compute.login,
compute.password,
compute.type,
vname)
status = conn.get_status()
autostart = conn.get_autostart()
vcpu = conn.get_vcpu()
@ -285,7 +314,10 @@ def instance(request, compute_id, vname):
disks = conn.get_disk_device()
media = conn.get_media_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()
memory_range = [256, 512, 768, 1024, 2048, 4096, 6144, 8192, 16384]
if memory not in memory_range:
@ -305,6 +337,16 @@ def instance(request, compute_id, vname):
console_passwd = conn.get_console_passwd()
clone_free_names = get_clone_free_names()
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:
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")
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_cur_vcpu = request.POST.get('cur_vcpu', '')
new_memory = request.POST.get('memory', '')
@ -444,6 +486,27 @@ def instance(request, compute_id, vname):
addlogmsg(request.user.username, instance.name, msg)
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:
image = request.POST.get('path', '')
dev = request.POST.get('umount_iso', '')
@ -595,6 +658,68 @@ def instance(request, compute_id, vname):
addlogmsg(request.user.username, instance.name, msg)
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:
instance.is_template = request.POST.get('is_template', False)
instance.save()
@ -609,38 +734,6 @@ def instance(request, compute_id, vname):
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', '')
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()
except libvirtError as lib_err:
@ -795,7 +888,7 @@ def guess_mac_address(request, vname):
if name_found and "hardware ethernet" in line:
data['mac'] = line.split(' ')[-1].strip().strip(';')
break
return HttpResponse(json.dumps(data));
return HttpResponse(json.dumps(data))
@login_required
def guess_clone_name(request):
@ -811,7 +904,7 @@ def guess_clone_name(request):
hostname = fqdn.split('.')[0]
if hostname.startswith(prefix) and hostname not in instance_names:
return HttpResponse(json.dumps({'name': hostname}))
return HttpResponse(json.dumps({}));
return HttpResponse(json.dumps({}))
@login_required
def check_instance(request, vname):
@ -819,4 +912,62 @@ def check_instance(request, vname):
data = { 'vname': vname, 'exists': False }
if check_instance:
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 = [
url(r'^$', views.showlogs, name='showlogs'),
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.http import HttpResponseRedirect
from django.http import HttpResponse, HttpResponseRedirect
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 django.conf import settings
import json
def addlogmsg(user, instance, message):
@ -14,15 +17,13 @@ def addlogmsg(user, instance, message):
add_log_msg.save()
@login_required
def showlogs(request, page=1):
"""
:param request:
:return:
"""
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index'))
@ -34,3 +35,27 @@ def showlogs(request, page=1):
# TODO: remove last element from queryset, but do not affect database
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>
function showPleaseWaitDialog() {
$('#pleaseWaitDialog').modal();
backgroundJobRunning = true;
}
function hidePleaseWaitDialog() {
$('#pleaseWaitDialog').modal('hide');
backgroundJobRunning = false;
}
</script>

View file

@ -380,6 +380,25 @@ class wvmConnect(object):
interface.append(inface)
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):
return self.wvm.interfaceLookupByName(name)
@ -424,55 +443,71 @@ class wvmConnect(object):
def get_net_device(self):
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):
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':
netdevice.append(util.get_xml_path(xml, '/device/capability/interface'))
netdevice.append(interface)
return netdevice
def get_host_instances(self):
vname = {}
for name in self.get_instances():
dom = self.get_instance(name)
mem = util.get_xml_path(dom.XMLDesc(0), "/domain/currentMemory")
def get_info(ctx):
mem = util.get_xpath(ctx, "/domain/currentMemory")
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:
vcpu = cur_vcpu
else:
vcpu = util.get_xml_path(dom.XMLDesc(0), "/domain/vcpu")
title = util.get_xml_path(dom.XMLDesc(0), "/domain/title")
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)
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()] = {
'status': dom.info()[0],
'uuid': dom.UUIDString(),
'vcpu': vcpu,
'memory': mem,
'title': title if title else '',
'description': description if description else '',
'title': title,
'description': description,
}
return vname
def get_user_instances(self, name):
dom = self.get_instance(name)
mem = util.get_xml_path(dom.XMLDesc(0), "/domain/currentMemory")
mem = int(mem) / 1024
cur_vcpu = util.get_xml_path(dom.XMLDesc(0), "/domain/vcpu/@current")
if cur_vcpu:
vcpu = cur_vcpu
else:
vcpu = util.get_xml_path(dom.XMLDesc(0), "/domain/vcpu")
title = util.get_xml_path(dom.XMLDesc(0), "/domain/title")
description = util.get_xml_path(dom.XMLDesc(0), "/domain/description")
xml = dom.XMLDesc(0)
def get_info(ctx):
mem = util.get_xpath(ctx, "/domain/currentMemory")
mem = int(mem) / 1024
cur_vcpu = util.get_xpath(ctx, "/domain/vcpu/@current")
if cur_vcpu:
vcpu = cur_vcpu
else:
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 {
'name': dom.name(),
'status': dom.info()[0],
'uuid': dom.UUIDString(),
'vcpu': vcpu,
'memory': mem,
'title': title if title else '',
'description': description if description else '',
'title': title,
'description': description,
}
def close(self):

View file

@ -48,23 +48,12 @@ class wvmCreate(wvmConnect):
"""Get guest capabilities"""
return util.get_xml_path(self.get_cap_xml(), "/capabilities/host/cpu/arch")
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 create_volume(self, storage, name, size, format='qcow2', metadata=False):
def create_volume(self, storage, name, size, format='qcow2', metadata=False, image_extension='img'):
size = int(size) * 1073741824
stg = self.get_storage(storage)
storage_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type")
if storage_type == 'dir':
name += '.img'
name += '.' + image_extension
alloc = 0
else:
alloc = size

View file

@ -340,6 +340,22 @@ class wvmInstance(wvmConnect):
xmldom = ElementTree.tostring(tree)
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):
cpu_usage = {}
if self.get_status() == 1:

View file

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

View file

@ -6,7 +6,7 @@ Django settings for webvirtcloud project.
import os
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
@ -44,10 +44,10 @@ MIDDLEWARE_CLASSES = (
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
#AUTHENTICATION_BACKENDS = (
# 'django.contrib.auth.backends.RemoteUserBackend',
# #'accounts.backends.MyRemoteUserBackend',
#)
AUTHENTICATION_BACKENDS = (
#'django.contrib.auth.backends.RemoteUserBackend',
#'accounts.backends.MyRemoteUserBackend',
)
LOGIN_URL = '/accounts/login'
@ -78,9 +78,13 @@ STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
TEMPLATE_DIRS = (
os.path.join(BASE_DIR, 'templates'),
)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [ os.path.join(BASE_DIR, 'templates'), ],
'APP_DIRS': True,
}
]
## WebVirtCloud settings
@ -113,7 +117,14 @@ LIBVIRT_KEEPALIVE_INTERVAL = 5
LIBVIRT_KEEPALIVE_COUNT = 5
ALLOW_INSTANCE_MULTIPLE_OWNER = True
CLONE_INSTANCE_DEFAULT_PREFIX = 'ourea'
NEW_USER_DEFAULT_INSTANCES = []
CLONE_INSTANCE_DEFAULT_PREFIX = 'instance'
LOGS_PER_PAGE = 100
QUOTA_DEBUG = 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'