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 class BookingPermissionException(Exception):
97 def parse_host_field(host_field_contents):
98 host_json = json.loads(host_field_contents)
99 lab_dict = host_json['labs'][0]
100 lab_id = list(lab_dict.keys())[0]
101 lab_user_id = int(lab_id.split("_")[-1])
102 lab = Lab.objects.get(lab_user__id=lab_user_id)
104 host_dict = host_json['hosts'][0]
105 profile_id = list(host_dict.keys())[0]
106 profile_id = int(profile_id.split("_")[-1])
107 profile = HostProfile.objects.get(id=profile_id)
109 # check validity of field data before trying to apply to models
110 if len(host_json['labs']) != 1:
111 raise NoLabSelectedError("No lab was selected")
113 raise LabDNE("Lab with provided ID does not exist")
115 raise HostProfileDNE("Host type with provided ID does not exist")
120 def check_available_matching_host(lab, hostprofile):
121 available_host_types = ResourceManager.getInstance().getAvailableHostTypes(lab)
122 if hostprofile not in available_host_types:
123 # TODO: handle deleting generic resource in this instance along with grb
124 raise HostNotAvailable('Requested host type is not available. Please try again later. Host availability can be viewed in the "Hosts" tab to the left.')
126 hostset = Host.objects.filter(lab=lab, profile=hostprofile).filter(booked=False).filter(working=True)
127 if not hostset.exists():
128 raise HostNotAvailable("Couldn't find any matching unbooked hosts")
133 def generate_grb(owner, lab, common_id):
134 grbundle = GenericResourceBundle(owner=owner)
136 grbundle.name = "grbundle for quick booking with uid " + common_id
137 grbundle.description = "grbundle created for quick-deploy booking"
143 def generate_gresource(bundle, hostname):
144 if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})$", hostname):
145 raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
146 gresource = GenericResource(bundle=bundle, name=hostname)
152 def generate_ghost(generic_resource, host_profile):
153 ghost = GenericHost()
154 ghost.resource = generic_resource
155 ghost.profile = host_profile
161 def generate_config_bundle(owner, common_id, grbundle):
162 cbundle = ConfigBundle()
163 cbundle.owner = owner
164 cbundle.name = "configbundle for quick booking with uid " + common_id
165 cbundle.description = "configbundle created for quick-deploy booking"
166 cbundle.bundle = grbundle
172 def generate_opnfvconfig(scenario, installer, config_bundle):
173 opnfvconfig = OPNFVConfig()
174 opnfvconfig.scenario = scenario
175 opnfvconfig.installer = installer
176 opnfvconfig.bundle = config_bundle
182 def generate_hostconfig(generic_host, image, config_bundle):
183 hconf = HostConfiguration()
184 hconf.host = generic_host
187 opnfvrole = OPNFVRole.objects.get(name="Jumphost")
189 raise OPNFVRoleDNE("No jumphost role was found.")
191 hconf.opnfvRole = opnfvrole
192 hconf.bundle = config_bundle
198 def generate_resource_bundle(generic_resource_bundle, config_bundle): # warning: requires cleanup
200 resource_manager = ResourceManager.getInstance()
201 resource_bundle = resource_manager.convertResourceBundle(generic_resource_bundle, config=config_bundle)
202 return resource_bundle
203 except ResourceAvailabilityException:
204 raise ResourceAvailabilityException("Requested resources not available")
205 except ModelValidationException:
206 raise ModelValidationException("Encountered error while saving grbundle")
209 def check_invariants(request, **kwargs):
210 installer = kwargs['installer']
211 image = kwargs['image']
212 scenario = kwargs['scenario']
214 host_profile = kwargs['host_profile']
215 length = kwargs['length']
216 # check that image os is compatible with installer
217 if installer in image.os.sup_installers.all():
218 # if installer not here, we can omit that and not check for scenario
220 raise IncompatibleScenarioForInstaller("An OPNFV Installer needs a scenario to be chosen to work properly")
221 if scenario not in installer.sup_scenarios.all():
222 raise IncompatibleScenarioForInstaller("The chosen installer does not support the chosen scenario")
223 if image.from_lab != lab:
224 raise ImageNotAvailableAtLab("The chosen image is not available at the chosen hosting lab")
225 if image.host_type != host_profile:
226 raise IncompatibleImageForHost("The chosen image is not available for the chosen host type")
227 if not image.public and image.owner != request.user:
228 raise ImageOwnershipInvalid("You are not the owner of the chosen private image")
229 if length < 1 or length > 21:
230 raise BookingLengthException("Booking must be between 1 and 21 days long")
233 def create_from_form(form, request):
234 quick_booking_id = str(uuid.uuid4())
236 host_field = form.cleaned_data['filter_field']
237 purpose_field = form.cleaned_data['purpose']
238 project_field = form.cleaned_data['project']
239 users_field = form.cleaned_data['users']
240 hostname = form.cleaned_data['hostname']
241 length = form.cleaned_data['length']
243 image = form.cleaned_data['image']
244 scenario = form.cleaned_data['scenario']
245 installer = form.cleaned_data['installer']
247 lab, host_profile = parse_host_field(host_field)
248 data = form.cleaned_data
250 data['host_profile'] = host_profile
251 check_invariants(request, **data)
253 # check booking privileges
254 if Booking.objects.filter(owner=request.user, end__gt=timezone.now()).count() >= 3 and not request.user.userprofile.booking_privledge:
255 raise BookingPermissionException("You do not have permission to have more than 3 bookings at a time.")
257 check_available_matching_host(lab, host_profile) # requires cleanup if failure after this point
259 grbundle = generate_grb(request.user, lab, quick_booking_id)
261 gresource = generate_gresource(grbundle, hostname)
263 ghost = generate_ghost(gresource, host_profile)
265 cbundle = generate_config_bundle(request.user, quick_booking_id, grbundle)
267 # if no installer provided, just create blank host
269 generate_opnfvconfig(scenario, installer, cbundle)
271 generate_hostconfig(ghost, image, cbundle)
273 # construct generic interfaces
274 for interface_profile in host_profile.interfaceprofile.all():
275 generic_interface = GenericInterface.objects.create(profile=interface_profile, host=ghost)
276 generic_interface.save()
278 # get vlan, assign to first interface
279 publicnetwork = lab.vlan_manager.get_public_vlan()
280 if not publicnetwork:
281 raise NoRemainingPublicNetwork("No public networks were available for your pod")
282 publicvlan = publicnetwork.vlan
283 lab.vlan_manager.reserve_public_vlan(publicvlan)
285 vlan = Vlan.objects.create(vlan_id=publicvlan, tagged=False, public=True)
288 ghost.generic_interfaces.first().vlans.add(vlan)
289 ghost.generic_interfaces.first().save()
291 # generate resource bundle
292 resource_bundle = generate_resource_bundle(grbundle, cbundle)
296 booking.purpose = purpose_field
297 booking.project = project_field
299 booking.owner = request.user
300 booking.start = timezone.now()
301 booking.end = timezone.now() + timedelta(days=int(length))
302 booking.resource = resource_bundle
303 booking.pdf = PDFTemplater.makePDF(booking.resource)
304 booking.config_bundle = cbundle
306 users_field = users_field[2:-2]
307 if users_field: # may be empty after split, if no collaborators entered
308 users_field = json.loads(users_field)
309 for collaborator in users_field:
310 user = User.objects.get(id=collaborator['id'])
311 booking.collaborators.add(user)
315 JobFactory.makeCompleteJob(booking)
316 NotificationHandler.notify_new_booking(booking)
319 def drop_filter(user):
320 installer_filter = {}
321 for image in Image.objects.all():
322 installer_filter[image.id] = {}
323 for installer in image.os.sup_installers.all():
324 installer_filter[image.id][installer.id] = 1
327 for installer in Installer.objects.all():
328 scenario_filter[installer.id] = {}
329 for scenario in installer.sup_scenarios.all():
330 scenario_filter[installer.id][scenario.id] = 1
332 images = Image.objects.filter(Q(public=True) | Q(owner=user))
335 image_filter[image.id] = {}
336 image_filter[image.id]['lab'] = 'lab_' + str(image.from_lab.lab_user.id)
337 image_filter[image.id]['host_profile'] = 'host_' + str(image.host_type.id)
338 image_filter[image.id]['name'] = image.name
340 return {'installer_filter': json.dumps(installer_filter),
341 'scenario_filter': json.dumps(scenario_filter),
342 'image_filter': json.dumps(image_filter)}