mirror of
https://github.com/retspen/webvirtcloud
synced 2024-12-24 23:25:24 +00:00
Rest framework (#24)
* Add rest framework for API: First Commit * modify some shell scripts to make variable references safer; modify some python scripts to reduce the code complexity and cyclomatic complexity of functions. * Add REST API for some webvirtcloud functions. Instance list/delete/create, compute list/delete/create, storages-network list/retrieve. Add swagger and redoc for API interface * update requirements Co-authored-by: herengui <herengui@uniontech.com>
This commit is contained in:
parent
92254401dc
commit
cfce71ec2b
42 changed files with 1170 additions and 348 deletions
11
README.md
11
README.md
|
@ -432,6 +432,17 @@ Now when you login with an LDAP user it will be assigned the rights defined. The
|
||||||
|
|
||||||
If you'd like to move a user from ldap to WebVirtCloud, just change its password from the UI and (eventually) remove from the group in ldap
|
If you'd like to move a user from ldap to WebVirtCloud, just change its password from the UI and (eventually) remove from the group in ldap
|
||||||
|
|
||||||
|
|
||||||
|
## REST API / BETA
|
||||||
|
Webvirtcloud provides a REST API for programmatic access.
|
||||||
|
To access API methods open your browser and check them with Swagger interface
|
||||||
|
```bash
|
||||||
|
http://<webvirtloud-address:port>/swagger
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
http://<webvirtloud-address:port>/redoc
|
||||||
|
```
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
Instance Detail:
|
Instance Detail:
|
||||||
|
|
27
computes/api/serializers.py
Normal file
27
computes/api/serializers.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from computes.models import Compute
|
||||||
|
from vrtManager.connection import (
|
||||||
|
CONN_SOCKET,
|
||||||
|
CONN_SSH,
|
||||||
|
CONN_TCP,
|
||||||
|
CONN_TLS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ComputeSerializer(serializers.ModelSerializer):
|
||||||
|
# Use <input type="password"> for the input.
|
||||||
|
password = serializers.CharField(style={'input_type': 'password'})
|
||||||
|
# Use a radio input instead of a select input.
|
||||||
|
conn_types = (
|
||||||
|
(CONN_SSH, 'SSH'),
|
||||||
|
(CONN_TCP, 'TCP'),
|
||||||
|
(CONN_TLS, 'TLS'),
|
||||||
|
(CONN_SOCKET, 'SOCK'),
|
||||||
|
)
|
||||||
|
type = serializers.ChoiceField(choices=conn_types)
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Compute
|
||||||
|
fields = ['id', 'name', 'hostname', 'login', 'password', 'type', 'details']
|
58
computes/api/viewsets.py
Normal file
58
computes/api/viewsets.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
from computes.models import Compute
|
||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework import permissions
|
||||||
|
|
||||||
|
from vrtManager.create import wvmCreate
|
||||||
|
from .serializers import ComputeSerializer
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
|
||||||
|
class ComputeViewSet(viewsets.ModelViewSet):
|
||||||
|
"""
|
||||||
|
API endpoint that allows computes to be viewed or edited.
|
||||||
|
"""
|
||||||
|
queryset = Compute.objects.all().order_by('name')
|
||||||
|
serializer_class = ComputeSerializer
|
||||||
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
|
class ComputeArchitecturesView(viewsets.ViewSet):
|
||||||
|
|
||||||
|
def list(self, request, compute_pk=None):
|
||||||
|
"""
|
||||||
|
Return a list of supported host architectures.
|
||||||
|
"""
|
||||||
|
compute = Compute.objects.get(pk=compute_pk)
|
||||||
|
conn = wvmCreate(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
)
|
||||||
|
return Response(conn.get_hypervisors_machines())
|
||||||
|
|
||||||
|
def retrieve(self, request, compute_pk=None, pk=None):
|
||||||
|
compute = Compute.objects.get(pk=compute_pk)
|
||||||
|
conn = wvmCreate(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
)
|
||||||
|
return Response(conn.get_machine_types(pk))
|
||||||
|
|
||||||
|
|
||||||
|
class ComputeMachinesView(viewsets.ViewSet):
|
||||||
|
|
||||||
|
def list(self, request, compute_pk=None, archs_pk=None):
|
||||||
|
"""
|
||||||
|
Return a list of supported host architectures.
|
||||||
|
"""
|
||||||
|
compute = Compute.objects.get(pk=compute_pk)
|
||||||
|
conn = wvmCreate(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
)
|
||||||
|
return Response(conn.get_machine_types(archs_pk))
|
|
@ -1,7 +1,6 @@
|
||||||
from virtsecrets.views import secrets
|
from virtsecrets.views import secrets
|
||||||
|
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
# from instances.views import create_instance, create_instance_select_type
|
|
||||||
from interfaces.views import interface, interfaces
|
from interfaces.views import interface, interfaces
|
||||||
from networks.views import network, networks
|
from networks.views import network, networks
|
||||||
from nwfilters.views import nwfilter, nwfilters
|
from nwfilters.views import nwfilter, nwfilters
|
||||||
|
@ -34,8 +33,6 @@ urlpatterns = [
|
||||||
path('nwfilters/', nwfilters, name='nwfilters'),
|
path('nwfilters/', nwfilters, name='nwfilters'),
|
||||||
path('nwfilter/<str:nwfltr>/', nwfilter, name='nwfilter'),
|
path('nwfilter/<str:nwfltr>/', nwfilter, name='nwfilter'),
|
||||||
path('virtsecrets/', secrets, name='virtsecrets'),
|
path('virtsecrets/', secrets, name='virtsecrets'),
|
||||||
# path('create/', create_instance_select_type, name='create_instance_select_type'),
|
|
||||||
# path('create/archs/<str:arch>/machines/<str:machine>/', create_instance, name='create_instance'),
|
|
||||||
path('archs/<str:arch>/machines/', views.get_compute_machine_types, name='machines'),
|
path('archs/<str:arch>/machines/', views.get_compute_machine_types, name='machines'),
|
||||||
path(
|
path(
|
||||||
'archs/<str:arch>/machines/<str:machine>/disks/<str:disk>/buses/',
|
'archs/<str:arch>/machines/<str:machine>/disks/<str:disk>/buses/',
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
Django==3.2.14
|
Django==3.2.15
|
||||||
django_bootstrap5==21.2
|
django_bootstrap5==22.1
|
||||||
django-icons==21.1
|
django-icons==22.1
|
||||||
django-login-required-middleware==0.8
|
django-login-required-middleware==0.8
|
||||||
django-otp==1.1.3
|
django-otp==1.1.3
|
||||||
django-qr-code==2.3.0
|
django-qr-code==2.3.0
|
||||||
gunicorn==20.1.0
|
gunicorn==20.1.0
|
||||||
libsass==0.21.0
|
libsass==0.21.0
|
||||||
libvirt-python==8.5.0
|
libvirt-python==8.6.0
|
||||||
lxml==4.9.1
|
lxml==4.9.1
|
||||||
qrcode==7.3.1
|
qrcode==7.3.1
|
||||||
rwlock==0.0.7
|
rwlock==0.0.7
|
||||||
websockify==0.10.0
|
websockify==0.10.0
|
||||||
zipp==3.6.0
|
zipp==3.6.0
|
||||||
ldap3==2.9.1
|
ldap3==2.9.1
|
||||||
python-socketio==5.7.0
|
python-engineio==4.3.4
|
||||||
eventlet==0.33.1
|
python-socketio==5.7.1
|
||||||
|
eventlet==0.33.1
|
||||||
|
djangorestframework==3.13.1
|
||||||
|
drf-nested-routers==0.93.4
|
||||||
|
drf-yasg==1.21.3
|
||||||
|
markdown==3.4.1
|
||||||
|
|
|
@ -112,7 +112,7 @@ def get_connection_infos(token):
|
||||||
connport = 22
|
connport = 22
|
||||||
connuser = instance.compute.login
|
connuser = instance.compute.login
|
||||||
conntype = instance.compute.type
|
conntype = instance.compute.type
|
||||||
console_host = conn.get_console_listen_addr()
|
console_host = conn.get_console_listener_addr()
|
||||||
console_port = conn.get_console_port()
|
console_port = conn.get_console_port()
|
||||||
console_socket = conn.get_console_socket()
|
console_socket = conn.get_console_socket()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
@ -45,7 +45,7 @@ echowarn() {
|
||||||
# DESCRIPTION: Echo debug information to stdout.
|
# DESCRIPTION: Echo debug information to stdout.
|
||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
echodebug() {
|
echodebug() {
|
||||||
if [ $_ECHO_DEBUG -eq $BS_TRUE ]; then
|
if [ "${_ECHO_DEBUG}" -eq "${BS_TRUE}" ]; then
|
||||||
printf "${BC} * DEBUG${EC}: %s\n" "$@";
|
printf "${BC} * DEBUG${EC}: %s\n" "$@";
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
@ -154,8 +154,7 @@ __gather_linux_system_info() {
|
||||||
DISTRO_VERSION=""
|
DISTRO_VERSION=""
|
||||||
|
|
||||||
# Let's test if the lsb_release binary is available
|
# Let's test if the lsb_release binary is available
|
||||||
rv=$(lsb_release >/dev/null 2>&1)
|
if lsb_release >/dev/null 2>&1; then
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
DISTRO_NAME=$(lsb_release -si)
|
DISTRO_NAME=$(lsb_release -si)
|
||||||
if [ "x$(echo "$DISTRO_NAME" | grep RedHat)" != "x" ]; then
|
if [ "x$(echo "$DISTRO_NAME" | grep RedHat)" != "x" ]; then
|
||||||
# Let's convert CamelCase to Camel Case
|
# Let's convert CamelCase to Camel Case
|
||||||
|
@ -208,7 +207,7 @@ __gather_linux_system_info() {
|
||||||
;;
|
;;
|
||||||
arch ) n="Arch Linux" ;;
|
arch ) n="Arch Linux" ;;
|
||||||
centos ) n="CentOS" ;;
|
centos ) n="CentOS" ;;
|
||||||
almalinux ) n="AlmaLinux" ;;
|
almalinux ) n="AlmaLinux" ;;
|
||||||
debian ) n="Debian" ;;
|
debian ) n="Debian" ;;
|
||||||
ubuntu ) n="Ubuntu" ;;
|
ubuntu ) n="Ubuntu" ;;
|
||||||
fedora ) n="Fedora" ;;
|
fedora ) n="Fedora" ;;
|
||||||
|
@ -248,7 +247,7 @@ __gather_linux_system_info() {
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
* ) n="${n}" ;
|
* ) ;;
|
||||||
esac
|
esac
|
||||||
DISTRO_NAME=$n
|
DISTRO_NAME=$n
|
||||||
DISTRO_VERSION=$v
|
DISTRO_VERSION=$v
|
||||||
|
@ -779,8 +778,7 @@ if [ "$INSTALL_FUNC" = "null" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
echoinfo "Running ${INSTALL_FUNC}()"
|
echoinfo "Running ${INSTALL_FUNC}()"
|
||||||
$INSTALL_FUNC
|
if ! $INSTALL_FUNC; then
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echoerror "Failed to run ${INSTALL_FUNC}()!!!"
|
echoerror "Failed to run ${INSTALL_FUNC}()!!!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
@ -803,8 +801,7 @@ if [ "$POST_INSTALL_FUNC" = "null" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
echoinfo "Running ${POST_INSTALL_FUNC}()"
|
echoinfo "Running ${POST_INSTALL_FUNC}()"
|
||||||
$POST_INSTALL_FUNC
|
if ! $POST_INSTALL_FUNC; then
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echoerror "Failed to run ${POST_INSTALL_FUNC}()!!!"
|
echoerror "Failed to run ${POST_INSTALL_FUNC}()!!!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
@ -827,8 +824,7 @@ if [ "$DAEMONS_RUNNING_FUNC" = "null" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
echoinfo "Running ${DAEMONS_RUNNING_FUNC}()"
|
echoinfo "Running ${DAEMONS_RUNNING_FUNC}()"
|
||||||
$DAEMONS_RUNNING_FUNC
|
if ! $DAEMONS_RUNNING_FUNC; then
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echoerror "Failed to run ${DAEMONS_RUNNING_FUNC}()!!!"
|
echoerror "Failed to run ${DAEMONS_RUNNING_FUNC}()!!!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
-r ../conf/requirements.txt
|
-r ../conf/requirements.txt
|
||||||
coverage==6.2
|
coverage==6.4.4
|
||||||
django-debug-toolbar==3.2.4
|
django-debug-toolbar==3.6.0
|
||||||
pycodestyle==2.8.0
|
pycodestyle==2.9.1
|
||||||
pyflakes==2.4.0
|
pyflakes==2.5.0
|
||||||
pylint==2.13.9
|
pylint==2.14.5
|
||||||
yapf==0.32.0
|
yapf==0.32.0
|
||||||
|
|
|
@ -75,10 +75,7 @@ backlog = 2048
|
||||||
|
|
||||||
def get_workers():
|
def get_workers():
|
||||||
procs = os.sysconf('SC_NPROCESSORS_ONLN')
|
procs = os.sysconf('SC_NPROCESSORS_ONLN')
|
||||||
if procs > 0:
|
return procs * 2 + 1 if procs > 0 else 3
|
||||||
return procs * 2 + 1
|
|
||||||
else:
|
|
||||||
return 3
|
|
||||||
|
|
||||||
|
|
||||||
workers = get_workers()
|
workers = get_workers()
|
||||||
|
|
100
instances/api/serializers.py
Normal file
100
instances/api/serializers.py
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from instances.models import Flavor, Instance, MigrateInstance, CreateInstance
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Instance
|
||||||
|
fields = ['id', 'compute', 'name', 'uuid', 'is_template', 'created', 'drbd']
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceDetailsSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Instance
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'compute',
|
||||||
|
'status',
|
||||||
|
'uuid',
|
||||||
|
'name',
|
||||||
|
'title',
|
||||||
|
'description',
|
||||||
|
'is_template',
|
||||||
|
'created',
|
||||||
|
'drbd',
|
||||||
|
'arch',
|
||||||
|
'machine',
|
||||||
|
'vcpu',
|
||||||
|
'memory',
|
||||||
|
'firmware',
|
||||||
|
'nvram',
|
||||||
|
'bootmenu',
|
||||||
|
'boot_order',
|
||||||
|
'disks',
|
||||||
|
'media',
|
||||||
|
'media_iso',
|
||||||
|
'snapshots',
|
||||||
|
'networks',
|
||||||
|
'console_type',
|
||||||
|
'console_port',
|
||||||
|
'console_keymap',
|
||||||
|
'console_listener_address',
|
||||||
|
'video_model',
|
||||||
|
'guest_agent_ready',
|
||||||
|
'autostart']
|
||||||
|
|
||||||
|
|
||||||
|
class FlavorSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Flavor
|
||||||
|
fields = ['label', 'memory', 'vcpu', 'disk']
|
||||||
|
|
||||||
|
|
||||||
|
class CreateInstanceSerializer(serializers.ModelSerializer):
|
||||||
|
firmware_choices = (
|
||||||
|
('', 'BIOS'),
|
||||||
|
#('UEFI', 'UEFI'),
|
||||||
|
)
|
||||||
|
firmware = serializers.ChoiceField(choices = firmware_choices)
|
||||||
|
graphics = serializers.CharField(initial='vnc')
|
||||||
|
video = serializers.CharField(initial='vga')
|
||||||
|
storage = serializers.CharField(initial='default')
|
||||||
|
cache_mode = serializers.CharField(initial='none')
|
||||||
|
virtio = serializers.BooleanField(initial=True)
|
||||||
|
qemu_ga = serializers.BooleanField(initial=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CreateInstance
|
||||||
|
fields = [
|
||||||
|
'name',
|
||||||
|
'firmware',
|
||||||
|
'vcpu',
|
||||||
|
'vcpu_mode',
|
||||||
|
'memory',
|
||||||
|
'networks',
|
||||||
|
'mac',
|
||||||
|
'nwfilter',
|
||||||
|
'storage',
|
||||||
|
'hdd_size',
|
||||||
|
'cache_mode',
|
||||||
|
'meta_prealloc',
|
||||||
|
'virtio',
|
||||||
|
'qemu_ga',
|
||||||
|
'console_pass',
|
||||||
|
'graphics',
|
||||||
|
'video',
|
||||||
|
'listener_addr'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class MigrateSerializer(serializers.ModelSerializer):
|
||||||
|
instance = Instance.objects.all().prefetch_related("userinstance_set")
|
||||||
|
live = serializers.BooleanField(initial=True)
|
||||||
|
xml_del = serializers.BooleanField(initial=True)
|
||||||
|
class Meta:
|
||||||
|
model = MigrateInstance
|
||||||
|
fields = ['instance', 'target_compute', 'live', 'xml_del', 'offline', 'autoconverge', 'compress', 'postcopy', 'unsafe']
|
224
instances/api/viewsets.py
Normal file
224
instances/api/viewsets.py
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from appsettings.settings import app_settings
|
||||||
|
from computes.models import Compute
|
||||||
|
from computes import utils
|
||||||
|
from instances.models import Flavor, Instance
|
||||||
|
from instances.views import get_instance
|
||||||
|
from instances.utils import migrate_instance
|
||||||
|
from instances.views import poweron, powercycle, poweroff, force_off, suspend, resume, destroy as instance_destroy
|
||||||
|
|
||||||
|
from rest_framework import status, viewsets, permissions
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from vrtManager import util
|
||||||
|
|
||||||
|
from vrtManager.create import wvmCreate
|
||||||
|
from .serializers import FlavorSerializer, InstanceSerializer, InstanceDetailsSerializer, MigrateSerializer, CreateInstanceSerializer
|
||||||
|
|
||||||
|
class InstancesViewSet(viewsets.ViewSet):
|
||||||
|
"""
|
||||||
|
A simple ViewSet for listing or retrieving ALL/Compute Instances.
|
||||||
|
"""
|
||||||
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
def list(self, request):
|
||||||
|
|
||||||
|
if request.user.is_superuser or request.user.has_perm("instances.view_instances"):
|
||||||
|
queryset = Instance.objects.all().prefetch_related("userinstance_set")
|
||||||
|
else:
|
||||||
|
queryset = Instance.objects.filter(userinstance__user=request.user).prefetch_related("userinstance_set")
|
||||||
|
serializer = InstanceSerializer(queryset, many=True, context={'request': request})
|
||||||
|
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
def retrieve(self, request, pk=None, compute_pk=None):
|
||||||
|
queryset = get_instance(request.user, pk)
|
||||||
|
serializer = InstanceSerializer(queryset, context={'request': request})
|
||||||
|
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceViewSet(viewsets.ViewSet):
|
||||||
|
"""
|
||||||
|
A simple ViewSet for listing or retrieving Compute Instances.
|
||||||
|
"""
|
||||||
|
#serializer_class = CreateInstanceSerializer
|
||||||
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
def list(self, request, compute_pk=None):
|
||||||
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
|
utils.refresh_instance_database(compute)
|
||||||
|
|
||||||
|
queryset = Instance.objects.filter(compute=compute).prefetch_related("userinstance_set")
|
||||||
|
serializer = InstanceSerializer(queryset, many=True, context={'request': request})
|
||||||
|
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
def retrieve(self, request, pk=None, compute_pk=None):
|
||||||
|
queryset = get_instance(request.user, pk)
|
||||||
|
serializer = InstanceDetailsSerializer(queryset, context={'request': request})
|
||||||
|
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
def destroy(self, request, pk=None, compute_pk=None):
|
||||||
|
instance_destroy(request, pk)
|
||||||
|
return Response({'status': 'Instance is destroyed'})
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def poweron(self, request, pk=None):
|
||||||
|
poweron(request, pk)
|
||||||
|
return Response({'status': 'poweron command send'})
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def poweroff(self, request, pk=None):
|
||||||
|
poweroff(request, pk)
|
||||||
|
return Response({'status': 'poweroff command send'})
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def powercycle(self, request, pk=None):
|
||||||
|
powercycle(request, pk)
|
||||||
|
return Response({'status': 'powercycle command send'})
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def forceoff(self, request, pk=None):
|
||||||
|
force_off(request, pk)
|
||||||
|
return Response({'status': 'force off command send'})
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def suspend(self, request, pk=None):
|
||||||
|
suspend(request, pk)
|
||||||
|
return Response({'status': 'suspend command send'})
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def resume(self, request, pk=None):
|
||||||
|
resume(request, pk)
|
||||||
|
return Response({'status': 'resume command send'})
|
||||||
|
|
||||||
|
|
||||||
|
class MigrateViewSet(viewsets.ViewSet):
|
||||||
|
"""
|
||||||
|
A viewset for migrating instances.
|
||||||
|
"""
|
||||||
|
serializer_class = MigrateSerializer
|
||||||
|
queryset = ""
|
||||||
|
|
||||||
|
def create(self, request):
|
||||||
|
serializer = MigrateSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
instance = serializer.validated_data['instance']
|
||||||
|
target_host = serializer.validated_data['target_compute']
|
||||||
|
live = serializer.validated_data['live']
|
||||||
|
unsafe = serializer.validated_data['unsafe']
|
||||||
|
xml_del = serializer.validated_data['xml_del']
|
||||||
|
offline = serializer.validated_data['offline']
|
||||||
|
autoconverge = serializer.validated_data['autoconverge']
|
||||||
|
postcopy = serializer.validated_data['postcopy']
|
||||||
|
compress = serializer.validated_data['compress']
|
||||||
|
|
||||||
|
migrate_instance(target_host, instance, request.user, live, unsafe, xml_del, offline, autoconverge, compress, postcopy)
|
||||||
|
|
||||||
|
return Response({'status': 'instance migrate is started'})
|
||||||
|
else:
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class FlavorViewSet(viewsets.ModelViewSet):
|
||||||
|
"""
|
||||||
|
API endpoint that allows flavor to be viewed.
|
||||||
|
"""
|
||||||
|
queryset = Flavor.objects.all().order_by('id')
|
||||||
|
serializer_class = FlavorSerializer
|
||||||
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
|
class CreateInstanceViewSet(viewsets.ViewSet):
|
||||||
|
"""
|
||||||
|
A viewset for creating instances.
|
||||||
|
"""
|
||||||
|
serializer_class = CreateInstanceSerializer
|
||||||
|
queryset = ""
|
||||||
|
|
||||||
|
def create(self, request, compute_pk=None, arch=None, machine=None):
|
||||||
|
serializer = CreateInstanceSerializer(data=request.data,
|
||||||
|
context = {'compute_pk': compute_pk,
|
||||||
|
'arch': arch,
|
||||||
|
'machine': machine
|
||||||
|
})
|
||||||
|
if serializer.is_valid():
|
||||||
|
volume_list = []
|
||||||
|
default_bus = app_settings.INSTANCE_VOLUME_DEFAULT_BUS
|
||||||
|
default_io = app_settings.INSTANCE_VOLUME_DEFAULT_IO
|
||||||
|
default_discard = app_settings.INSTANCE_VOLUME_DEFAULT_DISCARD
|
||||||
|
default_zeroes = app_settings.INSTANCE_VOLUME_DEFAULT_DETECT_ZEROES
|
||||||
|
default_scsi_disk_model = app_settings.INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER
|
||||||
|
default_disk_format = app_settings.INSTANCE_VOLUME_DEFAULT_FORMAT
|
||||||
|
default_disk_owner_uid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_UID)
|
||||||
|
default_disk_owner_gid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_GID)
|
||||||
|
compute = Compute.objects.get(pk=compute_pk)
|
||||||
|
conn = wvmCreate(
|
||||||
|
compute.hostname,
|
||||||
|
compute.login,
|
||||||
|
compute.password,
|
||||||
|
compute.type,
|
||||||
|
)
|
||||||
|
|
||||||
|
path = conn.create_volume(
|
||||||
|
serializer.validated_data['storage'],
|
||||||
|
serializer.validated_data['name'],
|
||||||
|
serializer.validated_data['hdd_size'],
|
||||||
|
default_disk_format,
|
||||||
|
serializer.validated_data['meta_prealloc'],
|
||||||
|
default_disk_owner_uid,
|
||||||
|
default_disk_owner_gid,
|
||||||
|
)
|
||||||
|
volume = {}
|
||||||
|
firmware = {}
|
||||||
|
volume["device"] = "disk"
|
||||||
|
volume["path"] = path
|
||||||
|
volume["type"] = conn.get_volume_format_type(path)
|
||||||
|
volume["cache_mode"] = serializer.validated_data["cache_mode"]
|
||||||
|
volume["bus"] = default_bus
|
||||||
|
if volume["bus"] == "scsi":
|
||||||
|
volume["scsi_model"] = default_scsi_disk_model
|
||||||
|
volume["discard_mode"] = default_discard
|
||||||
|
volume["detect_zeroes_mode"] = default_zeroes
|
||||||
|
volume["io_mode"] = default_io
|
||||||
|
|
||||||
|
volume_list.append(volume)
|
||||||
|
|
||||||
|
if "UEFI" in serializer.validated_data['firmware']:
|
||||||
|
firmware["loader"] = serializer.validated_data['firmware'].split(":")[1].strip()
|
||||||
|
firmware["secure"] = "no"
|
||||||
|
firmware["readonly"] = "yes"
|
||||||
|
firmware["type"] = "pflash"
|
||||||
|
if "secboot" in firmware["loader"] and machine != "q35":
|
||||||
|
machine = "q35"
|
||||||
|
firmware["secure"] = "yes"
|
||||||
|
|
||||||
|
ret = conn.create_instance(
|
||||||
|
serializer.validated_data['name'],
|
||||||
|
serializer.validated_data['memory'],
|
||||||
|
serializer.validated_data['vcpu'],
|
||||||
|
serializer.validated_data['vcpu_mode'],
|
||||||
|
util.randomUUID(),
|
||||||
|
arch,
|
||||||
|
machine,
|
||||||
|
firmware,
|
||||||
|
volume_list,
|
||||||
|
serializer.validated_data['networks'],
|
||||||
|
serializer.validated_data['nwfilter'],
|
||||||
|
serializer.validated_data['graphics'],
|
||||||
|
serializer.validated_data['virtio'],
|
||||||
|
serializer.validated_data['listener_addr'],
|
||||||
|
serializer.validated_data['video'],
|
||||||
|
serializer.validated_data['console_pass'],
|
||||||
|
serializer.validated_data['mac'],
|
||||||
|
serializer.validated_data['qemu_ga'],
|
||||||
|
)
|
||||||
|
msg = f"Instance {serializer.validated_data['name']} is created"
|
||||||
|
return Response({'status': msg })
|
||||||
|
else:
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
@ -4,9 +4,9 @@ from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from appsettings.models import AppSettings
|
from appsettings.models import AppSettings
|
||||||
from webvirtcloud.settings import QEMU_CONSOLE_LISTEN_ADDRESSES, QEMU_KEYMAPS
|
from webvirtcloud.settings import QEMU_CONSOLE_LISTENER_ADDRESSES, QEMU_KEYMAPS
|
||||||
|
|
||||||
from .models import Flavor
|
from .models import CreateInstance, Flavor
|
||||||
|
|
||||||
|
|
||||||
class FlavorForm(forms.ModelForm):
|
class FlavorForm(forms.ModelForm):
|
||||||
|
@ -29,32 +29,36 @@ class ConsoleForm(forms.Form):
|
||||||
type_choices = ((c, c) for c in AppSettings.objects.get(key="QEMU_CONSOLE_DEFAULT_TYPE").choices_as_list())
|
type_choices = ((c, c) for c in AppSettings.objects.get(key="QEMU_CONSOLE_DEFAULT_TYPE").choices_as_list())
|
||||||
keymap_choices = [('auto', 'Auto')] + list((c, c) for c in QEMU_KEYMAPS)
|
keymap_choices = [('auto', 'Auto')] + list((c, c) for c in QEMU_KEYMAPS)
|
||||||
self.fields['type'] = forms.ChoiceField(choices=type_choices)
|
self.fields['type'] = forms.ChoiceField(choices=type_choices)
|
||||||
self.fields['listen_on'] = forms.ChoiceField(choices=QEMU_CONSOLE_LISTEN_ADDRESSES)
|
self.fields['listen_on'] = forms.ChoiceField(choices=QEMU_CONSOLE_LISTENER_ADDRESSES)
|
||||||
self.fields['keymap'] = forms.ChoiceField(choices=keymap_choices)
|
self.fields['keymap'] = forms.ChoiceField(choices=keymap_choices)
|
||||||
|
|
||||||
|
|
||||||
class NewVMForm(forms.Form):
|
class NewVMForm(forms.ModelForm):
|
||||||
name = forms.CharField(error_messages={'required': _('No Virtual Machine name has been entered')}, max_length=64)
|
# name = forms.CharField(error_messages={'required': _('No Virtual Machine name has been entered')}, max_length=64)
|
||||||
firmware = forms.CharField(max_length=50, required=False)
|
# firmware = forms.CharField(max_length=50, required=False)
|
||||||
vcpu = forms.IntegerField(error_messages={'required': _('No VCPU has been entered')})
|
# vcpu = forms.IntegerField(error_messages={'required': _('No VCPU has been entered')})
|
||||||
vcpu_mode = forms.CharField(max_length=20, required=False)
|
# vcpu_mode = forms.CharField(max_length=20, required=False)
|
||||||
disk = forms.IntegerField(required=False)
|
# disk = forms.IntegerField(required=False)
|
||||||
memory = forms.IntegerField(error_messages={'required': _('No RAM size has been entered')})
|
# memory = forms.IntegerField(error_messages={'required': _('No RAM size has been entered')})
|
||||||
networks = forms.CharField(error_messages={'required': _('No Network pool has been choosen')})
|
# networks = forms.CharField(error_messages={'required': _('No Network pool has been choosen')})
|
||||||
nwfilter = forms.CharField(required=False)
|
# nwfilter = forms.CharField(required=False)
|
||||||
storage = forms.CharField(max_length=20, required=False)
|
# storage = forms.CharField(max_length=20, required=False)
|
||||||
template = forms.CharField(required=False)
|
# template = forms.CharField(required=False)
|
||||||
images = forms.CharField(required=False)
|
# images = forms.CharField(required=False)
|
||||||
cache_mode = forms.CharField(error_messages={'required': _('Please select HDD cache mode')})
|
# cache_mode = forms.CharField(error_messages={'required': _('Please select HDD cache mode')})
|
||||||
hdd_size = forms.IntegerField(required=False)
|
# hdd_size = forms.IntegerField(required=False)
|
||||||
meta_prealloc = forms.BooleanField(required=False)
|
# meta_prealloc = forms.BooleanField(required=False)
|
||||||
virtio = forms.BooleanField(required=False)
|
# virtio = forms.BooleanField(required=False)
|
||||||
qemu_ga = forms.BooleanField(required=False)
|
# qemu_ga = forms.BooleanField(required=False)
|
||||||
mac = forms.CharField(required=False)
|
# mac = forms.CharField(required=False)
|
||||||
console_pass = forms.CharField(required=False, empty_value="", widget=forms.PasswordInput())
|
# console_pass = forms.CharField(required=False, empty_value="", widget=forms.PasswordInput())
|
||||||
graphics = forms.CharField(error_messages={'required': _('Please select a graphics type')})
|
# graphics = forms.CharField(error_messages={'required': _('Please select a graphics type')})
|
||||||
video = forms.CharField(error_messages={'required': _('Please select a video driver')})
|
# video = forms.CharField(error_messages={'required': _('Please select a video driver')})
|
||||||
listener_addr = forms.ChoiceField(required=True, widget=forms.RadioSelect, choices=QEMU_CONSOLE_LISTEN_ADDRESSES)
|
# listener_addr = forms.ChoiceField(required=True, widget=forms.RadioSelect, choices=QEMU_CONSOLE_LISTENER_ADDRESSES)
|
||||||
|
class Meta:
|
||||||
|
model = CreateInstance
|
||||||
|
fields = '__all__'
|
||||||
|
exclude = ['compute']
|
||||||
|
|
||||||
def clean_name(self):
|
def clean_name(self):
|
||||||
name = self.cleaned_data['name']
|
name = self.cleaned_data['name']
|
||||||
|
|
22
instances/migrations/0010_auto_20220722_0812.py
Normal file
22
instances/migrations/0010_auto_20220722_0812.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-07-22 08:12
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('instances', '0009_auto_20200717_0524'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='permissionset',
|
||||||
|
options={'default_permissions': (), 'managed': False, 'permissions': [('clone_instances', 'Can clone instances'), ('passwordless_console', 'Can access console without password'), ('view_instances', 'Can view instances'), ('snapshot_instances', 'Can snapshot instances')]},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='instance',
|
||||||
|
name='drbd',
|
||||||
|
field=models.CharField(default='None', max_length=24, verbose_name='drbd'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,7 +1,10 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from libvirt import VIR_DOMAIN_XML_SECURE
|
from libvirt import VIR_DOMAIN_XML_SECURE
|
||||||
|
from vrtManager.create import wvmCreate
|
||||||
|
from webvirtcloud.settings import QEMU_CONSOLE_LISTENER_ADDRESSES
|
||||||
|
|
||||||
from computes.models import Compute
|
from computes.models import Compute
|
||||||
from vrtManager.instance import wvmInstance
|
from vrtManager.instance import wvmInstance
|
||||||
|
@ -150,8 +153,8 @@ class Instance(models.Model):
|
||||||
return self.proxy.get_console_keymap()
|
return self.proxy.get_console_keymap()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def console_listen_address(self):
|
def console_listener_address(self):
|
||||||
return self.proxy.get_console_listen_addr()
|
return self.proxy.get_console_listener_addr()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def guest_agent(self):
|
def guest_agent(self):
|
||||||
|
@ -206,6 +209,50 @@ class Instance(models.Model):
|
||||||
return self.proxy.get_image_formats()
|
return self.proxy.get_image_formats()
|
||||||
|
|
||||||
|
|
||||||
|
class MigrateInstance(models.Model):
|
||||||
|
instance = models.ForeignKey(Instance, on_delete=models.DO_NOTHING)
|
||||||
|
target_compute = models.ForeignKey(Compute, related_name='target', on_delete=models.DO_NOTHING)
|
||||||
|
|
||||||
|
live = models.BooleanField(_('Live'), blank=False)
|
||||||
|
xml_del = models.BooleanField(_('Undefine XML'), blank=False, default=True)
|
||||||
|
offline = models.BooleanField(_('Offline'), blank=False)
|
||||||
|
autoconverge = models.BooleanField(_('Auto Converge'), blank=False, default=True)
|
||||||
|
compress = models.BooleanField(_('Compress'), blank=False, default=False)
|
||||||
|
postcopy = models.BooleanField(_('Post Copy'), blank=False, default=False)
|
||||||
|
unsafe = models.BooleanField(_('Unsafe'), blank=False, default=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
|
||||||
|
|
||||||
|
class CreateInstance(models.Model):
|
||||||
|
compute = models.ForeignKey(Compute, related_name='host', on_delete=models.DO_NOTHING)
|
||||||
|
name = models.CharField(max_length=64, error_messages={'required': _('No Virtual Machine name has been entered')})
|
||||||
|
firmware = models.CharField(max_length=50)
|
||||||
|
vcpu = models.IntegerField(error_messages={'required': _('No VCPU has been entered')})
|
||||||
|
vcpu_mode = models.CharField(max_length=20, blank=True)
|
||||||
|
disk = models.IntegerField(blank=True)
|
||||||
|
memory = models.IntegerField(error_messages={'required': _('No RAM size has been entered')})
|
||||||
|
networks = models.CharField(max_length=256, error_messages={'required': _('No Network pool has been choosen')})
|
||||||
|
nwfilter = models.CharField(max_length=256, blank=True)
|
||||||
|
storage = models.CharField(max_length=256, blank=True)
|
||||||
|
template = models.CharField(max_length=256, blank=True)
|
||||||
|
images = models.CharField(max_length=256, blank=True)
|
||||||
|
cache_mode = models.CharField(max_length=12, error_messages={'required': _('Please select HDD cache mode')})
|
||||||
|
hdd_size = models.IntegerField(blank=True)
|
||||||
|
meta_prealloc = models.BooleanField(default=False, blank=True)
|
||||||
|
virtio = models.BooleanField(default=True)
|
||||||
|
qemu_ga = models.BooleanField(default=False)
|
||||||
|
mac = models.CharField(max_length=17, blank=True)
|
||||||
|
console_pass = models.CharField(max_length=64, blank=True)
|
||||||
|
graphics = models.CharField(max_length=12, error_messages={'required': _('Please select a graphics type')})
|
||||||
|
video = models.CharField(max_length=12, error_messages={'required': _('Please select a video driver')})
|
||||||
|
listener_addr = models.CharField(max_length=20, choices=QEMU_CONSOLE_LISTENER_ADDRESSES)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
|
||||||
|
|
||||||
class PermissionSet(models.Model):
|
class PermissionSet(models.Model):
|
||||||
"""
|
"""
|
||||||
Dummy model for holding set of permissions we need to be automatically added by Django
|
Dummy model for holding set of permissions we need to be automatically added by Django
|
||||||
|
|
|
@ -30,9 +30,9 @@
|
||||||
<td>
|
<td>
|
||||||
<span class="text-success">{% trans "Connected" %}</span>
|
<span class="text-success">{% trans "Connected" %}</span>
|
||||||
</td>
|
</td>
|
||||||
{% if app_settings.VM_DRBD_STATUS == 'True' %}
|
{% if app_settings.VM_DRBD_STATUS == 'True' %}
|
||||||
<td class="d-none d-sm-table-cell"></td>
|
<td class="d-none d-sm-table-cell"></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="d-none d-sm-table-cell text-center">{{ compute.cpu_count }}</td>
|
<td class="d-none d-sm-table-cell text-center">{{ compute.cpu_count }}</td>
|
||||||
<td class="d-none d-sm-table-cell text-right">{{ compute.ram_size|filesizeformat }}</td>
|
<td class="d-none d-sm-table-cell text-right">{{ compute.ram_size|filesizeformat }}</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -68,11 +68,11 @@
|
||||||
<span class="text-warning">{% trans "Suspended" %}</span>
|
<span class="text-warning">{% trans "Suspended" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% if app_settings.VM_DRBD_STATUS == 'True' %}
|
{% if app_settings.VM_DRBD_STATUS == 'True' %}
|
||||||
<td>
|
<td>
|
||||||
{% if instance.drbd == "Primary/OK" or instance.drbd == "Secondary/OK" %}<span class="text-success">{% else %}<span class="text-danger">{% endif %}{{ instance.drbd }}</span>
|
{% if instance.drbd == "Primary/OK" or instance.drbd == "Secondary/OK" %}<span class="text-success">{% else %}<span class="text-danger">{% endif %}{{ instance.drbd }}</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td>{{ instance.proxy.instance.info.3 }}</td>
|
<td>{{ instance.proxy.instance.info.3 }}</td>
|
||||||
<td>{{ instance.cur_memory }} MB</td>
|
<td>{{ instance.cur_memory }} MB</td>
|
||||||
<td class="text-nowrap">
|
<td class="text-nowrap">
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
{% for error in field.errors %}
|
{% for error in field.errors %}
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
<strong>{{ error|escape }}</strong>
|
<strong>{{ field.label }}:</strong> <span>{{ error|escape }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
{% for error in field.errors %}
|
{% for error in field.errors %}
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
<strong>{{ error|escape }}</strong>
|
<strong>{{ field.label }}:</strong> <span>{{ error|escape }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -269,9 +269,9 @@
|
||||||
});
|
});
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
// set current console listen address or fall back to default
|
// set current console listen address or fall back to default
|
||||||
var console_listen_address = "{{ console_listen_address }}";
|
var console_listener_address = "{{ console_listener_address }}";
|
||||||
if (console_listen_address != '') {
|
if (console_listener_address != '') {
|
||||||
$("#console_select_listen_address option[value='" + console_listen_address + "']").prop('selected', true);
|
$("#console_select_listener_address option[value='" + console_listener_address + "']").prop('selected', true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
|
|
@ -67,12 +67,12 @@ def instance(request, pk):
|
||||||
console_form = ConsoleForm(
|
console_form = ConsoleForm(
|
||||||
initial={
|
initial={
|
||||||
"type": instance.console_type,
|
"type": instance.console_type,
|
||||||
"listen_on": instance.console_listen_address,
|
"listen_on": instance.console_listener_address,
|
||||||
"password": instance.console_passwd,
|
"password": instance.console_passwd,
|
||||||
"keymap": instance.console_keymap,
|
"keymap": instance.console_keymap,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
console_listen_addresses = settings.QEMU_CONSOLE_LISTEN_ADDRESSES
|
console_listener_addresses = settings.QEMU_CONSOLE_LISTENER_ADDRESSES
|
||||||
bottom_bar = app_settings.VIEW_INSTANCE_DETAIL_BOTTOM_BAR
|
bottom_bar = app_settings.VIEW_INSTANCE_DETAIL_BOTTOM_BAR
|
||||||
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
|
||||||
try:
|
try:
|
||||||
|
@ -344,7 +344,7 @@ def destroy(request, pk):
|
||||||
except Exception:
|
except Exception:
|
||||||
userinstance = UserInstance(is_delete=request.user.is_superuser)
|
userinstance = UserInstance(is_delete=request.user.is_superuser)
|
||||||
|
|
||||||
if request.method == "POST" and userinstance.is_delete:
|
if request.method in ["POST", "DELETE"] and userinstance.is_delete:
|
||||||
if instance.proxy.get_status() == 1:
|
if instance.proxy.get_status() == 1:
|
||||||
instance.proxy.force_shutdown()
|
instance.proxy.force_shutdown()
|
||||||
|
|
||||||
|
@ -390,7 +390,7 @@ def migrate(request, pk):
|
||||||
target_host = Compute.objects.get(id=compute_id)
|
target_host = Compute.objects.get(id=compute_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.migrate_instance(target_host, instance, request.user, live, unsafe, xml_del, offline)
|
utils.migrate_instance(target_host, instance, request.user, live, unsafe, xml_del, offline, autoconverge, compress, postcopy)
|
||||||
except libvirtError as err:
|
except libvirtError as err:
|
||||||
messages.error(request, err)
|
messages.error(request, err)
|
||||||
|
|
||||||
|
@ -1239,7 +1239,7 @@ def update_console(request, pk):
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
||||||
|
|
||||||
if "listen_on" in form.changed_data:
|
if "listen_on" in form.changed_data:
|
||||||
instance.proxy.set_console_listen_addr(form.cleaned_data["listen_on"])
|
instance.proxy.set_console_listener_addr(form.cleaned_data["listen_on"])
|
||||||
msg = _("Set VNC listen address")
|
msg = _("Set VNC listen address")
|
||||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
||||||
|
|
||||||
|
@ -1385,7 +1385,7 @@ def create_instance(request, compute_id, arch, machine):
|
||||||
default_disk_owner_uid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_UID)
|
default_disk_owner_uid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_UID)
|
||||||
default_disk_owner_gid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_GID)
|
default_disk_owner_gid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_GID)
|
||||||
default_scsi_disk_model = app_settings.INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER
|
default_scsi_disk_model = app_settings.INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER
|
||||||
listener_addr = settings.QEMU_CONSOLE_LISTEN_ADDRESSES
|
listener_addr = settings.QEMU_CONSOLE_LISTENER_ADDRESSES
|
||||||
mac_auto = util.randomMAC()
|
mac_auto = util.randomMAC()
|
||||||
disk_devices = conn.get_disk_device_types(arch, machine)
|
disk_devices = conn.get_disk_device_types(arch, machine)
|
||||||
disk_buses = conn.get_disk_bus_types(arch, machine)
|
disk_buses = conn.get_disk_bus_types(arch, machine)
|
||||||
|
@ -1545,7 +1545,7 @@ def create_instance(request, compute_id, arch, machine):
|
||||||
volumes=volume_list,
|
volumes=volume_list,
|
||||||
networks=data["networks"],
|
networks=data["networks"],
|
||||||
virtio=data["virtio"],
|
virtio=data["virtio"],
|
||||||
listen_addr=data["listener_addr"],
|
listener_addr=data["listener_addr"],
|
||||||
nwfilter=data["nwfilter"],
|
nwfilter=data["nwfilter"],
|
||||||
graphics=data["graphics"],
|
graphics=data["graphics"],
|
||||||
video=data["video"],
|
video=data["video"],
|
||||||
|
@ -1568,6 +1568,7 @@ def create_instance(request, compute_id, arch, machine):
|
||||||
conn.close()
|
conn.close()
|
||||||
except libvirtError as lib_err:
|
except libvirtError as lib_err:
|
||||||
messages.error(request, lib_err)
|
messages.error(request, lib_err)
|
||||||
|
|
||||||
return render(request, "create_instance_w2.html", locals())
|
return render(request, "create_instance_w2.html", locals())
|
||||||
|
|
||||||
|
|
||||||
|
|
11
interfaces/api/serializers.py
Normal file
11
interfaces/api/serializers.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from interfaces.models import Interfaces
|
||||||
|
|
||||||
|
|
||||||
|
class InterfacesSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Interfaces
|
||||||
|
fields = ['name', 'type', 'state', 'mac']
|
||||||
|
|
32
interfaces/api/viewsets.py
Normal file
32
interfaces/api/viewsets.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from computes.models import Compute
|
||||||
|
from rest_framework import status, viewsets
|
||||||
|
|
||||||
|
|
||||||
|
from vrtManager.interface import wvmInterfaces, wvmInterface
|
||||||
|
|
||||||
|
from .serializers import InterfacesSerializer
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceViewSet(viewsets.ViewSet):
|
||||||
|
"""
|
||||||
|
A viewset for listing retrieving interfaces.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def list(self, request, compute_pk=None):
|
||||||
|
queryset = []
|
||||||
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
|
conn = wvmInterfaces(compute.hostname, compute.login, compute.password, compute.type)
|
||||||
|
ifaces = conn.get_ifaces()
|
||||||
|
|
||||||
|
for iface in ifaces:
|
||||||
|
interf = wvmInterface(compute.hostname, compute.login, compute.password, compute.type, iface)
|
||||||
|
queryset.append(interf.get_details())
|
||||||
|
|
||||||
|
serializer = InterfacesSerializer(queryset, many=True, context={'request': request})
|
||||||
|
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
class Interfaces(models.Model):
|
||||||
|
name = models.CharField(_('name'), max_length=20, error_messages={'required': _('No interface name has been entered')})
|
||||||
|
type = models.CharField(_('status'), max_length=12)
|
||||||
|
state = models.CharField(_('device'), max_length=100)
|
||||||
|
mac = models.CharField(_('forward'), max_length=24)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
|
19
networks/api/serializers.py
Normal file
19
networks/api/serializers.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from networks.models import Networks
|
||||||
|
|
||||||
|
|
||||||
|
class NetworksSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Networks
|
||||||
|
fields = ['name', 'status', 'device', 'forward']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# class VolumeSerializer(serializers.ModelSerializer):
|
||||||
|
# allocation = serializers.ReadOnlyField()
|
||||||
|
# meta_prealloc = serializers.BooleanField(write_only=True)
|
||||||
|
# class Meta:
|
||||||
|
# model = Volume
|
||||||
|
# fields = ['name', 'type', 'allocation', 'size', 'meta_prealloc']
|
26
networks/api/viewsets.py
Normal file
26
networks/api/viewsets.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from computes.models import Compute
|
||||||
|
from rest_framework import status, viewsets
|
||||||
|
|
||||||
|
from vrtManager.network import wvmNetworks
|
||||||
|
|
||||||
|
from .serializers import NetworksSerializer
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkViewSet(viewsets.ViewSet):
|
||||||
|
"""
|
||||||
|
A viewset for listing retrieving networks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def list(self, request, compute_pk=None):
|
||||||
|
|
||||||
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
|
conn = wvmNetworks(compute.hostname, compute.login, compute.password, compute.type)
|
||||||
|
queryset = conn.get_networks_info()
|
||||||
|
|
||||||
|
serializer = NetworksSerializer(queryset, many=True, context={'request': request})
|
||||||
|
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
class Networks(models.Model):
|
||||||
|
name = models.CharField(_('name'), max_length=20, error_messages={'required': _('No network name has been entered')})
|
||||||
|
status = models.CharField(_('status'), max_length=12)
|
||||||
|
device = models.CharField(_('device'), max_length=100)
|
||||||
|
forward = models.CharField(_('forward'), max_length=24)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
|
25
storages/api/serializers.py
Normal file
25
storages/api/serializers.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from storages.models import Storages, Storage, Volume
|
||||||
|
|
||||||
|
|
||||||
|
class StoragesSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Storages
|
||||||
|
fields = ['name', 'status', 'type', 'size', 'volumes']
|
||||||
|
|
||||||
|
|
||||||
|
class StorageSerializer(serializers.ModelSerializer):
|
||||||
|
volumes = serializers.ReadOnlyField()
|
||||||
|
class Meta:
|
||||||
|
model = Storage
|
||||||
|
fields = ['state', 'size', 'free', 'status', 'path', 'type', 'autostart', 'volumes']
|
||||||
|
|
||||||
|
|
||||||
|
class VolumeSerializer(serializers.ModelSerializer):
|
||||||
|
allocation = serializers.ReadOnlyField()
|
||||||
|
meta_prealloc = serializers.BooleanField(write_only=True)
|
||||||
|
class Meta:
|
||||||
|
model = Volume
|
||||||
|
fields = ['name', 'type', 'allocation', 'size', 'meta_prealloc']
|
162
storages/api/viewsets.py
Normal file
162
storages/api/viewsets.py
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from computes.models import Compute
|
||||||
|
from rest_framework import status, viewsets
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from appsettings.settings import app_settings
|
||||||
|
|
||||||
|
from vrtManager.storage import wvmStorages, wvmStorage
|
||||||
|
|
||||||
|
from .serializers import StoragesSerializer, StorageSerializer, VolumeSerializer
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class StorageViewSet(viewsets.ViewSet):
|
||||||
|
"""
|
||||||
|
A viewset for listing retrieving storages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def list(self, request, compute_pk=None):
|
||||||
|
|
||||||
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
|
conn = wvmStorages(compute.hostname, compute.login, compute.password, compute.type)
|
||||||
|
queryset = conn.get_storages_info()
|
||||||
|
|
||||||
|
serializer = StoragesSerializer(queryset, many=True, context={'request': request})
|
||||||
|
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
def retrieve(self, request, pk=None, compute_pk=None):
|
||||||
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
|
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, pk)
|
||||||
|
|
||||||
|
infoset = {
|
||||||
|
"state": conn.is_active(),
|
||||||
|
"size": conn.get_size()[0],
|
||||||
|
"free": conn.get_size()[1],
|
||||||
|
"status": conn.get_status(),
|
||||||
|
"path": conn.get_target_path(),
|
||||||
|
"type": conn.get_type(),
|
||||||
|
"autostart": conn.get_autostart(),
|
||||||
|
"volumes": conn.update_volumes()
|
||||||
|
}
|
||||||
|
|
||||||
|
serializer = StorageSerializer(infoset, many=False, context={'request': request})
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def start(self, request, pk=None, compute_pk=None):
|
||||||
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
|
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, pk)
|
||||||
|
ret = conn.start()
|
||||||
|
conn.close()
|
||||||
|
return Response({'status': 'Pool start command send: ' + str(ret)})
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def stop(self, request, pk=None, compute_pk=None):
|
||||||
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
|
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, pk)
|
||||||
|
ret = conn.stop()
|
||||||
|
conn.close()
|
||||||
|
return Response({'status': 'Pool stop command send: ' + str(ret)})
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def refresh(self, request, pk=None, compute_pk=None):
|
||||||
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
|
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, pk)
|
||||||
|
ret = conn.refresh()
|
||||||
|
conn.close()
|
||||||
|
return Response({'status': 'Pool refresh command send: ' + str(ret)})
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'])
|
||||||
|
def XML_description(self, request, pk=None, compute_pk=None):
|
||||||
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
|
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, pk)
|
||||||
|
ret = conn._XMLDesc(0)
|
||||||
|
conn.close()
|
||||||
|
return Response({'return': str(ret)})
|
||||||
|
|
||||||
|
|
||||||
|
class VolumeViewSet(viewsets.ViewSet):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A simple ViewSet for listing or retrieving Storage Volumes.
|
||||||
|
"""
|
||||||
|
serializer_class = VolumeSerializer
|
||||||
|
lookup_value_regex = "[^/]+"
|
||||||
|
|
||||||
|
def list(self, request, storage_pk=None, compute_pk=None):
|
||||||
|
|
||||||
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
|
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, storage_pk)
|
||||||
|
state = conn.is_active()
|
||||||
|
|
||||||
|
if state:
|
||||||
|
conn.refresh()
|
||||||
|
volume_queryset = conn.update_volumes()
|
||||||
|
else:
|
||||||
|
volume_queryset = None
|
||||||
|
conn.close()
|
||||||
|
serializer = VolumeSerializer(volume_queryset, many=True, context={'request': request})
|
||||||
|
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
def retrieve(self, request, storage_pk=None, compute_pk=None, pk=None):
|
||||||
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
|
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, storage_pk)
|
||||||
|
state = conn.is_active()
|
||||||
|
|
||||||
|
if state:
|
||||||
|
volume_queryset = conn.get_volume_details(pk)
|
||||||
|
else:
|
||||||
|
volume_queryset = None
|
||||||
|
conn.close()
|
||||||
|
serializer = VolumeSerializer(volume_queryset, many=False, context={'request': request})
|
||||||
|
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
def create(self, request, storage_pk=None, compute_pk=None):
|
||||||
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
|
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, storage_pk)
|
||||||
|
|
||||||
|
serializer = VolumeSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
state = conn.is_active()
|
||||||
|
if state:
|
||||||
|
conn.refresh()
|
||||||
|
ret = conn.create_volume(
|
||||||
|
serializer.validated_data['name'],
|
||||||
|
serializer.validated_data['size'],
|
||||||
|
serializer.validated_data['type'],
|
||||||
|
serializer.validated_data['meta_prealloc'],
|
||||||
|
int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_UID),
|
||||||
|
int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_GID),
|
||||||
|
)
|
||||||
|
conn.close()
|
||||||
|
return Response({'status': 'Volume: ' + ret + ' is created'})
|
||||||
|
else:
|
||||||
|
return Response({'status': 'Pool is not active'})
|
||||||
|
else:
|
||||||
|
return Response({'status': 'Data is not right for create volume'})
|
||||||
|
|
||||||
|
def destroy(self, request, storage_pk=None, compute_pk=None, pk=None):
|
||||||
|
compute = get_object_or_404(Compute, pk=compute_pk)
|
||||||
|
|
||||||
|
conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, storage_pk)
|
||||||
|
|
||||||
|
if conn.is_active():
|
||||||
|
conn.del_volume(pk)
|
||||||
|
conn.close()
|
||||||
|
return Response({'status': 'Volume: ' + pk + ' is deleted'})
|
||||||
|
else:
|
||||||
|
return Response({'status': 'Pool is not active'})
|
|
@ -1,3 +1,47 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
class Storages(models.Model):
|
||||||
|
name = models.CharField(_('name'), max_length=20, error_messages={'required': _('No pool name has been entered')})
|
||||||
|
status = models.IntegerField(_('status'))
|
||||||
|
type = models.CharField(_('type'), max_length=100)
|
||||||
|
size = models.IntegerField(_('size'))
|
||||||
|
volumes = models.IntegerField(_('volumes'))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name}'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
|
||||||
|
|
||||||
|
class Volume(models.Model):
|
||||||
|
name = models.CharField(_('name'), max_length=128)
|
||||||
|
type = models.CharField(_('format'), max_length=12, choices=(('qcow2', 'qcow2 (recommended)'), ('qcow', 'qcow'), ('raw', 'raw')))
|
||||||
|
allocation = models.IntegerField(_('allocation'))
|
||||||
|
size = models.IntegerField(_('size'))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name}'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
verbose_name_plural = "Volumes"
|
||||||
|
|
||||||
|
|
||||||
|
class Storage(models.Model):
|
||||||
|
state = models.IntegerField(_('state'))
|
||||||
|
size = models.IntegerField(_('size'))
|
||||||
|
free = models.IntegerField(_('free'))
|
||||||
|
status = models.CharField(_('status'), max_length=128)
|
||||||
|
path = models.CharField(_('path'), max_length=128)
|
||||||
|
type = models.CharField(_('type'), max_length=128)
|
||||||
|
autostart = models.BooleanField(_('autostart'))
|
||||||
|
volumes = models.ForeignKey(Volume, on_delete=models.DO_NOTHING)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.path}'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
|
|
@ -181,10 +181,7 @@ class IPint(object):
|
||||||
if isinstance(data, INT_TYPES):
|
if isinstance(data, INT_TYPES):
|
||||||
self.ip = int(data)
|
self.ip = int(data)
|
||||||
if ipversion == 0:
|
if ipversion == 0:
|
||||||
if self.ip <= MAX_IPV4_ADDRESS:
|
ipversion = 4 if self.ip <= MAX_IPV4_ADDRESS else 6
|
||||||
ipversion = 4
|
|
||||||
else:
|
|
||||||
ipversion = 6
|
|
||||||
if ipversion == 4:
|
if ipversion == 4:
|
||||||
if self.ip > MAX_IPV4_ADDRESS:
|
if self.ip > MAX_IPV4_ADDRESS:
|
||||||
raise ValueError("IPv4 Address can't be larger than %x: %x" % (MAX_IPV4_ADDRESS, self.ip))
|
raise ValueError("IPv4 Address can't be larger than %x: %x" % (MAX_IPV4_ADDRESS, self.ip))
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import string
|
import string
|
||||||
|
import contextlib
|
||||||
|
|
||||||
from vrtManager import util
|
from vrtManager import util
|
||||||
from vrtManager.connection import wvmConnect
|
from vrtManager.connection import wvmConnect
|
||||||
|
@ -30,19 +31,13 @@ class wvmCreate(wvmConnect):
|
||||||
"""
|
"""
|
||||||
Function return all images on all storages
|
Function return all images on all storages
|
||||||
"""
|
"""
|
||||||
images = list()
|
images = []
|
||||||
storages = self.get_storages(only_actives=True)
|
storages = self.get_storages(only_actives=True)
|
||||||
for storage in storages:
|
for storage in storages:
|
||||||
stg = self.get_storage(storage)
|
stg = self.get_storage(storage)
|
||||||
try:
|
with contextlib.suppress(Exception):
|
||||||
stg.refresh(0)
|
stg.refresh(0)
|
||||||
except Exception:
|
images.extend(img for img in stg.listVolumes() if not img.lower().endswith(".iso"))
|
||||||
pass
|
|
||||||
for img in stg.listVolumes():
|
|
||||||
if img.lower().endswith(".iso"):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
images.append(img)
|
|
||||||
return images
|
return images
|
||||||
|
|
||||||
def get_os_type(self):
|
def get_os_type(self):
|
||||||
|
@ -50,7 +45,7 @@ class wvmCreate(wvmConnect):
|
||||||
return util.get_xml_path(self.get_cap_xml(), "/capabilities/guest/os_type")
|
return util.get_xml_path(self.get_cap_xml(), "/capabilities/guest/os_type")
|
||||||
|
|
||||||
def get_host_arch(self):
|
def get_host_arch(self):
|
||||||
"""Get guest capabilities"""
|
"""Get host architecture"""
|
||||||
return util.get_xml_path(self.get_cap_xml(), "/capabilities/host/cpu/arch")
|
return util.get_xml_path(self.get_cap_xml(), "/capabilities/host/cpu/arch")
|
||||||
|
|
||||||
def create_volume(self, storage, name, size, image_format, metadata=False, disk_owner_uid=0, disk_owner_gid=0):
|
def create_volume(self, storage, name, size, image_format, metadata=False, disk_owner_uid=0, disk_owner_gid=0):
|
||||||
|
@ -58,10 +53,7 @@ class wvmCreate(wvmConnect):
|
||||||
stg = self.get_storage(storage)
|
stg = self.get_storage(storage)
|
||||||
storage_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type")
|
storage_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type")
|
||||||
if storage_type == "dir":
|
if storage_type == "dir":
|
||||||
if image_format in ("qcow", "qcow2"):
|
name += f".{image_format}" if image_format in ("qcow", "qcow2") else ".img"
|
||||||
name += "." + image_format
|
|
||||||
else:
|
|
||||||
name += ".img"
|
|
||||||
alloc = 0
|
alloc = 0
|
||||||
else:
|
else:
|
||||||
image_format = 'raw'
|
image_format = 'raw'
|
||||||
|
@ -87,28 +79,19 @@ class wvmCreate(wvmConnect):
|
||||||
</target>
|
</target>
|
||||||
</volume>"""
|
</volume>"""
|
||||||
stg.createXML(xml, metadata)
|
stg.createXML(xml, metadata)
|
||||||
try:
|
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
stg.refresh(0)
|
stg.refresh(0)
|
||||||
except:
|
|
||||||
pass
|
|
||||||
vol = stg.storageVolLookupByName(name)
|
vol = stg.storageVolLookupByName(name)
|
||||||
return vol.path()
|
return vol.path()
|
||||||
|
|
||||||
def get_volume_format_type(self, path):
|
def get_volume_format_type(self, path):
|
||||||
vol = self.get_volume_by_path(path)
|
vol = self.get_volume_by_path(path)
|
||||||
vol_type = util.get_xml_path(vol.XMLDesc(0), "/volume/target/format/@type")
|
vol_type = util.get_xml_path(vol.XMLDesc(0), "/volume/target/format/@type")
|
||||||
if vol_type == "unknown" or vol_type == "iso":
|
return "raw" if vol_type in ["unknown", "iso"] else vol_type or "raw"
|
||||||
return "raw"
|
|
||||||
if vol_type:
|
|
||||||
return vol_type
|
|
||||||
else:
|
|
||||||
return "raw"
|
|
||||||
|
|
||||||
def get_volume_path(self, volume, pool=None):
|
def get_volume_path(self, volume, pool=None):
|
||||||
if not pool:
|
storages = [pool] if pool else self.get_storages(only_actives=True)
|
||||||
storages = self.get_storages(only_actives=True)
|
|
||||||
else:
|
|
||||||
storages = [pool]
|
|
||||||
for storage in storages:
|
for storage in storages:
|
||||||
stg = self.get_storage(storage)
|
stg = self.get_storage(storage)
|
||||||
if stg.info()[0] != 0:
|
if stg.info()[0] != 0:
|
||||||
|
@ -124,10 +107,7 @@ class wvmCreate(wvmConnect):
|
||||||
|
|
||||||
def clone_from_template(self, clone, template, storage=None, metadata=False, disk_owner_uid=0, disk_owner_gid=0):
|
def clone_from_template(self, clone, template, storage=None, metadata=False, disk_owner_uid=0, disk_owner_gid=0):
|
||||||
vol = self.get_volume_by_path(template)
|
vol = self.get_volume_by_path(template)
|
||||||
if not storage:
|
stg = self.get_storage(storage) if storage else vol.storagePoolLookupByVolume()
|
||||||
stg = vol.storagePoolLookupByVolume()
|
|
||||||
else:
|
|
||||||
stg = self.get_storage(storage)
|
|
||||||
|
|
||||||
storage_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type")
|
storage_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type")
|
||||||
format = util.get_xml_path(vol.XMLDesc(0), "/volume/target/format/@type")
|
format = util.get_xml_path(vol.XMLDesc(0), "/volume/target/format/@type")
|
||||||
|
@ -180,7 +160,7 @@ class wvmCreate(wvmConnect):
|
||||||
nwfilter,
|
nwfilter,
|
||||||
graphics,
|
graphics,
|
||||||
virtio,
|
virtio,
|
||||||
listen_addr,
|
listener_addr,
|
||||||
video="vga",
|
video="vga",
|
||||||
console_pass="random",
|
console_pass="random",
|
||||||
mac=None,
|
mac=None,
|
||||||
|
@ -226,12 +206,8 @@ class wvmCreate(wvmConnect):
|
||||||
|
|
||||||
if caps["features"]:
|
if caps["features"]:
|
||||||
xml += """<features>"""
|
xml += """<features>"""
|
||||||
if "acpi" in caps["features"]:
|
for feat in [x for x in ("acpi", "apic", "pae",) if x in caps["features"]]:
|
||||||
xml += """<acpi/>"""
|
xml += f"""<{feat}/>"""
|
||||||
if "apic" in caps["features"]:
|
|
||||||
xml += """<apic/>"""
|
|
||||||
if "pae" in caps["features"]:
|
|
||||||
xml += """<pae/>"""
|
|
||||||
if firmware.get("secure", "no") == "yes":
|
if firmware.get("secure", "no") == "yes":
|
||||||
xml += """<smm state="on"/>"""
|
xml += """<smm state="on"/>"""
|
||||||
xml += """</features>"""
|
xml += """</features>"""
|
||||||
|
@ -240,9 +216,7 @@ class wvmCreate(wvmConnect):
|
||||||
xml += """<cpu mode='host-model'/>"""
|
xml += """<cpu mode='host-model'/>"""
|
||||||
elif vcpu_mode == "host-passthrough":
|
elif vcpu_mode == "host-passthrough":
|
||||||
xml += """<cpu mode='host-passthrough'/>"""
|
xml += """<cpu mode='host-passthrough'/>"""
|
||||||
elif vcpu_mode == "":
|
elif vcpu_mode != "":
|
||||||
pass
|
|
||||||
else:
|
|
||||||
xml += f"""<cpu mode='custom' match='exact' check='none'>
|
xml += f"""<cpu mode='custom' match='exact' check='none'>
|
||||||
<model fallback='allow'>{vcpu_mode}</model>"""
|
<model fallback='allow'>{vcpu_mode}</model>"""
|
||||||
xml += """</cpu>"""
|
xml += """</cpu>"""
|
||||||
|
@ -306,7 +280,7 @@ class wvmCreate(wvmConnect):
|
||||||
xml += """<target dev='hd%s' bus='%s'/>""" % (hd_disk_letters.pop(0), volume.get("bus"))
|
xml += """<target dev='hd%s' bus='%s'/>""" % (hd_disk_letters.pop(0), volume.get("bus"))
|
||||||
elif volume.get("bus") == "fdc":
|
elif volume.get("bus") == "fdc":
|
||||||
xml += """<target dev='fd%s' bus='%s'/>""" % (fd_disk_letters.pop(0), volume.get("bus"))
|
xml += """<target dev='fd%s' bus='%s'/>""" % (fd_disk_letters.pop(0), volume.get("bus"))
|
||||||
elif volume.get("bus") == "sata" or volume.get("bus") == "scsi":
|
elif volume.get("bus") in ["sata", "scsi"]:
|
||||||
xml += """<target dev='sd%s' bus='%s'/>""" % (sd_disk_letters.pop(0), volume.get("bus"))
|
xml += """<target dev='sd%s' bus='%s'/>""" % (sd_disk_letters.pop(0), volume.get("bus"))
|
||||||
else:
|
else:
|
||||||
xml += """<target dev='sd%s'/>""" % sd_disk_letters.pop(0)
|
xml += """<target dev='sd%s'/>""" % sd_disk_letters.pop(0)
|
||||||
|
@ -345,21 +319,20 @@ class wvmCreate(wvmConnect):
|
||||||
|
|
||||||
if console_pass == "random":
|
if console_pass == "random":
|
||||||
console_pass = "passwd='" + util.randomPasswd() + "'"
|
console_pass = "passwd='" + util.randomPasswd() + "'"
|
||||||
else:
|
elif console_pass != "":
|
||||||
if not console_pass == "":
|
console_pass = "passwd='" + console_pass + "'"
|
||||||
console_pass = "passwd='" + console_pass + "'"
|
|
||||||
|
|
||||||
if "usb" in dom_caps["disk_bus"]:
|
if "usb" in dom_caps["disk_bus"]:
|
||||||
xml += """<input type='mouse' bus='{}'/>""".format("virtio" if virtio else "usb")
|
xml += f"""<input type='mouse' bus='{"virtio" if virtio else "usb"}'/>"""
|
||||||
xml += """<input type='keyboard' bus='{}'/>""".format("virtio" if virtio else "usb")
|
xml += f"""<input type='keyboard' bus='{"virtio" if virtio else "usb"}'/>"""
|
||||||
xml += """<input type='tablet' bus='{}'/>""".format("virtio" if virtio else "usb")
|
xml += f"""<input type='tablet' bus='{"virtio" if virtio else "usb"}'/>"""
|
||||||
else:
|
else:
|
||||||
xml += """<input type='mouse'/>"""
|
xml += """<input type='mouse'/>"""
|
||||||
xml += """<input type='keyboard'/>"""
|
xml += """<input type='keyboard'/>"""
|
||||||
xml += """<input type='tablet'/>"""
|
xml += """<input type='tablet'/>"""
|
||||||
|
|
||||||
xml += f"""
|
xml += f"""
|
||||||
<graphics type='{graphics}' port='-1' autoport='yes' {console_pass} listen='{listen_addr}'/>
|
<graphics type='{graphics}' port='-1' autoport='yes' {console_pass} listen='{listener_addr}'/>
|
||||||
<console type='pty'/> """
|
<console type='pty'/> """
|
||||||
|
|
||||||
if qemu_ga and virtio:
|
if qemu_ga and virtio:
|
||||||
|
@ -372,4 +345,4 @@ class wvmCreate(wvmConnect):
|
||||||
</video>
|
</video>
|
||||||
</devices>
|
</devices>
|
||||||
</domain>"""
|
</domain>"""
|
||||||
self._defineXML(xml)
|
return self._defineXML(xml)
|
||||||
|
|
|
@ -21,12 +21,11 @@ class wvmHostDetails(wvmConnect):
|
||||||
freemem = self.wvm.getMemoryStats(-1, 0)
|
freemem = self.wvm.getMemoryStats(-1, 0)
|
||||||
if isinstance(freemem, dict):
|
if isinstance(freemem, dict):
|
||||||
free = (freemem["buffers"] + freemem["free"] + freemem["cached"]) * 1024
|
free = (freemem["buffers"] + freemem["free"] + freemem["cached"]) * 1024
|
||||||
percent = abs(100 - ((free * 100) // all_mem))
|
percent = abs(100 - free * 100 // all_mem)
|
||||||
usage = all_mem - free
|
usage = all_mem - free
|
||||||
mem_usage = {"total": all_mem, "usage": usage, "percent": percent}
|
return {"total": all_mem, "usage": usage, "percent": percent}
|
||||||
else:
|
else:
|
||||||
mem_usage = {"total": None, "usage": None, "percent": None}
|
return {"total": None, "usage": None, "percent": None}
|
||||||
return mem_usage
|
|
||||||
|
|
||||||
def get_cpu_usage(self):
|
def get_cpu_usage(self):
|
||||||
"""
|
"""
|
||||||
|
@ -35,30 +34,30 @@ class wvmHostDetails(wvmConnect):
|
||||||
prev_idle = 0
|
prev_idle = 0
|
||||||
prev_total = 0
|
prev_total = 0
|
||||||
cpu = self.wvm.getCPUStats(-1, 0)
|
cpu = self.wvm.getCPUStats(-1, 0)
|
||||||
if isinstance(cpu, dict):
|
if not isinstance(cpu, dict):
|
||||||
for num in range(2):
|
|
||||||
idle = self.wvm.getCPUStats(-1, 0)["idle"]
|
|
||||||
total = sum(self.wvm.getCPUStats(-1, 0).values())
|
|
||||||
diff_idle = idle - prev_idle
|
|
||||||
diff_total = total - prev_total
|
|
||||||
diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10
|
|
||||||
prev_total = total
|
|
||||||
prev_idle = idle
|
|
||||||
if num == 0:
|
|
||||||
time.sleep(1)
|
|
||||||
else:
|
|
||||||
if diff_usage < 0:
|
|
||||||
diff_usage = 0
|
|
||||||
else:
|
|
||||||
return {"usage": None}
|
return {"usage": None}
|
||||||
|
|
||||||
|
for num in range(2):
|
||||||
|
idle = self.wvm.getCPUStats(-1, 0)["idle"]
|
||||||
|
total = sum(self.wvm.getCPUStats(-1, 0).values())
|
||||||
|
diff_idle = idle - prev_idle
|
||||||
|
diff_total = total - prev_total
|
||||||
|
diff_usage = (1000 * (diff_total - diff_idle) /
|
||||||
|
diff_total + 5) / 10
|
||||||
|
prev_total = total
|
||||||
|
prev_idle = idle
|
||||||
|
if num == 0:
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
diff_usage = max(diff_usage, 0)
|
||||||
|
|
||||||
return {"usage": diff_usage}
|
return {"usage": diff_usage}
|
||||||
|
|
||||||
def get_node_info(self):
|
def get_node_info(self):
|
||||||
"""
|
"""
|
||||||
Function return host server information: hostname, cpu, memory, ...
|
Function return host server information: hostname, cpu, memory, ...
|
||||||
"""
|
"""
|
||||||
info = list()
|
info = [self.wvm.getHostname()] # hostname
|
||||||
info.append(self.wvm.getHostname()) # hostname
|
|
||||||
info.append(self.wvm.getInfo()[0]) # architecture
|
info.append(self.wvm.getInfo()[0]) # architecture
|
||||||
info.append(self.wvm.getInfo()[1] * 1048576) # memory
|
info.append(self.wvm.getInfo()[1] * 1048576) # memory
|
||||||
info.append(self.wvm.getInfo()[2]) # cpu core count
|
info.append(self.wvm.getInfo()[2]) # cpu core count
|
||||||
|
|
|
@ -130,12 +130,12 @@ class wvmInstances(wvmConnect):
|
||||||
|
|
||||||
def graphics_listen(self, name):
|
def graphics_listen(self, name):
|
||||||
inst = self.get_instance(name)
|
inst = self.get_instance(name)
|
||||||
listen_addr = util.get_xml_path(inst.XMLDesc(0), "/domain/devices/graphics/@listen")
|
listener_addr = util.get_xml_path(inst.XMLDesc(0), "/domain/devices/graphics/@listen")
|
||||||
if listen_addr is None:
|
if listener_addr is None:
|
||||||
listen_addr = util.get_xml_path(inst.XMLDesc(0), "/domain/devices/graphics/listen/@address")
|
listener_addr = util.get_xml_path(inst.XMLDesc(0), "/domain/devices/graphics/listen/@address")
|
||||||
if listen_addr is None:
|
if listener_addr is None:
|
||||||
return "None"
|
return "None"
|
||||||
return listen_addr
|
return listener_addr
|
||||||
|
|
||||||
def graphics_port(self, name):
|
def graphics_port(self, name):
|
||||||
inst = self.get_instance(name)
|
inst = self.get_instance(name)
|
||||||
|
@ -253,6 +253,9 @@ class wvmInstance(wvmConnect):
|
||||||
else:
|
else:
|
||||||
return self.get_vcpu()
|
return self.get_vcpu()
|
||||||
|
|
||||||
|
def get_vcpu_mode(self):
|
||||||
|
return util.get_xml_path(self._XMLDesc(0), "/domain/cpu/@current")
|
||||||
|
|
||||||
def get_arch(self):
|
def get_arch(self):
|
||||||
return util.get_xml_path(self._XMLDesc(0), "/domain/os/type/@arch")
|
return util.get_xml_path(self._XMLDesc(0), "/domain/os/type/@arch")
|
||||||
|
|
||||||
|
@ -979,15 +982,15 @@ class wvmInstance(wvmConnect):
|
||||||
telnet_port = service_port
|
telnet_port = service_port
|
||||||
return telnet_port
|
return telnet_port
|
||||||
|
|
||||||
def get_console_listen_addr(self):
|
def get_console_listener_addr(self):
|
||||||
listen_addr = util.get_xml_path(self._XMLDesc(0), "/domain/devices/graphics/@listen")
|
listener_addr = util.get_xml_path(self._XMLDesc(0), "/domain/devices/graphics/@listen")
|
||||||
if listen_addr is None:
|
if listener_addr is None:
|
||||||
listen_addr = util.get_xml_path(self._XMLDesc(0), "/domain/devices/graphics/listen/@address")
|
listener_addr = util.get_xml_path(self._XMLDesc(0), "/domain/devices/graphics/listen/@address")
|
||||||
if listen_addr is None:
|
if listener_addr is None:
|
||||||
return "127.0.0.1"
|
return "127.0.0.1"
|
||||||
return listen_addr
|
return listener_addr
|
||||||
|
|
||||||
def set_console_listen_addr(self, listen_addr):
|
def set_console_listener_addr(self, listener_addr):
|
||||||
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
||||||
root = ElementTree.fromstring(xml)
|
root = ElementTree.fromstring(xml)
|
||||||
console_type = self.get_console_type()
|
console_type = self.get_console_type()
|
||||||
|
@ -1001,9 +1004,9 @@ class wvmInstance(wvmConnect):
|
||||||
listen = graphic.find("listen[@type='address']")
|
listen = graphic.find("listen[@type='address']")
|
||||||
if listen is None:
|
if listen is None:
|
||||||
return False
|
return False
|
||||||
if listen_addr:
|
if listener_addr:
|
||||||
graphic.set("listen", listen_addr)
|
graphic.set("listen", listener_addr)
|
||||||
listen.set("address", listen_addr)
|
listen.set("address", listener_addr)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
graphic.attrib.pop("listen")
|
graphic.attrib.pop("listen")
|
||||||
|
|
|
@ -57,7 +57,7 @@ class wvmInterface(wvmConnect):
|
||||||
try:
|
try:
|
||||||
xml = self._XMLDesc(VIR_INTERFACE_XML_INACTIVE)
|
xml = self._XMLDesc(VIR_INTERFACE_XML_INACTIVE)
|
||||||
return util.get_xml_path(xml, "/interface/start/@mode")
|
return util.get_xml_path(xml, "/interface/start/@mode")
|
||||||
except:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
|
@ -65,10 +65,7 @@ class wvmInterface(wvmConnect):
|
||||||
|
|
||||||
def get_mac(self):
|
def get_mac(self):
|
||||||
mac = self.iface.MACString()
|
mac = self.iface.MACString()
|
||||||
if mac:
|
return mac or None
|
||||||
return mac
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_type(self):
|
def get_type(self):
|
||||||
xml = self._XMLDesc()
|
xml = self._XMLDesc()
|
||||||
|
@ -78,11 +75,8 @@ class wvmInterface(wvmConnect):
|
||||||
try:
|
try:
|
||||||
xml = self._XMLDesc(VIR_INTERFACE_XML_INACTIVE)
|
xml = self._XMLDesc(VIR_INTERFACE_XML_INACTIVE)
|
||||||
ipaddr = util.get_xml_path(xml, "/interface/protocol[@family='ipv4']/ip/@address")
|
ipaddr = util.get_xml_path(xml, "/interface/protocol[@family='ipv4']/ip/@address")
|
||||||
if ipaddr:
|
return "static" if ipaddr else "dhcp"
|
||||||
return "static"
|
except Exception:
|
||||||
else:
|
|
||||||
return "dhcp"
|
|
||||||
except:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_ipv4(self):
|
def get_ipv4(self):
|
||||||
|
@ -92,17 +86,14 @@ class wvmInterface(wvmConnect):
|
||||||
if not int_ipv4_ip or not int_ipv4_mask:
|
if not int_ipv4_ip or not int_ipv4_mask:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return int_ipv4_ip + "/" + int_ipv4_mask
|
return f"{int_ipv4_ip}/{int_ipv4_mask}"
|
||||||
|
|
||||||
def get_ipv6_type(self):
|
def get_ipv6_type(self):
|
||||||
try:
|
try:
|
||||||
xml = self._XMLDesc(VIR_INTERFACE_XML_INACTIVE)
|
xml = self._XMLDesc(VIR_INTERFACE_XML_INACTIVE)
|
||||||
ipaddr = util.get_xml_path(xml, "/interface/protocol[@family='ipv6']/ip/@address")
|
ipaddr = util.get_xml_path(xml, "/interface/protocol[@family='ipv6']/ip/@address")
|
||||||
if ipaddr:
|
return "static" if ipaddr else "dhcp"
|
||||||
return "static"
|
except Exception:
|
||||||
else:
|
|
||||||
return "dhcp"
|
|
||||||
except:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_ipv6(self):
|
def get_ipv6(self):
|
||||||
|
@ -112,40 +103,39 @@ class wvmInterface(wvmConnect):
|
||||||
if not int_ipv6_ip or not int_ipv6_mask:
|
if not int_ipv6_ip or not int_ipv6_mask:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return int_ipv6_ip + "/" + int_ipv6_mask
|
return f"{int_ipv6_ip}/{int_ipv6_mask}"
|
||||||
|
|
||||||
def get_bridge(self):
|
def get_bridge(self):
|
||||||
bridge = None
|
bridge = None
|
||||||
if self.get_type() == "bridge":
|
if self.get_type() != "bridge":
|
||||||
bridge = util.get_xml_path(self._XMLDesc(), "/interface/bridge/interface/@name")
|
|
||||||
for iface in self.get_bridge_slave_ifaces():
|
|
||||||
if iface.get("state") == "up" and iface.get("speed") != "unknown":
|
|
||||||
bridge = iface.get("name")
|
|
||||||
return bridge
|
|
||||||
return bridge
|
|
||||||
else:
|
|
||||||
return None
|
return None
|
||||||
|
bridge = util.get_xml_path(self._XMLDesc(), "/interface/bridge/interface/@name")
|
||||||
|
for iface in self.get_bridge_slave_ifaces():
|
||||||
|
if iface.get("state") == "up" and iface.get("speed") != "unknown":
|
||||||
|
bridge = iface.get("name")
|
||||||
|
return bridge
|
||||||
|
return bridge
|
||||||
|
|
||||||
def get_bridge_slave_ifaces(self):
|
def get_bridge_slave_ifaces(self):
|
||||||
ifaces = list()
|
if self.get_type() != "bridge":
|
||||||
if self.get_type() == "bridge":
|
|
||||||
tree = ElementTree.fromstring(self._XMLDesc())
|
|
||||||
for iface in tree.findall("./bridge/"):
|
|
||||||
address = state = speed = None
|
|
||||||
name = iface.get("name")
|
|
||||||
if_type = iface.get("type")
|
|
||||||
link = iface.find("link")
|
|
||||||
if link is not None:
|
|
||||||
state = link.get("state")
|
|
||||||
speed = link.get("speed")
|
|
||||||
mac = iface.find("mac")
|
|
||||||
if mac is not None:
|
|
||||||
address = mac.get("address")
|
|
||||||
ifaces.append({"name": name, "type": if_type, "state": state, "speed": speed, "mac": address})
|
|
||||||
return ifaces
|
|
||||||
else:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
ifaces = []
|
||||||
|
tree = ElementTree.fromstring(self._XMLDesc())
|
||||||
|
for iface in tree.findall("./bridge/"):
|
||||||
|
address = state = speed = None
|
||||||
|
name = iface.get("name")
|
||||||
|
if_type = iface.get("type")
|
||||||
|
link = iface.find("link")
|
||||||
|
if link is not None:
|
||||||
|
state = link.get("state")
|
||||||
|
speed = link.get("speed")
|
||||||
|
mac = iface.find("mac")
|
||||||
|
if mac is not None:
|
||||||
|
address = mac.get("address")
|
||||||
|
ifaces.append({"name": name, "type": if_type, "state": state, "speed": speed, "mac": address})
|
||||||
|
return ifaces
|
||||||
|
|
||||||
def get_details(self):
|
def get_details(self):
|
||||||
mac = self.get_mac()
|
mac = self.get_mac()
|
||||||
itype = self.get_type()
|
itype = self.get_type()
|
||||||
|
|
|
@ -23,10 +23,8 @@ def network_size(subnet, dhcp=None):
|
||||||
if addr.version() == 6:
|
if addr.version() == 6:
|
||||||
mask = mask.lstrip("/") if "/" in mask else mask
|
mask = mask.lstrip("/") if "/" in mask else mask
|
||||||
dhcp_pool = [IP(addr[0].strCompressed() + hex(256)), IP(addr[0].strCompressed() + hex(512 - 1))]
|
dhcp_pool = [IP(addr[0].strCompressed() + hex(256)), IP(addr[0].strCompressed() + hex(512 - 1))]
|
||||||
if dhcp:
|
|
||||||
return gateway, mask, dhcp_pool
|
return (gateway, mask, dhcp_pool) if dhcp else (gateway, mask, None)
|
||||||
else:
|
|
||||||
return gateway, mask, None
|
|
||||||
|
|
||||||
|
|
||||||
class wvmNetworks(wvmConnect):
|
class wvmNetworks(wvmConnect):
|
||||||
|
@ -42,7 +40,12 @@ class wvmNetworks(wvmConnect):
|
||||||
net_bridge = util.get_xml_path(net.XMLDesc(0), "/network/forward/interface/@dev")
|
net_bridge = util.get_xml_path(net.XMLDesc(0), "/network/forward/interface/@dev")
|
||||||
|
|
||||||
net_forward = util.get_xml_path(net.XMLDesc(0), "/network/forward/@mode")
|
net_forward = util.get_xml_path(net.XMLDesc(0), "/network/forward/@mode")
|
||||||
networks.append({"name": network, "status": net_status, "device": net_bridge, "forward": net_forward})
|
networks.append({
|
||||||
|
"name": network,
|
||||||
|
"status": net_status,
|
||||||
|
"device": net_bridge,
|
||||||
|
"forward": net_forward
|
||||||
|
})
|
||||||
return networks
|
return networks
|
||||||
|
|
||||||
def define_network(self, xml):
|
def define_network(self, xml):
|
||||||
|
@ -137,7 +140,7 @@ class wvmNetwork(wvmConnect):
|
||||||
def get_bridge_device(self):
|
def get_bridge_device(self):
|
||||||
try:
|
try:
|
||||||
return self.net.bridgeName()
|
return self.net.bridgeName()
|
||||||
except:
|
except Exception:
|
||||||
return util.get_xml_path(self._XMLDesc(0), "/network/forward/interface/@dev")
|
return util.get_xml_path(self._XMLDesc(0), "/network/forward/interface/@dev")
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
@ -153,7 +156,7 @@ class wvmNetwork(wvmConnect):
|
||||||
return self.net.update(command, section, parentIndex, xml, flags)
|
return self.net.update(command, section, parentIndex, xml, flags)
|
||||||
|
|
||||||
def get_ip_networks(self):
|
def get_ip_networks(self):
|
||||||
ip_networks = dict()
|
ip_networks = {}
|
||||||
xml = self._XMLDesc(0)
|
xml = self._XMLDesc(0)
|
||||||
if util.get_xml_path(xml, "/network/ip") is None:
|
if util.get_xml_path(xml, "/network/ip") is None:
|
||||||
return ip_networks
|
return ip_networks
|
||||||
|
@ -164,10 +167,10 @@ class wvmNetwork(wvmConnect):
|
||||||
netmask_str = ip.get("netmask")
|
netmask_str = ip.get("netmask")
|
||||||
prefix = ip.get("prefix")
|
prefix = ip.get("prefix")
|
||||||
family = ip.get("family", "ipv4")
|
family = ip.get("family", "ipv4")
|
||||||
base = 32 if family == "ipv4" else 128
|
|
||||||
if prefix:
|
if prefix:
|
||||||
prefix = int(prefix)
|
prefix = int(prefix)
|
||||||
binstr = (prefix * "1") + ((base - prefix) * "0")
|
base = 32 if family == "ipv4" else 128
|
||||||
|
binstr = prefix * "1" + (base - prefix) * "0"
|
||||||
netmask_str = str(IP(int(binstr, base=2)))
|
netmask_str = str(IP(int(binstr, base=2)))
|
||||||
|
|
||||||
if netmask_str:
|
if netmask_str:
|
||||||
|
@ -183,8 +186,7 @@ class wvmNetwork(wvmConnect):
|
||||||
|
|
||||||
def get_network_mac(self):
|
def get_network_mac(self):
|
||||||
xml = self._XMLDesc(0)
|
xml = self._XMLDesc(0)
|
||||||
mac = util.get_xml_path(xml, "/network/mac/@address")
|
return util.get_xml_path(xml, "/network/mac/@address")
|
||||||
return mac
|
|
||||||
|
|
||||||
def get_network_forward(self):
|
def get_network_forward(self):
|
||||||
xml = self._XMLDesc(0)
|
xml = self._XMLDesc(0)
|
||||||
|
@ -201,22 +203,15 @@ class wvmNetwork(wvmConnect):
|
||||||
dhcpstart = util.get_xml_path(xml, "/network/ip[@family='ipv6']/dhcp/range[1]/@start")
|
dhcpstart = util.get_xml_path(xml, "/network/ip[@family='ipv6']/dhcp/range[1]/@start")
|
||||||
dhcpend = util.get_xml_path(xml, "/network/ip[@family='ipv6']/dhcp/range[1]/@end")
|
dhcpend = util.get_xml_path(xml, "/network/ip[@family='ipv6']/dhcp/range[1]/@end")
|
||||||
|
|
||||||
if not dhcpstart or not dhcpend:
|
return None if not dhcpstart or not dhcpend else [IP(dhcpstart), IP(dhcpend)]
|
||||||
return None
|
|
||||||
|
|
||||||
return [IP(dhcpstart), IP(dhcpend)]
|
|
||||||
|
|
||||||
def get_dhcp_range_start(self, family="ipv4"):
|
def get_dhcp_range_start(self, family="ipv4"):
|
||||||
dhcp = self.get_dhcp_range(family)
|
dhcp = self.get_dhcp_range(family)
|
||||||
if not dhcp:
|
return dhcp[0] if dhcp else None
|
||||||
return None
|
|
||||||
return dhcp[0]
|
|
||||||
|
|
||||||
def get_dhcp_range_end(self, family="ipv4"):
|
def get_dhcp_range_end(self, family="ipv4"):
|
||||||
dhcp = self.get_dhcp_range(family)
|
dhcp = self.get_dhcp_range(family)
|
||||||
if not dhcp:
|
return dhcp[1] if dhcp else None
|
||||||
return None
|
|
||||||
return dhcp[1]
|
|
||||||
|
|
||||||
def can_pxe(self):
|
def can_pxe(self):
|
||||||
xml = self._XMLDesc(0)
|
xml = self._XMLDesc(0)
|
||||||
|
@ -226,21 +221,19 @@ class wvmNetwork(wvmConnect):
|
||||||
return bool(util.get_xml_path(xml, "/network/ip/dhcp/bootp/@file"))
|
return bool(util.get_xml_path(xml, "/network/ip/dhcp/bootp/@file"))
|
||||||
|
|
||||||
def get_dhcp_host_addr(self, family="ipv4"):
|
def get_dhcp_host_addr(self, family="ipv4"):
|
||||||
result = list()
|
result = []
|
||||||
tree = etree.fromstring(self._XMLDesc(0))
|
tree = etree.fromstring(self._XMLDesc(0))
|
||||||
|
|
||||||
for ipdhcp in tree.findall("./ip"):
|
for ipdhcp in tree.findall("./ip"):
|
||||||
if family == "ipv4":
|
if family == "ipv4":
|
||||||
if ipdhcp.get("family") is None:
|
if ipdhcp.get("family") is not None:
|
||||||
hosts = ipdhcp.findall("./dhcp/host")
|
|
||||||
for host in hosts:
|
|
||||||
host_ip = host.get("ip")
|
|
||||||
mac = host.get("mac")
|
|
||||||
name = host.get("name", "")
|
|
||||||
result.append({"ip": host_ip, "mac": mac, "name": name})
|
|
||||||
return result
|
|
||||||
else:
|
|
||||||
continue
|
continue
|
||||||
|
hosts = ipdhcp.findall("./dhcp/host")
|
||||||
|
for host in hosts:
|
||||||
|
host_ip = host.get("ip")
|
||||||
|
mac = host.get("mac")
|
||||||
|
name = host.get("name", "")
|
||||||
|
result.append({"ip": host_ip, "mac": mac, "name": name})
|
||||||
|
return result
|
||||||
if family == "ipv6":
|
if family == "ipv6":
|
||||||
hosts = tree.xpath("./ip[@family='ipv6']/dhcp/host")
|
hosts = tree.xpath("./ip[@family='ipv6']/dhcp/host")
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
|
@ -272,9 +265,9 @@ class wvmNetwork(wvmConnect):
|
||||||
for h in hosts:
|
for h in hosts:
|
||||||
if h.get("ip") == ip:
|
if h.get("ip") == ip:
|
||||||
if family == "ipv4":
|
if family == "ipv4":
|
||||||
new_xml = '<host mac="{}" name="{}" ip="{}"/>'.format(h.get("mac"), h.get("name"), ip)
|
new_xml = f'<host mac="{h.get("mac")}" name="{h.get("name")}" ip="{ip}"/>'
|
||||||
if family == "ipv6":
|
if family == "ipv6":
|
||||||
new_xml = '<host id="{}" name="{}" ip="{}"/>'.format(h.get("id"), h.get("name"), ip)
|
new_xml = f'<host id="{h.get("id")}" name="{h.get("name")}" ip="{ip}"/>'
|
||||||
|
|
||||||
self.update(
|
self.update(
|
||||||
VIR_NETWORK_UPDATE_COMMAND_DELETE,
|
VIR_NETWORK_UPDATE_COMMAND_DELETE,
|
||||||
|
@ -298,12 +291,7 @@ class wvmNetwork(wvmConnect):
|
||||||
compare_var = "id"
|
compare_var = "id"
|
||||||
parent_index = self.parent_count - 1
|
parent_index = self.parent_count - 1
|
||||||
new_host_xml = etree.fromstring(new_xml)
|
new_host_xml = etree.fromstring(new_xml)
|
||||||
|
host = next((h for h in hosts if h.get(compare_var) == mac_duid), None)
|
||||||
host = None
|
|
||||||
for h in hosts:
|
|
||||||
if h.get(compare_var) == mac_duid:
|
|
||||||
host = h
|
|
||||||
break
|
|
||||||
if host is None:
|
if host is None:
|
||||||
self.update(
|
self.update(
|
||||||
VIR_NETWORK_UPDATE_COMMAND_ADD_LAST,
|
VIR_NETWORK_UPDATE_COMMAND_ADD_LAST,
|
||||||
|
@ -326,7 +314,7 @@ class wvmNetwork(wvmConnect):
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_qos(self):
|
def get_qos(self):
|
||||||
qos_values = dict()
|
qos_values = {}
|
||||||
tree = etree.fromstring(self._XMLDesc(0))
|
tree = etree.fromstring(self._XMLDesc(0))
|
||||||
qos = tree.xpath("/network/bandwidth")
|
qos = tree.xpath("/network/bandwidth")
|
||||||
if qos:
|
if qos:
|
||||||
|
@ -348,13 +336,10 @@ class wvmNetwork(wvmConnect):
|
||||||
return qos_values
|
return qos_values
|
||||||
|
|
||||||
def set_qos(self, direction, average, peak, burst):
|
def set_qos(self, direction, average, peak, burst):
|
||||||
if direction == "inbound":
|
if direction not in ("inbound","outbound"):
|
||||||
xml = f"<inbound average='{average}' peak='{peak}' burst='{burst}'/>"
|
|
||||||
elif direction == "outbound":
|
|
||||||
xml = f"<outbound average='{average}' peak='{peak}' burst='{burst}'/>"
|
|
||||||
else:
|
|
||||||
raise Exception("Direction must be inbound or outbound")
|
raise Exception("Direction must be inbound or outbound")
|
||||||
|
|
||||||
|
xml = f"<{direction} average='{average}' peak='{peak}' burst='{burst}'/>"
|
||||||
tree = etree.fromstring(self._XMLDesc(0))
|
tree = etree.fromstring(self._XMLDesc(0))
|
||||||
|
|
||||||
band = tree.xpath("/network/bandwidth")
|
band = tree.xpath("/network/bandwidth")
|
||||||
|
@ -388,7 +373,7 @@ class wvmNetwork(wvmConnect):
|
||||||
self.leases = self.net.DHCPLeases()
|
self.leases = self.net.DHCPLeases()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.leases = []
|
self.leases = []
|
||||||
raise "Error getting {} DHCP leases: {}".format(self, e)
|
raise f"Error getting {self} DHCP leases: {e}" from e
|
||||||
|
|
||||||
def get_dhcp_leases(self):
|
def get_dhcp_leases(self):
|
||||||
if self.leases is None:
|
if self.leases is None:
|
||||||
|
|
|
@ -47,11 +47,8 @@ class wvmNWFilter(wvmConnect):
|
||||||
return ElementTree.tostring(tree).decode()
|
return ElementTree.tostring(tree).decode()
|
||||||
|
|
||||||
def get_filter_refs(self):
|
def get_filter_refs(self):
|
||||||
refs = []
|
|
||||||
tree = ElementTree.fromstring(self._XMLDesc(0))
|
tree = ElementTree.fromstring(self._XMLDesc(0))
|
||||||
for ref in tree.findall("./filterref"):
|
return [ref.get("filter") for ref in tree.findall("./filterref")]
|
||||||
refs.append(ref.get("filter"))
|
|
||||||
return refs
|
|
||||||
|
|
||||||
def get_rules(self):
|
def get_rules(self):
|
||||||
rules = []
|
rules = []
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import contextlib
|
||||||
|
|
||||||
from vrtManager import util
|
from vrtManager import util
|
||||||
from vrtManager.connection import wvmConnect
|
from vrtManager.connection import wvmConnect
|
||||||
|
|
||||||
|
@ -10,10 +12,7 @@ class wvmStorages(wvmConnect):
|
||||||
stg = self.get_storage(pool)
|
stg = self.get_storage(pool)
|
||||||
stg_status = stg.isActive()
|
stg_status = stg.isActive()
|
||||||
stg_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type")
|
stg_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type")
|
||||||
if stg_status:
|
stg_vol = len(stg.listVolumes()) if stg_status else None
|
||||||
stg_vol = len(stg.listVolumes())
|
|
||||||
else:
|
|
||||||
stg_vol = None
|
|
||||||
stg_size = stg.info()[1]
|
stg_size = stg.info()[1]
|
||||||
storages.append(
|
storages.append(
|
||||||
{"name": pool, "status": stg_status, "type": stg_type, "volumes": stg_vol, "size": stg_size}
|
{"name": pool, "status": stg_status, "type": stg_type, "volumes": stg_vol, "size": stg_size}
|
||||||
|
@ -33,7 +32,7 @@ class wvmStorages(wvmConnect):
|
||||||
<format type='lvm2'/>
|
<format type='lvm2'/>
|
||||||
</source>"""
|
</source>"""
|
||||||
if stg_type == "logical":
|
if stg_type == "logical":
|
||||||
target = "/dev/" + name
|
target = f"/dev/{name}"
|
||||||
xml += f"""
|
xml += f"""
|
||||||
<target>
|
<target>
|
||||||
<path>{target}</path>
|
<path>{target}</path>
|
||||||
|
@ -125,10 +124,10 @@ class wvmStorage(wvmConnect):
|
||||||
return self.pool.UUIDString()
|
return self.pool.UUIDString()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.pool.create(0)
|
return self.pool.create(0)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.pool.destroy()
|
return self.pool.destroy()
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
self.pool.undefine()
|
self.pool.undefine()
|
||||||
|
@ -225,26 +224,30 @@ class wvmStorage(wvmConnect):
|
||||||
return util.get_xml_path(vol_xml, "/volume/@type")
|
return util.get_xml_path(vol_xml, "/volume/@type")
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
self.pool.refresh(0)
|
return self.pool.refresh(0)
|
||||||
|
|
||||||
def update_volumes(self):
|
def get_volume_details(self, volname):
|
||||||
try:
|
try:
|
||||||
self.refresh()
|
self.refresh()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
vols = self.get_volumes()
|
|
||||||
vol_list = []
|
|
||||||
|
|
||||||
for volname in vols:
|
return {
|
||||||
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_format_type(volname),
|
||||||
"allocation": self.get_volume_allocation(volname),
|
}
|
||||||
"type": self.get_volume_format_type(volname),
|
|
||||||
}
|
|
||||||
)
|
def update_volumes(self):
|
||||||
return vol_list
|
with contextlib.suppress(Exception):
|
||||||
|
self.refresh()
|
||||||
|
vols = self.get_volumes()
|
||||||
|
return [{"name": volname, "size": self.get_volume_size(volname),
|
||||||
|
"allocation": self.get_volume_allocation(volname),
|
||||||
|
"type": self.get_volume_format_type(volname)} for volname in vols]
|
||||||
|
|
||||||
|
|
||||||
def create_volume(self, name, size, vol_fmt="qcow2", metadata=False, disk_owner_uid=0, disk_owner_gid=0):
|
def create_volume(self, name, size, vol_fmt="qcow2", metadata=False, disk_owner_uid=0, disk_owner_gid=0):
|
||||||
size = int(size) * 1073741824
|
size = int(size) * 1073741824
|
||||||
|
@ -253,10 +256,7 @@ class wvmStorage(wvmConnect):
|
||||||
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"):
|
name += f".{vol_fmt}" if vol_fmt in ("qcow", "qcow2") else ".img"
|
||||||
name += "." + vol_fmt
|
|
||||||
else:
|
|
||||||
name += ".img"
|
|
||||||
alloc = 0
|
alloc = 0
|
||||||
xml = f"""
|
xml = f"""
|
||||||
<volume>
|
<volume>
|
||||||
|
@ -300,9 +300,9 @@ class wvmStorage(wvmConnect):
|
||||||
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 += f".{vol_fmt}"
|
||||||
else:
|
else:
|
||||||
suffix = "." + file_suffix
|
suffix = f".{file_suffix}"
|
||||||
target_file += suffix if len(suffix) > 1 else ""
|
target_file += suffix if len(suffix) > 1 else ""
|
||||||
|
|
||||||
xml = f"""
|
xml = f"""
|
||||||
|
|
|
@ -9,10 +9,7 @@ import lxml.etree as etree
|
||||||
|
|
||||||
def is_kvm_available(xml):
|
def is_kvm_available(xml):
|
||||||
kvm_domains = get_xml_path(xml, "//domain/@type='kvm'")
|
kvm_domains = get_xml_path(xml, "//domain/@type='kvm'")
|
||||||
if kvm_domains > 0:
|
return kvm_domains > 0
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def randomMAC():
|
def randomMAC():
|
||||||
|
@ -26,40 +23,40 @@ def randomMAC():
|
||||||
|
|
||||||
def randomUUID():
|
def randomUUID():
|
||||||
"""Generate a random UUID."""
|
"""Generate a random UUID."""
|
||||||
u = [secrets.randbelow(256) for ignore in range(0, 16)]
|
u = [secrets.randbelow(256) for _ in range(16)]
|
||||||
u[6] = (u[6] & 0x0F) | (4 << 4)
|
u[6] = u[6] & 15 | 4 << 4
|
||||||
u[8] = (u[8] & 0x3F) | (2 << 6)
|
u[8] = u[8] & 63 | 2 << 6
|
||||||
return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2, "%02x" * 6]) % tuple(u)
|
return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2, "%02x" * 6]) % tuple(u)
|
||||||
|
|
||||||
|
|
||||||
def randomPasswd(length=12, alphabet=string.ascii_letters + string.digits):
|
def randomPasswd(length=12, alphabet=string.ascii_letters + string.digits):
|
||||||
"""Generate a random password"""
|
"""Generate a random password"""
|
||||||
return "".join([secrets.choice(alphabet) for i in range(length)])
|
return "".join([secrets.choice(alphabet) for _ in range(length)])
|
||||||
|
|
||||||
|
|
||||||
def get_max_vcpus(conn, type=None):
|
def get_max_vcpus(conn, guest_type=None):
|
||||||
"""@param conn: libvirt connection to poll for max possible vcpus
|
"""@param conn: libvirt connection to poll for max possible vcpus
|
||||||
@type type: optional guest type (kvm, etc.)"""
|
@param guest_type: optional guest type (kvm, etc.)"""
|
||||||
if type is None:
|
if type is None:
|
||||||
type = conn.getType()
|
guest_type = conn.getType()
|
||||||
try:
|
try:
|
||||||
m = conn.getMaxVcpus(type.lower())
|
m = conn.getMaxVcpus(guest_type.lower())
|
||||||
except libvirt.libvirtError:
|
except libvirt.libvirtError:
|
||||||
m = 32
|
m = 32
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
def xml_escape(str):
|
def xml_escape(xml_str):
|
||||||
"""Replaces chars ' " < > & with xml safe counterparts"""
|
"""Replaces chars ' " < > & with xml safe counterparts"""
|
||||||
if str is None:
|
if xml_str is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
str = str.replace("&", "&")
|
xml_str = xml_str.replace("&", "&")
|
||||||
str = str.replace("'", "'")
|
xml_str = xml_str.replace("'", "'")
|
||||||
str = str.replace('"', """)
|
xml_str = xml_str.replace('"', """)
|
||||||
str = str.replace("<", "<")
|
xml_str = xml_str.replace("<", "<")
|
||||||
str = str.replace(">", ">")
|
xml_str = xml_str.replace(">", ">")
|
||||||
return str
|
return xml_str
|
||||||
|
|
||||||
|
|
||||||
def compareMAC(p, q):
|
def compareMAC(p, q):
|
||||||
|
@ -108,10 +105,7 @@ def get_xpath(doc, path):
|
||||||
if ret is not None:
|
if ret is not None:
|
||||||
if isinstance(ret, list):
|
if isinstance(ret, list):
|
||||||
if len(ret) >= 1:
|
if len(ret) >= 1:
|
||||||
if hasattr(ret[0], "text"):
|
result = ret[0].text if hasattr(ret[0], "text") else ret[0]
|
||||||
result = ret[0].text
|
|
||||||
else:
|
|
||||||
result = ret[0]
|
|
||||||
else:
|
else:
|
||||||
result = ret
|
result = ret
|
||||||
|
|
||||||
|
|
|
@ -7,28 +7,20 @@ register = template.Library()
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def app_active(request, app_name):
|
def app_active(request, app_name):
|
||||||
if request.resolver_match.app_name == app_name:
|
return "active" if request.resolver_match.app_name == app_name else ""
|
||||||
return "active"
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def view_active(request, view_name):
|
def view_active(request, view_name):
|
||||||
if request.resolver_match.view_name == view_name:
|
return "active" if request.resolver_match.view_name == view_name else ""
|
||||||
return "active"
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def class_active(request, pattern):
|
def class_active(request, pattern):
|
||||||
if re.search(pattern, request.path):
|
# Not sure why 'class="active"' returns class=""active""
|
||||||
# Not sure why 'class="active"' returns class=""active""
|
return "active" if re.search(pattern, request.path) else ""
|
||||||
return "active"
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def has_perm(user, permission_codename):
|
def has_perm(user, permission_codename):
|
||||||
if user.has_perm(permission_codename):
|
return bool(user.has_perm(permission_codename))
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
|
@ -42,48 +42,49 @@ try:
|
||||||
|
|
||||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||||
if not settings.LDAP_ENABLED:
|
if not settings.LDAP_ENABLED:
|
||||||
return None
|
return None
|
||||||
print("authenticate_ldap")
|
print("authenticate_ldap")
|
||||||
# Get the user information from the LDAP if he can be authenticated
|
# Get the user information from the LDAP if he can be authenticated
|
||||||
isAdmin = False
|
isAdmin = False
|
||||||
isStaff = False
|
isStaff = False
|
||||||
isTechnician = False
|
isTechnician = False
|
||||||
|
|
||||||
requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_ADMINS)
|
requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_ADMINS)
|
||||||
|
isAdmin = requeteLdap is not None
|
||||||
|
isStaff = requeteLdap is not None
|
||||||
|
|
||||||
if requeteLdap is None:
|
if requeteLdap is None:
|
||||||
requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_STAFF)
|
requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_STAFF)
|
||||||
if requeteLdap is None:
|
isStaff = requeteLdap is not None
|
||||||
requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_TECHNICIANS)
|
|
||||||
if requeteLdap is None:
|
if requeteLdap is None:
|
||||||
requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_USERS)
|
requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_TECHNICIANS)
|
||||||
if requeteLdap is None:
|
isTechnician = requeteLdap is not None
|
||||||
print("User does not belong to any search group. Check LDAP_SEARCH_GROUP_FILTER in settings.")
|
|
||||||
return None
|
if requeteLdap is None:
|
||||||
else:
|
requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_USERS)
|
||||||
isTechnician = True
|
|
||||||
else:
|
if requeteLdap is None:
|
||||||
isStaff = True
|
print("User does not belong to any search group. Check LDAP_SEARCH_GROUP_FILTER in settings.")
|
||||||
else:
|
return None
|
||||||
isAdmin = True
|
|
||||||
isStaff = True
|
|
||||||
|
|
||||||
techniciansGroup = Group.objects.get(name='Technicians')
|
techniciansGroup = Group.objects.get(name='Technicians')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = User.objects.get(username=username)
|
user = User.objects.get(username=username)
|
||||||
attributes = UserAttributes.objects.get(user=user)
|
attributes = UserAttributes.objects.get(user=user)
|
||||||
user.is_staff = isStaff
|
user.is_staff = isStaff
|
||||||
user.is_superuser = isAdmin
|
user.is_superuser = isAdmin
|
||||||
if isTechnician is False and user.groups.filter(name='Technicians').exists():
|
if not isTechnician and user.groups.filter(name='Technicians').exists():
|
||||||
user.groups.remove(techniciansGroup)
|
user.groups.remove(techniciansGroup)
|
||||||
elif isTechnician is True and user.groups.filter(name='Technicians').exists() is False:
|
elif isTechnician and not user.groups.filter(name='Technicians').exists():
|
||||||
user.groups.add(techniciansGroup)
|
user.groups.add(techniciansGroup)
|
||||||
else:
|
else:
|
||||||
print("The user is already in the Technicians group")
|
print("The user is already in the Technicians group")
|
||||||
user.save()
|
user.save()
|
||||||
# TODO VERIFY
|
# TODO VERIFY
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
print("authenticate-create new user: {}".format(username))
|
print(f"authenticate-create new user: {username}")
|
||||||
user = User(username=username)
|
user = User(username=username)
|
||||||
user.first_name = requeteLdap[1]
|
user.first_name = requeteLdap[1]
|
||||||
user.last_name = requeteLdap[2]
|
user.last_name = requeteLdap[2]
|
||||||
|
@ -93,7 +94,7 @@ try:
|
||||||
user.is_superuser = isAdmin
|
user.is_superuser = isAdmin
|
||||||
user.set_password(uuid.uuid4().hex)
|
user.set_password(uuid.uuid4().hex)
|
||||||
user.save()
|
user.save()
|
||||||
if isTechnician is True:
|
if isTechnician:
|
||||||
user.groups.add(techniciansGroup)
|
user.groups.add(techniciansGroup)
|
||||||
maxInstances = 1
|
maxInstances = 1
|
||||||
maxCpus = 1
|
maxCpus = 1
|
||||||
|
|
|
@ -22,10 +22,12 @@ INSTALLED_APPS = [
|
||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
|
"rest_framework",
|
||||||
"django_bootstrap5",
|
"django_bootstrap5",
|
||||||
"django_icons",
|
"django_icons",
|
||||||
"django_otp",
|
"django_otp",
|
||||||
"django_otp.plugins.otp_totp",
|
"django_otp.plugins.otp_totp",
|
||||||
|
"drf_yasg",
|
||||||
"accounts",
|
"accounts",
|
||||||
"admin",
|
"admin",
|
||||||
"appsettings",
|
"appsettings",
|
||||||
|
@ -40,6 +42,7 @@ INSTALLED_APPS = [
|
||||||
"virtsecrets",
|
"virtsecrets",
|
||||||
"logs",
|
"logs",
|
||||||
"qr_code",
|
"qr_code",
|
||||||
|
"rest_framework",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -211,7 +214,7 @@ SOCKETIO_PUBLIC_PORT = 6081
|
||||||
SOCKETIO_PUBLIC_PATH = "socket.io/"
|
SOCKETIO_PUBLIC_PATH = "socket.io/"
|
||||||
|
|
||||||
# List of console listen addresses
|
# List of console listen addresses
|
||||||
QEMU_CONSOLE_LISTEN_ADDRESSES = (
|
QEMU_CONSOLE_LISTENER_ADDRESSES = (
|
||||||
("127.0.0.1", "Localhost"),
|
("127.0.0.1", "Localhost"),
|
||||||
("0.0.0.0", "All interfaces"),
|
("0.0.0.0", "All interfaces"),
|
||||||
)
|
)
|
||||||
|
|
37
webvirtcloud/urls-api.py
Normal file
37
webvirtcloud/urls-api.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
from django.urls import include, path
|
||||||
|
from rest_framework_nested import routers
|
||||||
|
|
||||||
|
from computes.api.viewsets import ComputeArchitecturesView, ComputeViewSet
|
||||||
|
from networks.api.viewsets import NetworkViewSet
|
||||||
|
from interfaces.api.viewsets import InterfaceViewSet
|
||||||
|
from storages.api.viewsets import StorageViewSet, VolumeViewSet
|
||||||
|
from instances.api.viewsets import FlavorViewSet, \
|
||||||
|
InstancesViewSet, \
|
||||||
|
InstanceViewSet, \
|
||||||
|
MigrateViewSet, \
|
||||||
|
CreateInstanceViewSet
|
||||||
|
|
||||||
|
|
||||||
|
router = routers.SimpleRouter()
|
||||||
|
router.register(r'computes', ComputeViewSet)
|
||||||
|
router.register(r'migrate', MigrateViewSet, basename='instance-migrate')
|
||||||
|
router.register(r'flavor', FlavorViewSet, basename='instance-flavor')
|
||||||
|
router.register(r'instances', InstancesViewSet, basename='instance')
|
||||||
|
|
||||||
|
compute_router = routers.NestedSimpleRouter(router, r'computes', lookup='compute')
|
||||||
|
compute_router.register(r'instances', InstanceViewSet, basename='compute-instance')
|
||||||
|
compute_router.register(r'instances/create/(?P<arch>[^/.]+)/(?P<machine>[^/.]+)', CreateInstanceViewSet, basename='instance-create')
|
||||||
|
compute_router.register(r'networks', NetworkViewSet, basename='compute-network')
|
||||||
|
compute_router.register(r'interfaces', InterfaceViewSet, basename='compute-interface')
|
||||||
|
compute_router.register(r'storages', StorageViewSet, basename='compute-storage')
|
||||||
|
compute_router.register(r'archs', ComputeArchitecturesView, basename='compute-archs')
|
||||||
|
|
||||||
|
storage_router = routers.NestedSimpleRouter(compute_router, r'storages', lookup='storage')
|
||||||
|
storage_router.register(r'volumes', VolumeViewSet, basename='compute-storage-volumes')
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include(router.urls)),
|
||||||
|
path('', include(compute_router.urls)),
|
||||||
|
path('', include(storage_router.urls)),
|
||||||
|
]
|
|
@ -1,9 +1,26 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.urls import include, path, re_path
|
||||||
|
from rest_framework import permissions
|
||||||
|
from drf_yasg.views import get_schema_view
|
||||||
|
from drf_yasg import openapi
|
||||||
from appsettings.views import appsettings
|
from appsettings.views import appsettings
|
||||||
from console.views import console
|
from console.views import console
|
||||||
from django.conf import settings
|
|
||||||
from django.urls import include, path
|
|
||||||
from instances.views import index
|
from instances.views import index
|
||||||
|
|
||||||
|
|
||||||
|
schema_view = get_schema_view(
|
||||||
|
openapi.Info(
|
||||||
|
title="Webvirtcloud REST-API",
|
||||||
|
default_version='v1',
|
||||||
|
description="Webvirtcloud REST API",
|
||||||
|
terms_of_service="https://www.google.com/policies/terms/",
|
||||||
|
contact=openapi.Contact(email="catborise@gmail.com"),
|
||||||
|
license=openapi.License(name="BSD License"),
|
||||||
|
),
|
||||||
|
public=True,
|
||||||
|
permission_classes=(permissions.AllowAny,),
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", index, name="index"),
|
path("", index, name="index"),
|
||||||
path("admin/", include(("admin.urls", "admin"), namespace="admin")),
|
path("admin/", include(("admin.urls", "admin"), namespace="admin")),
|
||||||
|
@ -15,6 +32,11 @@ urlpatterns = [
|
||||||
path("instances/", include("instances.urls")),
|
path("instances/", include("instances.urls")),
|
||||||
path("i18n/", include("django.conf.urls.i18n")),
|
path("i18n/", include("django.conf.urls.i18n")),
|
||||||
path("logs/", include("logs.urls")),
|
path("logs/", include("logs.urls")),
|
||||||
|
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||||
|
path('api/v1/', include("webvirtcloud.urls-api")),
|
||||||
|
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
||||||
|
re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||||
|
re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
@ -26,3 +48,4 @@ if settings.DEBUG:
|
||||||
]
|
]
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue