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):
100 lab, profile = (None, None)
101 lab_dict = host_json['lab']
102 for lab_info in lab_dict.values():
103 if lab_info['selected']:
104 lab = Lab.objects.get(lab_user__id=lab_info['id'])
106 host_dict = host_json['host']
107 for host_info in host_dict.values():
108 if host_info['selected']:
109 profile = HostProfile.objects.get(pk=host_info['id'])
112 raise NoLabSelectedError("No lab was selected")
114 raise HostProfileDNE("No Host was selected")
119 def check_available_matching_host(lab, hostprofile):
120 available_host_types = ResourceManager.getInstance().getAvailableHostTypes(lab)
121 if hostprofile not in available_host_types:
122 # TODO: handle deleting generic resource in this instance along with grb
123 raise HostNotAvailable('Requested host type is not available. Please try again later. Host availability can be viewed in the "Hosts" tab to the left.')
125 hostset = Host.objects.filter(lab=lab, profile=hostprofile).filter(booked=False).filter(working=True)
126 if not hostset.exists():
127 raise HostNotAvailable("Couldn't find any matching unbooked hosts")
132 def generate_grb(owner, lab, common_id):
133 grbundle = GenericResourceBundle(owner=owner)
135 grbundle.name = "grbundle for quick booking with uid " + common_id
136 grbundle.description = "grbundle created for quick-deploy booking"
142 def generate_gresource(bundle, hostname):
143 if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})$", hostname):
144 raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
145 gresource = GenericResource(bundle=bundle, name=hostname)
151 def generate_ghost(generic_resource, host_profile):
152 ghost = GenericHost()
153 ghost.resource = generic_resource
154 ghost.profile = host_profile
160 def generate_config_bundle(owner, common_id, grbundle):
161 cbundle = ConfigBundle()
162 cbundle.owner = owner
163 cbundle.name = "configbundle for quick booking with uid " + common_id
164 cbundle.description = "configbundle created for quick-deploy booking"
165 cbundle.bundle = grbundle
171 def generate_opnfvconfig(scenario, installer, config_bundle):
172 opnfvconfig = OPNFVConfig()
173 opnfvconfig.scenario = scenario
174 opnfvconfig.installer = installer
175 opnfvconfig.bundle = config_bundle
181 def generate_hostconfig(generic_host, image, config_bundle):
182 hconf = HostConfiguration()
183 hconf.host = generic_host
185 hconf.bundle = config_bundle
186 hconf.is_head_node = True
192 def generate_hostopnfv(hostconfig, opnfvconfig):
193 config = HostOPNFVConfig()
196 role = OPNFVRole.objects.get(name="Jumphost")
198 role = OPNFVRole.objects.create(
200 description="Single server jumphost role"
203 config.host_config = hostconfig
204 config.opnfv_config = opnfvconfig
209 def generate_resource_bundle(generic_resource_bundle, config_bundle): # warning: requires cleanup
211 resource_manager = ResourceManager.getInstance()
212 resource_bundle = resource_manager.convertResourceBundle(generic_resource_bundle, config=config_bundle)
213 return resource_bundle
214 except ResourceAvailabilityException:
215 raise ResourceAvailabilityException("Requested resources not available")
216 except ModelValidationException:
217 raise ModelValidationException("Encountered error while saving grbundle")
220 def check_invariants(request, **kwargs):
221 installer = kwargs['installer']
222 image = kwargs['image']
223 scenario = kwargs['scenario']
225 host_profile = kwargs['host_profile']
226 length = kwargs['length']
227 # check that image os is compatible with installer
228 if installer in image.os.sup_installers.all():
229 # if installer not here, we can omit that and not check for scenario
231 raise IncompatibleScenarioForInstaller("An OPNFV Installer needs a scenario to be chosen to work properly")
232 if scenario not in installer.sup_scenarios.all():
233 raise IncompatibleScenarioForInstaller("The chosen installer does not support the chosen scenario")
234 if image.from_lab != lab:
235 raise ImageNotAvailableAtLab("The chosen image is not available at the chosen hosting lab")
236 if image.host_type != host_profile:
237 raise IncompatibleImageForHost("The chosen image is not available for the chosen host type")
238 if not image.public and image.owner != request.user:
239 raise ImageOwnershipInvalid("You are not the owner of the chosen private image")
240 if length < 1 or length > 21:
241 raise BookingLengthException("Booking must be between 1 and 21 days long")
244 def configure_networking(grb, config):
246 net = Network.objects.create(name="public", bundle=grb, is_public=True)
247 # connect network to generic host
248 grb.getHosts()[0].generic_interfaces.first().connections.add(
249 NetworkConnection.objects.create(network=net, vlan_is_tagged=False)
252 role = NetworkRole.objects.create(name="public", network=net)
253 opnfv_config = config.opnfv_config.first()
255 opnfv_config.networks.add(role)
258 def create_from_form(form, request):
259 quick_booking_id = str(uuid.uuid4())
261 host_field = form.cleaned_data['filter_field']
262 purpose_field = form.cleaned_data['purpose']
263 project_field = form.cleaned_data['project']
264 users_field = form.cleaned_data['users']
265 hostname = form.cleaned_data['hostname']
266 length = form.cleaned_data['length']
268 image = form.cleaned_data['image']
269 scenario = form.cleaned_data['scenario']
270 installer = form.cleaned_data['installer']
272 lab, host_profile = parse_host_field(host_field)
273 data = form.cleaned_data
275 data['host_profile'] = host_profile
276 check_invariants(request, **data)
278 # check booking privileges
279 if Booking.objects.filter(owner=request.user, end__gt=timezone.now()).count() >= 3 and not request.user.userprofile.booking_privledge:
280 raise BookingPermissionException("You do not have permission to have more than 3 bookings at a time.")
282 check_available_matching_host(lab, host_profile) # requires cleanup if failure after this point
284 grbundle = generate_grb(request.user, lab, quick_booking_id)
285 gresource = generate_gresource(grbundle, hostname)
286 ghost = generate_ghost(gresource, host_profile)
287 cbundle = generate_config_bundle(request.user, quick_booking_id, grbundle)
288 hconf = generate_hostconfig(ghost, image, cbundle)
290 # if no installer provided, just create blank host
293 opnfv_config = generate_opnfvconfig(scenario, installer, cbundle)
294 generate_hostopnfv(hconf, opnfv_config)
296 # construct generic interfaces
297 for interface_profile in host_profile.interfaceprofile.all():
298 generic_interface = GenericInterface.objects.create(profile=interface_profile, host=ghost)
299 generic_interface.save()
301 configure_networking(grbundle, cbundle)
303 # generate resource bundle
304 resource_bundle = generate_resource_bundle(grbundle, cbundle)
307 booking = Booking.objects.create(
308 purpose=purpose_field,
309 project=project_field,
312 start=timezone.now(),
313 end=timezone.now() + timedelta(days=int(length)),
314 resource=resource_bundle,
315 config_bundle=cbundle,
316 opnfv_config=opnfv_config
318 booking.pdf = PDFTemplater.makePDF(booking)
320 for collaborator in users_field: # list of UserProfiles
321 booking.collaborators.add(collaborator.user)
326 JobFactory.makeCompleteJob(booking)
327 NotificationHandler.notify_new_booking(booking)
332 def drop_filter(user):
333 installer_filter = {}
334 for image in Image.objects.all():
335 installer_filter[image.id] = {}
336 for installer in image.os.sup_installers.all():
337 installer_filter[image.id][installer.id] = 1
340 for installer in Installer.objects.all():
341 scenario_filter[installer.id] = {}
342 for scenario in installer.sup_scenarios.all():
343 scenario_filter[installer.id][scenario.id] = 1
345 images = Image.objects.filter(Q(public=True) | Q(owner=user))
348 image_filter[image.id] = {}
349 image_filter[image.id]['lab'] = 'lab_' + str(image.from_lab.lab_user.id)
350 image_filter[image.id]['host_profile'] = 'host_' + str(image.host_type.id)
351 image_filter[image.id]['name'] = image.name
353 return {'installer_filter': json.dumps(installer_filter),
354 'scenario_filter': json.dumps(scenario_filter),
355 'image_filter': json.dumps(image_filter)}