diff options
Diffstat (limited to 'src/booking')
-rw-r--r-- | src/booking/forms.py | 1 | ||||
-rw-r--r-- | src/booking/lib.py | 4 | ||||
-rw-r--r-- | src/booking/migrations/0009_booking_complete.py | 18 | ||||
-rw-r--r-- | src/booking/models.py | 2 | ||||
-rw-r--r-- | src/booking/quick_deployer.py | 147 | ||||
-rw-r--r-- | src/booking/stats.py | 2 | ||||
-rw-r--r-- | src/booking/urls.py | 2 | ||||
-rw-r--r-- | src/booking/views.py | 7 |
8 files changed, 122 insertions, 61 deletions
diff --git a/src/booking/forms.py b/src/booking/forms.py index cbc3407..ff829b2 100644 --- a/src/booking/forms.py +++ b/src/booking/forms.py @@ -22,6 +22,7 @@ class QuickBookingForm(forms.Form): purpose = forms.CharField(max_length=1000) project = forms.CharField(max_length=400) hostname = forms.CharField(required=False, max_length=400) + global_cloud_config = forms.CharField(widget=forms.Textarea, required=False) installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=False) scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=False) diff --git a/src/booking/lib.py b/src/booking/lib.py index 7a4c261..8c87979 100644 --- a/src/booking/lib.py +++ b/src/booking/lib.py @@ -28,9 +28,9 @@ def get_user_items(exclude=None): for up in qs: item = { 'id': up.id, - 'expanded_name': up.full_name, + 'expanded_name': up.full_name if up.full_name else up.user.username, 'small_name': up.user.username, - 'string': up.email_addr + 'string': up.email_addr if up.email_addr else up.user.username, } items[up.id] = item return items diff --git a/src/booking/migrations/0009_booking_complete.py b/src/booking/migrations/0009_booking_complete.py new file mode 100644 index 0000000..e291a83 --- /dev/null +++ b/src/booking/migrations/0009_booking_complete.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2 on 2021-09-07 15:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0008_auto_20201109_1947'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='complete', + field=models.BooleanField(default=False), + ), + ] diff --git a/src/booking/models.py b/src/booking/models.py index cfdf7bc..966f1c2 100644 --- a/src/booking/models.py +++ b/src/booking/models.py @@ -39,6 +39,8 @@ class Booking(models.Model): pdf = models.TextField(blank=True, default="") idf = models.TextField(blank=True, default="") + complete = models.BooleanField(default=False) + class Meta: db_table = 'booking' diff --git a/src/booking/quick_deployer.py b/src/booking/quick_deployer.py index 0a3bfc6..4b85d76 100644 --- a/src/booking/quick_deployer.py +++ b/src/booking/quick_deployer.py @@ -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. @@ -80,6 +82,8 @@ def update_template(old_template, image, hostname, user): description=old_template.description, public=False, temporary=True, + private_vlan_pool=old_template.private_vlan_pool, + public_vlan_pool=old_template.public_vlan_pool, copy_of=old_template ) @@ -112,9 +116,17 @@ def update_template(old_template, image, hostname, user): 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 + 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, @@ -167,28 +179,19 @@ 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'] length = kwargs['length'] # check that image os is compatible with installer if image: - if installer or scenario: - 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: + 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") @@ -196,62 +199,105 @@ def check_invariants(request, **kwargs): 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'] + # users_field = form.cleaned_data['users'] hostname = 'opnfv_host' if not form.cleaned_data['hostname'] else form.cleaned_data['hostname'] - length = form.cleaned_data['length'] - image = form.cleaned_data['image'] - scenario = form.cleaned_data['scenario'] - installer = form.cleaned_data['installer'] + 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'] 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']) - resource_template = update_template(resource_template, image, hostname, request.user) - - # if no installer provided, just create blank host - opnfv_config = None - if installer: - hconf = resource_template.getConfigs()[0] - 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() @@ -272,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 } @@ -296,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), diff --git a/src/booking/stats.py b/src/booking/stats.py index 626ed79..70f91fa 100644 --- a/src/booking/stats.py +++ b/src/booking/stats.py @@ -104,5 +104,5 @@ class StatisticsManager(object): "user": [x, users], "utils": [in_use, not_in_use, maintenance], "projects": [project_keys, project_counts], - "colors": anuket_colors if os.environ['TEMPLATE_OVERRIDE_DIR'] == 'laas' else lfedge_colors + "colors": anuket_colors if os.environ.get('TEMPLATE_OVERRIDE_DIR') == 'laas' else lfedge_colors } diff --git a/src/booking/urls.py b/src/booking/urls.py index cdf18ae..0b60351 100644 --- a/src/booking/urls.py +++ b/src/booking/urls.py @@ -38,7 +38,7 @@ from booking.views import ( booking_modify_image ) -app_name = "booking" +app_name = 'booking' urlpatterns = [ url(r'^detail/(?P<booking_id>[0-9]+)/$', booking_detail_view, name='detail'), url(r'^(?P<booking_id>[0-9]+)/$', booking_detail_view, name='booking_detail'), diff --git a/src/booking/views.py b/src/booking/views.py index 2b910e7..940428b 100644 --- a/src/booking/views.py +++ b/src/booking/views.py @@ -28,6 +28,7 @@ from api.models import JobFactory from workflow.views import login from booking.forms import QuickBookingForm from booking.quick_deployer import create_from_form, drop_filter +import traceback def quick_create_clear_fields(request): @@ -62,6 +63,9 @@ def quick_create(request): "Check Account->My Bookings for the status of your new booking") return redirect(reverse('booking:booking_detail', kwargs={'booking_id': booking.id})) except Exception as e: + print("Error occurred while handling quick deployment:") + traceback.print_exc() + print(str(e)) messages.error(request, "Whoops, an error occurred: " + str(e)) context.update(drop_filter(request.user)) return render(request, 'booking/quick_deploy.html', context) @@ -127,7 +131,6 @@ class ResourceBookingsJSON(View): 'start', 'end', 'purpose', - 'jira_issue_status', 'config_bundle__name' ) return JsonResponse({'bookings': list(bookings)}) @@ -138,7 +141,7 @@ def build_image_mapping(lab, user): for profile in ResourceProfile.objects.filter(labs=lab): images = Image.objects.filter( from_lab=lab, - host_type=profile + architecture=profile.architecture ).filter( Q(public=True) | Q(owner=user) ) |