Modifies Resource Models for ongoing refactor
[laas.git] / src / booking / quick_deployer.py
1 ##############################################################################
2 # Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
3 #
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9
10
11 import json
12 from django.db.models import Q
13 from datetime import timedelta
14 from django.utils import timezone
15 from django.form import ValidationException
16 from account.models import Lab
17
18 from resource_inventory.models import (
19     ResourceTemplate,
20     Installer,
21     Image,
22     OPNFVRole,
23     OPNFVConfig,
24     HostOPNFVConfig,
25 )
26 from resource_inventory.resource_manager import ResourceManager
27 from resource_inventory.pdf_templater import PDFTemplater
28 from notifier.manager import NotificationHandler
29 from booking.models import Booking
30 from dashboard.exceptions import BookingLengthException
31 from api.models import JobFactory
32
33
34 def parse_resource_field(resource_json):
35     """
36     Parse the json from the frontend.
37
38     returns a reference to the selected Lab and ResourceTemplate objects
39     """
40     lab, template = (None, None)
41     lab_dict = resource_json['lab']
42     for lab_info in lab_dict.values():
43         if lab_info['selected']:
44             lab = Lab.objects.get(lab_user__id=lab_info['id'])
45
46     resource_dict = resource_json['resource']
47     for resource_info in resource_dict.values():
48         if resource_info['selected']:
49             template = ResourceTemplate.objects.get(pk=resource_info['id'])
50
51     if lab is None:
52         raise ValidationException("No lab was selected")
53     if template is None:
54         raise ValidationException("No Host was selected")
55
56     return lab, template
57
58
59 def update_template(template, image, lab, hostname):
60     """
61     Update and copy a resource template to the user's profile.
62
63     TODO: How, why, should we?
64     """
65     pass
66
67
68 def generate_opnfvconfig(scenario, installer, template):
69     return OPNFVConfig.objects.create(
70         scenario=scenario,
71         installer=installer,
72         template=template
73     )
74
75
76 def generate_hostopnfv(hostconfig, opnfvconfig):
77     role = None
78     try:
79         role = OPNFVRole.objects.get(name="Jumphost")
80     except Exception:
81         role = OPNFVRole.objects.create(
82             name="Jumphost",
83             description="Single server jumphost role"
84         )
85     return HostOPNFVConfig.objects.create(
86         role=role,
87         host_config=hostconfig,
88         opnfv_config=opnfvconfig
89     )
90
91
92 def generate_resource_bundle(template):
93     resource_manager = ResourceManager.getInstance()
94     resource_bundle = resource_manager.convertResourceBundle(template)
95     return resource_bundle
96
97
98 def check_invariants(request, **kwargs):
99     # TODO: This should really happen in the BookingForm validation methods
100     installer = kwargs['installer']
101     image = kwargs['image']
102     scenario = kwargs['scenario']
103     lab = kwargs['lab']
104     host_profile = kwargs['host_profile']
105     length = kwargs['length']
106     # check that image os is compatible with installer
107     if installer in image.os.sup_installers.all():
108         # if installer not here, we can omit that and not check for scenario
109         if not scenario:
110             raise ValidationException("An OPNFV Installer needs a scenario to be chosen to work properly")
111         if scenario not in installer.sup_scenarios.all():
112             raise ValidationException("The chosen installer does not support the chosen scenario")
113     if image.from_lab != lab:
114         raise ValidationException("The chosen image is not available at the chosen hosting lab")
115     if image.host_type != host_profile:
116         raise ValidationException("The chosen image is not available for the chosen host type")
117     if not image.public and image.owner != request.user:
118         raise ValidationException("You are not the owner of the chosen private image")
119     if length < 1 or length > 21:
120         raise BookingLengthException("Booking must be between 1 and 21 days long")
121
122
123 def create_from_form(form, request):
124     """
125     Create a Booking from the user's form.
126
127     Large, nasty method to create a booking or return a useful error
128     based on the form from the frontend
129     """
130     resource_field = form.cleaned_data['filter_field']
131     purpose_field = form.cleaned_data['purpose']
132     project_field = form.cleaned_data['project']
133     users_field = form.cleaned_data['users']
134     hostname = form.cleaned_data['hostname']
135     length = form.cleaned_data['length']
136
137     image = form.cleaned_data['image']
138     scenario = form.cleaned_data['scenario']
139     installer = form.cleaned_data['installer']
140
141     lab, resource_template = parse_resource_field(resource_field)
142     data = form.cleaned_data
143     data['lab'] = lab
144     data['resource_template'] = resource_template
145     check_invariants(request, **data)
146
147     # check booking privileges
148     # TODO: use the canonical booking_allowed method because now template might have multiple
149     # machines
150     if Booking.objects.filter(owner=request.user, end__gt=timezone.now()).count() >= 3 and not request.user.userprofile.booking_privledge:
151         raise PermissionError("You do not have permission to have more than 3 bookings at a time.")
152
153     ResourceManager.getInstance().templateIsReservable(resource_template)
154
155     hconf = update_template(resource_template, image, lab, hostname)
156
157     # if no installer provided, just create blank host
158     opnfv_config = None
159     if installer:
160         opnfv_config = generate_opnfvconfig(scenario, installer, resource_template)
161         generate_hostopnfv(hconf, opnfv_config)
162
163     # generate resource bundle
164     resource_bundle = generate_resource_bundle(resource_template)
165
166     # generate booking
167     booking = Booking.objects.create(
168         purpose=purpose_field,
169         project=project_field,
170         lab=lab,
171         owner=request.user,
172         start=timezone.now(),
173         end=timezone.now() + timedelta(days=int(length)),
174         resource=resource_bundle,
175         opnfv_config=opnfv_config
176     )
177     booking.pdf = PDFTemplater.makePDF(booking)
178
179     for collaborator in users_field:  # list of UserProfiles
180         booking.collaborators.add(collaborator.user)
181
182     booking.save()
183
184     # generate job
185     JobFactory.makeCompleteJob(booking)
186     NotificationHandler.notify_new_booking(booking)
187
188     return booking
189
190
191 def drop_filter(user):
192     """
193     Return a dictionary that contains filters.
194
195     Only certain installlers are supported on certain images, etc
196     so the image filter indexed at [imageid][installerid] is truthy if
197     that installer is supported on that image
198     """
199     installer_filter = {}
200     for image in Image.objects.all():
201         installer_filter[image.id] = {}
202         for installer in image.os.sup_installers.all():
203             installer_filter[image.id][installer.id] = 1
204
205     scenario_filter = {}
206     for installer in Installer.objects.all():
207         scenario_filter[installer.id] = {}
208         for scenario in installer.sup_scenarios.all():
209             scenario_filter[installer.id][scenario.id] = 1
210
211     images = Image.objects.filter(Q(public=True) | Q(owner=user))
212     image_filter = {}
213     for image in images:
214         image_filter[image.id] = {
215             'lab': 'lab_' + str(image.from_lab.lab_user.id),
216             'host_profile': 'host_' + str(image.host_type.id),
217             'name': image.name
218         }
219
220     return {'installer_filter': json.dumps(installer_filter),
221             'scenario_filter': json.dumps(scenario_filter),
222             'image_filter': json.dumps(image_filter)}