1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-03-23 11:04:49 +00:00

Added V2 from scratch

This commit is contained in:
retspen 2018-09-23 13:17:48 +03:00
parent 5c2232f4e8
commit 6c2925a35d
478 changed files with 21437 additions and 134206 deletions

20
.gitignore vendored
View file

@ -1,12 +1,10 @@
.vagrant
venv
.vscode
.idea
.DS_*
*.pyc
db.sqlite3*
console/cert.pem*
tags
dhcpd.*
webvirtcloud/settings.py
*migrations/*
*.pyo
.idea
.vscode
.vagrant
.DS_*
__pycache__
venv
node_modules
db.sqlite3

3
CHANGELOG.md Normal file
View file

@ -0,0 +1,3 @@
v0.0.1 (2018-09-01)
* Init project

View file

@ -1,50 +1,23 @@
FROM phusion/baseimage:0.9.17
MAINTAINER Jethro Yu <comet.jc@gmail.com>
FROM ubuntu:18.04
RUN echo 'APT::Get::Clean=always;' >> /etc/apt/apt.conf.d/99AutomaticClean
WORKDIR /usr/src/
RUN apt-get update -qqy
RUN DEBIAN_FRONTEND=noninteractive apt-get -qyy install \
-o APT::Install-Suggests=false \
git python-virtualenv python-dev python-lxml libvirt-dev zlib1g-dev nginx libsasl2-modules
COPY backend/requirements.txt /usr/src/requirements.txt
COPY backend/requirements-dev.txt /usr/src/requirements-dev.txt
ADD . /srv/webvirtcloud
RUN chown -R www-data:www-data /srv/webvirtcloud
RUN set -ex \
&& apt-get update -q \
&& apt-get -y install \
python3-pip \
libvirt-dev \
libmariadbclient-dev
# Setup webvirtcloud
RUN cd /srv/webvirtcloud && \
virtualenv venv && \
. venv/bin/activate && \
pip install -U pip && \
pip install -r conf/requirements.txt && \
chown -R www-data:www-data /srv/webvirtcloud
RUN pip3 install -r requirements-dev.txt
RUN rm -f requirements.txt requirements-dev.txt
RUN cd /srv/webvirtcloud && . venv/bin/activate && \
python manage.py migrate && \
chown -R www-data:www-data /srv/webvirtcloud
WORKDIR /app
VOLUME /app
# Setup Nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf && \
rm /etc/nginx/sites-enabled/default && \
chown -R www-data:www-data /var/lib/nginx
EXPOSE 8000 6080
ADD conf/nginx/webvirtcloud.conf /etc/nginx/conf.d/
# Register services to runit
RUN mkdir /etc/service/nginx && \
mkdir /etc/service/nginx-log-forwarder && \
mkdir /etc/service/webvirtcloud && \
mkdir /etc/service/novnc
ADD conf/runit/nginx /etc/service/nginx/run
ADD conf/runit/nginx-log-forwarder /etc/service/nginx-log-forwarder/run
ADD conf/runit/novncd.sh /etc/service/novnc/run
ADD conf/runit/webvirtcloud.sh /etc/service/webvirtcloud/run
EXPOSE 80
EXPOSE 6080
# Define mountable directories.
#VOLUME []
# Use baseimage-docker's init system.
CMD ["/sbin/my_init"]
CMD ["/bin/bash"]

177
LICENSE.md Normal file
View file

@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

273
README.md
View file

@ -1,272 +1 @@
## WebVirtCloud Beta
## Features
* User can add SSH public key to root in Instance (Tested only Ubuntu)
* User can change root password in Instance (Tested only Ubuntu)
* Supports cloud-init datasource interface
### Warning!!!
How to update <code>gstfsd</code> daemon on hypervisor:
```bash
wget -O - https://clck.ru/9VMRH | sudo tee -a /usr/local/bin/gstfsd
sudo service supervisor restart
```
### Description
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 virtualenv python-virtualenv python-dev python-lxml libvirt-dev zlib1g-dev libxslt1-dev nginx supervisor libsasl2-modules gcc pkg-config python-guestfs
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 ..
sudo mv webvirtcloud /srv
sudo chown -R www-data:www-data /srv/webvirtcloud
cd /srv/webvirtcloud
virtualenv venv
source venv/bin/activate
pip install -r conf/requirements.txt
python manage.py migrate
sudo chown -R www-data:www-data /srv/webvirtcloud
sudo rm /etc/nginx/sites-enabled/default
```
Restart services for running WebVirtCloud:
```bash
sudo service nginx restart
sudo service supervisor restart
```
Setup libvirt and KVM on server
```bash
wget -O - https://clck.ru/9V9fH | sudo sh
```
### Install WebVirtCloud panel (CentOS)
```bash
sudo yum -y install python-virtualenv python-devel libvirt-devel glibc gcc nginx supervisor python-lxml git python-libguestfs
```
#### Creating directories and cloning repo
```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
```
sudo virtualenv venv
sudo source venv/bin/activate
sudo venv/bin/pip install -r conf/requirements.txt
sudo cp conf/nginx/webvirtcloud.conf /etc/nginx/conf.d/
sudo venv/bin/python manage.py migrate
```
#### Configure the supervisor for CentOS
Add the following after the [include] line (after **files = ... ** actually):
```bash
sudo vim /etc/supervisord.conf
[program:webvirtcloud]
command=/srv/webvirtcloud/venv/bin/gunicorn webvirtcloud.wsgi:application -c /srv/webvirtcloud/gunicorn.conf.py
directory=/srv/webvirtcloud
user=nginx
autostart=true
autorestart=true
redirect_stderr=true
[program:novncd]
command=/srv/webvirtcloud/venv/bin/python /srv/webvirtcloud/console/novncd
directory=/srv/webvirtcloud
user=nginx
autostart=true
autorestart=true
redirect_stderr=true
```
#### Edit the nginx.conf file
You will need to edit the main nginx.conf file as the one that comes from the rpm's will not work. Comment the following lines:
```
# server {
# listen 80 default_server;
# listen [::]:80 default_server;
# server_name _;
# root /usr/share/nginx/html;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# location / {
# }
#
# error_page 404 /404.html;
# location = /40x.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }
}
```
Also make sure file in **/etc/nginx/conf.d/webvirtcloud.conf** has the proper paths:
```
upstream gunicorn_server {
#server unix:/srv/webvirtcloud/venv/wvcloud.socket fail_timeout=0;
server 127.0.0.1:8000 fail_timeout=0;
}
server {
listen 80;
server_name servername.domain.com;
access_log /var/log/nginx/webvirtcloud-access_log;
location /static/ {
root /srv/webvirtcloud;
expires max;
}
location / {
proxy_pass http://gunicorn_server;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-for $proxy_add_x_forwarded_for;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-Proto $remote_addr;
proxy_connect_timeout 600;
proxy_read_timeout 600;
proxy_send_timeout 600;
client_max_body_size 1024M;
}
}
```
Change permissions so nginx can read the webvirtcloud folder:
```bash
sudo chown -R nginx:nginx /srv/webvirtcloud
```
Change permission for selinux:
```bash
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/webvirtcloud(/.*)"
```
Add required user to the kvm group:
```bash
sudo usermod -G kvm -a webvirtmgr
```
Let's restart nginx and the supervisord services:
```bash
sudo systemctl restart nginx && systemctl restart supervisord
```
And finally, check everything is running:
```bash
sudo supervisorctl status
gstfsd RUNNING pid 24662, uptime 6:01:40
novncd RUNNING pid 24661, uptime 6:01:40
webvirtcloud RUNNING pid 24660, uptime 6:01:40
```
#### Apache mod_wsgi configuration
```
WSGIDaemonProcess webvirtcloud threads=2 maximum-requests=1000 display-name=webvirtcloud
WSGIScriptAlias / /srv/webvirtcloud/webvirtcloud/wsgi_custom.py
```
#### Install final required packages for libvirtd and others on Host Server
```bash
wget -O - https://clck.ru/9V9fH | sudo sh
```
Done!!
Go to http://serverip and you should see the login screen.
### Alternative running novncd via runit
Alternative to running nonvcd via supervisor is runit.
On Debian systems install runit and configure novncd service
```
apt install runit runit-systemd
mkdir /etc/service/novncd/
ln -s /srv/webvirtcloud/conf/runit/novncd.sh /etc/service/novncd/run
systemctl start runit.service
```
### Default credentials
<pre>
login: admin
password: admin
</pre>
### Configuring Compute SSH connection
This is a short example of configuring cloud and compute side of the ssh connection.
On the webvirtcloud machine you need to generate ssh keys and optionally disable StrictHostKeyChecking.
```
chown www-data -R ~www-data
sudo -u www-data ssh-keygen
cat > ~www-data/.ssh/config << EOF
Host *
StrictHostKeyChecking no
EOF
chown www-data -R ~www-data/.ssh/config
```
You need to put cloud public key into authorized keys on the compute node. Simpliest way of doing this is to use ssh tool from the webvirtcloud server.
```
sudo -u www-data ssh-copy-id root@compute1
```
### Cloud-init
Currently supports only root ssh authorized keys and hostname. Example configuration of the cloud-init client follows.
```
datasource:
OpenStack:
metadata_urls: [ "http://webvirtcloud.domain.com/datasource" ]
```
### How To Update
```bash
sudo virtualenv venv
sudo source venv/bin/activate
git pull
pip install -U -r conf/requirements.txt
python manage.py migrate
sudo service supervisor restart
```
### License
WebVirtCloud is licensed under the [Apache Licence, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).
# webvirtcloud

31
Vagrantfile vendored
View file

@ -2,17 +2,22 @@
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.hostname = "webvirtcloud"
config.vm.network "private_network", ip: "192.168.33.10"
config.vm.provision "shell", inline: <<-SHELL
sudo sh /vagrant/dev/libvirt-bootstrap.sh
sudo sed -i 's/auth_tcp = \"sasl\"/auth_tcp = \"none\"/g' /etc/libvirt/libvirtd.conf
sudo service libvirt-bin restart
sudo adduser vagrant libvirtd
sudo apt-get -y install python-virtualenv python-dev python-lxml libvirt-dev zlib1g-dev
virtualenv /vagrant/venv
source /vagrant/venv/bin/activate
pip install -r /vagrant/dev/requirements.txt
SHELL
config.vm.box = "bento/centos-7.5"
config.ssh.insert_key = false
config.vm.provision :shell, path: "devenv/vagrant/bootstrap.sh"
config.vm.network "private_network", ip: "192.168.250.254", auto_config: false
config.vm.network "private_network", ip: "10.16.0.254", auto_config: false
config.vm.network "forwarded_port", guest: 9090, host: 9090
config.vm.network "forwarded_port", guest: 16509, host: 16509
(0..1 - 1).each do |i|
config.vm.define "node#{i}" do |kvm|
kvm.vm.hostname = "node#{i}"
kvm.vm.provider :virtualbox do |vb|
vb.cpus = "2"
vb.memory = "4096"
end
end
end
end

View file

@ -1 +0,0 @@
theme: jekyll-theme-cayman

View file

@ -1,13 +0,0 @@
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
UserAttributes.configure_user(user)
return user

View file

@ -1,25 +0,0 @@
import re
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from django.conf import settings
class UserAddForm(forms.Form):
name = forms.CharField(label="Name",
error_messages={'required': _('No User name has been entered')},
max_length=20)
password = forms.CharField(required=not settings.ALLOW_EMPTY_PASSWORD, error_messages={'required': _('No password has been entered')},)
def clean_name(self):
name = self.cleaned_data['name']
have_symbol = re.match('^[a-z0-9]+$', name)
if not have_symbol:
raise forms.ValidationError(_('The flavor name must not contain any special characters'))
elif len(name) > 20:
raise forms.ValidationError(_('The flavor name must not exceed 20 characters'))
try:
User.objects.get(username=name)
except User.DoesNotExist:
return name
raise forms.ValidationError(_('Flavor name is already use'))

View file

@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('instances', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='UserInstance',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('is_change', models.BooleanField(default=False)),
('is_delete', models.BooleanField(default=False)),
('instance', models.ForeignKey(to='instances.Instance')),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
options={
},
bases=(models.Model,),
),
]

View file

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations
def add_useradmin(apps, schema_editor):
from django.utils import timezone
from django.contrib.auth.models import User
User.objects.create_superuser('admin', None, 'admin',
last_login=timezone.now()
)
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.RunPython(add_useradmin),
]

View file

@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('accounts', '0002_auto_20150325_0846'),
]
operations = [
migrations.CreateModel(
name='UserSSHKey',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('keyname', models.CharField(max_length=25)),
('keypublic', models.CharField(max_length=500)),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('accounts', '0003_usersshkey'),
]
operations = [
migrations.CreateModel(
name='UserAttributes',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('max_instances', models.IntegerField(default=0)),
('max_cpus', models.IntegerField(default=0)),
('max_memory', models.IntegerField(default=0)),
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0003_usersshkey'),
]
operations = [
migrations.AddField(
model_name='userinstance',
name='is_vnc',
field=models.BooleanField(default=False),
),
]

View file

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

View file

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0005_userattributes_can_clone_instances'),
]
operations = [
migrations.AddField(
model_name='userattributes',
name='max_disk_size',
field=models.IntegerField(default=0),
),
]

View file

@ -1,34 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0006_userattributes_max_disk_size'),
]
operations = [
migrations.AlterField(
model_name='userattributes',
name='max_cpus',
field=models.IntegerField(default=1),
),
migrations.AlterField(
model_name='userattributes',
name='max_disk_size',
field=models.IntegerField(default=20),
),
migrations.AlterField(
model_name='userattributes',
name='max_instances',
field=models.IntegerField(default=1),
),
migrations.AlterField(
model_name='userattributes',
name='max_memory',
field=models.IntegerField(default=2048),
),
]

View file

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

View file

@ -1,19 +0,0 @@
# -*- 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,35 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2018-06-25 12:36
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0009_auto_20171026_0805'),
]
operations = [
migrations.AlterField(
model_name='userattributes',
name='max_cpus',
field=models.IntegerField(default=1, help_text=b'-1 for unlimited. Any integer value'),
),
migrations.AlterField(
model_name='userattributes',
name='max_disk_size',
field=models.IntegerField(default=20, help_text=b'-1 for unlimited. Any integer value'),
),
migrations.AlterField(
model_name='userattributes',
name='max_instances',
field=models.IntegerField(default=1, help_text=b'-1 for unlimited. Any integer value'),
),
migrations.AlterField(
model_name='userattributes',
name='max_memory',
field=models.IntegerField(default=2048, help_text=b'-1 for unlimited. Any integer value'),
),
]

View file

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2018-06-25 13:13
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0010_auto_20180625_1236'),
]
operations = [
migrations.AlterField(
model_name='userattributes',
name='max_cpus',
field=models.IntegerField(default=1, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.MinValueValidator(-1)]),
),
migrations.AlterField(
model_name='userattributes',
name='max_disk_size',
field=models.IntegerField(default=20, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.MinValueValidator(-1)]),
),
migrations.AlterField(
model_name='userattributes',
name='max_instances',
field=models.IntegerField(default=1, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.MinValueValidator(-1)]),
),
migrations.AlterField(
model_name='userattributes',
name='max_memory',
field=models.IntegerField(default=2048, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.MinValueValidator(-1)]),
),
]

View file

@ -1,37 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2018-06-25 13:31
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
import re
class Migration(migrations.Migration):
dependencies = [
('accounts', '0011_auto_20180625_1313'),
]
operations = [
migrations.AlterField(
model_name='userattributes',
name='max_cpus',
field=models.IntegerField(default=1, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.RegexValidator(re.compile('^-?\\d+\\Z'), code='invalid', message='Enter a valid integer.')]),
),
migrations.AlterField(
model_name='userattributes',
name='max_disk_size',
field=models.IntegerField(default=20, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.RegexValidator(re.compile('^-?\\d+\\Z'), code='invalid', message='Enter a valid integer.')]),
),
migrations.AlterField(
model_name='userattributes',
name='max_instances',
field=models.IntegerField(default=1, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.RegexValidator(re.compile('^-?\\d+\\Z'), code='invalid', message='Enter a valid integer.')]),
),
migrations.AlterField(
model_name='userattributes',
name='max_memory',
field=models.IntegerField(default=2048, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.RegexValidator(re.compile('^-?\\d+\\Z'), code='invalid', message='Enter a valid integer.')]),
),
]

View file

@ -1,37 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2018-06-25 13:58
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
import re
class Migration(migrations.Migration):
dependencies = [
('accounts', '0012_auto_20180625_1331'),
]
operations = [
migrations.AlterField(
model_name='userattributes',
name='max_cpus',
field=models.IntegerField(default=1, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.RegexValidator(re.compile('^-?\\d+\\Z'), code='invalid', message='Enter a valid integer.'), django.core.validators.MinValueValidator(-1)]),
),
migrations.AlterField(
model_name='userattributes',
name='max_disk_size',
field=models.IntegerField(default=20, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.RegexValidator(re.compile('^-?\\d+\\Z'), code='invalid', message='Enter a valid integer.'), django.core.validators.MinValueValidator(-1)]),
),
migrations.AlterField(
model_name='userattributes',
name='max_instances',
field=models.IntegerField(default=1, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.RegexValidator(re.compile('^-?\\d+\\Z'), code='invalid', message='Enter a valid integer.'), django.core.validators.MinValueValidator(-1)]),
),
migrations.AlterField(
model_name='userattributes',
name='max_memory',
field=models.IntegerField(default=2048, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.RegexValidator(re.compile('^-?\\d+\\Z'), code='invalid', message='Enter a valid integer.'), django.core.validators.MinValueValidator(-1)]),
),
]

View file

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2018-08-08 11:36
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0013_auto_20180625_1358'),
]
operations = [
migrations.AlterField(
model_name='userattributes',
name='max_cpus',
field=models.IntegerField(default=1, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.MinValueValidator(-1)]),
),
migrations.AlterField(
model_name='userattributes',
name='max_disk_size',
field=models.IntegerField(default=20, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.MinValueValidator(-1)]),
),
migrations.AlterField(
model_name='userattributes',
name='max_instances',
field=models.IntegerField(default=1, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.MinValueValidator(-1)]),
),
migrations.AlterField(
model_name='userattributes',
name='max_memory',
field=models.IntegerField(default=2048, help_text=b'-1 for unlimited. Any integer value', validators=[django.core.validators.MinValueValidator(-1)]),
),
]

View file

@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2018-08-08 11:49
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('accounts', '0014_auto_20180808_1436'),
]
operations = [
migrations.AlterField(
model_name='usersshkey',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL),
),
]

View file

