1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2024-12-25 15:45:23 +00:00

Merge pull request #527 from catborise/master

Rest framework (#24)
This commit is contained in:
catborise 2022-08-22 15:35:19 +03:00 committed by GitHub
commit a67a51eaed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 1014 additions and 88 deletions

View file

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

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

View file

@ -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/',

View file

@ -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
python-socketio==5.7.1
eventlet==0.33.1 eventlet==0.33.1
djangorestframework==3.13.1
drf-nested-routers==0.93.4
drf-yasg==1.21.3
markdown==3.4.1

View file

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

View file

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

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

View file

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

View 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'),
),
]

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

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

View file

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

View 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
View 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'})

View file

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

View file

@ -45,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):
@ -160,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,
@ -332,7 +332,7 @@ class wvmCreate(wvmConnect):
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:
@ -345,4 +345,4 @@ class wvmCreate(wvmConnect):
</video> </video>
</devices> </devices>
</domain>""" </domain>"""
self._defineXML(xml) return self._defineXML(xml)

View file

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

View file

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

View file

@ -124,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()
@ -224,7 +224,27 @@ 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 get_volume_details(self, volname):
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 get_volume_details(self, volname):
with contextlib.suppress(Exception):
self.refresh()
return {
"name": volname,
"size": self.get_volume_size(volname),
"allocation": self.get_volume_allocation(volname),
"type": self.get_volume_format_type(volname),
}
def update_volumes(self): def update_volumes(self):
with contextlib.suppress(Exception): with contextlib.suppress(Exception):

View file

@ -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
View 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)),
]

View file

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