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 django.contrib.auth.models import User
16 from datetime import timedelta
17 from django.utils import timezone
18 from account.models import Lab
20 from resource_inventory.models import (
23 GenericResourceBundle,
35 from resource_inventory.resource_manager import ResourceManager
36 from resource_inventory.pdf_templater import PDFTemplater
37 from notifier.manager import NotificationHandler
38 from booking.models import Booking
39 from dashboard.exceptions import (
40 InvalidHostnameException,
41 ResourceAvailabilityException,
42 ModelValidationException,
43 BookingLengthException
45 from api.models import JobFactory
48 # model validity exceptions
49 class IncompatibleInstallerForOS(Exception):
53 class IncompatibleScenarioForInstaller(Exception):
57 class IncompatibleImageForHost(Exception):
61 class ImageOwnershipInvalid(Exception):
65 class ImageNotAvailableAtLab(Exception):
69 class LabDNE(Exception):
73 class HostProfileDNE(Exception):
77 class HostNotAvailable(Exception):
81 class NoLabSelectedError(Exception):
85 class OPNFVRoleDNE(Exception):
89 class NoRemainingPublicNetwork(Exception):
93 def parse_host_field(host_field_contents):
94 host_json = json.loads(host_field_contents)
95 lab_dict = host_json['labs'][0]
96 lab_id = list(lab_dict.keys())[0]
97 lab_user_id = int(lab_id.split("_")[-1])
98 lab = Lab.objects.get(lab_user__id=lab_user_id)
100 host_dict = host_json['hosts'][0]
101 profile_id = list(host_dict.keys())[0]
102 profile_id = int(profile_id.split("_")[-1])
103 profile = HostProfile.objects.get(id=profile_id)
105 # check validity of field data before trying to apply to models
106 if len(host_json['labs']) != 1:
107 raise NoLabSelectedError("No lab was selected")
109 raise LabDNE("Lab with provided ID does not exist")
111 raise HostProfileDNE("Host type with provided ID does not exist")
116 def check_available_matching_host(lab, hostprofile):
117 available_host_types = ResourceManager.getInstance().getAvailableHostTypes(lab)
118 if hostprofile not in available_host_types:
119 # TODO: handle deleting generic resource in this instance along with grb
120 raise HostNotAvailable('Requested host type is not available. Please try again later. Host availability can be viewed in the "Hosts" tab to the left.')
122 hostset = Host.objects.filter(lab=lab, profile=hostprofile).filter(booked=False).filter(working=True)
123 if not hostset.exists():
124 raise HostNotAvailable("Couldn't find any matching unbooked hosts")
129 def generate_grb(owner, lab, common_id):
130 grbundle = GenericResourceBundle(owner=owner)
132 grbundle.name = "grbundle for quick booking with uid " + common_id
133 grbundle.description = "grbundle created for quick-deploy booking"
139 def generate_gresource(bundle, hostname):
140 if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})$", hostname):
141 raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
142 gresource = GenericResource(bundle=bundle, name=hostname)
148 def generate_ghost(generic_resource, host_profile):
149 ghost = GenericHost()
150 ghost.resource = generic_resource
151 ghost.profile = host_profile
157 def generate_config_bundle(owner, common_id, grbundle):
158 cbundle = ConfigBundle()
159 cbundle.owner = owner
160 cbundle.name = "configbundle for quick booking with uid " + common_id
161 cbundle.description = "configbundle created for quick-deploy booking"
162 cbundle.bundle = grbundle
168 def generate_opnfvconfig(scenario, installer, config_bundle):
169 opnfvconfig = OPNFVConfig()
170 opnfvconfig.scenario = scenario
171 opnfvconfig.installer = installer
172 opnfvconfig.bundle = config_bundle
178 def generate_hostconfig(generic_host, image, config_bundle):
179 hconf = HostConfiguration()
180 hconf.host = generic_host
183 opnfvrole = OPNFVRole.objects.get(name="Jumphost")
185 raise OPNFVRoleDNE("No jumphost role was found.")
187 hconf.opnfvRole = opnfvrole
188 hconf.bundle = config_bundle
194 def generate_resource_bundle(generic_resource_bundle, config_bundle): # warning: requires cleanup
196 resource_manager = ResourceManager.getInstance()
197 resource_bundle = resource_manager.convertResourceBundle(generic_resource_bundle, config=config_bundle)
198 return resource_bundle
199 except ResourceAvailabilityException:
200 raise ResourceAvailabilityException("Requested resources not available")
201 except ModelValidationException:
202 raise ModelValidationException("Encountered error while saving grbundle")
205 def check_invariants(request, **kwargs):
206 installer = kwargs['installer']
207 image = kwargs['image']
208 scenario = kwargs['scenario']
210 host_profile = kwargs['host_profile']
211 length = kwargs['length']
212 # check that image os is compatible with installer
213 if installer in image.os.sup_installers.all():
214 # if installer not here, we can omit that and not check for scenario
216 raise IncompatibleScenarioForInstaller("An OPNFV Installer needs a scenario to be chosen to work properly")
217 if scenario not in installer.sup_scenarios.all():
218 raise IncompatibleScenarioForInstaller("The chosen installer does not support the chosen scenario")
219 if image.from_lab != lab:
220 raise ImageNotAvailableAtLab("The chosen image is not available at the chosen hosting lab")
221 if image.host_type != host_profile:
222 raise IncompatibleImageForHost("The chosen image is not available for the chosen host type")
223 if not image.public and image.owner != request.user:
224 raise ImageOwnershipInvalid("You are not the owner of the chosen private image")
225 if length < 1 or length > 21:
226 raise BookingLengthException("Booking must be between 1 and 21 days long")
229 def create_from_form(form, request):
230 quick_booking_id = str(uuid.uuid4())
232 host_field = form.cleaned_data['filter_field']
233 purpose_field = form.cleaned_data['purpose']
234 project_field = form.cleaned_data['project']
235 users_field = form.cleaned_data['users']
236 hostname = form.cleaned_data['hostname']
237 length = form.cleaned_data['length']
239 image = form.cleaned_data['image']
240 scenario = form.cleaned_data['scenario']
241 installer = form.cleaned_data['installer']
243 lab, host_profile = parse_host_field(host_field)
244 data = form.cleaned_data
246 data['host_profile'] = host_profile
247 check_invariants(request, **data)
249 check_available_matching_host(lab, host_profile) # requires cleanup if failure after this point
251 grbundle = generate_grb(request.user, lab, quick_booking_id)
253 gresource = generate_gresource(grbundle, hostname)
255 ghost = generate_ghost(gresource, host_profile)
257 cbundle = generate_config_bundle(request.user, quick_booking_id, grbundle)
259 # if no installer provided, just create blank host
261 generate_opnfvconfig(scenario, installer, cbundle)
263 generate_hostconfig(ghost, image, cbundle)
265 # construct generic interfaces
266 for interface_profile in host_profile.interfaceprofile.all():
267 generic_interface = GenericInterface.objects.create(profile=interface_profile, host=ghost)
268 generic_interface.save()
270 # get vlan, assign to first interface
271 publicnetwork = lab.vlan_manager.get_public_vlan()
272 if not publicnetwork:
273 raise NoRemainingPublicNetwork("No public networks were available for your pod")
274 publicvlan = publicnetwork.vlan
275 lab.vlan_manager.reserve_public_vlan(publicvlan)
277 vlan = Vlan.objects.create(vlan_id=publicvlan, tagged=False, public=True)
280 ghost.generic_interfaces.first().vlans.add(vlan)
281 ghost.generic_interfaces.first().save()
283 # generate resource bundle
284 resource_bundle = generate_resource_bundle(grbundle, cbundle)
288 booking.purpose = purpose_field
289 booking.project = project_field
291 booking.owner = request.user
292 booking.start = timezone.now()
293 booking.end = timezone.now() + timedelta(days=int(length))
294 booking.resource = resource_bundle
295 booking.pdf = PDFTemplater.makePDF(booking.resource)
296 booking.config_bundle = cbundle
298 users_field = users_field[2:-2]
299 if users_field: # may be empty after split, if no collaborators entered
300 users_field = json.loads(users_field)
301 for collaborator in users_field:
302 user = User.objects.get(id=collaborator['id'])
303 booking.collaborators.add(user)
307 JobFactory.makeCompleteJob(booking)
308 NotificationHandler.notify_new_booking(booking)
311 def drop_filter(user):
312 installer_filter = {}
313 for image in Image.objects.all():
314 installer_filter[image.id] = {}
315 for installer in image.os.sup_installers.all():
316 installer_filter[image.id][installer.id] = 1
319 for installer in Installer.objects.all():
320 scenario_filter[installer.id] = {}
321 for scenario in installer.sup_scenarios.all():
322 scenario_filter[installer.id][scenario.id] = 1
324 images = Image.objects.filter(Q(public=True) | Q(owner=user))
327 image_filter[image.id] = {}
328 image_filter[image.id]['lab'] = 'lab_' + str(image.from_lab.lab_user.id)
329 image_filter[image.id]['host_profile'] = 'host_' + str(image.host_type.id)
330 image_filter[image.id]['name'] = image.name
332 return {'installer_filter': json.dumps(installer_filter),
333 'scenario_filter': json.dumps(scenario_filter),
334 'image_filter': json.dumps(image_filter)}