add nick
[laas.git] / src / booking / quick_deployer.py
index 951ff47..4b85d76 100644 (file)
@@ -9,15 +9,16 @@
 
 
 import json
+import yaml
 from django.db.models import Q
+from django.db import transaction
 from datetime import timedelta
 from django.utils import timezone
 from django.core.exceptions import ValidationError
-from account.models import Lab
+from account.models import Lab, UserProfile
 
 from resource_inventory.models import (
     ResourceTemplate,
-    Installer,
     Image,
     OPNFVRole,
     OPNFVConfig,
@@ -26,6 +27,7 @@ from resource_inventory.models import (
     NetworkConnection,
     InterfaceConfiguration,
     Network,
+    CloudInitFile,
 )
 from resource_inventory.resource_manager import ResourceManager
 from resource_inventory.pdf_templater import PDFTemplater
@@ -60,7 +62,7 @@ def parse_resource_field(resource_json):
     return lab, template
 
 
-def update_template(old_template, image, hostname, user):
+def update_template(old_template, image, hostname, user, global_cloud_config=None):
     """
     Duplicate a template to the users account and update configured fields.
 
@@ -79,14 +81,17 @@ def update_template(old_template, image, hostname, user):
         lab=old_template.lab,
         description=old_template.description,
         public=False,
-        temporary=True
+        temporary=True,
+        private_vlan_pool=old_template.private_vlan_pool,
+        public_vlan_pool=old_template.public_vlan_pool,
+        copy_of=old_template
     )
 
     for old_network in old_template.networks.all():
         Network.objects.create(
             name=old_network.name,
-            bundle=old_template,
-            is_public=False
+            bundle=template,
+            is_public=old_network.is_public
         )
     # We are assuming there is only one opnfv config per public resource template
     old_opnfv = template.opnfv_config.first()
@@ -102,12 +107,26 @@ def update_template(old_template, image, hostname, user):
     # there is never multiple networks anyway. This may have to change in the future
 
     for old_config in old_template.getConfigs():
+        image_to_set = image
+        if not image:
+            image_to_set = old_config.image
+
         config = ResourceConfiguration.objects.create(
             profile=old_config.profile,
-            image=image,
-            template=template
+            image=image_to_set,
+            template=template,
+            is_head_node=old_config.is_head_node,
+            name=hostname if len(old_template.getConfigs()) == 1 else old_config.name,
+            # cloud_init_files=old_config.cloud_init_files.set()
         )
 
+        for file in old_config.cloud_init_files.all():
+            config.cloud_init_files.add(file)
+
+        if global_cloud_config:
+            config.cloud_init_files.add(global_cloud_config)
+            config.save()
+
         for old_iface_config in old_config.interface_configs.all():
             iface_config = InterfaceConfiguration.objects.create(
                 profile=old_iface_config.profile,
@@ -127,6 +146,7 @@ def update_template(old_template, image, hostname, user):
                     resource_config=config,
                     opnfv_config=opnfv_config
                 )
+    return template
 
 
 def generate_opnfvconfig(scenario, installer, template):
@@ -159,89 +179,125 @@ def generate_resource_bundle(template):
     return resource_bundle
 
 
-def check_invariants(request, **kwargs):
+def check_invariants(**kwargs):
     # TODO: This should really happen in the BookingForm validation methods
-    installer = kwargs['installer']
     image = kwargs['image']
-    scenario = kwargs['scenario']
     lab = kwargs['lab']
-    resource_template = kwargs['resource_template']
     length = kwargs['length']
     # check that image os is compatible with installer
-    if installer in image.os.sup_installers.all():
-        # if installer not here, we can omit that and not check for scenario
-        if not scenario:
-            raise ValidationError("An OPNFV Installer needs a scenario to be chosen to work properly")
-        if scenario not in installer.sup_scenarios.all():
-            raise ValidationError("The chosen installer does not support the chosen scenario")
-    if image.from_lab != lab:
-        raise ValidationError("The chosen image is not available at the chosen hosting lab")
-    #TODO
-    #if image.host_type != host_profile:
-    #    raise ValidationError("The chosen image is not available for the chosen host type")
-    if not image.public and image.owner != request.user:
-        raise ValidationError("You are not the owner of the chosen private image")
+    if image:
+        if image.from_lab != lab:
+            raise ValidationError("The chosen image is not available at the chosen hosting lab")
+        # TODO
+        # if image.host_type != host_profile:
+        #    raise ValidationError("The chosen image is not available for the chosen host type")
+        if not image.public and image.owner != kwargs['owner']:
+            raise ValidationError("You are not the owner of the chosen private image")
     if length < 1 or length > 21:
         raise BookingLengthException("Booking must be between 1 and 21 days long")
 
 
 def create_from_form(form, request):
     """
-    Create a Booking from the user's form.
-
-    Large, nasty method to create a booking or return a useful error
-    based on the form from the frontend
+    Parse data from QuickBookingForm to create booking
     """
     resource_field = form.cleaned_data['filter_field']
-    purpose_field = form.cleaned_data['purpose']
-    project_field = form.cleaned_data['project']
-    users_field = form.cleaned_data['users']
-    hostname = form.cleaned_data['hostname']
-    length = form.cleaned_data['length']
+    # users_field = form.cleaned_data['users']
+    hostname = 'opnfv_host' if not form.cleaned_data['hostname'] else form.cleaned_data['hostname']
+
+    global_cloud_config = None if not form.cleaned_data['global_cloud_config'] else form.cleaned_data['global_cloud_config']
+
+    if global_cloud_config:
+        form.cleaned_data['global_cloud_config'] = create_ci_file(global_cloud_config)
 
-    image = form.cleaned_data['image']
-    scenario = form.cleaned_data['scenario']
-    installer = form.cleaned_data['installer']
+    image = form.cleaned_data['image']
+    scenario = form.cleaned_data['scenario']
+    installer = form.cleaned_data['installer']
 
     lab, resource_template = parse_resource_field(resource_field)
     data = form.cleaned_data
+    data['hostname'] = hostname
     data['lab'] = lab
     data['resource_template'] = resource_template
-    check_invariants(request, **data)
+    data['owner'] = request.user
+
+    return _create_booking(data)
+
+
+def create_from_API(body, user):
+    """
+    Parse data from Automation API to create booking
+    """
+    booking_info = json.loads(body.decode('utf-8'))
+
+    data = {}
+    data['purpose'] = booking_info['purpose']
+    data['project'] = booking_info['project']
+    data['users'] = [UserProfile.objects.get(user__username=username)
+                     for username in booking_info['collaborators']]
+    data['hostname'] = booking_info['hostname']
+    data['length'] = booking_info['length']
+    data['installer'] = None
+    data['scenario'] = None
+
+    data['image'] = Image.objects.get(pk=booking_info['imageLabID'])
+
+    data['resource_template'] = ResourceTemplate.objects.get(pk=booking_info['templateID'])
+    data['lab'] = data['resource_template'].lab
+    data['owner'] = user
+
+    if 'global_cloud_config' in data.keys():
+        data['global_cloud_config'] = CloudInitFile.objects.get(id=data['global_cloud_config'])
+
+    return _create_booking(data)
+
+
+def create_ci_file(data: str) -> CloudInitFile:
+    try:
+        d = yaml.load(data)
+        if not (type(d) is dict):
+            raise Exception("CI file was valid yaml but was not a dict")
+    except Exception:
+        raise ValidationError("The provided Cloud Config is not valid yaml, please refer to the Cloud Init documentation for expected structure")
+    print("about to create global cloud config")
+    config = CloudInitFile.create(text=data, priority=CloudInitFile.objects.count())
+    print("made global cloud config")
+
+    return config
+
+
+@transaction.atomic
+def _create_booking(data):
+    check_invariants(**data)
 
     # check booking privileges
     # TODO: use the canonical booking_allowed method because now template might have multiple
     # machines
-    if Booking.objects.filter(owner=request.user, end__gt=timezone.now()).count() >= 3 and not request.user.userprofile.booking_privledge:
+    if Booking.objects.filter(owner=data['owner'], end__gt=timezone.now()).count() >= 3 and not data['owner'].userprofile.booking_privledge:
         raise PermissionError("You do not have permission to have more than 3 bookings at a time.")
 
-    ResourceManager.getInstance().templateIsReservable(resource_template)
+    ResourceManager.getInstance().templateIsReservable(data['resource_template'])
 
-    hconf = update_template(resource_template, image, hostname, request.user)
-
-    # if no installer provided, just create blank host
-    opnfv_config = None
-    if installer:
-        opnfv_config = generate_opnfvconfig(scenario, installer, resource_template)
-        generate_hostopnfv(hconf, opnfv_config)
+    resource_template = update_template(data['resource_template'], data['image'], data['hostname'], data['owner'], global_cloud_config=data['global_cloud_config'])
 
     # generate resource bundle
     resource_bundle = generate_resource_bundle(resource_template)
 
     # generate booking
     booking = Booking.objects.create(
-        purpose=purpose_field,
-        project=project_field,
-        lab=lab,
-        owner=request.user,
+        purpose=data['purpose'],
+        project=data['project'],
+        lab=data['lab'],
+        owner=data['owner'],
         start=timezone.now(),
-        end=timezone.now() + timedelta(days=int(length)),
+        end=timezone.now() + timedelta(days=int(data['length'])),
         resource=resource_bundle,
-        opnfv_config=opnfv_config
+        opnfv_config=None
     )
+
     booking.pdf = PDFTemplater.makePDF(booking)
 
-    for collaborator in users_field:  # list of UserProfiles
+    for collaborator in data['users']:   # list of Users (not UserProfile)
         booking.collaborators.add(collaborator.user)
 
     booking.save()
@@ -262,23 +318,14 @@ def drop_filter(user):
     that installer is supported on that image
     """
     installer_filter = {}
-    for image in Image.objects.all():
-        installer_filter[image.id] = {}
-        for installer in image.os.sup_installers.all():
-            installer_filter[image.id][installer.id] = 1
-
     scenario_filter = {}
-    for installer in Installer.objects.all():
-        scenario_filter[installer.id] = {}
-        for scenario in installer.sup_scenarios.all():
-            scenario_filter[installer.id][scenario.id] = 1
 
     images = Image.objects.filter(Q(public=True) | Q(owner=user))
     image_filter = {}
     for image in images:
         image_filter[image.id] = {
             'lab': 'lab_' + str(image.from_lab.lab_user.id),
-            'host_profile': str(image.host_type.id),
+            'architecture': str(image.architecture),
             'name': image.name
         }
 
@@ -286,7 +333,7 @@ def drop_filter(user):
     templates = ResourceTemplate.objects.filter(Q(public=True) | Q(owner=user))
     for rt in templates:
         profiles = [conf.profile for conf in rt.getConfigs()]
-        resource_filter["resource_" + str(rt.id)] = [str(p.id) for p in profiles]
+        resource_filter["resource_" + str(rt.id)] = [str(p.architecture) for p in profiles]
 
     return {
         'installer_filter': json.dumps(installer_filter),