@ -1,58 +0,0 @@
from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
from instances.models import Instance
from django.core.validators import MinValueValidator
class UserInstance(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
instance = models.ForeignKey(Instance, on_delete=models.CASCADE)
is_change = models.BooleanField(default=False)
is_delete = models.BooleanField(default=False)
is_vnc = models.BooleanField(default=False)
def __unicode__(self):
return self.instance.name
class UserSSHKey(models.Model):
user = models.ForeignKey(User, on_delete=models.DO_NOTHING)
keyname = models.CharField(max_length=25)
keypublic = models.CharField(max_length=500)
def __unicode__(self):
return self.keyname
class UserAttributes(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
can_clone_instances = models.BooleanField(default=True)
max_instances = models.IntegerField(default=1, help_text="-1 for unlimited. Any integer value", validators=[MinValueValidator(-1),])
max_cpus = models.IntegerField(default=1, help_text="-1 for unlimited. Any integer value", validators=[MinValueValidator(-1)])
max_memory = models.IntegerField(default=2048, help_text="-1 for unlimited. Any integer value", validators=[MinValueValidator(-1)])
max_disk_size = models.IntegerField(default=20, help_text="-1 for unlimited. Any integer value", validators=[MinValueValidator(-1)])
@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

@ -1,140 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{% trans "User" %} - {{ user }}{% endblock %}
{% block content %}
<!-- Page Heading -->
<div class="row">
<div class="col-lg-12">
{% include 'create_user_inst_block.html' %}
<h1 class="page-header">{{ user }}</h1>
</div>
</div>
<!-- /.row -->
{% 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 %}
<div class="col-lg-12">
<div class="alert alert-warning alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<i class="fa fa-exclamation-triangle"></i> <strong>{% trans "Warning:" %}</strong> {% trans "User doesn't have any Instace" %}
</div>
</div>
{% else %}
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>{% trans "Instance" %}</th>
<th>{% trans "VNC" %}</th>
<th>{% trans "Resize" %}</th>
<th>{% trans "Delete" %}</th>
<th colspan="2">{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for inst in user_insts %}
<tr>
<td>{{ forloop.counter }}</td>
<td><a href="{% url 'instance' inst.instance.compute.id inst.instance.name %}">{{ inst.instance.name }}</a></td>
<td>{{ inst.is_vnc }}</td>
<td>{{ inst.is_change }}</td>
<td>{{ inst.is_delete }}</td>
<td style="width:5px;">
<a href="#editPriv{{ forloop.counter }}" type="button" class="btn btn-xs btn-default" data-toggle="modal">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
</a>
<!-- Modal pool -->
<div class="modal fade" id="editPriv{{ forloop.counter }}" tabindex="-1" role="dialog" aria-labelledby="editPrivLabel" 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 "Edit privilegies for" %} {{ inst.instance.name }}</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" method="post" action="" role="form">{% csrf_token %}
<input type="hidden" name="user_inst" value="{{ inst.id }}">
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "VNC" %}</label>
<div class="col-sm-6">
<select type="text" class="form-control" name="inst_vnc">
<option value="">False</option>
<option value="1" {% if inst.is_vnc %}selected{% endif %}>True</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Resize" %}</label>
<div class="col-sm-6">
<select type="text" class="form-control" name="inst_change">
<option value="">False</option>
<option value="1" {% if inst.is_change %}selected{% endif %}>True</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Delete" %}</label>
<div class="col-sm-6">
<select type="text" class="form-control" name="inst_delete">
<option value="">False</option>
<option value="1" {% if inst.is_delete %}selected{% endif %}>True</option>
</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="permission">{% trans "Edit" %}</button>
</div>
</form>
</div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog -->
</div> <!-- /.modal -->
</td>
<td style="width:5px;">
<form action="" method="post" role="form">{% csrf_token %}
<input type="hidden" name="user_inst" value="{{ inst.id }}">
<button type="submit" class="btn btn-xs btn-default" name="delete" tittle="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View file

