1 ##############################################################################
2 # Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
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 ##############################################################################
12 from django.db.models import Q
13 from datetime import timedelta
14 from django.utils import timezone
15 from django.core.exceptions import ValidationError
16 from account.models import Lab
18 from resource_inventory.models import (
25 ResourceConfiguration,
27 InterfaceConfiguration,
30 from resource_inventory.resource_manager import ResourceManager
31 from resource_inventory.pdf_templater import PDFTemplater
32 from notifier.manager import NotificationHandler
33 from booking.models import Booking
34 from dashboard.exceptions import BookingLengthException
35 from api.models import JobFactory
38 def parse_resource_field(resource_json):
40 Parse the json from the frontend.
42 returns a reference to the selected Lab and ResourceTemplate objects
44 lab, template = (None, None)
45 lab_dict = resource_json['lab']
46 for lab_info in lab_dict.values():
47 if lab_info['selected']:
48 lab = Lab.objects.get(lab_user__id=lab_info['id'])
50 resource_dict = resource_json['resource']
51 for resource_info in resource_dict.values():
52 if resource_info['selected']:
53 template = ResourceTemplate.objects.get(pk=resource_info['id'])
56 raise ValidationError("No lab was selected")
58 raise ValidationError("No Host was selected")
63 def update_template(old_template, image, hostname, user):
65 Duplicate a template to the users account and update configured fields.
67 The dashboard presents users with preconfigured resource templates,
68 but the user might want to make small modifications, e.g hostname and
69 linux distro. So we copy the public template and create a private version
70 to the user's profile, and mark it temporary. When the booking ends the
71 new template is deleted
73 name = user.username + "'s Copy of '" + old_template.name + "'"
74 num_copies = ResourceTemplate.objects.filter(name__startswith=name).count()
75 template = ResourceTemplate.objects.create(
76 name=name if num_copies == 0 else name + " (" + str(num_copies) + ")",
80 description=old_template.description,
86 for old_network in old_template.networks.all():
87 Network.objects.create(
88 name=old_network.name,
90 is_public=old_network.is_public
92 # We are assuming there is only one opnfv config per public resource template
93 old_opnfv = template.opnfv_config.first()
95 opnfv_config = OPNFVConfig.objects.create(
96 installer=old_opnfv.installer,
97 scenario=old_opnfv.installer,
99 name=old_opnfv.installer,
101 # I am explicitly leaving opnfv_config.networks empty to avoid
102 # problems with duplicated / shared networks. In the quick deploy,
103 # there is never multiple networks anyway. This may have to change in the future
105 for old_config in old_template.getConfigs():
108 image_to_set = old_config.image
110 config = ResourceConfiguration.objects.create(
111 profile=old_config.profile,
114 is_head_node=old_config.is_head_node
117 for old_iface_config in old_config.interface_configs.all():
118 iface_config = InterfaceConfiguration.objects.create(
119 profile=old_iface_config.profile,
120 resource_config=config
123 for old_connection in old_iface_config.connections.all():
124 iface_config.connections.add(NetworkConnection.objects.create(
125 network=template.networks.get(name=old_connection.network.name),
126 vlan_is_tagged=old_connection.vlan_is_tagged
129 for old_res_opnfv in old_config.resource_opnfv_config.all():
131 ResourceOPNFVConfig.objects.create(
133 resource_config=config,
134 opnfv_config=opnfv_config
139 def generate_opnfvconfig(scenario, installer, template):
140 return OPNFVConfig.objects.create(
147 def generate_hostopnfv(hostconfig, opnfvconfig):
150 role = OPNFVRole.objects.get(name="Jumphost")
152 role = OPNFVRole.objects.create(
154 description="Single server jumphost role"
156 return ResourceOPNFVConfig.objects.create(
158 host_config=hostconfig,
159 opnfv_config=opnfvconfig
163 def generate_resource_bundle(template):
164 resource_manager = ResourceManager.getInstance()
165 resource_bundle = resource_manager.instantiateTemplate(template)
166 return resource_bundle
169 def check_invariants(request, **kwargs):
170 # TODO: This should really happen in the BookingForm validation methods
171 installer = kwargs['installer']
172 image = kwargs['image']
173 scenario = kwargs['scenario']
175 length = kwargs['length']
176 # check that image os is compatible with installer
178 if installer or scenario:
179 if installer in image.os.sup_installers.all():
180 # if installer not here, we can omit that and not check for scenario
182 raise ValidationError("An OPNFV Installer needs a scenario to be chosen to work properly")
183 if scenario not in installer.sup_scenarios.all():
184 raise ValidationError("The chosen installer does not support the chosen scenario")
185 if image.from_lab != lab:
186 raise ValidationError("The chosen image is not available at the chosen hosting lab")
188 # if image.host_type != host_profile:
189 # raise ValidationError("The chosen image is not available for the chosen host type")
190 if not image.public and image.owner != request.user:
191 raise ValidationError("You are not the owner of the chosen private image")
192 if length < 1 or length > 21:
193 raise BookingLengthException("Booking must be between 1 and 21 days long")
196 def create_from_form(form, request):
198 Create a Booking from the user's form.
200 Large, nasty method to create a booking or return a useful error
201 based on the form from the frontend
203 resource_field = form.cleaned_data['filter_field']
204 purpose_field = form.cleaned_data['purpose']
205 project_field = form.cleaned_data['project']
206 users_field = form.cleaned_data['users']
207 hostname = form.cleaned_data['hostname']
208 length = form.cleaned_data['length']
210 image = form.cleaned_data['image']
211 scenario = form.cleaned_data['scenario']
212 installer = form.cleaned_data['installer']
214 lab, resource_template = parse_resource_field(resource_field)
215 data = form.cleaned_data
217 data['resource_template'] = resource_template
218 check_invariants(request, **data)
220 # check booking privileges
221 # TODO: use the canonical booking_allowed method because now template might have multiple
223 if Booking.objects.filter(owner=request.user, end__gt=timezone.now()).count() >= 3 and not request.user.userprofile.booking_privledge:
224 raise PermissionError("You do not have permission to have more than 3 bookings at a time.")
226 ResourceManager.getInstance().templateIsReservable(resource_template)
228 resource_template = update_template(resource_template, image, hostname, request.user)
230 # if no installer provided, just create blank host
233 hconf = resource_template.getConfigs()[0]
234 opnfv_config = generate_opnfvconfig(scenario, installer, resource_template)
235 generate_hostopnfv(hconf, opnfv_config)
237 # generate resource bundle
238 resource_bundle = generate_resource_bundle(resource_template)
241 booking = Booking.objects.create(
242 purpose=purpose_field,
243 project=project_field,
246 start=timezone.now(),
247 end=timezone.now() + timedelta(days=int(length)),
248 resource=resource_bundle,
249 opnfv_config=opnfv_config
251 booking.pdf = PDFTemplater.makePDF(booking)
253 for collaborator in users_field: # list of UserProfiles
254 booking.collaborators.add(collaborator.user)
259 JobFactory.makeCompleteJob(booking)
260 NotificationHandler.notify_new_booking(booking)
265 def drop_filter(user):
267 Return a dictionary that contains filters.
269 Only certain installlers are supported on certain images, etc
270 so the image filter indexed at [imageid][installerid] is truthy if
271 that installer is supported on that image
273 installer_filter = {}
274 for image in Image.objects.all():
275 installer_filter[image.id] = {}
276 for installer in image.os.sup_installers.all():
277 installer_filter[image.id][installer.id] = 1
280 for installer in Installer.objects.all():
281 scenario_filter[installer.id] = {}
282 for scenario in installer.sup_scenarios.all():
283 scenario_filter[installer.id][scenario.id] = 1
285 images = Image.objects.filter(Q(public=True) | Q(owner=user))
288 image_filter[image.id] = {
289 'lab': 'lab_' + str(image.from_lab.lab_user.id),
290 'host_profile': str(image.host_type.id),
295 templates = ResourceTemplate.objects.filter(Q(public=True) | Q(owner=user))
297 profiles = [conf.profile for conf in rt.getConfigs()]
298 resource_filter["resource_" + str(rt.id)] = [str(p.id) for p in profiles]
301 'installer_filter': json.dumps(installer_filter),
302 'scenario_filter': json.dumps(scenario_filter),
303 'image_filter': json.dumps(image_filter),
304 'resource_profile_map': json.dumps(resource_filter),