mirror of
https://github.com/retspen/webvirtcloud
synced 2024-10-31 19:44:16 +00:00
commit
cbac82ba07
25 changed files with 285 additions and 205 deletions
11
.github/workflows/linter.yml
vendored
11
.github/workflows/linter.yml
vendored
|
@ -32,10 +32,15 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
if [ -f dev/requirements.txt ]; then pip3 install -r dev/requirements.txt; fi
|
if [ -f dev/requirements.txt ]; then pip3 install -r dev/requirements.txt; else pip3 install -r conf/requirements.txt; fi
|
||||||
- name: Super-Linter
|
################################
|
||||||
uses: docker://github/super-linter:v3.3.2
|
# Run Linter against code base #
|
||||||
|
################################
|
||||||
|
- name: Lint Code Base
|
||||||
|
uses: docker://github/super-linter:latest
|
||||||
env:
|
env:
|
||||||
|
FILTER_REGEX_EXCLUDE: .*[static|scss]/.*
|
||||||
|
DEFAULT_BRANCH: master
|
||||||
VALIDATE_ANSIBLE: false
|
VALIDATE_ANSIBLE: false
|
||||||
VALIDATE_CLOJURE: false
|
VALIDATE_CLOJURE: false
|
||||||
VALIDATE_COFFEE: false
|
VALIDATE_COFFEE: false
|
||||||
|
|
|
@ -2,7 +2,7 @@ language: python
|
||||||
python:
|
python:
|
||||||
- "3.6"
|
- "3.6"
|
||||||
env:
|
env:
|
||||||
- DJANGO=2.2.14
|
- DJANGO=2.2.16
|
||||||
install:
|
install:
|
||||||
- pip install -r dev/requirements.txt
|
- pip install -r dev/requirements.txt
|
||||||
script:
|
script:
|
||||||
|
|
77
README.md
77
README.md
|
@ -10,11 +10,10 @@
|
||||||
* Manage Hypervisor Networks
|
* Manage Hypervisor Networks
|
||||||
* Instance Console Access with Browsers
|
* Instance Console Access with Browsers
|
||||||
* Libvirt API based web management UI
|
* Libvirt API based web management UI
|
||||||
* User Based Authorization and Authentication
|
* User Based Authorization and Authentication
|
||||||
* User can add SSH public key to root in Instance (Tested only Ubuntu)
|
* User can add SSH public key to root in Instance (Tested only Ubuntu)
|
||||||
* User can change root password in Instance (Tested only Ubuntu)
|
* User can change root password in Instance (Tested only Ubuntu)
|
||||||
* Supports cloud-init datasource interface
|
* Supports cloud-init datasource interface
|
||||||
|
|
||||||
|
|
||||||
### Warning!!!
|
### Warning!!!
|
||||||
|
|
||||||
|
@ -30,8 +29,10 @@ sudo service supervisor restart
|
||||||
WebVirtCloud is a virtualization web interface for admins and users. It can delegate Virtual Machine's to users. A noVNC viewer presents a full graphical console to the guest domain. KVM is currently the only hypervisor supported.
|
WebVirtCloud is a virtualization web interface for admins and users. It can delegate Virtual Machine's to users. A noVNC viewer presents a full graphical console to the guest domain. KVM is currently the only hypervisor supported.
|
||||||
|
|
||||||
## Quick Install with Installer (Beta)
|
## Quick Install with Installer (Beta)
|
||||||
|
|
||||||
Install an OS and run specified commands. Installer supported OSes: Ubuntu 18.04, Debian 10, Centos/OEL/RHEL 8.
|
Install an OS and run specified commands. Installer supported OSes: Ubuntu 18.04, Debian 10, Centos/OEL/RHEL 8.
|
||||||
It can be installed on a virtual machine, physical host or on a KVM host.
|
It can be installed on a virtual machine, physical host or on a KVM host.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wget https://raw.githubusercontent.com/retspen/webvirtcloud/master/install.sh
|
wget https://raw.githubusercontent.com/retspen/webvirtcloud/master/install.sh
|
||||||
chmod 744 install.sh
|
chmod 744 install.sh
|
||||||
|
@ -40,7 +41,9 @@ chmod 744 install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Manual Installation
|
## Manual Installation
|
||||||
|
|
||||||
### Generate secret key
|
### Generate secret key
|
||||||
|
|
||||||
You should generate SECRET_KEY after cloning repo. Then put it into webvirtcloud/settings.py.
|
You should generate SECRET_KEY after cloning repo. Then put it into webvirtcloud/settings.py.
|
||||||
|
|
||||||
```python3
|
```python3
|
||||||
|
@ -83,6 +86,7 @@ Setup libvirt and KVM on server
|
||||||
```bash
|
```bash
|
||||||
wget -O - https://clck.ru/9V9fH | sudo sh
|
wget -O - https://clck.ru/9V9fH | sudo sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Done!!
|
Done!!
|
||||||
|
|
||||||
Go to http://serverip and you should see the login screen.
|
Go to http://serverip and you should see the login screen.
|
||||||
|
@ -106,6 +110,7 @@ sudo sed -r "s/SECRET_KEY = ''/SECRET_KEY = '"`python3 /srv/webvirtcloud/conf/ru
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Start installation webvirtcloud
|
#### Start installation webvirtcloud
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
virtualenv-3 venv
|
virtualenv-3 venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
|
@ -115,7 +120,8 @@ python3 manage.py migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Configure the supervisor for CentOS
|
#### Configure the supervisor for CentOS
|
||||||
Add the following after the [include] line (after **files = ... ** actually):
|
|
||||||
|
Add the following after the [include] line (after **files = ...** actually):
|
||||||
```bash
|
```bash
|
||||||
sudo vim /etc/supervisord.conf
|
sudo vim /etc/supervisord.conf
|
||||||
|
|
||||||
|
@ -137,9 +143,10 @@ redirect_stderr=true
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Edit the nginx.conf file
|
#### 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:
|
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:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
# server {
|
# server {
|
||||||
# listen 80 default_server;
|
# listen 80 default_server;
|
||||||
# listen [::]:80 default_server;
|
# listen [::]:80 default_server;
|
||||||
|
@ -164,7 +171,8 @@ You will need to edit the main nginx.conf file as the one that comes from the rp
|
||||||
```
|
```
|
||||||
|
|
||||||
Also make sure file in **/etc/nginx/conf.d/webvirtcloud.conf** has the proper paths:
|
Also make sure file in **/etc/nginx/conf.d/webvirtcloud.conf** has the proper paths:
|
||||||
```
|
|
||||||
|
```bash
|
||||||
upstream gunicorn_server {
|
upstream gunicorn_server {
|
||||||
#server unix:/srv/webvirtcloud/venv/wvcloud.socket fail_timeout=0;
|
#server unix:/srv/webvirtcloud/venv/wvcloud.socket fail_timeout=0;
|
||||||
server 127.0.0.1:8000 fail_timeout=0;
|
server 127.0.0.1:8000 fail_timeout=0;
|
||||||
|
@ -208,11 +216,13 @@ sudo setsebool -P httpd_can_network_connect on -P
|
||||||
```
|
```
|
||||||
|
|
||||||
Add required user to the kvm group(if you not install with root):
|
Add required user to the kvm group(if you not install with root):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo usermod -G kvm -a <username>
|
sudo usermod -G kvm -a <username>
|
||||||
```
|
```
|
||||||
|
|
||||||
Allow http ports on firewall:
|
Allow http ports on firewall:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo firewall-cmd --add-service=http
|
sudo firewall-cmd --add-service=http
|
||||||
sudo firewall-cmd --add-service=http --permanent
|
sudo firewall-cmd --add-service=http --permanent
|
||||||
|
@ -221,11 +231,13 @@ sudo firewall-cmd --add-port=6080/tcp --permanent
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's restart nginx and the supervisord services:
|
Let's restart nginx and the supervisord services:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl restart nginx && systemctl restart supervisord
|
sudo systemctl restart nginx && systemctl restart supervisord
|
||||||
```
|
```
|
||||||
|
|
||||||
And finally, check everything is running:
|
And finally, check everything is running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo supervisorctl status
|
sudo supervisorctl status
|
||||||
gstfsd RUNNING pid 24662, uptime 6:01:40
|
gstfsd RUNNING pid 24662, uptime 6:01:40
|
||||||
|
@ -234,12 +246,14 @@ webvirtcloud RUNNING pid 24660, uptime 6:01:40
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Apache mod_wsgi configuration
|
#### Apache mod_wsgi configuration
|
||||||
```
|
|
||||||
|
```bash
|
||||||
WSGIDaemonProcess webvirtcloud threads=2 maximum-requests=1000 display-name=webvirtcloud
|
WSGIDaemonProcess webvirtcloud threads=2 maximum-requests=1000 display-name=webvirtcloud
|
||||||
WSGIScriptAlias / /srv/webvirtcloud/webvirtcloud/wsgi_custom.py
|
WSGIScriptAlias / /srv/webvirtcloud/webvirtcloud/wsgi_custom.py
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Install final required packages for libvirtd and others on Host Server
|
#### Install final required packages for libvirtd and others on Host Server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wget -O - https://clck.ru/9V9fH | sudo sh
|
wget -O - https://clck.ru/9V9fH | sudo sh
|
||||||
```
|
```
|
||||||
|
@ -249,10 +263,12 @@ Done!!
|
||||||
Go to http://serverip and you should see the login screen.
|
Go to http://serverip and you should see the login screen.
|
||||||
|
|
||||||
### Alternative running novncd via runit(Debian)
|
### Alternative running novncd via runit(Debian)
|
||||||
|
|
||||||
Alternative to running nonvcd via supervisor is runit.
|
Alternative to running nonvcd via supervisor is runit.
|
||||||
|
|
||||||
On Debian systems install runit and configure novncd service
|
On Debian systems install runit and configure novncd service:
|
||||||
```
|
|
||||||
|
```bash
|
||||||
apt install runit runit-systemd
|
apt install runit runit-systemd
|
||||||
mkdir /etc/service/novncd/
|
mkdir /etc/service/novncd/
|
||||||
ln -s /srv/webvirtcloud/conf/runit/novncd.sh /etc/service/novncd/run
|
ln -s /srv/webvirtcloud/conf/runit/novncd.sh /etc/service/novncd/run
|
||||||
|
@ -260,16 +276,19 @@ systemctl start runit.service
|
||||||
```
|
```
|
||||||
|
|
||||||
### Default credentials
|
### Default credentials
|
||||||
<pre>
|
|
||||||
|
```html
|
||||||
login: admin
|
login: admin
|
||||||
password: admin
|
password: admin
|
||||||
</pre>
|
```
|
||||||
|
|
||||||
### Configuring Compute SSH connection
|
### Configuring Compute SSH connection
|
||||||
|
|
||||||
This is a short example of configuring cloud and compute side of the 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.
|
On the webvirtcloud machine you need to generate ssh keys and optionally disable StrictHostKeyChecking.
|
||||||
```
|
|
||||||
|
```bash
|
||||||
chown www-data -R ~www-data
|
chown www-data -R ~www-data
|
||||||
sudo -u www-data ssh-keygen
|
sudo -u www-data ssh-keygen
|
||||||
cat > ~www-data/.ssh/config << EOF
|
cat > ~www-data/.ssh/config << EOF
|
||||||
|
@ -280,44 +299,55 @@ 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.
|
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.
|
||||||
```
|
|
||||||
|
```bash
|
||||||
sudo -u www-data ssh-copy-id root@compute1
|
sudo -u www-data ssh-copy-id root@compute1
|
||||||
```
|
```
|
||||||
|
|
||||||
### Host SMBIOS information is not available
|
### Host SMBIOS information is not available
|
||||||
|
|
||||||
If you see warning
|
If you see warning
|
||||||
```
|
|
||||||
|
```bash
|
||||||
Unsupported configuration: Host SMBIOS information is not available
|
Unsupported configuration: Host SMBIOS information is not available
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you need to install `dmidecode` package on your host using your package manager and restart libvirt daemon.
|
Then you need to install `dmidecode` package on your host using your package manager and restart libvirt daemon.
|
||||||
|
|
||||||
Debian/Ubuntu like:
|
Debian/Ubuntu like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install dmidecode
|
||||||
|
sudo service libvirt-bin restart
|
||||||
```
|
```
|
||||||
$ sudo apt-get install dmidecode
|
|
||||||
$ sudo service libvirt-bin restart
|
|
||||||
```
|
|
||||||
Arch Linux
|
Arch Linux
|
||||||
```
|
|
||||||
$ sudo pacman -S dmidecode
|
```bash
|
||||||
$ systemctl restart libvirtd
|
sudo pacman -S dmidecode
|
||||||
|
systemctl restart libvirtd
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cloud-init
|
### Cloud-init
|
||||||
|
|
||||||
Currently supports only root ssh authorized keys and hostname. Example configuration of the cloud-init client follows.
|
Currently supports only root ssh authorized keys and hostname. Example configuration of the cloud-init client follows.
|
||||||
```
|
|
||||||
|
```bash
|
||||||
datasource:
|
datasource:
|
||||||
OpenStack:
|
OpenStack:
|
||||||
metadata_urls: [ "http://webvirtcloud.domain.com/datasource" ]
|
metadata_urls: [ "http://webvirtcloud.domain.com/datasource" ]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Reverse-Proxy
|
### Reverse-Proxy
|
||||||
|
|
||||||
Edit WS_PUBLIC_PORT at settings.py file to expose redirect to 80 or 443. Default: 6080
|
Edit WS_PUBLIC_PORT at settings.py file to expose redirect to 80 or 443. Default: 6080
|
||||||
```
|
|
||||||
|
```bash
|
||||||
WS_PUBLIC_PORT = 80
|
WS_PUBLIC_PORT = 80
|
||||||
```
|
```
|
||||||
|
|
||||||
## How To Update
|
## How To Update
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Go to Installation Directory
|
# Go to Installation Directory
|
||||||
cd /srv/webvirtcloud
|
cd /srv/webvirtcloud
|
||||||
|
@ -329,22 +359,27 @@ sudo service supervisor restart
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running tests
|
### Running tests
|
||||||
|
|
||||||
Server on which tests will be performed must have libvirt up and running.
|
Server on which tests will be performed must have libvirt up and running.
|
||||||
It must not contain vms.
|
It must not contain vms.
|
||||||
It must have `default` storage which not contain any disk images.
|
It must have `default` storage which not contain any disk images.
|
||||||
It must have `default` network which must be on.
|
It must have `default` network which must be on.
|
||||||
Setup venv
|
Setup venv
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
pip install -r conf/requirements.txt
|
pip install -r conf/requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
Run tests
|
Run tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python manage.py test
|
python manage.py test
|
||||||
```
|
```
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
Instance Detail:
|
Instance Detail:
|
||||||
<img src="doc/images/instance.PNG" width="96%" align="center"/>
|
<img src="doc/images/instance.PNG" width="96%" align="center"/>
|
||||||
Instance List:</br>
|
Instance List:</br>
|
||||||
|
|
|
@ -21,7 +21,7 @@ class UserInstance(models.Model):
|
||||||
objects = UserInstanceManager()
|
objects = UserInstanceManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _('Instance "%(inst)s" of user %(user)s') % {'inst': self.instance, 'user': self.user}
|
return _('Instance "%(inst)s" of user %(user)s') % {"inst": self.instance, "user": self.user}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ['user', 'instance']
|
unique_together = ['user', 'instance']
|
||||||
|
|
|
@ -23,7 +23,7 @@ class AccountsTestCase(TestCase):
|
||||||
|
|
||||||
client = Client()
|
client = Client()
|
||||||
|
|
||||||
response = client.post(reverse('login'), {'username': 'test', 'password': 'test'})
|
response = client.post(reverse("login"), {"username": "test", "password": "test"})
|
||||||
self.assertRedirects(response, reverse('profile'))
|
self.assertRedirects(response, reverse('profile'))
|
||||||
|
|
||||||
response = client.get(reverse('logout'))
|
response = client.get(reverse('logout'))
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import sass
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth import update_session_auth_hash
|
from django.contrib.auth import update_session_auth_hash
|
||||||
from django.contrib.auth.decorators import permission_required
|
from django.contrib.auth.decorators import permission_required
|
||||||
|
@ -13,7 +12,6 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from accounts.models import *
|
from accounts.models import *
|
||||||
from admin.decorators import superuser_only
|
from admin.decorators import superuser_only
|
||||||
from appsettings.models import AppSettings
|
|
||||||
from instances.models import Instance
|
from instances.models import Instance
|
||||||
|
|
||||||
from . import forms
|
from . import forms
|
||||||
|
@ -23,17 +21,17 @@ def profile(request):
|
||||||
error_messages = []
|
error_messages = []
|
||||||
publickeys = UserSSHKey.objects.filter(user_id=request.user.id)
|
publickeys = UserSSHKey.objects.filter(user_id=request.user.id)
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == "POST":
|
||||||
if 'username' in request.POST:
|
if "username" in request.POST:
|
||||||
username = request.POST.get('username', '')
|
username = request.POST.get("username", "")
|
||||||
email = request.POST.get('email', '')
|
email = request.POST.get("email", "")
|
||||||
user.first_name = username
|
request.user.first_name = username
|
||||||
user.email = email
|
request.user.email = email
|
||||||
request.user.save()
|
request.user.save()
|
||||||
return HttpResponseRedirect(request.get_full_path())
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
if 'keyname' in request.POST:
|
if "keyname" in request.POST:
|
||||||
keyname = request.POST.get('keyname', '')
|
keyname = request.POST.get("keyname", "")
|
||||||
keypublic = request.POST.get('keypublic', '')
|
keypublic = request.POST.get("keypublic", "")
|
||||||
for key in publickeys:
|
for key in publickeys:
|
||||||
if keyname == key.keyname:
|
if keyname == key.keyname:
|
||||||
msg = _("Key name already exist")
|
msg = _("Key name already exist")
|
||||||
|
@ -41,19 +39,20 @@ def profile(request):
|
||||||
if keypublic == key.keypublic:
|
if keypublic == key.keypublic:
|
||||||
msg = _("Public key already exist")
|
msg = _("Public key already exist")
|
||||||
error_messages.append(msg)
|
error_messages.append(msg)
|
||||||
if '\n' in keypublic or '\r' in keypublic:
|
if "\n" in keypublic or "\r" in keypublic:
|
||||||
msg = _("Invalid characters in public key")
|
msg = _("Invalid characters in public key")
|
||||||
error_messages.append(msg)
|
error_messages.append(msg)
|
||||||
if not error_messages:
|
if not error_messages:
|
||||||
addkeypublic = UserSSHKey(user_id=request.user.id, keyname=keyname, keypublic=keypublic)
|
addkeypublic = UserSSHKey(
|
||||||
|
user_id=request.user.id, keyname=keyname, keypublic=keypublic)
|
||||||
addkeypublic.save()
|
addkeypublic.save()
|
||||||
return HttpResponseRedirect(request.get_full_path())
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
if 'keydelete' in request.POST:
|
if "keydelete" in request.POST:
|
||||||
keyid = request.POST.get('keyid', '')
|
keyid = request.POST.get("keyid", "")
|
||||||
delkeypublic = UserSSHKey.objects.get(id=keyid)
|
delkeypublic = UserSSHKey.objects.get(id=keyid)
|
||||||
delkeypublic.delete()
|
delkeypublic.delete()
|
||||||
return HttpResponseRedirect(request.get_full_path())
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
return render(request, 'profile.html', locals())
|
return render(request, "profile.html", locals())
|
||||||
|
|
||||||
|
|
||||||
@superuser_only
|
@superuser_only
|
||||||
|
@ -61,43 +60,47 @@ def account(request, user_id):
|
||||||
error_messages = []
|
error_messages = []
|
||||||
user = User.objects.get(id=user_id)
|
user = User.objects.get(id=user_id)
|
||||||
user_insts = UserInstance.objects.filter(user_id=user_id)
|
user_insts = UserInstance.objects.filter(user_id=user_id)
|
||||||
instances = Instance.objects.all().order_by('name')
|
instances = Instance.objects.all().order_by("name")
|
||||||
publickeys = UserSSHKey.objects.filter(user_id=user_id)
|
publickeys = UserSSHKey.objects.filter(user_id=user_id)
|
||||||
|
|
||||||
return render(request, 'account.html', locals())
|
return render(request, "account.html", locals())
|
||||||
|
|
||||||
|
|
||||||
@permission_required('accounts.change_password', raise_exception=True)
|
@permission_required("accounts.change_password", raise_exception=True)
|
||||||
def change_password(request):
|
def change_password(request):
|
||||||
if request.method == 'POST':
|
if request.method == "POST":
|
||||||
form = PasswordChangeForm(request.user, request.POST)
|
form = PasswordChangeForm(request.user, request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
user = form.save()
|
user = form.save()
|
||||||
update_session_auth_hash(request, user) # Important!
|
update_session_auth_hash(request, user) # Important!
|
||||||
messages.success(request, _('Password Changed'))
|
messages.success(request, _("Password Changed"))
|
||||||
return redirect('profile')
|
return redirect("profile")
|
||||||
else:
|
else:
|
||||||
messages.error(request, _('Wrong Data Provided'))
|
messages.error(request, _("Wrong Data Provided"))
|
||||||
else:
|
else:
|
||||||
form = PasswordChangeForm(request.user)
|
form = PasswordChangeForm(request.user)
|
||||||
return render(request, 'accounts/change_password_form.html', {'form': form})
|
return render(
|
||||||
|
request,
|
||||||
|
"accounts/change_password_form.html",
|
||||||
|
{"form": form}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@superuser_only
|
@superuser_only
|
||||||
def user_instance_create(request, user_id):
|
def user_instance_create(request, user_id):
|
||||||
user = get_object_or_404(User, pk=user_id)
|
user = get_object_or_404(User, pk=user_id)
|
||||||
|
|
||||||
form = forms.UserInstanceForm(request.POST or None, initial={'user': user})
|
form = forms.UserInstanceForm(request.POST or None, initial={"user": user})
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
return redirect(reverse('account', args=[user.id]))
|
return redirect(reverse("account", args=[user.id]))
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'common/form.html',
|
"common/form.html",
|
||||||
{
|
{
|
||||||
'form': form,
|
"form": form,
|
||||||
'title': _('Create User Instance'),
|
"title": _("Create User Instance"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -108,14 +111,14 @@ def user_instance_update(request, pk):
|
||||||
form = forms.UserInstanceForm(request.POST or None, instance=user_instance)
|
form = forms.UserInstanceForm(request.POST or None, instance=user_instance)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
return redirect(reverse('account', args=[user_instance.user.id]))
|
return redirect(reverse("account", args=[user_instance.user.id]))
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'common/form.html',
|
'common/form.html',
|
||||||
{
|
{
|
||||||
'form': form,
|
"form": form,
|
||||||
'title': _('Update User Instance'),
|
"title": _("Update User Instance"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -123,17 +126,17 @@ def user_instance_update(request, pk):
|
||||||
@superuser_only
|
@superuser_only
|
||||||
def user_instance_delete(request, pk):
|
def user_instance_delete(request, pk):
|
||||||
user_instance = get_object_or_404(UserInstance, pk=pk)
|
user_instance = get_object_or_404(UserInstance, pk=pk)
|
||||||
if request.method == 'POST':
|
if request.method == "POST":
|
||||||
user = user_instance.user
|
user = user_instance.user
|
||||||
user_instance.delete()
|
user_instance.delete()
|
||||||
next = request.GET.get('next', None)
|
next = request.GET.get("next", None)
|
||||||
if next:
|
if next:
|
||||||
return redirect(next)
|
return redirect(next)
|
||||||
else:
|
else:
|
||||||
return redirect(reverse('account', args=[user.id]))
|
return redirect(reverse("account", args=[user.id]))
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'common/confirm_delete.html',
|
"common/confirm_delete.html",
|
||||||
{'object': user_instance},
|
{"object": user_instance},
|
||||||
)
|
)
|
||||||
|
|
|
@ -74,11 +74,12 @@ class UserForm(forms.ModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(UserForm, self).__init__(*args, **kwargs)
|
super(UserForm, self).__init__(*args, **kwargs)
|
||||||
if self.instance.id:
|
if self.instance.id:
|
||||||
password = ReadOnlyPasswordHashField(label=_("Password"),
|
password = ReadOnlyPasswordHashField(
|
||||||
|
label=_("Password"),
|
||||||
help_text=format_lazy(_("""Raw passwords are not stored, so there is no way to see
|
help_text=format_lazy(_("""Raw passwords are not stored, so there is no way to see
|
||||||
this user's password, but you can change the password
|
this user's password, but you can change the password using <a href='{}'>this form</a>."""),
|
||||||
using <a href='{}'>this form</a>."""),
|
reverse_lazy('admin:user_update_password',
|
||||||
reverse_lazy('admin:user_update_password', args=[self.instance.id,]))
|
args=[self.instance.id,]))
|
||||||
)
|
)
|
||||||
self.fields['Password'] = password
|
self.fields['Password'] = password
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
from django.contrib.auth.models import Permission as P
|
from django.contrib.auth.models import Permission as P
|
||||||
|
|
||||||
|
|
||||||
class Permission(P):
|
class Permission(P):
|
||||||
"""
|
"""
|
||||||
Proxy model to Django Permissions model allows us to override __str__
|
Proxy model to Django Permissions model allows us to override __str__
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.content_type.app_label}: {self.name}'
|
return f'{self.content_type.app_label}: {self.name}'
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from accounts.models import UserAttributes, UserInstance, Instance
|
from accounts.models import UserAttributes, UserInstance, Instance
|
||||||
from appsettings.models import AppSettings
|
from appsettings.settings import app_settings
|
||||||
from logs.models import Logs
|
from logs.models import Logs
|
||||||
|
|
||||||
from . import forms
|
from . import forms
|
||||||
|
@ -20,9 +20,9 @@ def group_list(request):
|
||||||
groups = Group.objects.all()
|
groups = Group.objects.all()
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'admin/group_list.html',
|
"admin/group_list.html",
|
||||||
{
|
{
|
||||||
'groups': groups,
|
"groups": groups,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,14 +32,14 @@ def group_create(request):
|
||||||
form = forms.GroupForm(request.POST or None)
|
form = forms.GroupForm(request.POST or None)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
return redirect('admin:group_list')
|
return redirect("admin:group_list")
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'common/form.html',
|
"common/form.html",
|
||||||
{
|
{
|
||||||
'form': form,
|
"form": form,
|
||||||
'title': _('Create Group'),
|
"title": _("Create Group"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,10 +54,10 @@ def group_update(request, pk):
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'common/form.html',
|
"common/form.html",
|
||||||
{
|
{
|
||||||
'form': form,
|
"form": form,
|
||||||
'title': _('Update Group'),
|
"title": _("Update Group"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -65,14 +65,14 @@ def group_update(request, pk):
|
||||||
@superuser_only
|
@superuser_only
|
||||||
def group_delete(request, pk):
|
def group_delete(request, pk):
|
||||||
group = get_object_or_404(Group, pk=pk)
|
group = get_object_or_404(Group, pk=pk)
|
||||||
if request.method == 'POST':
|
if request.method == "POST":
|
||||||
group.delete()
|
group.delete()
|
||||||
return redirect('admin:group_list')
|
return redirect("admin:group_list")
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'common/confirm_delete.html',
|
"common/confirm_delete.html",
|
||||||
{'object': group},
|
{"object": group},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,10 +81,10 @@ def user_list(request):
|
||||||
users = User.objects.all()
|
users = User.objects.all()
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'admin/user_list.html',
|
"admin/user_list.html",
|
||||||
{
|
{
|
||||||
'users': users,
|
"users": users,
|
||||||
'title': _('Users'),
|
"title": _("Users"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -95,22 +95,22 @@ def user_create(request):
|
||||||
attributes_form = forms.UserAttributesForm(request.POST or None)
|
attributes_form = forms.UserAttributesForm(request.POST or None)
|
||||||
if user_form.is_valid() and attributes_form.is_valid():
|
if user_form.is_valid() and attributes_form.is_valid():
|
||||||
user = user_form.save()
|
user = user_form.save()
|
||||||
password = user_form.cleaned_data['password']
|
password = user_form.cleaned_data["password"]
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.save()
|
user.save()
|
||||||
attributes = attributes_form.save(commit=False)
|
attributes = attributes_form.save(commit=False)
|
||||||
attributes.user = user
|
attributes.user = user
|
||||||
attributes.save()
|
attributes.save()
|
||||||
add_default_instances(user)
|
add_default_instances(user)
|
||||||
return redirect('admin:user_list')
|
return redirect("admin:user_list")
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'admin/user_form.html',
|
"admin/user_form.html",
|
||||||
{
|
{
|
||||||
'user_form': user_form,
|
"user_form": user_form,
|
||||||
'attributes_form': attributes_form,
|
"attributes_form": attributes_form,
|
||||||
'title': _('Create User')
|
"title": _("Create User")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -120,22 +120,24 @@ def user_update(request, pk):
|
||||||
user = get_object_or_404(User, pk=pk)
|
user = get_object_or_404(User, pk=pk)
|
||||||
attributes = UserAttributes.objects.get(user=user)
|
attributes = UserAttributes.objects.get(user=user)
|
||||||
user_form = forms.UserForm(request.POST or None, instance=user)
|
user_form = forms.UserForm(request.POST or None, instance=user)
|
||||||
attributes_form = forms.UserAttributesForm(request.POST or None, instance=attributes)
|
attributes_form = forms.UserAttributesForm(
|
||||||
|
request.POST or None, instance=attributes)
|
||||||
if user_form.is_valid() and attributes_form.is_valid():
|
if user_form.is_valid() and attributes_form.is_valid():
|
||||||
user_form.save()
|
user_form.save()
|
||||||
attributes_form.save()
|
attributes_form.save()
|
||||||
return redirect('admin:user_list')
|
return redirect("admin:user_list")
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'admin/user_form.html',
|
"admin/user_form.html",
|
||||||
{
|
{
|
||||||
'user_form': user_form,
|
"user_form": user_form,
|
||||||
'attributes_form': attributes_form,
|
"attributes_form": attributes_form,
|
||||||
'title': _('Update User')
|
"title": _("Update User")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@superuser_only
|
@superuser_only
|
||||||
def user_update_password(request, pk):
|
def user_update_password(request, pk):
|
||||||
user = get_object_or_404(User, pk=pk)
|
user = get_object_or_404(User, pk=pk)
|
||||||
|
@ -144,33 +146,35 @@ def user_update_password(request, pk):
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
user = form.save()
|
user = form.save()
|
||||||
update_session_auth_hash(request, user) # Important!
|
update_session_auth_hash(request, user) # Important!
|
||||||
messages.success(request, _('User password changed: {}'.format(user.username)))
|
messages.success(request, _(
|
||||||
return redirect('admin:user_list')
|
"User password changed: {}".format(user.username)))
|
||||||
|
return redirect("admin:user_list")
|
||||||
else:
|
else:
|
||||||
messages.error(request, _('Wrong Data Provided'))
|
messages.error(request, _("Wrong Data Provided"))
|
||||||
else:
|
else:
|
||||||
form = AdminPasswordChangeForm(user)
|
form = AdminPasswordChangeForm(user)
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'accounts/change_password_form.html',
|
"accounts/change_password_form.html",
|
||||||
{
|
{
|
||||||
'form': form,
|
"form": form,
|
||||||
'user': user.username
|
"user": user.username
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@superuser_only
|
@superuser_only
|
||||||
def user_delete(request, pk):
|
def user_delete(request, pk):
|
||||||
user = get_object_or_404(User, pk=pk)
|
user = get_object_or_404(User, pk=pk)
|
||||||
if request.method == 'POST':
|
if request.method == "POST":
|
||||||
user.delete()
|
user.delete()
|
||||||
return redirect('admin:user_list')
|
return redirect("admin:user_list")
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'common/confirm_delete.html',
|
"common/confirm_delete.html",
|
||||||
{'object': user},
|
{"object": user},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -179,7 +183,7 @@ def user_block(request, pk):
|
||||||
user: User = get_object_or_404(User, pk=pk)
|
user: User = get_object_or_404(User, pk=pk)
|
||||||
user.is_active = False
|
user.is_active = False
|
||||||
user.save()
|
user.save()
|
||||||
return redirect('admin:user_list')
|
return redirect("admin:user_list")
|
||||||
|
|
||||||
|
|
||||||
@superuser_only
|
@superuser_only
|
||||||
|
@ -187,16 +191,16 @@ def user_unblock(request, pk):
|
||||||
user: User = get_object_or_404(User, pk=pk)
|
user: User = get_object_or_404(User, pk=pk)
|
||||||
user.is_active = True
|
user.is_active = True
|
||||||
user.save()
|
user.save()
|
||||||
return redirect('admin:user_list')
|
return redirect("admin:user_list")
|
||||||
|
|
||||||
|
|
||||||
@superuser_only
|
@superuser_only
|
||||||
def logs(request):
|
def logs(request):
|
||||||
l = Logs.objects.order_by('-date')
|
l = Logs.objects.order_by("-date")
|
||||||
paginator = Paginator(l, int(AppSettings.objects.get(key="LOGS_PER_PAGE").value))
|
paginator = Paginator(l, int(app_settings.LOGS_PER_PAGE))
|
||||||
page = request.GET.get('page', 1)
|
page = request.GET.get("page", 1)
|
||||||
logs = paginator.page(page)
|
logs = paginator.page(page)
|
||||||
return render(request, 'admin/logs.html', {'logs': logs})
|
return render(request, "admin/logs.html", {"logs": logs})
|
||||||
|
|
||||||
|
|
||||||
def add_default_instances(user):
|
def add_default_instances(user):
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
beautifulsoup4==4.9.1
|
beautifulsoup4==4.9.1
|
||||||
Django==2.2.14
|
Django==2.2.16
|
||||||
django-bootstrap4==2.2.0
|
django-bootstrap4==2.2.0
|
||||||
django-icons==2.1.1
|
django-icons==2.1.1
|
||||||
django-login-required-middleware==0.5.0
|
django-login-required-middleware==0.5.0
|
||||||
gunicorn==20.0.4
|
gunicorn==20.0.4
|
||||||
libsass==0.20.0
|
libsass==0.20.1
|
||||||
libvirt-python==6.4.0
|
libvirt-python==6.7.0
|
||||||
lxml==4.5.2
|
lxml==4.5.2
|
||||||
numpy==1.18.5
|
numpy==1.18.5
|
||||||
pytz==2020.1
|
pytz==2020.1
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# `/sbin/setuser www-data` runs the given command as the user `www-data`.
|
# `/sbin/setuser www-data` runs the given command as the user `www-data`.
|
||||||
RUNAS=`which setuser`
|
RUNAS=$(which setuser)
|
||||||
[ -z $RUNAS ] && RUNAS="`which sudo` -u"
|
[ -z "$RUNAS" ] && RUNAS="$(which sudo) -u"
|
||||||
USER=www-data
|
USER=www-data
|
||||||
|
|
||||||
DJANGO_PROJECT=/srv/webvirtcloud
|
DJANGO_PROJECT=/srv/webvirtcloud
|
||||||
|
@ -14,5 +14,5 @@ NOVNCD=$DJANGO_PROJECT/console/novncd
|
||||||
|
|
||||||
LOG=/var/log/novncd.log
|
LOG=/var/log/novncd.log
|
||||||
|
|
||||||
cd $DJANGO_PROJECT
|
cd $DJANGO_PROJECT || exit
|
||||||
exec $RUNAS $USER $PYTHON $NOVNCD $PARAMS >> $LOG 2>&1
|
exec "$RUNAS" "$USER" "$PYTHON" "$NOVNCD" "$PARAMS" >> $LOG 2>&1
|
||||||
|
|
|
@ -21,6 +21,7 @@ class _TunnelScheduler(object):
|
||||||
It's only instantiated once for the whole app, because we serialize
|
It's only instantiated once for the whole app, because we serialize
|
||||||
independent of connection, vm, etc.
|
independent of connection, vm, etc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._thread = None
|
self._thread = None
|
||||||
self._queue = queue.Queue()
|
self._queue = queue.Queue()
|
||||||
|
@ -44,6 +45,7 @@ class _TunnelScheduler(object):
|
||||||
|
|
||||||
def lock(self):
|
def lock(self):
|
||||||
self._lock.acquire()
|
self._lock.acquire()
|
||||||
|
|
||||||
def unlock(self):
|
def unlock(self):
|
||||||
self._lock.release()
|
self._lock.release()
|
||||||
|
|
||||||
|
@ -63,7 +65,7 @@ class _Tunnel(object):
|
||||||
self._closed = True
|
self._closed = True
|
||||||
|
|
||||||
log.debug("Close tunnel PID=%s ERRFD=%s",
|
log.debug("Close tunnel PID=%s ERRFD=%s",
|
||||||
self._pid, self._errfd and self._errfd.fileno() or None)
|
self._pid, self._errfd and self._errfd.fileno() or None)
|
||||||
|
|
||||||
# Since this is a socket object, the file descriptor is closed
|
# Since this is a socket object, the file descriptor is closed
|
||||||
# when it's garbage collected.
|
# when it's garbage collected.
|
||||||
|
@ -110,13 +112,12 @@ class _Tunnel(object):
|
||||||
self._errfd = errfds[0]
|
self._errfd = errfds[0]
|
||||||
self._errfd.setblocking(0)
|
self._errfd.setblocking(0)
|
||||||
log.debug("Opened tunnel PID=%d ERRFD=%d",
|
log.debug("Opened tunnel PID=%d ERRFD=%d",
|
||||||
pid, self._errfd.fileno())
|
pid, self._errfd.fileno())
|
||||||
|
|
||||||
self._pid = pid
|
self._pid = pid
|
||||||
|
|
||||||
|
|
||||||
def _make_ssh_command(connhost, connuser, connport, gaddr, gport, gsocket):
|
def _make_ssh_command(connhost, connuser, connport, gaddr, gport, gsocket):
|
||||||
|
|
||||||
|
|
||||||
# Build SSH cmd
|
# Build SSH cmd
|
||||||
argv = ["ssh", "ssh"]
|
argv = ["ssh", "ssh"]
|
||||||
|
@ -165,7 +166,8 @@ def _make_ssh_command(connhost, connuser, connport, gaddr, gport, gsocket):
|
||||||
class SSHTunnels(object):
|
class SSHTunnels(object):
|
||||||
def __init__(self, connhost, connuser, connport, gaddr, gport, gsocket):
|
def __init__(self, connhost, connuser, connport, gaddr, gport, gsocket):
|
||||||
self._tunnels = []
|
self._tunnels = []
|
||||||
self._sshcommand = _make_ssh_command(connhost, connuser, connport, gaddr, gport, gsocket)
|
self._sshcommand = _make_ssh_command(
|
||||||
|
connhost, connuser, connport, gaddr, gport, gsocket)
|
||||||
self._locked = False
|
self._locked = False
|
||||||
|
|
||||||
def open_new(self):
|
def open_new(self):
|
||||||
|
@ -206,4 +208,4 @@ class SSHTunnels(object):
|
||||||
def unlock(self, *args, **kwargs):
|
def unlock(self, *args, **kwargs):
|
||||||
if self._locked:
|
if self._locked:
|
||||||
_tunnel_scheduler.unlock(*args, **kwargs)
|
_tunnel_scheduler.unlock(*args, **kwargs)
|
||||||
self._locked = False
|
self._locked = False
|
||||||
|
|
|
@ -6,8 +6,7 @@ from libvirt import libvirtError
|
||||||
from appsettings.settings import app_settings
|
from appsettings.settings import app_settings
|
||||||
from instances.models import Instance
|
from instances.models import Instance
|
||||||
from vrtManager.instance import wvmInstance
|
from vrtManager.instance import wvmInstance
|
||||||
from webvirtcloud.settings import (WS_PUBLIC_HOST, WS_PUBLIC_PATH,
|
from webvirtcloud.settings import WS_PUBLIC_HOST, WS_PUBLIC_PATH, WS_PUBLIC_PORT
|
||||||
WS_PUBLIC_PORT)
|
|
||||||
|
|
||||||
|
|
||||||
def console(request):
|
def console(request):
|
||||||
|
@ -17,16 +16,19 @@ def console(request):
|
||||||
"""
|
"""
|
||||||
console_error = None
|
console_error = None
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == "GET":
|
||||||
token = request.GET.get('token', '')
|
token = request.GET.get("token", "")
|
||||||
view_type = request.GET.get('view', 'lite')
|
view_type = request.GET.get("view", "lite")
|
||||||
view_only = request.GET.get('view_only', app_settings.CONSOLE_VIEW_ONLY.lower())
|
view_only = request.GET.get(
|
||||||
scale = request.GET.get('scale', app_settings.CONSOLE_SCALE.lower())
|
"view_only", app_settings.CONSOLE_VIEW_ONLY.lower())
|
||||||
resize_session = request.GET.get('resize_session', app_settings.CONSOLE_RESIZE_SESSION.lower())
|
scale = request.GET.get("scale", app_settings.CONSOLE_SCALE.lower())
|
||||||
clip_viewport = request.GET.get('clip_viewport', app_settings.CONSOLE_CLIP_VIEWPORT.lower())
|
resize_session = request.GET.get(
|
||||||
|
"resize_session", app_settings.CONSOLE_RESIZE_SESSION.lower())
|
||||||
|
clip_viewport = request.GET.get(
|
||||||
|
"clip_viewport", app_settings.CONSOLE_CLIP_VIEWPORT.lower())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
temptoken = token.split('-', 1)
|
temptoken = token.split("-", 1)
|
||||||
host = int(temptoken[0])
|
host = int(temptoken[0])
|
||||||
uuid = temptoken[1]
|
uuid = temptoken[1]
|
||||||
instance = Instance.objects.get(compute_id=host, uuid=uuid)
|
instance = Instance.objects.get(compute_id=host, uuid=uuid)
|
||||||
|
@ -47,20 +49,20 @@ def console(request):
|
||||||
|
|
||||||
ws_port = console_websocket_port if console_websocket_port else WS_PUBLIC_PORT
|
ws_port = console_websocket_port if console_websocket_port else WS_PUBLIC_PORT
|
||||||
ws_host = WS_PUBLIC_HOST if WS_PUBLIC_HOST else request.get_host()
|
ws_host = WS_PUBLIC_HOST if WS_PUBLIC_HOST else request.get_host()
|
||||||
ws_path = WS_PUBLIC_PATH if WS_PUBLIC_PATH else '/'
|
ws_path = WS_PUBLIC_PATH if WS_PUBLIC_PATH else "/"
|
||||||
|
|
||||||
if ':' in ws_host:
|
if ":" in ws_host:
|
||||||
ws_host = re.sub(':[0-9]+', '', ws_host)
|
ws_host = re.sub(":[0-9]+", "", ws_host)
|
||||||
|
|
||||||
if console_type == 'vnc' or console_type == 'spice':
|
if console_type == "vnc" or console_type == "spice":
|
||||||
console_page = "console-" + console_type + "-" + view_type + ".html"
|
console_page = "console-" + console_type + "-" + view_type + ".html"
|
||||||
response = render(request, console_page, locals())
|
response = render(request, console_page, locals())
|
||||||
else:
|
else:
|
||||||
if console_type is None:
|
if console_type is None:
|
||||||
console_error = f"Fail to get console. Please check the console configuration of your VM."
|
console_error = "Fail to get console. Please check the console configuration of your VM."
|
||||||
else:
|
else:
|
||||||
console_error = f"Console type: {console_type} no support"
|
console_error = f"Console type: {console_type} no support"
|
||||||
response = render(request, 'console-vnc-lite.html', locals())
|
response = render(request, "console-vnc-lite.html", locals())
|
||||||
|
|
||||||
response.set_cookie('token', token)
|
response.set_cookie("token", token)
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
|
|
@ -48,10 +48,10 @@ def os_userdata(request, version):
|
||||||
ip = get_client_ip(request)
|
ip = get_client_ip(request)
|
||||||
hostname = get_hostname_by_ip(ip)
|
hostname = get_hostname_by_ip(ip)
|
||||||
vname = hostname.split('.')[0]
|
vname = hostname.split('.')[0]
|
||||||
|
|
||||||
instance_keys = []
|
instance_keys = []
|
||||||
userinstances = UserInstance.objects.filter(instance__name=vname)
|
userinstances = UserInstance.objects.filter(instance__name=vname)
|
||||||
|
|
||||||
for ui in userinstances:
|
for ui in userinstances:
|
||||||
keys = UserSSHKey.objects.filter(user=ui.user)
|
keys = UserSSHKey.objects.filter(user=ui.user)
|
||||||
for k in keys:
|
for k in keys:
|
||||||
|
@ -95,7 +95,7 @@ def get_vdi_url(request, compute_id, vname):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
compute = get_object_or_404(Compute, pk=compute_id)
|
compute = get_object_or_404(Compute, pk=compute_id)
|
||||||
data = {}
|
|
||||||
try:
|
try:
|
||||||
conn = wvmInstance(compute.hostname,
|
conn = wvmInstance(compute.hostname,
|
||||||
compute.login,
|
compute.login,
|
||||||
|
@ -107,6 +107,6 @@ def get_vdi_url(request, compute_id, vname):
|
||||||
url = f"{conn.get_console_type()}://{fqdn}:{conn.get_console_port()}"
|
url = f"{conn.get_console_type()}://{fqdn}:{conn.get_console_port()}"
|
||||||
response = url
|
response = url
|
||||||
return HttpResponse(response)
|
return HttpResponse(response)
|
||||||
except libvirtError as lib_err:
|
except libvirtError:
|
||||||
err = f"Error getting VDI URL for {vname}"
|
err = f"Error getting VDI URL for {vname}"
|
||||||
raise Http404(err)
|
raise Http404(err)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
-r ../conf/requirements.txt
|
-r ../conf/requirements.txt
|
||||||
coverage==5.2
|
coverage==5.3
|
||||||
django-debug-toolbar==2.2
|
django-debug-toolbar==2.2
|
||||||
pycodestyle==2.6.0
|
pycodestyle==2.6.0
|
||||||
pyflakes==2.2.0
|
pyflakes==2.2.0
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
# ensure running as root
|
# ensure running as root
|
||||||
if [ "$(id -u)" != "0" ]; then
|
if [ "$(id -u)" != "0" ]; then
|
||||||
#Debian doesnt have sudo if root has a password.
|
#Debian doesnt have sudo if root has a password.
|
||||||
|
|
|
@ -1,16 +1,26 @@
|
||||||
import time
|
import time
|
||||||
import os.path
|
import os.path
|
||||||
try:
|
try:
|
||||||
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_DOMAIN_RUNNING, VIR_DOMAIN_AFFECT_LIVE, \
|
from libvirt import (
|
||||||
VIR_DOMAIN_AFFECT_CONFIG, VIR_DOMAIN_UNDEFINE_NVRAM, VIR_DOMAIN_UNDEFINE_KEEP_NVRAM, VIR_DOMAIN_START_PAUSED
|
libvirtError,
|
||||||
from libvirt import VIR_MIGRATE_LIVE, \
|
VIR_DOMAIN_XML_SECURE,
|
||||||
VIR_MIGRATE_UNSAFE, \
|
VIR_DOMAIN_RUNNING,
|
||||||
VIR_MIGRATE_PERSIST_DEST, \
|
VIR_DOMAIN_AFFECT_LIVE,
|
||||||
VIR_MIGRATE_UNDEFINE_SOURCE, \
|
VIR_DOMAIN_AFFECT_CONFIG,
|
||||||
VIR_MIGRATE_OFFLINE,\
|
VIR_DOMAIN_UNDEFINE_NVRAM,
|
||||||
VIR_MIGRATE_COMPRESSED, \
|
VIR_DOMAIN_UNDEFINE_KEEP_NVRAM,
|
||||||
VIR_MIGRATE_AUTO_CONVERGE, \
|
VIR_DOMAIN_START_PAUSED
|
||||||
|
)
|
||||||
|
from libvirt import (
|
||||||
|
VIR_MIGRATE_LIVE,
|
||||||
|
VIR_MIGRATE_UNSAFE,
|
||||||
|
VIR_MIGRATE_PERSIST_DEST,
|
||||||
|
VIR_MIGRATE_UNDEFINE_SOURCE,
|
||||||
|
VIR_MIGRATE_OFFLINE,
|
||||||
|
VIR_MIGRATE_COMPRESSED,
|
||||||
|
VIR_MIGRATE_AUTO_CONVERGE,
|
||||||
VIR_MIGRATE_POSTCOPY
|
VIR_MIGRATE_POSTCOPY
|
||||||
|
)
|
||||||
from libvirt import VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT
|
from libvirt import VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT
|
||||||
except:
|
except:
|
||||||
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE
|
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE
|
||||||
|
|
|
@ -19,6 +19,7 @@ from time import time
|
||||||
# Read write lock
|
# Read write lock
|
||||||
# ---------------
|
# ---------------
|
||||||
|
|
||||||
|
|
||||||
class ReadWriteLock(object):
|
class ReadWriteLock(object):
|
||||||
"""Read-Write lock class. A read-write lock differs from a standard
|
"""Read-Write lock class. A read-write lock differs from a standard
|
||||||
threading.RLock() by allowing multiple threads to simultaneously hold a
|
threading.RLock() by allowing multiple threads to simultaneously hold a
|
||||||
|
|
|
@ -15,9 +15,15 @@ class wvmStorages(wvmConnect):
|
||||||
else:
|
else:
|
||||||
stg_vol = None
|
stg_vol = None
|
||||||
stg_size = stg.info()[1]
|
stg_size = stg.info()[1]
|
||||||
storages.append({'name': pool, 'status': stg_status,
|
storages.append(
|
||||||
'type': stg_type, 'volumes': stg_vol,
|
{
|
||||||
'size': stg_size})
|
"name": pool,
|
||||||
|
"status": stg_status,
|
||||||
|
"type": stg_type,
|
||||||
|
"volumes": stg_vol,
|
||||||
|
"size": stg_size
|
||||||
|
}
|
||||||
|
)
|
||||||
return storages
|
return storages
|
||||||
|
|
||||||
def define_storage(self, xml, flag):
|
def define_storage(self, xml, flag):
|
||||||
|
@ -26,14 +32,14 @@ class wvmStorages(wvmConnect):
|
||||||
def create_storage(self, stg_type, name, source, target):
|
def create_storage(self, stg_type, name, source, target):
|
||||||
xml = f"""<pool type='{stg_type}'>
|
xml = f"""<pool type='{stg_type}'>
|
||||||
<name>{name}</name>"""
|
<name>{name}</name>"""
|
||||||
if stg_type == 'logical':
|
if stg_type == "logical":
|
||||||
xml += f"""<source>
|
xml += f"""<source>
|
||||||
<device path='{source}'/>
|
<device path='{source}'/>
|
||||||
<name>{name}</name>
|
<name>{name}</name>
|
||||||
<format type='lvm2'/>
|
<format type='lvm2'/>
|
||||||
</source>"""
|
</source>"""
|
||||||
if stg_type == 'logical':
|
if stg_type == "logical":
|
||||||
target = '/dev/' + name
|
target = "/dev/" + name
|
||||||
xml += f"""
|
xml += f"""
|
||||||
<target>
|
<target>
|
||||||
<path>{target}</path>
|
<path>{target}</path>
|
||||||
|
@ -41,7 +47,7 @@ class wvmStorages(wvmConnect):
|
||||||
</pool>"""
|
</pool>"""
|
||||||
self.define_storage(xml, 0)
|
self.define_storage(xml, 0)
|
||||||
stg = self.get_storage(name)
|
stg = self.get_storage(name)
|
||||||
if stg_type == 'logical':
|
if stg_type == "logical":
|
||||||
stg.build(0)
|
stg.build(0)
|
||||||
stg.create(0)
|
stg.create(0)
|
||||||
stg.setAutostart(1)
|
stg.setAutostart(1)
|
||||||
|
@ -97,11 +103,16 @@ class wvmStorage(wvmConnect):
|
||||||
return self.pool.name()
|
return self.pool.name()
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
status = ['Not running', 'Initializing pool, not available', 'Running normally', 'Running degraded']
|
status = [
|
||||||
|
"Not running",
|
||||||
|
"Initializing pool, not available",
|
||||||
|
"Running normally",
|
||||||
|
"Running degraded"
|
||||||
|
]
|
||||||
try:
|
try:
|
||||||
return status[self.pool.info()[0]]
|
return status[self.pool.info()[0]]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return 'Unknown'
|
return "Unknown"
|
||||||
|
|
||||||
def get_size(self):
|
def get_size(self):
|
||||||
return [self.pool.info()[1], self.pool.info()[3]]
|
return [self.pool.info()[1], self.pool.info()[3]]
|
||||||
|
@ -202,10 +213,12 @@ class wvmStorage(wvmConnect):
|
||||||
|
|
||||||
for volname in vols:
|
for volname in vols:
|
||||||
vol_list.append(
|
vol_list.append(
|
||||||
{'name': volname,
|
{
|
||||||
'size': self.get_volume_size(volname),
|
"name": volname,
|
||||||
'allocation': self.get_volume_allocation(volname),
|
"size": self.get_volume_size(volname),
|
||||||
'type': self.get_volume_type(volname)}
|
"allocation": self.get_volume_allocation(volname),
|
||||||
|
"type": self.get_volume_type(volname)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
return vol_list
|
return vol_list
|
||||||
|
|
||||||
|
@ -213,13 +226,13 @@ class wvmStorage(wvmConnect):
|
||||||
size = int(size) * 1073741824
|
size = int(size) * 1073741824
|
||||||
storage_type = self.get_type()
|
storage_type = self.get_type()
|
||||||
alloc = size
|
alloc = size
|
||||||
if vol_fmt == 'unknown':
|
if vol_fmt == "unknown":
|
||||||
vol_fmt = 'raw'
|
vol_fmt = "raw"
|
||||||
if storage_type == 'dir':
|
if storage_type == "dir":
|
||||||
if vol_fmt in ('qcow', 'qcow2'):
|
if vol_fmt in ("qcow", "qcow2"):
|
||||||
name += '.' + vol_fmt
|
name += "." + vol_fmt
|
||||||
else:
|
else:
|
||||||
name += '.img'
|
name += ".img"
|
||||||
alloc = 0
|
alloc = 0
|
||||||
xml = f"""
|
xml = f"""
|
||||||
<volume>
|
<volume>
|
||||||
|
@ -234,7 +247,7 @@ class wvmStorage(wvmConnect):
|
||||||
<mode>0644</mode>
|
<mode>0644</mode>
|
||||||
<label>virt_image_t</label>
|
<label>virt_image_t</label>
|
||||||
</permissions>"""
|
</permissions>"""
|
||||||
if vol_fmt == 'qcow2':
|
if vol_fmt == "qcow2":
|
||||||
xml += """
|
xml += """
|
||||||
<compat>1.1</compat>
|
<compat>1.1</compat>
|
||||||
<features>
|
<features>
|
||||||
|
@ -251,8 +264,8 @@ class wvmStorage(wvmConnect):
|
||||||
vol_fmt = self.get_volume_type(name)
|
vol_fmt = self.get_volume_type(name)
|
||||||
|
|
||||||
storage_type = self.get_type()
|
storage_type = self.get_type()
|
||||||
if storage_type == 'dir':
|
if storage_type == "dir":
|
||||||
if vol_fmt in ['qcow', 'qcow2']:
|
if vol_fmt in ["qcow", "qcow2"]:
|
||||||
target_file += '.' + vol_fmt
|
target_file += '.' + vol_fmt
|
||||||
else:
|
else:
|
||||||
suffix = '.' + file_suffix
|
suffix = '.' + file_suffix
|
||||||
|
@ -271,7 +284,7 @@ class wvmStorage(wvmConnect):
|
||||||
<mode>{mode}</mode>
|
<mode>{mode}</mode>
|
||||||
<label>virt_image_t</label>
|
<label>virt_image_t</label>
|
||||||
</permissions>"""
|
</permissions>"""
|
||||||
if vol_fmt == 'qcow2':
|
if vol_fmt == "qcow2":
|
||||||
xml += """
|
xml += """
|
||||||
<compat>1.1</compat>
|
<compat>1.1</compat>
|
||||||
<features>
|
<features>
|
||||||
|
|
|
@ -139,13 +139,13 @@ configure_nginx () {
|
||||||
rm /etc/nginx/sites-enabled/default
|
rm /etc/nginx/sites-enabled/default
|
||||||
fi
|
fi
|
||||||
|
|
||||||
chown -R $nginx_group:$nginx_group /var/lib/nginx
|
chown -R "$nginx_group":"$nginx_group" /var/lib/nginx
|
||||||
# Copy new configuration and webvirtcloud.conf
|
# Copy new configuration and webvirtcloud.conf
|
||||||
echo " * Copying Nginx configuration"
|
echo " * Copying Nginx configuration"
|
||||||
cp $APP_PATH/conf/nginx/"$distro"_nginx.conf /etc/nginx/nginx.conf
|
cp "$APP_PATH"/conf/nginx/"$distro"_nginx.conf /etc/nginx/nginx.conf
|
||||||
cp $APP_PATH/conf/nginx/webvirtcloud.conf /etc/nginx/conf.d/
|
cp "$APP_PATH"/conf/nginx/webvirtcloud.conf /etc/nginx/conf.d/
|
||||||
|
|
||||||
if ! [ -z "$fqdn" ]; then
|
if [ -n "$fqdn" ]; then
|
||||||
sed -i "s|\\(#server_name\\).*|server_name = $fqdn|" "$nginxfile"
|
sed -i "s|\\(#server_name\\).*|server_name = $fqdn|" "$nginxfile"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ configure_nginx () {
|
||||||
configure_supervisor () {
|
configure_supervisor () {
|
||||||
# Copy template supervisor service for gunicorn and novnc
|
# Copy template supervisor service for gunicorn and novnc
|
||||||
echo " * Copying supervisor configuration"
|
echo " * Copying supervisor configuration"
|
||||||
cp $APP_PATH/conf/supervisor/webvirtcloud.conf $supervisor_conf_path/$supervisor_file_name
|
cp "$APP_PATH"/conf/supervisor/webvirtcloud.conf "$supervisor_conf_path"/"$supervisor_file_name"
|
||||||
sed -i "s|^\\(user=\\).*|\\1$nginx_group|" "$supervisor_conf_path/$supervisor_file_name"
|
sed -i "s|^\\(user=\\).*|\\1$nginx_group|" "$supervisor_conf_path/$supervisor_file_name"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,20 +174,20 @@ create_user () {
|
||||||
|
|
||||||
run_as_app_user () {
|
run_as_app_user () {
|
||||||
if ! hash sudo 2>/dev/null; then
|
if ! hash sudo 2>/dev/null; then
|
||||||
su -c "$@" $APP_USER
|
su -c "$@" "$APP_USER"
|
||||||
else
|
else
|
||||||
sudo -i -u $APP_USER "$@"
|
sudo -i -u "$APP_USER" "$@"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
activate_python_environment () {
|
activate_python_environment () {
|
||||||
cd $APP_PATH
|
cd "$APP_PATH" || exit
|
||||||
virtualenv -p $PYTHON venv
|
virtualenv -p "$PYTHON" venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
}
|
}
|
||||||
|
|
||||||
generate_secret_key() {
|
generate_secret_key() {
|
||||||
$PYTHON - <<END
|
"$PYTHON" - <<END
|
||||||
import random
|
import random
|
||||||
print(''.join(random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)))
|
print(''.join(random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)))
|
||||||
END
|
END
|
||||||
|
@ -418,7 +418,7 @@ case $distro in
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
ubuntu)
|
ubuntu)
|
||||||
if [ "$version" -ge "18.04" ]; then
|
if [ "$version" == "18.04" ] || [ "$version" == "20.04" ]; then
|
||||||
# Install for Ubuntu 18 / 20
|
# Install for Ubuntu 18 / 20
|
||||||
tzone=\'$(cat /etc/timezone)\'
|
tzone=\'$(cat /etc/timezone)\'
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from libvirt import libvirtError
|
from libvirt import libvirtError
|
||||||
|
|
|
@ -12,17 +12,17 @@ TEMPLATE_DEBUG = True
|
||||||
|
|
||||||
|
|
||||||
INSTALLED_APPS += [
|
INSTALLED_APPS += [
|
||||||
'debug_toolbar',
|
"debug_toolbar",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE += [
|
MIDDLEWARE += [
|
||||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
# DebugToolBar
|
# DebugToolBar
|
||||||
INTERNAL_IPS = (
|
INTERNAL_IPS = (
|
||||||
'127.0.0.1',
|
"127.0.0.1",
|
||||||
)
|
)
|
||||||
DEBUG_TOOLBAR_CONFIG = {
|
DEBUG_TOOLBAR_CONFIG = {
|
||||||
'INTERCEPT_REDIRECTS': False,
|
"INTERCEPT_REDIRECTS": False,
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,5 +25,5 @@ if settings.DEBUG:
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
path('__debug__/', include(debug_toolbar.urls)),
|
path('__debug__/', include(debug_toolbar.urls)),
|
||||||
]
|
]
|
||||||
except:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -12,11 +12,16 @@ import sys
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
# execfile('/srv/webvirtcloud/venv/bin/activate_this.py', dict(__file__='/srv/webvirtcloud/venv/bin/activate_this.py'))
|
# execfile('/srv/webvirtcloud/venv/bin/activate_this.py', dict(__file__='/srv/webvirtcloud/venv/bin/activate_this.py'))
|
||||||
exec(compile(open('/srv/webvirtcloud/venv/bin/activate', "rb").read(),
|
exec(
|
||||||
'/srv/webvirtcloud/venv/bin/activate', 'exec'))
|
compile(
|
||||||
|
open("/srv/webvirtcloud/venv/bin/activate", "rb").read(),
|
||||||
|
"/srv/webvirtcloud/venv/bin/activate",
|
||||||
|
"exec"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
sys.path.append('/srv/webvirtcloud')
|
sys.path.append("/srv/webvirtcloud")
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webvirtcloud.settings')
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webvirtcloud.settings")
|
||||||
|
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
||||||
|
|
Loading…
Reference in a new issue