@ -1,175 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% load staticfiles %}
{% block title %}{% trans "Users" %}{% endblock %}
{% block content %}
<!-- Page Heading -->
<div class="row">
<div class="col-lg-12">
{% include 'create_user_block.html' %}
<div class="pull-right search">
<input id="filter" class="form-control" type="text" placeholder="Search">
</div>
<h1 class="page-header">{% trans "Users" %}</h1>
</div>
</div>
<!-- /.row -->
{% include 'errors_block.html' %}
<div class="row">
{% if not users %}
<div class="col-lg-12">
<div class="alert alert-warning alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<i class="fa fa-exclamation-triangle"></i> <strong>{% trans "Warning:" %}</strong> {% trans "You don't have any User" %}
</div>
</div>
{% else %}
<div class="col-lg-12">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Username</th>
<th>Status</th>
<th>Staff</th>
<th>Superuser</th>
<th>Clone</th>
</tr>
</thead>
<tbody class="searchable">
{% for user in users %}
<tr class="{% if not user.is_active %}danger{% endif %}">
<td>
<a href="{% url 'account' user.id %}"><strong>{{ user.username }}</strong></a>
<a data-toggle="modal" href="#editUser{{ user.id }}" class="pull-right" title="{% trans "Edit" %}">
<span class="glyphicon glyphicon-cog"></span>
</a>
</td>
<td>
{% if user.is_active %}
{% trans "Active" %}
{% else %}
{% trans "Blocked" %}
{% endif %}
</td>
<td>{% if user.is_staff %}<span class="glyphicon glyphicon-ok"></span>{% endif %}</td>
<td>{% if user.is_superuser %}<span class="glyphicon glyphicon-ok"></span>{% endif %}</td>
<td>{% if user.userattributes.can_clone_instances %}<span class="glyphicon glyphicon-ok"></span>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% for user in users %}
<!-- Modal Edit -->
<div class="modal fade" id="editUser{{ user.id }}" tabindex="-1" role="dialog" aria-labelledby="editUserLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form class="form-horizontal" method="post" role="form">{% csrf_token %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{% trans "Edit user info" %}</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Name" %}</label>
<div class="col-sm-6">
<input type="hidden" name="user_id" value="{{ user.id }}">
<input type="text" name="name" class="form-control" value="{{ user.username }}" disabled>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Password" %}</label>
<div class="col-sm-6">
<input type="password" name="user_pass" class="form-control" value="">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Is staff" %}</label>
<div class="col-sm-2">
<input type="checkbox" name="user_is_staff" {% if user.is_staff %}checked{% endif %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Is superuser" %}</label>
<div class="col-sm-2">
<input type="checkbox" name="user_is_superuser" {% if user.is_superuser %}checked{% endif %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Can clone instances" %}</label>
<div class="col-sm-2">
<input type="checkbox" name="userattributes_can_clone_instances" {% if user.userattributes.can_clone_instances %}checked{% endif %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max instances" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_instances" class="form-control" value="{{ user.userattributes.max_instances }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max cpus" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_cpus" class="form-control" value="{{ user.userattributes.max_cpus }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max memory (MB)" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_memory" class="form-control" value="{{ user.userattributes.max_memory }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max disk size (GB)" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_disk_size" class="form-control" value="{{ user.userattributes.max_disk_size }}">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="pull-left btn btn-danger" name="delete">
{% trans "Delete" %}
</button>
{% if user.is_active %}
<button type="submit" class="pull-left btn btn-warning" name="block">
{% trans "Block" %}
</button>
{% else %}
<button type="submit" class="pull-left btn btn-success" name="unblock">
{% trans "Unblock" %}
</button>
{% endif %}
<button type="button" class="btn btn-default" data-dismiss="modal">
{% trans "Close" %}
</button>
<button type="submit" class="btn btn-primary" name="edit">
{% trans "Edit" %}
</button>
</div>
</form>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
{% endfor %}
</div>
{% endif %}
</div>
{% endblock %}
{% block script %}
<script>
function filter_table() {
var rex = new RegExp($(this).val(), 'i');
$('.searchable tr').hide();
$('.searchable tr').filter(function () {
return rex.test($(this).text());
}).show();
}
$(document).ready(function () {
(function ($) {
$('#filter').keyup(filter_table)
}(jQuery));
});
</script>
{% endblock %}

View file

@ -1,144 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{% trans "Users" %}{% endblock %}
{% block content %}
<!-- Page Heading -->
<div class="row">
<div class="col-lg-12">
{% include 'create_user_block.html' %}
<h1 class="page-header">{% trans "Users" %}</h1>
</div>
</div>
<!-- /.row -->
{% include 'errors_block.html' %}
<div class="row">
{% if not users %}
<div class="col-lg-12">
<div class="alert alert-warning alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<i class="fa fa-exclamation-triangle"></i> <strong>{% trans "Warning:" %}</strong> {% trans "You don't have any User" %}
</div>
</div>
{% else %}
{% for user in users %}
<div id="{{ user.username }}" class="col-xs-12 col-sm-4">
<div class="panel {% if user.is_active %}panel-success{% else %}panel-danger{% endif %} panel-data">
<div class="panel-heading">
<h3 class="panel-title">
<a href="{% url 'account' user.id %}"><strong>{{ user.username }}</strong></a>
<a data-toggle="modal" href="#editUser{{ user.id }}" class="pull-right" title="{% trans "Edit" %}">
<span class="glyphicon glyphicon-cog"></span>
</a>
</h3>
</div>
<div class="panel-body">
<div class="col-xs-4 col-sm-4">
<p><strong>{% trans "Status:" %}</strong></p>
</div>
<div class="col-xs-4 col-sm-6">
{% if user.is_active %}
<p>{% trans "Active" %}</p>
{% else %}
<p>{% trans "Blocked" %}</p>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Modal Edit -->
<div class="modal fade" id="editUser{{ user.id }}" tabindex="-1" role="dialog" aria-labelledby="editUserLabel" 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 "Edit user info" %}</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" method="post" role="form">{% csrf_token %}
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Name" %}</label>
<div class="col-sm-6">
<input type="hidden" name="user_id" value="{{ user.id }}">
<input type="text" name="name" class="form-control" value="{{ user.username }}" disabled>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Password" %}</label>
<div class="col-sm-6">
<input type="password" name="user_pass" class="form-control" value="">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Is staff" %}</label>
<div class="col-sm-2">
<input type="checkbox" name="user_is_staff" {% if user.is_staff %}checked{% endif %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Is superuser" %}</label>
<div class="col-sm-2">
<input type="checkbox" name="user_is_superuser" {% if user.is_superuser %}checked{% endif %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Can clone instances" %}</label>
<div class="col-sm-2">
<input type="checkbox" name="userattributes_can_clone_instances" {% if user.userattributes.can_clone_instances %}checked{% endif %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max instances" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_instances" class="form-control" value="{{ user.userattributes.max_instances}}" required="True" >
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max cpus" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_cpus" class="form-control" value="{{ user.userattributes.max_cpus }}" required="True">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max memory (MB)" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_memory" class="form-control" value="{{ user.userattributes.max_memory}}" required="True">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max disk size (GB)" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_disk_size" class="form-control" value="{{ user.userattributes.max_disk_size }}" required="True">
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="pull-left btn btn-danger" name="delete">
{% trans "Delete" %}
</button>
{% if user.is_active %}
<button type="submit" class="pull-left btn btn-warning" name="block">
{% trans "Block" %}
</button>
{% else %}
<button type="submit" class="pull-left btn btn-success" name="unblock">
{% trans "Unblock" %}
</button>
{% endif %}
<button type="button" class="btn btn-default" data-dismiss="modal">
{% trans "Close" %}
</button>
<button type="submit" class="btn btn-primary" name="edit">
{% trans "Edit" %}
</button>
</form>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
{% endfor %}
{% endif %}
</div>
{% endblock %}

View file

@ -1,41 +0,0 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="WebVirtMgr panel for manage virtual machine">
<meta name="author" content="anatoliy.guskov@gmail.com">
<title>{% block title %}{% endblock %}</title>
<!-- Bootstrap Core CSS -->
<link href="{% static "css/bootstrap.min.css" %}" rel="stylesheet">
<!-- SB admin CSS -->
<link href="{% static "css/signin.css" %}" rel="stylesheet">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
{% block content %}{% endblock %}
</div>
<!-- jQuery -->
<script src="{% static "js/jquery.js" %}"></script>
<!-- Bootstrap Core JavaScript -->
<script src="{% static "js/bootstrap.min.js" %}"></script>
</body>
</html>

View file

@ -1,38 +0,0 @@
{% load i18n %}
{% if request.user.is_superuser %}
<a href="#AddUser" type="button" class="btn btn-success btn-header pull-right" data-toggle="modal">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
</a>
<!-- Modal pool -->
<div class="modal fade" id="AddUser" tabindex="-1" role="dialog" aria-labelledby="AddUserLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form class="form-horizontal" method="post" action="" role="form">{% csrf_token %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{% trans "Add New User" %}</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Name" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="name" placeholder="john" required pattern="[a-z0-9]+">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Password" %}</label>
<div class="col-sm-6">
<input type="password" class="form-control" name="password" placeholder="*******" {% if not allow_empty_password %}required{% endif %}>
</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="create">{% trans "Create" %}</button>
</div>
</form>
</div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog -->
</div> <!-- /.modal -->
{% endif %}

View file

@ -1,36 +0,0 @@
{% load i18n %}
{% if request.user.is_superuser %}
<a href="#addUserInst" 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="addUserInst" tabindex="-1" role="dialog" aria-labelledby="addUserInstLabel" 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 for User" %}</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 "Host / Instance" %}</label>
<div class="col-sm-6">
<select class="form-control" name="inst_id">
{% for inst in instances %}
<option value="{{ inst.id }}">{{ inst.compute.name }} / {{ inst.name }}</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">{% trans "Add" %}</button>
</div>
</form>
</div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog -->
</div> <!-- /.modal -->
{% endif %}

View file

@ -1,25 +0,0 @@
{% extends "base_auth.html" %}
{% load i18n %}
{% block title %}{% trans "WebVirtCloud - Sign In" %}{% endblock %}
{% block content %}
<div class="row">
<div class="page-header">
<a href="/"><h1>WebVirtCloud</h1></a>
</div>
<div class="col-xs-12" role="main">
{% if form.errors %}
<div class="alert alert-danger">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
{% trans "Incorrect username or password." %}
</div>
{% endif %}
<form class="form-signin" method="post" role="form">{% csrf_token %}
<h2 class="form-signin-heading">{% trans "Sign In" %}</h2>
<input type="text" class="form-control" name="username" placeholder="Login" autocapitalize="none" autocorrect="off" autofocus>
<input type="password" class="form-control" name="password" placeholder="Password">
<input name="next" id="next" type="hidden" value="{% url 'instances' %}">
<button class="btn btn-lg btn-success btn-block" type="submit">{% trans "Sign In" %}</button>
</form>
</div>
</div>
{% endblock %}

View file

@ -1,15 +0,0 @@
{% extends "base_auth.html" %}
{% load i18n %}
{% block title %}{% trans "WebVirtCloud - Sign Out" %}{% endblock %}
{% block content %}
<div class="row">
<div class="page-header">
<a href="/"><h1>WebVirtCloud</h1></a>
</div>
<div class="col-xs-12" role="main">
<div class="logout">
<h1>{% trans "Successful log out" %}</h1>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,117 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% load tags_fingerprint %}
{% block title %}{% trans "Profile" %}{% endblock %}
{% block content %}
<!-- Page Heading -->
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">{% trans "Profile" %}</h1>
</div>
</div>
<!-- /.row -->
{% include 'errors_block.html' %}
<div class="row">
<div class="col-lg-12">
<h3 class="page-header">{% trans "Edit Profile" %}</h3>
<form class="form-horizontal" method="post" action="" role="form">{% csrf_token %}
<div class="form-group">
<label class="col-sm-2 control-label">{% trans "Login" %}</label>
<div class="col-sm-4">
<input type="text" class="form-control" value="{{ request.user.username }}" disabled>
</div>
</div>
<div class="form-group bridge_name_form_group_dhcp">
<label class="col-sm-2 control-label">{% trans "Username" %}</label>
<div class="col-sm-4">
<input type="text" class="form-control" name="username" value="{{ request.user.first_name }}" pattern="[0-9a-zA-Z]+">
</div>
</div>
<div class="form-group bridge_name_form_group_dhcp">
<label class="col-sm-2 control-label">{% trans "Email" %}</label>
<div class="col-sm-4">
<input type="email" class="form-control" name="email" value="{{ request.user.email }}">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">{% trans "Change" %}</button>
</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">
<label class="col-sm-2 control-label">{% trans "Old" %}</label>
<div class="col-sm-4">
<input type="password" class="form-control" name="oldpasswd" value="">
</div>
</div>
<div class="form-group bridge_name_form_group_dhcp">
<label class="col-sm-2 control-label">{% trans "New" %}</label>
<div class="col-sm-4">
<input type="password" class="form-control" name="passwd1" value="">
</div>
</div>
<div class="form-group bridge_name_form_group_dhcp">
<label class="col-sm-2 control-label">{% trans "Retry" %}</label>
<div class="col-sm-4">
<input type="password" class="form-control" name="passwd2" value="">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">{% trans "Change" %}</button>
</div>
</div>
</form>
{% endif %}
<h3 class="page-header">{% trans "SSH Keys" %}</h3>
{% if publickeys %}
<div class="col-lg-12">
<div class="table-responsive">
<table class="table table-hover">
<tbody style="text-align: center;">
{% for key in publickeys %}
<tr>
<td>{{ key.keyname }} ({% ssh_to_fingerprint key.keypublic %})</td>
<td>
<form action="" method="post" role="form">{% csrf_token %}
<input type="hidden" name="keyid" value="{{ key.id }}"/>
<button type="submit" class="btn btn-sm btn-default" name="keydelete" title="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
<span class="glyphicon glyphicon-trash"></span>
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% 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 "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 "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 "Add" %}</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View file

@ -1,15 +0,0 @@
from django import template
import base64
import hashlib
register = template.Library()
@register.simple_tag
def ssh_to_fingerprint(line):
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

@ -1,10 +0,0 @@
from django.conf.urls import url
from django.contrib.auth import views as auth_views
from . import views
urlpatterns = [
url(r'^login/$', auth_views.LoginView.as_view(template_name='login.html'), name='login'),
url(r'^logout/$', auth_views.LogoutView.as_view(template_name='logout.html'), name='logout'),
url(r'^profile/$', views.profile, name='profile'), url(r'^$', views.accounts, name='accounts'),
url(r'^profile/(?P<user_id>[0-9]+)/$', views.account, name='account'),
]

View file

@ -1,202 +0,0 @@
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from accounts.models import *
from instances.models import Instance
from accounts.forms import UserAddForm
from django.conf import settings
from django.core.validators import ValidationError
@login_required
def profile(request):
"""
:param request:
:return:
"""
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:
username = request.POST.get('username', '')
email = request.POST.get('email', '')
user.first_name = username
user.email = email
user.save()
return HttpResponseRedirect(request.get_full_path())
if 'oldpasswd' in request.POST:
oldpasswd = request.POST.get('oldpasswd', '')
password1 = request.POST.get('passwd1', '')
password2 = request.POST.get('passwd2', '')
if not password1 or not password2:
error_messages.append("Passwords didn't enter")
if password1 and password2 and password1 != password2:
error_messages.append("Passwords don't match")
if not user.check_password(oldpasswd):
error_messages.append("Old password is wrong!")
if not error_messages:
user.set_password(password1)
user.save()
return HttpResponseRedirect(request.get_full_path())
if 'keyname' in request.POST:
keyname = request.POST.get('keyname', '')
keypublic = request.POST.get('keypublic', '')
for key in publickeys:
if keyname == key.keyname:
msg = _("Key name already exist")
error_messages.append(msg)
if keypublic == key.keypublic:
msg = _("Public key already exist")
error_messages.append(msg)
if '\n' in keypublic or '\r' in keypublic:
msg = _("Invalid characters in public key")
error_messages.append(msg)
if not error_messages:
addkeypublic = UserSSHKey(user_id=request.user.id, keyname=keyname, keypublic=keypublic)
addkeypublic.save()
return HttpResponseRedirect(request.get_full_path())
if 'keydelete' in request.POST:
keyid = request.POST.get('keyid', '')
delkeypublic = UserSSHKey.objects.get(id=keyid)
delkeypublic.delete()
return HttpResponseRedirect(request.get_full_path())
return render(request, 'profile.html', locals())
@login_required
def accounts(request):
"""
:param request:
:return:
"""
if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index'))
error_messages = []
users = User.objects.all().order_by('username')
allow_empty_password = settings.ALLOW_EMPTY_PASSWORD
if request.method == 'POST':
if 'create' in request.POST:
form = UserAddForm(request.POST)
if form.is_valid():
data = form.cleaned_data
else:
for msg_err in form.errors.values():
error_messages.append(msg_err.as_text())
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:
CHECKBOX_MAPPING = {'on': True, 'off': False, }
user_id = request.POST.get('user_id', '')
user_pass = request.POST.get('user_pass', '')
user_edit = User.objects.get(id=user_id)
if user_pass != '': user_edit.set_password(user_pass)
user_edit.is_staff = CHECKBOX_MAPPING.get(request.POST.get('user_is_staff', 'off'))
user_edit.is_superuser = CHECKBOX_MAPPING.get(request.POST.get('user_is_superuser', 'off'))
user_edit.save()
UserAttributes.create_missing_userattributes(user_edit)
user_edit.userattributes.can_clone_instances = CHECKBOX_MAPPING.get(request.POST.get('userattributes_can_clone_instances', 'off'))
user_edit.userattributes.max_instances = request.POST.get('userattributes_max_instances', 0)
user_edit.userattributes.max_cpus = request.POST.get('userattributes_max_cpus', 0)
user_edit.userattributes.max_memory = request.POST.get('userattributes_max_memory', 0)
user_edit.userattributes.max_disk_size = request.POST.get('userattributes_max_disk_size', 0)
try:
user_edit.userattributes.clean_fields()
except ValidationError as exc:
error_messages.append(exc)
else:
user_edit.userattributes.save()
return HttpResponseRedirect(request.get_full_path())
if 'block' in request.POST:
user_id = request.POST.get('user_id', '')
user_block = User.objects.get(id=user_id)
user_block.is_active = False
user_block.save()
return HttpResponseRedirect(request.get_full_path())
if 'unblock' in request.POST:
user_id = request.POST.get('user_id', '')
user_unblock = User.objects.get(id=user_id)
user_unblock.is_active = True
user_unblock.save()
return HttpResponseRedirect(request.get_full_path())
if 'delete' in request.POST:
user_id = request.POST.get('user_id', '')
try:
del_user_inst = UserInstance.objects.filter(user_id=user_id)
del_user_inst.delete()
finally:
user_delete = User.objects.get(id=user_id)
user_delete.delete()
return HttpResponseRedirect(request.get_full_path())
accounts_template_file = 'accounts.html'
if settings.VIEW_ACCOUNTS_STYLE == "list":
accounts_template_file = 'accounts-list.html'
return render(request, accounts_template_file, locals())
@login_required
def account(request, user_id):
"""
:param request:
:return:
"""
if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index'))
error_messages = []
user = User.objects.get(id=user_id)
user_insts = UserInstance.objects.filter(user_id=user_id)
instances = Instance.objects.all().order_by('name')
publickeys = UserSSHKey.objects.filter(user_id=user_id)
if request.method == 'POST':
if 'delete' in request.POST:
user_inst = request.POST.get('user_inst', '')
del_user_inst = UserInstance.objects.get(id=user_inst)
del_user_inst.delete()
return HttpResponseRedirect(request.get_full_path())
if 'permission' in request.POST:
user_inst = request.POST.get('user_inst', '')
inst_vnc = request.POST.get('inst_vnc', '')
inst_change = request.POST.get('inst_change', '')
inst_delete = request.POST.get('inst_delete', '')
edit_user_inst = UserInstance.objects.get(id=user_inst)
edit_user_inst.is_change = bool(inst_change)
edit_user_inst.is_delete = bool(inst_delete)
edit_user_inst.is_vnc = bool(inst_vnc)
edit_user_inst.save()
return HttpResponseRedirect(request.get_full_path())
if 'add' in request.POST:
inst_id = request.POST.get('inst_id', '')
if settings.ALLOW_INSTANCE_MULTIPLE_OWNER:
check_inst = UserInstance.objects.filter(instance_id=int(inst_id), user_id=int(user_id))
else:
check_inst = UserInstance.objects.filter(instance_id=int(inst_id))
if check_inst:
msg = _("Instance already added")
error_messages.append(msg)
else:
add_user_inst = UserInstance(instance_id=int(inst_id), user_id=int(user_id))
add_user_inst.save()
return HttpResponseRedirect(request.get_full_path())
return render(request, 'account.html', locals())

View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class CloudinitConfig(AppConfig):
name = 'cloudinit'

View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

5
backend/compute/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class ComputeConfig(AppConfig):
name = 'compute'

View file

@ -0,0 +1,39 @@
# Generated by Django 2.0.8 on 2018-09-22 07:20
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('region', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Compute',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, validators=[django.core.validators.RegexValidator('^[a-zA-Z0-9.-_]+$')])),
('description', models.CharField(blank=True, max_length=255)),
('address', models.CharField(max_length=50, validators=[django.core.validators.RegexValidator('^[a-zA-Z0-9._-]+$')], verbose_name='FQDN or IP Address')),
('login', models.CharField(blank=True, max_length=20)),
('password', models.CharField(blank=True, max_length=20)),
('conn', models.IntegerField(choices=[(1, 'TCP'), (3, 'TLS'), (2, 'SSH'), (4, 'SOCKET')], default=1)),
('is_active', models.BooleanField(default=False, verbose_name='Active')),
('created_at', models.DateTimeField(auto_now_add=True)),
('is_deleted', models.BooleanField(default=False, verbose_name='Deleted')),
('deleted_at', models.DateTimeField(blank=True, null=True)),
('region', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='region.Region')),
],
options={
'verbose_name': 'Compute',
'verbose_name_plural': 'Computes',
'ordering': ['-id'],
},
),
]

81
backend/compute/models.py Normal file
View file

@ -0,0 +1,81 @@
from django.db import models
from django.core.validators import RegexValidator
from region.models import Region
class Compute(models.Model):
TCP = 1
TLS = 3
SSH = 2
SOCKET = 4
CONN_CHOICES = (
(TCP, 'TCP'),
(TLS, 'TLS'),
(SSH, 'SSH'),
(SOCKET, 'SOCKET'),
)
name = models.CharField(
max_length=200,
validators=[RegexValidator('^[a-zA-Z0-9.-_]+$')],
)
region = models.ForeignKey(
Region,
on_delete=models.CASCADE,
)
description = models.CharField(
max_length=255,
blank=True,
)
address = models.CharField(
'FQDN or IP Address',
max_length=50,
validators=[RegexValidator('^[a-zA-Z0-9._-]+$')],
)
login = models.CharField(
max_length=20,
blank=True,
)
password = models.CharField(
max_length=20,
blank=True,
)
conn = models.IntegerField(
choices=CONN_CHOICES,
default=TCP,
)
is_active = models.BooleanField(
'Active',
default=False,
)
created_at = models.DateTimeField(
auto_now_add=True,
)
is_deleted = models.BooleanField(
'Deleted',
default=False,
)
deleted_at = models.DateTimeField(
blank=True,
null=True,
)
class Meta:
ordering = ['-id']
verbose_name = "Compute"
verbose_name_plural = "Computes"
def __unicode__(self):
return self.name

3
backend/compute/views.py Normal file
View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

5
backend/flavor/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class FlavorConfig(AppConfig):
name = 'flavor'

View file

@ -0,0 +1,61 @@
# Generated by Django 2.0.8 on 2018-09-22 07:20
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import re
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Image',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(max_length=200, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9_]+\\Z'), "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", 'invalid')])),
('name', models.CharField(max_length=255)),
('url', models.URLField()),
('md5sum', models.CharField(max_length=50)),
('is_active', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('is_deleted', models.BooleanField(default=False)),
('deleted_at', models.DateTimeField(blank=True, null=True)),
],
options={
'verbose_name': 'Instance Image',
'verbose_name_plural': 'Instance Images',
'ordering': ['-id'],
},
),
migrations.CreateModel(
name='Size',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.CharField(max_length=200, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9_]+\\Z'), "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", 'invalid')])),
('name', models.CharField(blank=True, max_length=255)),
('cpu', models.IntegerField(default=1)),
('disk_bytes', models.BigIntegerField(default=21474836480)),
('memory_bytes', models.BigIntegerField(default=536870912)),
('is_active', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('is_deleted', models.BooleanField(default=False)),
('deleted_at', models.DateTimeField(blank=True, null=True)),
],
options={
'verbose_name': 'Instance Size',
'verbose_name_plural': 'Instance Sizes',
'ordering': ['-id'],
},
),
migrations.AddField(
model_name='image',
name='required_size',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='flavor.Size'),
),
]

