1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2024-10-31 19:44:16 +00:00

Merge pull request #360 from catborise/master

some linter fixes
This commit is contained in:
Anatoliy Guskov 2020-09-27 17:12:01 +03:00 committed by GitHub
commit cbac82ba07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 285 additions and 205 deletions

View file

@ -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

View file

@ -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:

View file

@ -15,7 +15,6 @@
* 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!!!
How to update <code>gstfsd</code> daemon on hypervisor: How to update <code>gstfsd</code> daemon on hypervisor:
@ -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>

View file

@ -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']

View file

@ -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'))

View file

@ -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},
) )

View file

@ -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

View file

@ -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}'

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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()
@ -117,7 +119,6 @@ class _Tunnel(object):
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"]
if connport: if connport:
@ -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):

View file

@ -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

View file

@ -1,3 +1 @@
from django.db import models
# Create your models here. # Create your models here.

View file

@ -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)

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -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)\'

View file

@ -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

View file

@ -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,
} }

View file

@ -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

View file

@ -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()