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 ##############################################################################
14 from django.db.models import Q
15 from datetime import timedelta
16 from django.utils import timezone
17 from account.models import Lab
19 from resource_inventory.models import (
22 GenericResourceBundle,
37 from resource_inventory.resource_manager import ResourceManager
38 from resource_inventory.pdf_templater import PDFTemplater
39 from notifier.manager import NotificationHandler
40 from booking.models import Booking
41 from dashboard.exceptions import (
42 InvalidHostnameException,
43 ResourceAvailabilityException,
44 ModelValidationException,
45 BookingLengthException
47 from api.models import JobFactory
50 # model validity exceptions
51 class IncompatibleInstallerForOS(Exception):
55 class IncompatibleScenarioForInstaller(Exception):
59 class IncompatibleImageForHost(Exception):
63 class ImageOwnershipInvalid(Exception):
67 class ImageNotAvailableAtLab(Exception):
71 class LabDNE(Exception):
75 class HostProfileDNE(Exception):
79 class HostNotAvailable(Exception):
83 class NoLabSelectedError(Exception):
87 class OPNFVRoleDNE(Exception):
91 class NoRemainingPublicNetwork(Exception):
95 class BookingPermissionException(Exception):
99 def parse_host_field(host_json):
101 Parse the json from the frontend.
103 returns a reference to the selected Lab and HostProfile objects
105 lab, profile = (None, None)
106 lab_dict = host_json['lab']
107 for lab_info in lab_dict.values():
108 if lab_info['selected']:
109 lab = Lab.objects.get(lab_user__id=lab_info['id'])
111 host_dict = host_json['host']
112 for host_info in host_dict.values():
113 if host_info['selected']:
114 profile = HostProfile.objects.get(pk=host_info['id'])
117 raise NoLabSelectedError("No lab was selected")
119 raise HostProfileDNE("No Host was selected")
124 def check_available_matching_host(lab, hostprofile):
126 Check the resources are available.
128 Returns true if the requested host type is availble,
129 Or throws an exception
131 available_host_types = ResourceManager.getInstance().getAvailableHostTypes(lab)
132 if hostprofile not in available_host_types:
133 # TODO: handle deleting generic resource in this instance along with grb
134 raise HostNotAvailable('Requested host type is not available. Please try again later. Host availability can be viewed in the "Hosts" tab to the left.')
136 hostset = Host.objects.filter(lab=lab, profile=hostprofile).filter(booked=False).filter(working=True)
137 if not hostset.exists():
138 raise HostNotAvailable("Couldn't find any matching unbooked hosts")
143 # Functions to create models
145 def generate_grb(owner, lab, common_id):
146 """Create a Generic Resource Bundle."""
147 grbundle = GenericResourceBundle(owner=owner)
149 grbundle.name = "grbundle for quick booking with uid " + common_id
150 grbundle.description = "grbundle created for quick-deploy booking"
156 def generate_gresource(bundle, hostname):
157 """Create a Generic Resource."""
158 if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})$", hostname):
159 raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
160 gresource = GenericResource(bundle=bundle, name=hostname)
166 def generate_ghost(generic_resource, host_profile):
167 """Create a Generic Host."""
168 ghost = GenericHost()
169 ghost.resource = generic_resource
170 ghost.profile = host_profile
176 def generate_config_bundle(owner, common_id, grbundle):
177 """Create a Configuration Bundle."""
178 cbundle = ConfigBundle()
179 cbundle.owner = owner
180 cbundle.name = "configbundle for quick booking with uid " + common_id
181 cbundle.description = "configbundle created for quick-deploy booking"
182 cbundle.bundle = grbundle
188 def generate_opnfvconfig(scenario, installer, config_bundle):
189 """Create an OPNFV Configuration."""
190 opnfvconfig = OPNFVConfig()
191 opnfvconfig.scenario = scenario
192 opnfvconfig.installer = installer
193 opnfvconfig.bundle = config_bundle
199 def generate_hostconfig(generic_host, image, config_bundle):
200 """Create a Host Configuration."""
201 hconf = HostConfiguration()
202 hconf.host = generic_host
204 hconf.bundle = config_bundle
205 hconf.is_head_node = True
211 def generate_hostopnfv(hostconfig, opnfvconfig):
212 """Relate the Host and OPNFV Configs."""
213 config = HostOPNFVConfig()
216 role = OPNFVRole.objects.get(name="Jumphost")
218 role = OPNFVRole.objects.create(
220 description="Single server jumphost role"
223 config.host_config = hostconfig
224 config.opnfv_config = opnfvconfig
229 def generate_resource_bundle(generic_resource_bundle, config_bundle): # warning: requires cleanup
230 """Create a Resource Bundle."""
232 resource_manager = ResourceManager.getInstance()
233 resource_bundle = resource_manager.convertResourceBundle(generic_resource_bundle, config=config_bundle)
234 return resource_bundle
235 except ResourceAvailabilityException:
236 raise ResourceAvailabilityException("Requested resources not available")
237 except ModelValidationException:
238 raise ModelValidationException("Encountered error while saving grbundle")
241 def check_invariants(request, **kwargs):
243 Verify all the contraints on the requested booking.
245 verifies software compatibility, booking length, etc
247 installer = kwargs['installer']
248 image = kwargs['image']
249 scenario = kwargs['scenario']
251 host_profile = kwargs['host_profile']
252 length = kwargs['length']
253 # check that image os is compatible with installer
254 if installer in image.os.sup_installers.all():
255 # if installer not here, we can omit that and not check for scenario
257 raise IncompatibleScenarioForInstaller("An OPNFV Installer needs a scenario to be chosen to work properly")
258 if scenario not in installer.sup_scenarios.all():
259 raise IncompatibleScenarioForInstaller("The chosen installer does not support the chosen scenario")
260 if image.from_lab != lab:
261 raise ImageNotAvailableAtLab("The chosen image is not available at the chosen hosting lab")
262 if image.host_type != host_profile:
263 raise IncompatibleImageForHost("The chosen image is not available for the chosen host type")
264 if not image.public and image.owner != request.user:
265 raise ImageOwnershipInvalid("You are not the owner of the chosen private image")
266 if length < 1 or length > 21:
267 raise BookingLengthException("Booking must be between 1 and 21 days long")
270 def configure_networking(grb, config):
272 net = Network.objects.create(name="public", bundle=grb, is_public=True)
273 # connect network to generic host
274 grb.getResources()[0].generic_interfaces.first().connections.add(
275 NetworkConnection.objects.create(network=net, vlan_is_tagged=False)
278 role = NetworkRole.objects.create(name="public", network=net)
279 opnfv_config = config.opnfv_config.first()
281 opnfv_config.networks.add(role)
284 def create_from_form(form, request):
286 Create a Booking from the user's form.
288 Large, nasty method to create a booking or return a useful error
289 based on the form from the frontend
291 quick_booking_id = str(uuid.uuid4())
293 host_field = form.cleaned_data['filter_field']
294 purpose_field = form.cleaned_data['purpose']
295 project_field = form.cleaned_data['project']
296 users_field = form.cleaned_data['users']
297 hostname = form.cleaned_data['hostname']
298 length = form.cleaned_data['length']
300 image = form.cleaned_data['image']
301 scenario = form.cleaned_data['scenario']
302 installer = form.cleaned_data['installer']
304 lab, host_profile = parse_host_field(host_field)
305 data = form.cleaned_data
307 data['host_profile'] = host_profile
308 check_invariants(request, **data)
310 # check booking privileges
311 if Booking.objects.filter(owner=request.user, end__gt=timezone.now()).count() >= 3 and not request.user.userprofile.booking_privledge:
312 raise BookingPermissionException("You do not have permission to have more than 3 bookings at a time.")
314 check_available_matching_host(lab, host_profile) # requires cleanup if failure after this point
316 grbundle = generate_grb(request.user, lab, quick_booking_id)
317 gresource = generate_gresource(grbundle, hostname)
318 ghost = generate_ghost(gresource, host_profile)
319 cbundle = generate_config_bundle(request.user, quick_booking_id, grbundle)
320 hconf = generate_hostconfig(ghost, image, cbundle)
322 # if no installer provided, just create blank host
325 opnfv_config = generate_opnfvconfig(scenario, installer, cbundle)
326 generate_hostopnfv(hconf, opnfv_config)
328 # construct generic interfaces
329 for interface_profile in host_profile.interfaceprofile.all():
330 generic_interface = GenericInterface.objects.create(profile=interface_profile, host=ghost)
331 generic_interface.save()
333 configure_networking(grbundle, cbundle)
335 # generate resource bundle
336 resource_bundle = generate_resource_bundle(grbundle, cbundle)
339 booking = Booking.objects.create(
340 purpose=purpose_field,
341 project=project_field,
344 start=timezone.now(),
345 end=timezone.now() + timedelta(days=int(length)),
346 resource=resource_bundle,
347 config_bundle=cbundle,
348 opnfv_config=opnfv_config
350 booking.pdf = PDFTemplater.makePDF(booking)
352 for collaborator in users_field: # list of UserProfiles
353 booking.collaborators.add(collaborator.user)
358 JobFactory.makeCompleteJob(booking)
359 NotificationHandler.notify_new_booking(booking)
364 def drop_filter(user):
366 Return a dictionary that contains filters.
368 Only certain installlers are supported on certain images, etc
369 so the image filter indexed at [imageid][installerid] is truthy if
370 that installer is supported on that image
372 installer_filter = {}
373 for image in Image.objects.all():
374 installer_filter[image.id] = {}
375 for installer in image.os.sup_installers.all():
376 installer_filter[image.id][installer.id] = 1
379 for installer in Installer.objects.all():
380 scenario_filter[installer.id] = {}
381 for scenario in installer.sup_scenarios.all():
382 scenario_filter[installer.id][scenario.id] = 1
384 images = Image.objects.filter(Q(public=True) | Q(owner=user))
387 image_filter[image.id] = {}
388 image_filter[image.id]['lab'] = 'lab_' + str(image.from_lab.lab_user.id)
389 image_filter[image.id]['host_profile'] = 'host_' + str(image.host_type.id)
390 image_filter[image.id]['name'] = image.name
392 return {'installer_filter': json.dumps(installer_filter),
393 'scenario_filter': json.dumps(scenario_filter),
394 'image_filter': json.dumps(image_filter)}