111
backend/flavor/models.py Normal file
View file

@ -0,0 +1,111 @@
from django.db import models
from django.core.validators import validate_slug
class Size(models.Model):
slug = models.CharField(
max_length=200,
validators=[validate_slug],
)
name = models.CharField(
max_length=255,
blank=True,
)
cpu = models.IntegerField(
default=1,
)
disk_bytes = models.BigIntegerField(
default=21474836480,
)
memory_bytes = models.BigIntegerField(
default=536870912,
)
is_active = models.BooleanField(
default=False,
)
created_at = models.DateTimeField(
auto_now_add=True,
)
is_deleted = models.BooleanField(
default=False,
)
deleted_at = models.DateTimeField(
blank=True,
null=True,
)
class Meta:
ordering = ['-id']
verbose_name = "Instance Size"
verbose_name_plural = "Instance Sizes"
def __unicode__(self):
return self.name
def save(self):
if self.ram < 1 * (1024 ** 2):
self.ram = self.ram * (1024 ** 2)
if self.disk < 1 * (1024 ** 3):
self.disk = self.disk * (1024 ** 3)
super(Size, self).save()
class Image(models.Model):
slug = models.SlugField(
max_length=200,
validators=[validate_slug],
)
name = models.CharField(
max_length=255,
)
url = models.URLField(
max_length=200,
)
md5sum = models.CharField(
max_length=50,
)
is_active = models.BooleanField(
default=False,
)
created_at = models.DateTimeField(
auto_now_add=True,
)
is_deleted = models.BooleanField(
default=False,
)
deleted_at = models.DateTimeField(
blank=True,
null=True,
)
required_size = models.ForeignKey(
Size,
blank=True,
null=True,
on_delete=models.CASCADE,
)
class Meta:
ordering = ['-id']
verbose_name = "Instance Image"
verbose_name_plural = "Instance Images"
def __unicode__(self):
return self.name

