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_field_contents):
100 host_json = json.loads(host_field_contents)
101 lab_dict = host_json['labs'][0]
102 lab_id = list(lab_dict.keys())[0]
103 lab_user_id = int(lab_id.split("_")[-1])
104 lab = Lab.objects.get(lab_user__id=lab_user_id)
106 host_dict = host_json['hosts'][0]
107 profile_id = list(host_dict.keys())[0]
108 profile_id = int(profile_id.split("_")[-1])
109 profile = HostProfile.objects.get(id=profile_id)
111 # check validity of field data before trying to apply to models
112 if len(host_json['labs']) != 1:
113 raise NoLabSelectedError("No lab was selected")
115 raise LabDNE("Lab with provided ID does not exist")
117 raise HostProfileDNE("Host type with provided ID does not exist")
122 def check_available_matching_host(lab, hostprofile):
123 available_host_types = ResourceManager.getInstance().getAvailableHostTypes(lab)
124 if hostprofile not in available_host_types:
125 # TODO: handle deleting generic resource in this instance along with grb
126 raise HostNotAvailable('Requested host type is not available. Please try again later. Host availability can be viewed in the "Hosts" tab to the left.')
128 hostset = Host.objects.filter(lab=lab, profile=hostprofile).filter(booked=False).filter(working=True)
129 if not hostset.exists():
130 raise HostNotAvailable("Couldn't find any matching unbooked hosts")
135 def generate_grb(owner, lab, common_id):
136 grbundle = GenericResourceBundle(owner=owner)
138 grbundle.name = "grbundle for quick booking with uid " + common_id
139 grbundle.description = "grbundle created for quick-deploy booking"
145 def generate_gresource(bundle, hostname):
146 if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})$", hostname):
147 raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
148 gresource = GenericResource(bundle=bundle, name=hostname)
154 def generate_ghost(generic_resource, host_profile):
155 ghost = GenericHost()
156 ghost.resource = generic_resource
157 ghost.profile = host_profile
163 def generate_config_bundle(owner, common_id, grbundle):
164 cbundle = ConfigBundle()
165 cbundle.owner = owner
166 cbundle.name = "configbundle for quick booking with uid " + common_id
167 cbundle.description = "configbundle created for quick-deploy booking"
168 cbundle.bundle = grbundle
174 def generate_opnfvconfig(scenario, installer, config_bundle):
175 opnfvconfig = OPNFVConfig()
176 opnfvconfig.scenario = scenario
177 opnfvconfig.installer = installer
178 opnfvconfig.bundle = config_bundle
184 def generate_hostconfig(generic_host, image, config_bundle):
185 hconf = HostConfiguration()
186 hconf.host = generic_host
188 hconf.bundle = config_bundle
189 hconf.is_head_node = True
195 def generate_hostopnfv(hostconfig, opnfvconfig):
196 config = HostOPNFVConfig()
199 role = OPNFVRole.objects.get(name="Jumphost")
201 role = OPNFVRole.objects.create(
203 description="Single server jumphost role"
206 config.host_config = hostconfig
207 config.opnfv_config = opnfvconfig
212 def generate_resource_bundle(generic_resource_bundle, config_bundle): # warning: requires cleanup
214 resource_manager = ResourceManager.getInstance()
215 resource_bundle = resource_manager.convertResourceBundle(generic_resource_bundle, config=config_bundle)
216 return resource_bundle
217 except ResourceAvailabilityException:
218 raise ResourceAvailabilityException("Requested resources not available")
219 except ModelValidationException:
220 raise ModelValidationException("Encountered error while saving grbundle")
223 def check_invariants(request, **kwargs):
224 installer = kwargs['installer']
225 image = kwargs['image']
226 scenario = kwargs['scenario']
228 host_profile = kwargs['host_profile']
229 length = kwargs['length']
230 # check that image os is compatible with installer
231 if installer in image.os.sup_installers.all():
232 # if installer not here, we can omit that and not check for scenario
234 raise IncompatibleScenarioForInstaller("An OPNFV Installer needs a scenario to be chosen to work properly")
235 if scenario not in installer.sup_scenarios.all():
236 raise IncompatibleScenarioForInstaller("The chosen installer does not support the chosen scenario")
237 if image.from_lab != lab:
238 raise ImageNotAvailableAtLab("The chosen image is not available at the chosen hosting lab")
239 if image.host_type != host_profile:
240 raise IncompatibleImageForHost("The chosen image is not available for the chosen host type")
241 if not image.public and image.owner != request.user:
242 raise ImageOwnershipInvalid("You are not the owner of the chosen private image")
243 if length < 1 or length > 21:
244 raise BookingLengthException("Booking must be between 1 and 21 days long")
247 def configure_networking(grb, config):
249 net = Network.objects.create(name="public", bundle=grb, is_public=True)
250 # connect network to generic host
251 grb.getHosts()[0].generic_interfaces.first().connections.add(
252 NetworkConnection.objects.create(network=net, vlan_is_tagged=False)
255 role = NetworkRole.objects.create(name="public", network=net)
256 opnfv_config = config.opnfv_config.first()
258 opnfv_config.networks.add(role)
261 def create_from_form(form, request):
262 quick_booking_id = str(uuid.uuid4())
264 host_field = form.cleaned_data['filter_field']
265 purpose_field = form.cleaned_data['purpose']
266 project_field = form.cleaned_data['project']
267 users_field = form.cleaned_data['users']
268 hostname = form.cleaned_data['hostname']
269 length = form.cleaned_data['length']
271 image = form.cleaned_data['image']
272 scenario = form.cleaned_data['scenario']
273 installer = form.cleaned_data['installer']
275 lab, host_profile = parse_host_field(host_field)
276 data = form.cleaned_data
278 data['host_profile'] = host_profile
279 check_invariants(request, **data)
281 # check booking privileges
282 if Booking.objects.filter(owner=request.user, end__gt=timezone.now()).count() >= 3 and not request.user.userprofile.booking_privledge:
283 raise BookingPermissionException("You do not have permission to have more than 3 bookings at a time.")
285 check_available_matching_host(lab, host_profile) # requires cleanup if failure after this point
287 grbundle = generate_grb(request.user, lab, quick_booking_id)
288 gresource = generate_gresource(grbundle, hostname)
289 ghost = generate_ghost(gresource, host_profile)
290 cbundle = generate_config_bundle(request.user, quick_booking_id, grbundle)
291 hconf = generate_hostconfig(ghost, image, cbundle)
293 # if no installer provided, just create blank host
296 opnfv_config = generate_opnfvconfig(scenario, installer, cbundle)
297 generate_hostopnfv(hconf, opnfv_config)
299 # construct generic interfaces
300 for interface_profile in host_profile.interfaceprofile.all():
301 generic_interface = GenericInterface.objects.create(profile=interface_profile, host=ghost)
302 generic_interface.save()
304 configure_networking(grbundle, cbundle)
306 # generate resource bundle
307 resource_bundle = generate_resource_bundle(grbundle, cbundle)
310 booking = Booking.objects.create(
311 purpose=purpose_field,
312 project=project_field,
315 start=timezone.now(),
316 end=timezone.now() + timedelta(days=int(length)),
317 resource=resource_bundle,
318 config_bundle=cbundle,
319 opnfv_config=opnfv_config
321 booking.pdf = PDFTemplater.makePDF(booking)
323 for collaborator in users_field: # list of UserProfiles
324 booking.collaborators.add(collaborator.user)
329 JobFactory.makeCompleteJob(booking)
330 NotificationHandler.notify_new_booking(booking)
335 def drop_filter(user):
336 installer_filter = {}
337 for image in Image.objects.all():
338 installer_filter[image.id] = {}
339 for installer in image.os.sup_installers.all():
340 installer_filter[image.id][installer.id] = 1
343 for installer in Installer.objects.all():
344 scenario_filter[installer.id] = {}
345 for scenario in installer.sup_scenarios.all():
346 scenario_filter[installer.id][scenario.id] = 1
348 images = Image.objects.filter(Q(public=True) | Q(owner=user))
351 image_filter[image.id] = {}
352 image_filter[image.id]['lab'] = 'lab_' + str(image.from_lab.lab_user.id)
353 image_filter[image.id]['host_profile'] = 'host_' + str(image.host_type.id)
354 image_filter[image.id]['name'] = image.name
356 return {'installer_filter': json.dumps(installer_filter),
357 'scenario_filter': json.dumps(scenario_filter),
358 'image_filter': json.dumps(image_filter)}