From aee7fa34b81b3d0010ed1288eb67f69c3a95ffef Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 25 Sep 2020 09:21:37 +0300 Subject: [PATCH 1/5] fix user not found while editing user profile --- accounts/views.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/accounts/views.py b/accounts/views.py index 371400e..8b18b73 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -1,6 +1,5 @@ import os -import sass from django.contrib import messages from django.contrib.auth import update_session_auth_hash 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 admin.decorators import superuser_only -from appsettings.models import AppSettings from instances.models import Instance from . import forms @@ -27,8 +25,8 @@ def profile(request): if 'username' in request.POST: username = request.POST.get('username', '') email = request.POST.get('email', '') - user.first_name = username - user.email = email + request.user.first_name = username + request.user.email = email request.user.save() return HttpResponseRedirect(request.get_full_path()) if 'keyname' in request.POST: From 32343c1ddc698afd9b766c63e3367d1980d978b0 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 25 Sep 2020 09:53:34 +0300 Subject: [PATCH 2/5] update libvirt-python, django, libsass requirements --- conf/requirements.txt | 6 +++--- dev/requirements.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/conf/requirements.txt b/conf/requirements.txt index 73313b0..26f6535 100644 --- a/conf/requirements.txt +++ b/conf/requirements.txt @@ -1,11 +1,11 @@ beautifulsoup4==4.9.1 -Django==2.2.14 +Django==2.2.16 django-bootstrap4==2.2.0 django-icons==2.1.1 django-login-required-middleware==0.5.0 gunicorn==20.0.4 -libsass==0.20.0 -libvirt-python==6.4.0 +libsass==0.20.1 +libvirt-python==6.7.0 lxml==4.5.2 numpy==1.18.5 pytz==2020.1 diff --git a/dev/requirements.txt b/dev/requirements.txt index eb2abf6..233e29d 100644 --- a/dev/requirements.txt +++ b/dev/requirements.txt @@ -1,5 +1,5 @@ -r ../conf/requirements.txt -coverage==5.2 +coverage==5.3 django-debug-toolbar==2.2 pycodestyle==2.6.0 pyflakes==2.2.0 From ba8fa207371611ddbc5ffc7a203b644b67ec38e4 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 25 Sep 2020 13:26:17 +0300 Subject: [PATCH 3/5] fix linter warnings for bash --- conf/runit/novncd.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/conf/runit/novncd.sh b/conf/runit/novncd.sh index 05b6007..b071864 100755 --- a/conf/runit/novncd.sh +++ b/conf/runit/novncd.sh @@ -1,8 +1,8 @@ #!/bin/sh # `/sbin/setuser www-data` runs the given command as the user `www-data`. -RUNAS=`which setuser` -[ -z $RUNAS ] && RUNAS="`which sudo` -u" +RUNAS=$(which setuser) +[ -z "$RUNAS" ] && RUNAS="$(which sudo) -u" USER=www-data DJANGO_PROJECT=/srv/webvirtcloud @@ -14,5 +14,5 @@ NOVNCD=$DJANGO_PROJECT/console/novncd LOG=/var/log/novncd.log -cd $DJANGO_PROJECT -exec $RUNAS $USER $PYTHON $NOVNCD $PARAMS >> $LOG 2>&1 +cd $DJANGO_PROJECT || exit +exec "$RUNAS" "$USER" "$PYTHON" "$NOVNCD" "$PARAMS" >> $LOG 2>&1 From 119d874e857d758b978902be4c705b4fb7230336 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 25 Sep 2020 14:11:03 +0300 Subject: [PATCH 4/5] linter update & django update --- .github/workflows/linter.yml | 11 ++++++++--- .travis.yml | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 4bc6f7f..8182f1a 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -32,10 +32,15 @@ jobs: - name: Install dependencies run: | python3 -m pip install --upgrade pip - if [ -f dev/requirements.txt ]; then pip3 install -r dev/requirements.txt; fi - - name: Super-Linter - uses: docker://github/super-linter:v3.3.2 + if [ -f dev/requirements.txt ]; then pip3 install -r dev/requirements.txt; else pip3 install -r conf/requirements.txt; fi + ################################ + # Run Linter against code base # + ################################ + - name: Lint Code Base + uses: docker://github/super-linter:latest env: + FILTER_REGEX_EXCLUDE: .*[static|scss]/.* + DEFAULT_BRANCH: master VALIDATE_ANSIBLE: false VALIDATE_CLOJURE: false VALIDATE_COFFEE: false diff --git a/.travis.yml b/.travis.yml index 01d56bf..2eed2e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: python python: - "3.6" env: - - DJANGO=2.2.14 + - DJANGO=2.2.16 install: - pip install -r dev/requirements.txt script: From 922a8e62d1402066c21bbf53078b772a7a18990b Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 25 Sep 2020 15:57:17 +0300 Subject: [PATCH 5/5] fix some linter warnings --- README.md | 77 +++++++++++++++++++++-------- accounts/models.py | 2 +- accounts/tests.py | 2 +- accounts/views.py | 71 ++++++++++++++------------- admin/forms.py | 9 ++-- admin/models.py | 2 + admin/views.py | 94 +++++++++++++++++++----------------- console/sshtunnels.py | 12 +++-- console/views.py | 36 +++++++------- datasource/models.py | 2 - datasource/views.py | 8 +-- install.sh | 2 + vrtManager/instance.py | 28 +++++++---- vrtManager/rwlock.py | 1 + vrtManager/storage.py | 59 +++++++++++++--------- webvirtcloud.sh | 22 ++++----- webvirtcloud/middleware.py | 3 -- webvirtcloud/settings-dev.py | 10 ++-- webvirtcloud/urls.py | 2 +- webvirtcloud/wsgi_custom.py | 13 +++-- 20 files changed, 266 insertions(+), 189 deletions(-) diff --git a/README.md b/README.md index 53341c8..501bbf8 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,10 @@ * Manage Hypervisor Networks * Instance Console Access with Browsers * 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 change root password in Instance (Tested only Ubuntu) * Supports cloud-init datasource interface - ### 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. ## Quick Install with Installer (Beta) + 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. + ```bash wget https://raw.githubusercontent.com/retspen/webvirtcloud/master/install.sh chmod 744 install.sh @@ -40,7 +41,9 @@ chmod 744 install.sh ``` ## Manual Installation + ### Generate secret key + You should generate SECRET_KEY after cloning repo. Then put it into webvirtcloud/settings.py. ```python3 @@ -83,6 +86,7 @@ Setup libvirt and KVM on server ```bash wget -O - https://clck.ru/9V9fH | sudo sh ``` + Done!! 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 + ```bash virtualenv-3 venv source venv/bin/activate @@ -115,7 +120,8 @@ python3 manage.py migrate ``` #### 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 sudo vim /etc/supervisord.conf @@ -137,9 +143,10 @@ redirect_stderr=true ``` #### Edit the nginx.conf file + You will need to edit the main nginx.conf file as the one that comes from the rpm's will not work. Comment the following lines: -``` +```bash # 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: -``` + +```bash upstream gunicorn_server { #server unix:/srv/webvirtcloud/venv/wvcloud.socket 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): + ```bash sudo usermod -G kvm -a ``` Allow http ports on firewall: + ```bash sudo firewall-cmd --add-service=http 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: + ```bash sudo systemctl restart nginx && systemctl restart supervisord ``` And finally, check everything is running: + ```bash sudo supervisorctl status gstfsd RUNNING pid 24662, uptime 6:01:40 @@ -234,12 +246,14 @@ webvirtcloud RUNNING pid 24660, uptime 6:01:40 ``` #### Apache mod_wsgi configuration -``` + +```bash WSGIDaemonProcess webvirtcloud threads=2 maximum-requests=1000 display-name=webvirtcloud WSGIScriptAlias / /srv/webvirtcloud/webvirtcloud/wsgi_custom.py ``` #### Install final required packages for libvirtd and others on Host Server + ```bash wget -O - https://clck.ru/9V9fH | sudo sh ``` @@ -249,10 +263,12 @@ Done!! Go to http://serverip and you should see the login screen. ### Alternative running novncd via runit(Debian) + 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 mkdir /etc/service/novncd/ ln -s /srv/webvirtcloud/conf/runit/novncd.sh /etc/service/novncd/run @@ -260,16 +276,19 @@ systemctl start runit.service ``` ### Default credentials -
+
+```html
 login: admin
 password: admin
-
+``` ### Configuring Compute SSH connection + This is a short example of configuring cloud and compute side of the ssh connection. On the webvirtcloud machine you need to generate ssh keys and optionally disable StrictHostKeyChecking. -``` + +```bash chown www-data -R ~www-data sudo -u www-data ssh-keygen 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. -``` + +```bash sudo -u www-data ssh-copy-id root@compute1 ``` ### Host SMBIOS information is not available If you see warning -``` + +```bash 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. 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 -``` -$ sudo pacman -S dmidecode -$ systemctl restart libvirtd + +```bash +sudo pacman -S dmidecode +systemctl restart libvirtd ``` ### Cloud-init + Currently supports only root ssh authorized keys and hostname. Example configuration of the cloud-init client follows. -``` + +```bash datasource: OpenStack: metadata_urls: [ "http://webvirtcloud.domain.com/datasource" ] ``` ### Reverse-Proxy + Edit WS_PUBLIC_PORT at settings.py file to expose redirect to 80 or 443. Default: 6080 -``` + +```bash WS_PUBLIC_PORT = 80 ``` ## How To Update + ```bash # Go to Installation Directory cd /srv/webvirtcloud @@ -329,22 +359,27 @@ sudo service supervisor restart ``` ### Running tests + Server on which tests will be performed must have libvirt up and running. It must not contain vms. It must have `default` storage which not contain any disk images. It must have `default` network which must be on. Setup venv + ```bash python -m venv venv source venv/bin/activate pip install -r conf/requirements.txt ``` + Run tests + ```bash python manage.py test ``` ## Screenshots + Instance Detail: Instance List:
diff --git a/accounts/models.py b/accounts/models.py index afc1960..33b120c 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -21,7 +21,7 @@ class UserInstance(models.Model): objects = UserInstanceManager() 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: unique_together = ['user', 'instance'] diff --git a/accounts/tests.py b/accounts/tests.py index fba29bf..051eb0a 100644 --- a/accounts/tests.py +++ b/accounts/tests.py @@ -23,7 +23,7 @@ class AccountsTestCase(TestCase): 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')) response = client.get(reverse('logout')) diff --git a/accounts/views.py b/accounts/views.py index 8b18b73..2cf3bc1 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -21,17 +21,17 @@ def profile(request): error_messages = [] publickeys = UserSSHKey.objects.filter(user_id=request.user.id) - if request.method == 'POST': - if 'username' in request.POST: - username = request.POST.get('username', '') - email = request.POST.get('email', '') + if request.method == "POST": + if "username" in request.POST: + username = request.POST.get("username", "") + email = request.POST.get("email", "") request.user.first_name = username request.user.email = email request.user.save() return HttpResponseRedirect(request.get_full_path()) - if 'keyname' in request.POST: - keyname = request.POST.get('keyname', '') - keypublic = request.POST.get('keypublic', '') + if "keyname" in request.POST: + keyname = request.POST.get("keyname", "") + keypublic = request.POST.get("keypublic", "") for key in publickeys: if keyname == key.keyname: msg = _("Key name already exist") @@ -39,19 +39,20 @@ def profile(request): if keypublic == key.keypublic: msg = _("Public key already exist") 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") error_messages.append(msg) 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() return HttpResponseRedirect(request.get_full_path()) - if 'keydelete' in request.POST: - keyid = request.POST.get('keyid', '') + if "keydelete" in request.POST: + keyid = request.POST.get("keyid", "") delkeypublic = UserSSHKey.objects.get(id=keyid) delkeypublic.delete() return HttpResponseRedirect(request.get_full_path()) - return render(request, 'profile.html', locals()) + return render(request, "profile.html", locals()) @superuser_only @@ -59,43 +60,47 @@ def account(request, user_id): error_messages = [] user = User.objects.get(id=user_id) user_insts = UserInstance.objects.filter(user_id=user_id) - instances = Instance.objects.all().order_by('name') + instances = Instance.objects.all().order_by("name") 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): - if request.method == 'POST': + if request.method == "POST": form = PasswordChangeForm(request.user, request.POST) if form.is_valid(): user = form.save() update_session_auth_hash(request, user) # Important! - messages.success(request, _('Password Changed')) - return redirect('profile') + messages.success(request, _("Password Changed")) + return redirect("profile") else: - messages.error(request, _('Wrong Data Provided')) + messages.error(request, _("Wrong Data Provided")) else: 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 def user_instance_create(request, 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(): form.save() - return redirect(reverse('account', args=[user.id])) + return redirect(reverse("account", args=[user.id])) return render( request, - 'common/form.html', + "common/form.html", { - 'form': form, - 'title': _('Create User Instance'), + "form": form, + "title": _("Create User Instance"), }, ) @@ -106,14 +111,14 @@ def user_instance_update(request, pk): form = forms.UserInstanceForm(request.POST or None, instance=user_instance) if form.is_valid(): form.save() - return redirect(reverse('account', args=[user_instance.user.id])) + return redirect(reverse("account", args=[user_instance.user.id])) return render( request, 'common/form.html', { - 'form': form, - 'title': _('Update User Instance'), + "form": form, + "title": _("Update User Instance"), }, ) @@ -121,17 +126,17 @@ def user_instance_update(request, pk): @superuser_only def user_instance_delete(request, pk): user_instance = get_object_or_404(UserInstance, pk=pk) - if request.method == 'POST': + if request.method == "POST": user = user_instance.user user_instance.delete() - next = request.GET.get('next', None) + next = request.GET.get("next", None) if next: return redirect(next) else: - return redirect(reverse('account', args=[user.id])) + return redirect(reverse("account", args=[user.id])) return render( request, - 'common/confirm_delete.html', - {'object': user_instance}, + "common/confirm_delete.html", + {"object": user_instance}, ) diff --git a/admin/forms.py b/admin/forms.py index 72e6d0a..c045edc 100644 --- a/admin/forms.py +++ b/admin/forms.py @@ -74,11 +74,12 @@ class UserForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(UserForm, self).__init__(*args, **kwargs) 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 - this user's password, but you can change the password - using this form."""), - reverse_lazy('admin:user_update_password', args=[self.instance.id,])) + this user's password, but you can change the password using this form."""), + reverse_lazy('admin:user_update_password', + args=[self.instance.id,])) ) self.fields['Password'] = password diff --git a/admin/models.py b/admin/models.py index 73ddb5d..b743377 100644 --- a/admin/models.py +++ b/admin/models.py @@ -1,9 +1,11 @@ from django.contrib.auth.models import Permission as P + class Permission(P): """ Proxy model to Django Permissions model allows us to override __str__ """ + def __str__(self): return f'{self.content_type.app_label}: {self.name}' diff --git a/admin/views.py b/admin/views.py index 2cd1e71..bcadc0b 100644 --- a/admin/views.py +++ b/admin/views.py @@ -8,7 +8,7 @@ from django.shortcuts import get_object_or_404, redirect, render from django.utils.translation import ugettext_lazy as _ from accounts.models import UserAttributes, UserInstance, Instance -from appsettings.models import AppSettings +from appsettings.settings import app_settings from logs.models import Logs from . import forms @@ -20,9 +20,9 @@ def group_list(request): groups = Group.objects.all() return render( 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) if form.is_valid(): form.save() - return redirect('admin:group_list') + return redirect("admin:group_list") return render( request, - 'common/form.html', + "common/form.html", { - 'form': form, - 'title': _('Create Group'), + "form": form, + "title": _("Create Group"), }, ) @@ -54,10 +54,10 @@ def group_update(request, pk): return render( request, - 'common/form.html', + "common/form.html", { - 'form': form, - 'title': _('Update Group'), + "form": form, + "title": _("Update Group"), }, ) @@ -65,14 +65,14 @@ def group_update(request, pk): @superuser_only def group_delete(request, pk): group = get_object_or_404(Group, pk=pk) - if request.method == 'POST': + if request.method == "POST": group.delete() - return redirect('admin:group_list') + return redirect("admin:group_list") return render( request, - 'common/confirm_delete.html', - {'object': group}, + "common/confirm_delete.html", + {"object": group}, ) @@ -81,10 +81,10 @@ def user_list(request): users = User.objects.all() return render( request, - 'admin/user_list.html', + "admin/user_list.html", { - 'users': users, - 'title': _('Users'), + "users": users, + "title": _("Users"), }, ) @@ -95,22 +95,22 @@ def user_create(request): attributes_form = forms.UserAttributesForm(request.POST or None) if user_form.is_valid() and attributes_form.is_valid(): user = user_form.save() - password = user_form.cleaned_data['password'] + password = user_form.cleaned_data["password"] user.set_password(password) user.save() attributes = attributes_form.save(commit=False) attributes.user = user attributes.save() add_default_instances(user) - return redirect('admin:user_list') + return redirect("admin:user_list") return render( request, - 'admin/user_form.html', + "admin/user_form.html", { - 'user_form': user_form, - 'attributes_form': attributes_form, - 'title': _('Create User') + "user_form": user_form, + "attributes_form": attributes_form, + "title": _("Create User") }, ) @@ -120,22 +120,24 @@ def user_update(request, pk): user = get_object_or_404(User, pk=pk) attributes = UserAttributes.objects.get(user=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(): user_form.save() attributes_form.save() - return redirect('admin:user_list') + return redirect("admin:user_list") return render( request, - 'admin/user_form.html', + "admin/user_form.html", { - 'user_form': user_form, - 'attributes_form': attributes_form, - 'title': _('Update User') + "user_form": user_form, + "attributes_form": attributes_form, + "title": _("Update User") }, ) + @superuser_only def user_update_password(request, pk): user = get_object_or_404(User, pk=pk) @@ -144,33 +146,35 @@ def user_update_password(request, pk): if form.is_valid(): user = form.save() update_session_auth_hash(request, user) # Important! - messages.success(request, _('User password changed: {}'.format(user.username))) - return redirect('admin:user_list') + messages.success(request, _( + "User password changed: {}".format(user.username))) + return redirect("admin:user_list") else: - messages.error(request, _('Wrong Data Provided')) + messages.error(request, _("Wrong Data Provided")) else: form = AdminPasswordChangeForm(user) return render( request, - 'accounts/change_password_form.html', + "accounts/change_password_form.html", { - 'form': form, - 'user': user.username + "form": form, + "user": user.username } ) + @superuser_only def user_delete(request, pk): user = get_object_or_404(User, pk=pk) - if request.method == 'POST': + if request.method == "POST": user.delete() - return redirect('admin:user_list') + return redirect("admin:user_list") return render( request, - 'common/confirm_delete.html', - {'object': user}, + "common/confirm_delete.html", + {"object": user}, ) @@ -179,7 +183,7 @@ def user_block(request, pk): user: User = get_object_or_404(User, pk=pk) user.is_active = False user.save() - return redirect('admin:user_list') + return redirect("admin:user_list") @superuser_only @@ -187,16 +191,16 @@ def user_unblock(request, pk): user: User = get_object_or_404(User, pk=pk) user.is_active = True user.save() - return redirect('admin:user_list') + return redirect("admin:user_list") @superuser_only def logs(request): - l = Logs.objects.order_by('-date') - paginator = Paginator(l, int(AppSettings.objects.get(key="LOGS_PER_PAGE").value)) - page = request.GET.get('page', 1) + l = Logs.objects.order_by("-date") + paginator = Paginator(l, int(app_settings.LOGS_PER_PAGE)) + page = request.GET.get("page", 1) 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): diff --git a/console/sshtunnels.py b/console/sshtunnels.py index 7e8e6fb..336febd 100644 --- a/console/sshtunnels.py +++ b/console/sshtunnels.py @@ -21,6 +21,7 @@ class _TunnelScheduler(object): It's only instantiated once for the whole app, because we serialize independent of connection, vm, etc. """ + def __init__(self): self._thread = None self._queue = queue.Queue() @@ -44,6 +45,7 @@ class _TunnelScheduler(object): def lock(self): self._lock.acquire() + def unlock(self): self._lock.release() @@ -63,7 +65,7 @@ class _Tunnel(object): self._closed = True 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 # when it's garbage collected. @@ -110,13 +112,12 @@ class _Tunnel(object): self._errfd = errfds[0] self._errfd.setblocking(0) log.debug("Opened tunnel PID=%d ERRFD=%d", - pid, self._errfd.fileno()) + pid, self._errfd.fileno()) self._pid = pid def _make_ssh_command(connhost, connuser, connport, gaddr, gport, gsocket): - # Build SSH cmd argv = ["ssh", "ssh"] @@ -165,7 +166,8 @@ def _make_ssh_command(connhost, connuser, connport, gaddr, gport, gsocket): class SSHTunnels(object): def __init__(self, connhost, connuser, connport, gaddr, gport, gsocket): 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 def open_new(self): @@ -206,4 +208,4 @@ class SSHTunnels(object): def unlock(self, *args, **kwargs): if self._locked: _tunnel_scheduler.unlock(*args, **kwargs) - self._locked = False \ No newline at end of file + self._locked = False diff --git a/console/views.py b/console/views.py index 00d6498..f5ac707 100644 --- a/console/views.py +++ b/console/views.py @@ -6,8 +6,7 @@ from libvirt import libvirtError from appsettings.settings import app_settings from instances.models import Instance from vrtManager.instance import wvmInstance -from webvirtcloud.settings import (WS_PUBLIC_HOST, WS_PUBLIC_PATH, - WS_PUBLIC_PORT) +from webvirtcloud.settings import WS_PUBLIC_HOST, WS_PUBLIC_PATH, WS_PUBLIC_PORT def console(request): @@ -17,16 +16,19 @@ def console(request): """ console_error = None - if request.method == 'GET': - token = request.GET.get('token', '') - view_type = request.GET.get('view', 'lite') - view_only = request.GET.get('view_only', app_settings.CONSOLE_VIEW_ONLY.lower()) - scale = request.GET.get('scale', app_settings.CONSOLE_SCALE.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()) + if request.method == "GET": + token = request.GET.get("token", "") + view_type = request.GET.get("view", "lite") + view_only = request.GET.get( + "view_only", app_settings.CONSOLE_VIEW_ONLY.lower()) + scale = request.GET.get("scale", app_settings.CONSOLE_SCALE.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: - temptoken = token.split('-', 1) + temptoken = token.split("-", 1) host = int(temptoken[0]) uuid = temptoken[1] 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_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: - ws_host = re.sub(':[0-9]+', '', ws_host) + if ":" in 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" response = render(request, console_page, locals()) else: 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: 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 diff --git a/datasource/models.py b/datasource/models.py index 71a8362..6b20219 100644 --- a/datasource/models.py +++ b/datasource/models.py @@ -1,3 +1 @@ -from django.db import models - # Create your models here. diff --git a/datasource/views.py b/datasource/views.py index c9426df..b389122 100644 --- a/datasource/views.py +++ b/datasource/views.py @@ -48,10 +48,10 @@ def os_userdata(request, version): ip = get_client_ip(request) hostname = get_hostname_by_ip(ip) vname = hostname.split('.')[0] - + instance_keys = [] userinstances = UserInstance.objects.filter(instance__name=vname) - + for ui in userinstances: keys = UserSSHKey.objects.filter(user=ui.user) for k in keys: @@ -95,7 +95,7 @@ def get_vdi_url(request, compute_id, vname): :return: """ compute = get_object_or_404(Compute, pk=compute_id) - data = {} + try: conn = wvmInstance(compute.hostname, 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()}" response = url return HttpResponse(response) - except libvirtError as lib_err: + except libvirtError: err = f"Error getting VDI URL for {vname}" raise Http404(err) diff --git a/install.sh b/install.sh index 3a59b62..e42588d 100644 --- a/install.sh +++ b/install.sh @@ -1,3 +1,5 @@ +#!/bin/bash + # ensure running as root if [ "$(id -u)" != "0" ]; then #Debian doesnt have sudo if root has a password. diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 149ca0f..e78b314 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -1,16 +1,26 @@ import time import os.path try: - from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_DOMAIN_RUNNING, VIR_DOMAIN_AFFECT_LIVE, \ - VIR_DOMAIN_AFFECT_CONFIG, VIR_DOMAIN_UNDEFINE_NVRAM, VIR_DOMAIN_UNDEFINE_KEEP_NVRAM, 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, \ + from libvirt import ( + libvirtError, + VIR_DOMAIN_XML_SECURE, + VIR_DOMAIN_RUNNING, + VIR_DOMAIN_AFFECT_LIVE, + VIR_DOMAIN_AFFECT_CONFIG, + VIR_DOMAIN_UNDEFINE_NVRAM, + VIR_DOMAIN_UNDEFINE_KEEP_NVRAM, + 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 + ) from libvirt import VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT except: from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE, VIR_MIGRATE_LIVE diff --git a/vrtManager/rwlock.py b/vrtManager/rwlock.py index c1bea66..2ee82f9 100644 --- a/vrtManager/rwlock.py +++ b/vrtManager/rwlock.py @@ -19,6 +19,7 @@ from time import time # Read write lock # --------------- + class ReadWriteLock(object): """Read-Write lock class. A read-write lock differs from a standard threading.RLock() by allowing multiple threads to simultaneously hold a diff --git a/vrtManager/storage.py b/vrtManager/storage.py index 7ea802a..7726b95 100644 --- a/vrtManager/storage.py +++ b/vrtManager/storage.py @@ -15,9 +15,15 @@ class wvmStorages(wvmConnect): else: stg_vol = None stg_size = stg.info()[1] - storages.append({'name': pool, 'status': stg_status, - 'type': stg_type, 'volumes': stg_vol, - 'size': stg_size}) + storages.append( + { + "name": pool, + "status": stg_status, + "type": stg_type, + "volumes": stg_vol, + "size": stg_size + } + ) return storages def define_storage(self, xml, flag): @@ -26,14 +32,14 @@ class wvmStorages(wvmConnect): def create_storage(self, stg_type, name, source, target): xml = f""" {name}""" - if stg_type == 'logical': + if stg_type == "logical": xml += f""" {name} """ - if stg_type == 'logical': - target = '/dev/' + name + if stg_type == "logical": + target = "/dev/" + name xml += f""" {target} @@ -41,7 +47,7 @@ class wvmStorages(wvmConnect): """ self.define_storage(xml, 0) stg = self.get_storage(name) - if stg_type == 'logical': + if stg_type == "logical": stg.build(0) stg.create(0) stg.setAutostart(1) @@ -97,11 +103,16 @@ class wvmStorage(wvmConnect): return self.pool.name() 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: return status[self.pool.info()[0]] except ValueError: - return 'Unknown' + return "Unknown" def get_size(self): return [self.pool.info()[1], self.pool.info()[3]] @@ -202,10 +213,12 @@ class wvmStorage(wvmConnect): for volname in vols: vol_list.append( - {'name': volname, - 'size': self.get_volume_size(volname), - 'allocation': self.get_volume_allocation(volname), - 'type': self.get_volume_type(volname)} + { + "name": volname, + "size": self.get_volume_size(volname), + "allocation": self.get_volume_allocation(volname), + "type": self.get_volume_type(volname) + } ) return vol_list @@ -213,13 +226,13 @@ class wvmStorage(wvmConnect): size = int(size) * 1073741824 storage_type = self.get_type() alloc = size - if vol_fmt == 'unknown': - vol_fmt = 'raw' - if storage_type == 'dir': - if vol_fmt in ('qcow', 'qcow2'): - name += '.' + vol_fmt + if vol_fmt == "unknown": + vol_fmt = "raw" + if storage_type == "dir": + if vol_fmt in ("qcow", "qcow2"): + name += "." + vol_fmt else: - name += '.img' + name += ".img" alloc = 0 xml = f""" @@ -234,7 +247,7 @@ class wvmStorage(wvmConnect): 0644 """ - if vol_fmt == 'qcow2': + if vol_fmt == "qcow2": xml += """ 1.1 @@ -251,8 +264,8 @@ class wvmStorage(wvmConnect): vol_fmt = self.get_volume_type(name) storage_type = self.get_type() - if storage_type == 'dir': - if vol_fmt in ['qcow', 'qcow2']: + if storage_type == "dir": + if vol_fmt in ["qcow", "qcow2"]: target_file += '.' + vol_fmt else: suffix = '.' + file_suffix @@ -271,7 +284,7 @@ class wvmStorage(wvmConnect): {mode} """ - if vol_fmt == 'qcow2': + if vol_fmt == "qcow2": xml += """ 1.1 diff --git a/webvirtcloud.sh b/webvirtcloud.sh index 4a1b79b..68eaafb 100644 --- a/webvirtcloud.sh +++ b/webvirtcloud.sh @@ -139,13 +139,13 @@ configure_nginx () { rm /etc/nginx/sites-enabled/default 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 echo " * Copying Nginx configuration" - 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/"$distro"_nginx.conf /etc/nginx/nginx.conf + 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" fi @@ -156,7 +156,7 @@ configure_nginx () { configure_supervisor () { # Copy template supervisor service for gunicorn and novnc 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" } @@ -174,20 +174,20 @@ create_user () { run_as_app_user () { if ! hash sudo 2>/dev/null; then - su -c "$@" $APP_USER + su -c "$@" "$APP_USER" else - sudo -i -u $APP_USER "$@" + sudo -i -u "$APP_USER" "$@" fi } activate_python_environment () { - cd $APP_PATH - virtualenv -p $PYTHON venv + cd "$APP_PATH" || exit + virtualenv -p "$PYTHON" venv source venv/bin/activate } generate_secret_key() { - $PYTHON - <