3
backend/flavor/views.py Normal file
View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

5
backend/instance/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class InstanceConfig(AppConfig):
name = 'instance'

107
backend/instance/models.py Normal file
View file

@ -0,0 +1,107 @@
from django.db import models
from django.conf import settings
from region.models import Region
from compute.models import Compute
from flavor.models import Size, Image
class Instance(models.Model):
ACTIVE = 1
INACTIVE = 0
STATUS_CHOICES = (
(ACTIVE, 'active'),
(INACTIVE, 'inactive'),
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
)
name = models.CharField(
max_length=255,
)
status = models.IntegerField(
choices=STATUS_CHOICES,
default=INACTIVE,
)
uuid = models.CharField(
max_length=40,
blank=True,
null=True,
)
compute = models.ForeignKey(
Compute,
blank=True,
null=True,
)
size = models.ForeignKey(
Size,
)
image = models.ForeignKey(
Image,
on_delete=models.CASCADE,
)
region = models.ForeignKey(
Region,
on_delete=models.CASCADE,
)
disk_bytes = models.BigIntegerField(
default=21474836480,
)
created_at = models.DateTimeField(
auto_now_add=True,
)
deleted_at = models.DateTimeField(
blank=True,
null=True,
)
is_task = models.BooleanField(
default=True,
)
task_id = models.IntegerField(
default=1,
)
is_private_network = models.BooleanField(
default=False,
)
is_locked = models.BooleanField(
default=False,
)
is_deleted = models.BooleanField(
default=False,
)
public_net_mac = models.CharField(
max_length=18,
default="52:54:00:01:02:03",
)
private_net_mac = models.CharField(
max_length=18,
blank=True,
null=True,
)
class Meta:
ordering = ['-id']
verbose_name = "Instance"
verbose_name_plural = "Instances"
def __unicode__(self):
return str(self.name)

View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View file

@ -0,0 +1,2 @@
__author__ = "Anatoliy Guskov"
__license__ = "Apache 2.0"

View file

@ -1,9 +1,8 @@
import libvirt
import threading
import socket
from vrtManager import util
from rwlock import ReadWriteLock
from django.conf import settings
from libvmgr import util
from libvmgr.rwlock import ReadWriteLock
from libvirt import libvirtError
@ -14,9 +13,36 @@ CONN_TCP = 1
TLS_PORT = 16514
SSH_PORT = 22
TCP_PORT = 16509
KEEPALIVE_COUNT = 30
KEEPALIVE_INTERVAL = 5
class wvmEventLoop(threading.Thread):
def host_is_up(conn_type, hostname):
"""
returns True if the given host is up and we are able to establish
a connection using the given credentials.
"""
try:
socket_host = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_host.settimeout(2)
if conn_type == CONN_SSH:
if ':' in hostname:
LIBVIRT_HOST, PORT = (hostname).split(":")
PORT = int(PORT)
else:
PORT = SSH_PORT
LIBVIRT_HOST = hostname
socket_host.connect((LIBVIRT_HOST, PORT))
if conn_type == CONN_TCP:
socket_host.connect((hostname, TCP_PORT))
if conn_type == CONN_TLS:
socket_host.connect((hostname, TLS_PORT))
socket_host.close()
except socket.error:
raise libvirtError('Unable to connect to host server: Operation timed out')
class wvcEventLoop(threading.Thread):
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
# register the default event implementation
# of libvirt, as we do not have an existing
@ -26,7 +52,7 @@ class wvmEventLoop(threading.Thread):
if name is None:
name = 'libvirt event loop'
super(wvmEventLoop, self).__init__(group, target, name, args, kwargs)
super(wvcEventLoop, self).__init__(group, target, name, args, kwargs)
# we run this thread in deamon mode, so it does
# not block shutdown of the server
@ -40,7 +66,7 @@ class wvmEventLoop(threading.Thread):
libvirt.virEventRunDefaultImpl()
class wvmConnection(object):
class wvcConnection(object):
"""
class representing a single connection stored in the Connection Manager
# to-do: may also need some locking to ensure to not connect simultaniously in 2 threads
@ -86,10 +112,11 @@ class wvmConnection(object):
# * set keep alive interval
# * set connection close/fail handler
try:
self.connection.setKeepAlive(connection_manager.keepalive_interval, connection_manager.keepalive_count)
self.connection.setKeepAlive(connection_manager.keepalive_interval,
connection_manager.keepalive_count)
try:
self.connection.registerCloseCallback(self.__connection_close_callback, None)
except:
except Exception:
# Temporary fix for libvirt > libvirt-0.10.2-41
pass
except libvirtError as e:
@ -124,16 +151,7 @@ class wvmConnection(object):
# on server shutdown libvirt module gets freed before the close callbacks are called
# so we just check here if it is still present
if libvirt is not None:
if (reason == libvirt.VIR_CONNECT_CLOSE_REASON_ERROR):
self.last_error = 'connection closed: Misc I/O error'
elif (reason == libvirt.VIR_CONNECT_CLOSE_REASON_EOF):
self.last_error = 'connection closed: End-of-file from server'
elif (reason == libvirt.VIR_CONNECT_CLOSE_REASON_KEEPALIVE):
self.last_error = 'connection closed: Keepalive timer triggered'
elif (reason == libvirt.VIR_CONNECT_CLOSE_REASON_CLIENT):
self.last_error = 'connection closed: Client requested it'
else:
self.last_error = 'connection closed: Unknown error'
self.last_error = 'Connection closed'
# prevent other threads from using the connection (in the future)
self.connection = None
@ -211,7 +229,7 @@ class wvmConnection(object):
# unregister callback (as it is no longer valid if this instance gets deleted)
try:
self.connection.unregisterCloseCallback()
except:
except Exception:
pass
def __unicode__(self):
@ -227,10 +245,10 @@ class wvmConnection(object):
return u'qemu+{type}://{user}@{host}/system'.format(type=type_str, user=self.login, host=self.host)
def __repr__(self):
return '<wvmConnection {connection_str}>'.format(connection_str=unicode(self))
return '<wvcConnection {connection_str}>'.format(connection_str=str(self))
class wvmConnectionManager(object):
class wvcConnectionManager(object):
def __init__(self, keepalive_interval=5, keepalive_count=5):
self.keepalive_interval = keepalive_interval
self.keepalive_count = keepalive_count
@ -245,7 +263,7 @@ class wvmConnectionManager(object):
self._connections_lock = ReadWriteLock()
# start event loop to handle keepalive requests and other events
self._event_loop = wvmEventLoop()
self._event_loop = wvcEventLoop()
self._event_loop.start()
def _search_connection(self, host, login, passwd, conn):
@ -271,10 +289,10 @@ class wvmConnectionManager(object):
returns a connection object (as returned by the libvirt.open* methods) for the given host and credentials
raises libvirtError if (re)connecting fails
"""
# force all string values to unicode
host = unicode(host)
login = unicode(login)
passwd = unicode(passwd) if passwd is not None else None
# force all string values to unicode changed for Python3 to str
host = str(host)
login = str(login)
passwd = str(passwd) if passwd is not None else None
connection = self._search_connection(host, login, passwd, conn)
@ -286,7 +304,7 @@ class wvmConnectionManager(object):
connection = self._search_connection(host, login, passwd, conn)
if (connection is None):
# create a new connection if a matching connection does not already exist
connection = wvmConnection(host, login, passwd, conn)
connection = wvcConnection(host, login, passwd, conn)
# add new connection to connection dict
if host in self._connections:
@ -307,268 +325,175 @@ class wvmConnectionManager(object):
# raise libvirt error
raise libvirtError(connection.last_error)
def host_is_up(self, conn_type, hostname):
"""
returns True if the given host is up and we are able to establish
a connection using the given credentials.
"""
try:
socket_host = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_host.settimeout(1)
if conn_type == CONN_SSH:
if ':' in hostname:
LIBVIRT_HOST, PORT = (hostname).split(":")
PORT = int(PORT)
else:
PORT = SSH_PORT
LIBVIRT_HOST = hostname
socket_host.connect((LIBVIRT_HOST, PORT))
if conn_type == CONN_TCP:
socket_host.connect((hostname, TCP_PORT))
if conn_type == CONN_TLS:
socket_host.connect((hostname, TLS_PORT))
socket_host.close()
return True
except Exception as err:
return err
connection_manager = wvmConnectionManager(
settings.LIBVIRT_KEEPALIVE_INTERVAL if hasattr(settings, 'LIBVIRT_KEEPALIVE_INTERVAL') else 5,
settings.LIBVIRT_KEEPALIVE_COUNT if hasattr(settings, 'LIBVIRT_KEEPALIVE_COUNT') else 5
)
connection_manager = wvcConnectionManager(KEEPALIVE_INTERVAL, KEEPALIVE_COUNT)
class wvmConnect(object):
def __init__(self, host, login, passwd, conn):
class wvcConnect(object):
def __init__(self, host, login=None, passwd=None, conn_type=CONN_SOCKET, keepalive=True):
self.login = login
self.host = host
self.passwd = passwd
self.conn = conn
self.conn_type = conn_type
self.keepalive = keepalive
# is host up?
host_is_up(self.conn_type, self.host)
# get connection from connection manager
self.wvm = connection_manager.get_connection(host, login, passwd, conn)
if self.keepalive:
self.conn = connection_manager.get_connection(host, login, passwd, conn_type)
else:
if self.conn_type == CONN_TCP:
def creds(credentials, user_data):
for credential in credentials:
if credential[0] == libvirt.VIR_CRED_AUTHNAME:
credential[4] = self.login
if len(credential[4]) == 0:
credential[4] = credential[3]
elif credential[0] == libvirt.VIR_CRED_PASSPHRASE:
credential[4] = self.passwd
else:
return -1
return 0
flags = [libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_PASSPHRASE]
auth = [flags, creds, None]
uri = 'qemu+tcp://%s/system' % self.host
try:
self.conn = libvirt.openAuth(uri, auth, 0)
except libvirtError:
raise libvirtError('Connection Failed')
if self.conn_type == CONN_SSH:
uri = 'qemu+ssh://%s@%s/system' % (self.login, self.host)
try:
self.conn = libvirt.open(uri)
except libvirtError as err:
raise err
if self.conn_type == CONN_TLS:
def creds(credentials, user_data):
for credential in credentials:
if credential[0] == libvirt.VIR_CRED_AUTHNAME:
credential[4] = self.login
if len(credential[4]) == 0:
credential[4] = credential[3]
elif credential[0] == libvirt.VIR_CRED_PASSPHRASE:
credential[4] = self.passwd
else:
return -1
return 0
flags = [libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_PASSPHRASE]
auth = [flags, creds, None]
uri = 'qemu+tls://%s@%s/system' % (self.login, self.host)
try:
self.conn = libvirt.openAuth(uri, auth, 0)
except libvirtError:
raise libvirtError('Connection Failed')
def get_cap_xml(self):
"""Return xml capabilities"""
return self.wvm.getCapabilities()
def get_dom_cap_xml(self):
""" Return domcapabilities xml"""
emulatorbin = self.emulator()
machine = self.machine()
arch = self.wvm.getInfo()[0]
virttype = self.hypervisor_type()[arch][0]
return self.wvm.getDomainCapabilities(emulatorbin, arch, machine, virttype)
return self.conn.getCapabilities()
def is_kvm_supported(self):
"""Return KVM capabilities."""
return util.is_kvm_available(self.get_cap_xml())
def get_storages(self, only_actives=False):
def get_storages(self):
storages = []
for pool in self.wvm.listStoragePools():
for pool in self.conn.listStoragePools():
storages.append(pool)
for pool in self.conn.listDefinedStoragePools():
storages.append(pool)
if not only_actives:
for pool in self.wvm.listDefinedStoragePools():
storages.append(pool)
return storages
def get_networks(self):
virtnet = []
for net in self.wvm.listNetworks():
for net in self.conn.listNetworks():
virtnet.append(net)
for net in self.wvm.listDefinedNetworks():
for net in self.conn.listDefinedNetworks():
virtnet.append(net)
return virtnet
def get_ifaces(self):
interface = []
for inface in self.wvm.listInterfaces():
for inface in self.conn.listInterfaces():
interface.append(inface)
for inface in self.wvm.listDefinedInterfaces():
for inface in self.conn.listDefinedInterfaces():
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 hypervisor_type(self):
"""Return hypervisor type"""
def hypervisors(ctx):
result = {}
for arch in ctx.xpath('/capabilities/guest/arch'):
domain_types = arch.xpath('domain/@type')
arch_name = arch.xpath('@name')[0]
result[arch_name]= domain_types
return result
return util.get_xml_path(self.get_cap_xml(), func=hypervisors)
def emulator(self):
"""Return emulator """
return util.get_xml_path(self.get_cap_xml(), "/capabilities/guest/arch/emulator")
def machine(self):
""" Return machine type of emulation"""
return util.get_xml_path(self.get_cap_xml(), "/capabilities/guest/arch/machine")
def get_busses(self):
"""Get available busses"""
def get_bus_list(ctx):
result = []
for disk_enum in ctx.xpath('/domainCapabilities/devices/disk/enum'):
if disk_enum.xpath("@name")[0] == "bus":
for values in disk_enum: result.append(values.text)
return result
# return [ 'ide', 'scsi', 'usb', 'virtio' ]
return util.get_xml_path(self.get_dom_cap_xml(), func=get_bus_list)
def get_image_formats(self):
"""Get available image formats"""
return [ 'raw', 'qcow', 'qcow2' ]
def get_file_extensions(self):
"""Get available image filename extensions"""
return [ 'img', 'qcow', 'qcow2' ]
def get_video(self):
""" Get available graphics video types """
def get_video_list(ctx):
result = []
for video_enum in ctx.xpath('/domainCapabilities/devices/video/enum'):
if video_enum.xpath("@name")[0] == "modelType":
for values in video_enum: result.append(values.text)
return result
return util.get_xml_path(self.get_dom_cap_xml(),func=get_video_list)
def get_iface(self, name):
return self.wvm.interfaceLookupByName(name)
return self.conn.interfaceLookupByName(name)
def get_secrets(self):
return self.wvm.listSecrets()
return self.conn.listSecrets()
def get_secret(self, uuid):
return self.wvm.secretLookupByUUIDString(uuid)
return self.conn.secretLookupByUUIDString(uuid)
def get_storage(self, name):
return self.wvm.storagePoolLookupByName(name)
return self.conn.storagePoolLookupByName(name)
def get_volume_by_path(self, path):
return self.wvm.storageVolLookupByPath(path)
return self.conn.storageVolLookupByPath(path)
def get_network(self, net):
return self.wvm.networkLookupByName(net)
return self.conn.networkLookupByName(net)
def get_instance(self, name):
return self.wvm.lookupByName(name)
return self.conn.lookupByName(name)
def get_instance_status(self, name):
dom = self.conn.lookupByName(name)
return dom.info()[0]
def get_instances(self):
instances = []
for inst_id in self.wvm.listDomainsID():
dom = self.wvm.lookupByID(int(inst_id))
for inst_id in self.conn.listDomainsID():
dom = self.conn.lookupByID(int(inst_id))
instances.append(dom.name())
for name in self.wvm.listDefinedDomains():
for name in self.conn.listDefinedDomains():
instances.append(name)
return instances
def get_snapshots(self):
instance = []
for snap_id in self.wvm.listDomainsID():
dom = self.wvm.lookupByID(int(snap_id))
for snap_id in self.conn.listDomainsID():
dom = self.conn.lookupByID(int(snap_id))
if dom.snapshotNum(0) != 0:
instance.append(dom.name())
for name in self.wvm.listDefinedDomains():
dom = self.wvm.lookupByName(name)
for name in self.conn.listDefinedDomains():
dom = self.conn.lookupByName(name)
if dom.snapshotNum(0) != 0:
instance.append(dom.name())
return instance
def get_net_device(self):
netdevice = []
def get_info(doc):
dev_type = util.get_xpath(doc, '/device/capability/@type')
interface = util.get_xpath(doc, '/device/capability/interface')
return dev_type, interface
for dev in self.wvm.listAllDevices(0):
for dev in self.conn.listAllDevices(0):
xml = dev.XMLDesc(0)
(dev_type, interface) = util.get_xml_path(xml, func=get_info)
if dev_type == 'net':
netdevice.append(interface)
if util.get_xml_data(xml, 'capability', 'type') == 'net':
netdevice.append(util.get_xml_data(xml, 'capability/interface'))
return netdevice
def get_host_instances(self, raw_mem_size=False):
def get_host_instances(self):
vname = {}
def get_info(doc):
mem = util.get_xpath(doc, "/domain/currentMemory")
mem = int(mem) / 1024
if raw_mem_size:
mem = int(mem) * (1024*1024)
cur_vcpu = util.get_xpath(doc, "/domain/vcpu/@current")
if cur_vcpu:
vcpu = cur_vcpu
else:
vcpu = util.get_xpath(doc, "/domain/vcpu")
title = util.get_xpath(doc, "/domain/title")
title = title if title else ''
description = util.get_xpath(doc, "/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,
'description': description,
}
return vname
def get_user_instances(self, name):
dom = self.get_instance(name)
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")
mem = util.get_xml_data(dom.XMLDesc(0), 'currentMemory')
mem = round(int(mem) / 1024)
cur_vcpu = util.get_xml_data(dom.XMLDesc(0), '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,
'description': description,
}
vcpu = util.get_xml_data(dom.XMLDesc(0), 'vcpu')
vname[dom.name()] = {'status': dom.info()[0], 'uuid': dom.UUIDString(), 'vcpu': vcpu, 'memory': mem}
return vname
def close(self):
"""Close connection"""
# to-do: do not close connection ;)
# self.wvm.close()
pass
if not self.keepalive:
self.conn.close()

