mirror of
				https://github.com/retspen/webvirtcloud
				synced 2025-07-31 12:41:08 +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
				
			
		
							
								
								
									
										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 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):
 | 
			
		||||
| 
						 | 
				
			
			@ -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())
 | 
			
		||||
        keymap_choices = [('auto', 'Auto')] + list((c, c) for c in QEMU_KEYMAPS)
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewVMForm(forms.Form):
 | 
			
		||||
    name = forms.CharField(error_messages={'required': _('No Virtual Machine name has been entered')}, max_length=64)
 | 
			
		||||
    firmware = forms.CharField(max_length=50, required=False)
 | 
			
		||||
    vcpu = forms.IntegerField(error_messages={'required': _('No VCPU has been entered')})
 | 
			
		||||
    vcpu_mode = forms.CharField(max_length=20, required=False)
 | 
			
		||||
    disk = forms.IntegerField(required=False)
 | 
			
		||||
    memory = forms.IntegerField(error_messages={'required': _('No RAM size has been entered')})
 | 
			
		||||
    networks = forms.CharField(error_messages={'required': _('No Network pool has been choosen')})
 | 
			
		||||
    nwfilter = forms.CharField(required=False)
 | 
			
		||||
    storage = forms.CharField(max_length=20, required=False)
 | 
			
		||||
    template = forms.CharField(required=False)
 | 
			
		||||
    images = forms.CharField(required=False)
 | 
			
		||||
    cache_mode = forms.CharField(error_messages={'required': _('Please select HDD cache mode')})
 | 
			
		||||
    hdd_size = forms.IntegerField(required=False)
 | 
			
		||||
    meta_prealloc = forms.BooleanField(required=False)
 | 
			
		||||
    virtio = forms.BooleanField(required=False)
 | 
			
		||||
    qemu_ga = forms.BooleanField(required=False)
 | 
			
		||||
    mac = forms.CharField(required=False)
 | 
			
		||||
    console_pass = forms.CharField(required=False, empty_value="", widget=forms.PasswordInput())
 | 
			
		||||
    graphics = forms.CharField(error_messages={'required': _('Please select a graphics type')})
 | 
			
		||||
    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)
 | 
			
		||||
class NewVMForm(forms.ModelForm):
 | 
			
		||||
    # name = forms.CharField(error_messages={'required': _('No Virtual Machine name has been entered')}, max_length=64)
 | 
			
		||||
    # firmware = forms.CharField(max_length=50, required=False)
 | 
			
		||||
    # vcpu = forms.IntegerField(error_messages={'required': _('No VCPU has been entered')})
 | 
			
		||||
    # vcpu_mode = forms.CharField(max_length=20, required=False)
 | 
			
		||||
    # disk = forms.IntegerField(required=False)
 | 
			
		||||
    # memory = forms.IntegerField(error_messages={'required': _('No RAM size has been entered')})
 | 
			
		||||
    # networks = forms.CharField(error_messages={'required': _('No Network pool has been choosen')})
 | 
			
		||||
    # nwfilter = forms.CharField(required=False)
 | 
			
		||||
    # storage = forms.CharField(max_length=20, required=False)
 | 
			
		||||
    # template = forms.CharField(required=False)
 | 
			
		||||
    # images = forms.CharField(required=False)
 | 
			
		||||
    # cache_mode = forms.CharField(error_messages={'required': _('Please select HDD cache mode')})
 | 
			
		||||
    # hdd_size = forms.IntegerField(required=False)
 | 
			
		||||
    # meta_prealloc = forms.BooleanField(required=False)
 | 
			
		||||
    # virtio = forms.BooleanField(required=False)
 | 
			
		||||
    # qemu_ga = forms.BooleanField(required=False)
 | 
			
		||||
    # mac = forms.CharField(required=False)
 | 
			
		||||
    # console_pass = forms.CharField(required=False, empty_value="", widget=forms.PasswordInput())
 | 
			
		||||
    # graphics = forms.CharField(error_messages={'required': _('Please select a graphics type')})
 | 
			
		||||
    # video = forms.CharField(error_messages={'required': _('Please select a video driver')})
 | 
			
		||||
    # 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):
 | 
			
		||||
        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.utils.functional import cached_property
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
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 vrtManager.instance import wvmInstance
 | 
			
		||||