81
backend/libvmgr/host.py Normal file
View file

@ -0,0 +1,81 @@
import time
from libvmgr import util
from libvmgr.connect import wvcConnect
class wvcHost(wvcConnect):
def __init__(self, conn):
self.conn = conn
def get_node_info(self):
"""
Function return host server information: hostname, cpu, memory, ...
"""
info = list()
info.append(self.conn.getHostname())
info.append(self.conn.getInfo()[0])
info.append(self.conn.getInfo()[1] * (1024**2))
info.append(self.conn.getInfo()[2])
info.append(util.get_xml_data(self.conn.getSysinfo(0), 'processor/entry[6]'))
info.append(self.conn.getURI())
return info
def hypervisor_type(self):
"""
Return hypervisor type
"""
return util.get_xml_data(self.get_cap_xml(), 'guest/arch/domain', 'type')
def get_memory_usage(self):
"""
Function return memory usage on node.
"""
host_mem = self.conn.getInfo()[1] * (1024**2)
free_mem = self.conn.getMemoryStats(-1, 0)
if isinstance(free_mem, dict):
mem = list(free_mem.values())
free = (mem[1] + mem[2] + mem[3]) * 1024
percent = (100 - ((free * 100) / host_mem))
usage = (host_mem - free)
mem_usage = {'size': host_mem, 'usage': usage, 'percent': round(percent)}
else:
mem_usage = {'size': 0, 'usage': 0, 'percent': 0}
return mem_usage
def get_storage_usage(self, name):
"""
Function return storage usage on node by name.
"""
pool = self.get_storage(name)
pool.refresh()
if pool.isActive():
size = pool.info()[1]
free = pool.info()[3]
used = size - free
percent = (used * 100) / size
return {'size': size, 'used': used, 'percent': percent}
else:
return {'size': 0, 'used': 0, 'percent': 0}
def get_cpu_usage(self):
"""
Function return cpu usage on node.
"""
prev_idle = 0
prev_total = 0
diff_usage = 0
cpu = self.conn.getCPUStats(-1, 0)
if isinstance(cpu, dict):
for num in range(2):
idle = self.conn.getCPUStats(-1, 0)['idle']
total = sum(self.conn.getCPUStats(-1, 0).values())
diff_idle = idle - prev_idle
diff_total = total - prev_total
diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10
prev_total = total
prev_idle = idle
if num == 0:
time.sleep(1)
if diff_usage < 0:
diff_usage = 0
return {'usage': round(diff_usage)}

View file

@ -1,15 +1,20 @@
import re
import socket
import random
import lxml.etree as etree
import libvirt
import string
import paramiko
from time import sleep
from xml.etree import ElementTree
from string import ascii_letters, digits
from passlib.hash import sha512_crypt
def is_kvm_available(xml):
kvm_domains = get_xml_path(xml, "//domain/@type='kvm'")
if kvm_domains > 0:
return True
else:
return False
tree = ElementTree.fromstring(xml)
for dom in tree.findall('guest/arch/domain'):
if 'kvm' in dom.get('type'):
return True
return False
def randomMAC():
@ -30,11 +35,6 @@ def randomUUID():
return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2, "%02x" * 6]) % tuple(u)
def randomPasswd(length=12, alphabet=string.letters + string.digits):
"""Generate a random password"""
return ''.join([random.choice(alphabet) for i in xrange(length)])
def get_max_vcpus(conn, type=None):
"""@param conn: libvirt connection to poll for max possible vcpus
@type type: optional guest type (kvm, etc.)"""
@ -71,7 +71,7 @@ def compareMAC(p, q):
else:
return -1
for i in xrange(len(pa)):
for i in range(len(pa)):
n = int(pa[i], 0x10) - int(qa[i], 0x10)
if n > 0:
return 1
@ -80,40 +80,28 @@ def compareMAC(p, q):
return 0
def get_xml_path(xml, path=None, func=None):
"""
Return the content from the passed xml xpath, or return the result
of a passed function (receives xpathContext as its only arg)
"""
doc = None
result = None
def get_xml_data(xml, path=None, element=None):
res = ''
if not path and not element:
return ''
doc = etree.fromstring(xml)
tree = ElementTree.fromstring(xml)
if path:
result = get_xpath(doc, path)
elif func:
result = func(doc)
child = tree.find(path)
if child is not None:
if element:
res = child.get(element)
else:
res = child.text
else:
raise ValueError("'path' or 'func' is required.")
return result
res = tree.get(element)
return res
def get_xpath(doc, path):
result = None
def get_xml_findall(xml, string):
tree = ElementTree.fromstring(xml)
return tree.findall(string)
ret = doc.xpath(path)
if ret is not None:
if type(ret) == list:
if len(ret) >= 1:
if hasattr(ret[0],'text'):
result = ret[0].text
else:
result = ret[0]
else:
result = ret
return result
def pretty_mem(val):
val = int(val)
@ -129,3 +117,43 @@ def pretty_bytes(val):
return "%2.2f GB" % (val / (1024.0 * 1024.0 * 1024.0))
else:
return "%2.2f MB" % (val / (1024.0 * 1024.0))
def gen_password(length=14):
password = ''.join(
[random.choice(ascii_letters + digits) for dummy in range(length)]
)
return password
def password_to_hash(password):
salt = gen_password(8)
password_hash = sha512_crypt.encrypt(password, salt=salt, rounds=5000)
return password_hash
def similar_name(pattern, names):
res = []
for name in names:
match = re.match(pattern, name)
if match:
res.append(name)
return res
def check_ssh_connection(hostname, password, username='root', timeout=90):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
for i in range(timeout):
try:
ssh.connect(hostname, username=username, password=password)
ssh.close()
return True
except (paramiko.BadHostKeyException,
paramiko.AuthenticationException,
paramiko.SSHException,
socket.error,
EOFError):
sleep(1)
return False

15
backend/manage.py Executable file
View file

@ -0,0 +1,15 @@
#!/usr/bin/env python
import os
import sys
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webvirtcloud.settings.prod')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)

5
backend/network/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class NetworkConfig(AppConfig):
name = 'network'

3
backend/network/views.py Normal file
View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

5
backend/region/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class RegionConfig(AppConfig):
name = 'region'

View file

@ -0,0 +1,33 @@
# Generated by Django 2.0.8 on 2018-09-22 07:20
import django.core.validators
from django.db import migrations, models
import re
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Region',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(max_length=200, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9_]+\\Z'), "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", 'invalid')])),
('name', models.CharField(blank=True, max_length=255)),
('is_active', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('is_deleted', models.BooleanField(default=False)),
('deleted_at', models.DateTimeField(blank=True, null=True)),
],
options={
'verbose_name': 'Region',
'verbose_name_plural': 'Regions',
'ordering': ['-id'],
},
),
]

40
backend/region/models.py Normal file
View file

@ -0,0 +1,40 @@
from django.db import models
from django.core.validators import validate_slug
class Region(models.Model):
slug = models.SlugField(
max_length=200,
validators=[validate_slug],
)
name = models.CharField(
max_length=255,
blank=True,
)
is_active = models.BooleanField(
default=False,
)
created_at = models.DateTimeField(
auto_now_add=True,
)
is_deleted = models.BooleanField(
default=False
)
deleted_at = models.DateTimeField(
blank=True,
null=True,
)
class Meta:
ordering = ['-id']
verbose_name = "Region"
verbose_name_plural = "Regions"
def __unicode__(self):
return self.name

3
backend/region/views.py Normal file
View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View file

@ -0,0 +1,6 @@
-r requirements.txt
flake8
bpython
django-cors-headers
django-rest-swagger
django-debug-toolbar

8
backend/requirements.txt Normal file
View file

@ -0,0 +1,8 @@
Django==2.0.8
djangorestframework==3.8.2
libvirt-python==4.6.0
passlib==1.7.1
paramiko==2.4.1
django-rest-auth==0.9.3
django-allauth==0.37.1
mysqlclient==1.3.13

5
backend/support/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class SupportConfig(AppConfig):
name = 'support'

3
backend/support/views.py Normal file
View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

Some files were not shown because too many files have changed in this diff Show more