| 
						 | 
				
			
			@ -150,8 +153,8 @@ class Instance(models.Model):
 | 
			
		|||
        return self.proxy.get_console_keymap()
 | 
			
		||||
 | 
			
		||||
    @cached_property
 | 
			
		||||
    def console_listen_address(self):
 | 
			
		||||
        return self.proxy.get_console_listen_addr()
 | 
			
		||||
    def console_listener_address(self):
 | 
			
		||||
        return self.proxy.get_console_listener_addr()
 | 
			
		||||
 | 
			
		||||
    @cached_property
 | 
			
		||||
    def guest_agent(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -206,6 +209,50 @@ class Instance(models.Model):
 | 
			
		|||
        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):
 | 
			
		||||
    """
 | 
			
		||||
    Dummy model for holding set of permissions we need to be automatically added by Django
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,9 +30,9 @@
 | 
			
		|||
                    <td>
 | 
			
		||||
                        <span class="text-success">{% trans "Connected" %}</span>
 | 
			
		||||
                    </td>
 | 
			
		||||
		    {% if app_settings.VM_DRBD_STATUS == 'True' %}
 | 
			
		||||
		    <td class="d-none d-sm-table-cell"></td>
 | 
			
		||||
		    {% endif %}
 | 
			
		||||
                    {% if app_settings.VM_DRBD_STATUS == 'True' %}
 | 
			
		||||
                    <td class="d-none d-sm-table-cell"></td>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    <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>
 | 
			
		||||
| 
						 | 
				
			
			@ -68,11 +68,11 @@
 | 
			
		|||
                                <span class="text-warning">{% trans "Suspended" %}</span>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </td>
 | 
			
		||||
			{% if app_settings.VM_DRBD_STATUS == 'True' %}
 | 
			
		||||
			<td>
 | 
			
		||||
				{% if instance.drbd == "Primary/OK" or instance.drbd == "Secondary/OK" %}<span class="text-success">{% else %}<span class="text-danger">{% endif %}{{ instance.drbd }}</span>
 | 
			
		||||
			</td>
 | 
			
		||||
			{% endif %}
 | 
			
		||||
                        {% if app_settings.VM_DRBD_STATUS == 'True' %}
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {% if instance.drbd == "Primary/OK" or instance.drbd == "Secondary/OK" %}<span class="text-success">{% else %}<span class="text-danger">{% endif %}{{ instance.drbd }}</span>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        <td>{{ instance.proxy.instance.info.3 }}</td>
 | 
			
		||||
                        <td>{{ instance.cur_memory }} MB</td>
 | 
			
		||||
                        <td class="text-nowrap">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,7 @@
 | 
			
		|||
    {% for field in form %}
 | 
			
		||||
        {% for error in field.errors %}
 | 
			
		||||
            <div class="alert alert-danger">
 | 
			
		||||
                <strong>{{ error|escape }}</strong>
 | 
			
		||||
                <strong>{{ field.label }}:</strong> <span>{{ error|escape }}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,7 @@
 | 
			
		|||
    {% for field in form %}
 | 
			
		||||
        {% for error in field.errors %}
 | 
			
		||||
            <div class="alert alert-danger">
 | 
			
		||||
                <strong>{{ error|escape }}</strong>
 | 
			
		||||
                <strong>{{ field.label }}:</strong> <span>{{ error|escape }}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -269,9 +269,9 @@
 | 
			
		|||
    });
 | 
			
		||||
    $(document).ready(function () {
 | 
			
		||||
        // set current console listen address or fall back to default
 | 
			
		||||
        var console_listen_address = "{{ console_listen_address }}";
 | 
			
		||||
        if (console_listen_address != '') {
 | 
			
		||||
            $("#console_select_listen_address option[value='" + console_listen_address + "']").prop('selected', true);
 | 
			
		||||
        var console_listener_address = "{{ console_listener_address }}";
 | 
			
		||||
        if (console_listener_address != '') {
 | 
			
		||||
            $("#console_select_listener_address option[value='" + console_listener_address + "']").prop('selected', true);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    $(document).ready(function () {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,12 +67,12 @@ def instance(request, pk):
 | 
			
		|||
    console_form = ConsoleForm(
 | 
			
		||||
        initial={
 | 
			
		||||
            "type": instance.console_type,
 | 
			
		||||
            "listen_on": instance.console_listen_address,
 | 
			
		||||
            "listen_on": instance.console_listener_address,
 | 
			
		||||
            "password": instance.console_passwd,
 | 
			
		||||
            "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
 | 
			
		||||
    allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
 | 
			
		||||
    try:
 | 
			
		||||
| 
						 | 
				
			
			@ -344,7 +344,7 @@ def destroy(request, pk):
 | 
			
		|||
    except Exception:
 | 
			
		||||
        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:
 | 
			
		||||
            instance.proxy.force_shutdown()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -390,7 +390,7 @@ def migrate(request, pk):
 | 
			
		|||
    target_host = Compute.objects.get(id=compute_id)
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
        messages.error(request, err)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1239,7 +1239,7 @@ def update_console(request, pk):
 | 
			
		|||
                addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
 | 
			
		||||
 | 
			
		||||
            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")
 | 
			
		||||
                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_gid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_GID)
 | 
			
		||||
        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()
 | 
			
		||||
        disk_devices = conn.get_disk_device_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,
 | 
			
		||||
                                networks=data["networks"],
 | 
			
		||||
                                virtio=data["virtio"],
 | 
			
		||||
                                listen_addr=data["listener_addr"],
 | 
			
		||||
                                listener_addr=data["listener_addr"],
 | 
			
		||||
                                nwfilter=data["nwfilter"],
 | 
			
		||||
                                graphics=data["graphics"],
 | 
			
		||||
                                video=data["video"],
 | 
			
		||||
| 
						 | 
				
			
			@ -1568,6 +1568,7 @@ def create_instance(request, compute_id, arch, machine):
 | 
			
		|||
            conn.close()
 | 
			
		||||
    except libvirtError as lib_err:
 | 
			
		||||
        messages.error(request, lib_err)
 | 
			
		||||
 | 
			
		||||
    return render(request, "create_instance_w2.html", locals())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue