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,
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 def parse_host_field(host_field_contents):
96 host_json = json.loads(host_field_contents)
97 lab_dict = host_json['labs'][0]
98 lab_id = list(lab_dict.keys())[0]
99 lab_user_id = int(lab_id.split("_")[-1])
100 lab = Lab.objects.get(lab_user__id=lab_user_id)
102 host_dict = host_json['hosts'][0]
103 profile_id = list(host_dict.keys())[0]
104 profile_id = int(profile_id.split("_")[-1])
105 profile = HostProfile.objects.get(id=profile_id)
107 # check validity of field data before trying to apply to models
108 if len(host_json['labs']) != 1:
109 raise NoLabSelectedError("No lab was selected")
111 raise LabDNE("Lab with provided ID does not exist")
113 raise HostProfileDNE("Host type with provided ID does not exist")
118 def check_available_matching_host(lab, hostprofile):
119 available_host_types = ResourceManager.getInstance().getAvailableHostTypes(lab)
120 if hostprofile not in available_host_types:
121 # TODO: handle deleting generic resource in this instance along with grb
122 raise HostNotAvailable("Could not book selected host due to changed availability. Try again later")
124 hostset = Host.objects.filter(lab=lab, profile=hostprofile).filter(booked=False).filter(working=True)
125 if not hostset.exists():
126 raise HostNotAvailable("Couldn't find any matching unbooked hosts")
131 def generate_grb(owner, lab, common_id):
132 grbundle = GenericResourceBundle(owner=owner)
134 grbundle.name = "grbundle for quick booking with uid " + common_id
135 grbundle.description = "grbundle created for quick-deploy booking"
141 def generate_gresource(bundle, hostname):
142 if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})$", hostname):
143 raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
144 gresource = GenericResource(bundle=bundle, name=hostname)
150 def generate_ghost(generic_resource, host_profile):
151 ghost = GenericHost()
152 ghost.resource = generic_resource
153 ghost.profile = host_profile
159 def generate_config_bundle(owner, common_id, grbundle):
160 cbundle = ConfigBundle()
161 cbundle.owner = owner
162 cbundle.name = "configbundle for quick booking with uid " + common_id
163 cbundle.description = "configbundle created for quick-deploy booking"
164 cbundle.bundle = grbundle
170 def generate_opnfvconfig(scenario, installer, config_bundle):
171 opnfvconfig = OPNFVConfig()
172 opnfvconfig.scenario = scenario
173 opnfvconfig.installer = installer
174 opnfvconfig.bundle = config_bundle
180 def generate_hostconfig(generic_host, image, config_bundle):
181 hconf = HostConfiguration()
182 hconf.host = generic_host
185 opnfvrole = OPNFVRole.objects.get(name="Jumphost")
187 raise OPNFVRoleDNE("No jumphost role was found.")
189 hconf.opnfvRole = opnfvrole
190 hconf.bundle = config_bundle
196 def generate_resource_bundle(generic_resource_bundle, config_bundle): # warning: requires cleanup
198 resource_manager = ResourceManager.getInstance()
199 resource_bundle = resource_manager.convertResourceBundle(generic_resource_bundle, config=config_bundle)
200 return resource_bundle
201 except ResourceAvailabilityException:
202 raise ResourceAvailabilityException("Requested resources not available")
203 except ModelValidationException:
204 raise ModelValidationException("Encountered error while saving grbundle")
207 def check_invariants(request, **kwargs):
208 installer = kwargs['installer']
209 image = kwargs['image']
210 scenario = kwargs['scenario']
212 host_profile = kwargs['host_profile']
213 length = kwargs['length']
214 # check that image os is compatible with installer
215 if installer in image.os.sup_installers.all():
216 # if installer not here, we can omit that and not check for scenario
218 raise IncompatibleScenarioForInstaller("An OPNFV Installer needs a scenario to be chosen to work properly")
219 if scenario not in installer.sup_scenarios.all():
220 raise IncompatibleScenarioForInstaller("The chosen installer does not support the chosen scenario")
221 if image.from_lab != lab:
222 raise ImageNotAvailableAtLab("The chosen image is not available at the chosen hosting lab")
223 if image.host_type != host_profile:
224 raise IncompatibleImageForHost("The chosen image is not available for the chosen host type")
225 if not image.public and image.owner != request.user:
226 raise ImageOwnershipInvalid("You are not the owner of the chosen private image")
227 if length < 1 or length > 21:
228 raise BookingLengthException("Booking must be between 1 and 21 days long")
231 def configure_networking(grb, config):
233 net = Network.objects.create(name="public", bundle=grb, is_public=True)
234 # connect network to generic host
235 grb.getHosts()[0].generic_interfaces.first().connections.add(
236 NetworkConnection.objects.create(network=net, vlan_is_tagged=False)
239 role = NetworkRole.objects.create(name="public", network=net)
240 opnfv_config = config.opnfv_config.first()
242 opnfv_config.networks.add(role)
245 def create_from_form(form, request):
246 quick_booking_id = str(uuid.uuid4())
248 host_field = form.cleaned_data['filter_field']
249 purpose_field = form.cleaned_data['purpose']
250 project_field = form.cleaned_data['project']
251 users_field = form.cleaned_data['users']
252 hostname = form.cleaned_data['hostname']
253 length = form.cleaned_data['length']
255 image = form.cleaned_data['image']
256 scenario = form.cleaned_data['scenario']
257 installer = form.cleaned_data['installer']
259 lab, host_profile = parse_host_field(host_field)
260 data = form.cleaned_data
262 data['host_profile'] = host_profile
263 check_invariants(request, **data)
265 check_available_matching_host(lab, host_profile) # requires cleanup if failure after this point
267 grbundle = generate_grb(request.user, lab, quick_booking_id)
269 gresource = generate_gresource(grbundle, hostname)
271 ghost = generate_ghost(gresource, host_profile)
273 cbundle = generate_config_bundle(request.user, quick_booking_id, grbundle)
275 # if no installer provided, just create blank host
277 generate_opnfvconfig(scenario, installer, cbundle)
279 generate_hostconfig(ghost, image, cbundle)
281 # construct generic interfaces
282 for interface_profile in host_profile.interfaceprofile.all():
283 generic_interface = GenericInterface.objects.create(profile=interface_profile, host=ghost)
284 generic_interface.save()
286 configure_networking(grbundle, cbundle)
288 # generate resource bundle
289 resource_bundle = generate_resource_bundle(grbundle, cbundle)
293 booking.purpose = purpose_field
294 booking.project = project_field
296 booking.owner = request.user
297 booking.start = timezone.now()
298 booking.end = timezone.now() + timedelta(days=int(length))
299 booking.resource = resource_bundle
300 booking.pdf = PDFTemplater.makePDF(booking.resource)
301 booking.config_bundle = cbundle
303 users_field = users_field[2:-2]
304 if users_field: # may be empty after split, if no collaborators entered
305 users_field = json.loads(users_field)
306 for collaborator in users_field:
307 user = User.objects.get(id=collaborator['id'])
308 booking.collaborators.add(user)
312 JobFactory.makeCompleteJob(booking)
313 NotificationHandler.notify_new_booking(booking)
316 def drop_filter(user):
317 installer_filter = {}
318 for image in Image.objects.all():
319 installer_filter[image.id] = {}
320 for installer in image.os.sup_installers.all():
321 installer_filter[image.id][installer.id] = 1
324 for installer in Installer.objects.all():
325 scenario_filter[installer.id] = {}
326 for scenario in installer.sup_scenarios.all():
327 scenario_filter[installer.id][scenario.id] = 1
329 images = Image.objects.filter(Q(public=True) | Q(owner=user))
332 image_filter[image.id] = {}
333 image_filter[image.id]['lab'] = 'lab_' + str(image.from_lab.lab_user.id)
334 image_filter[image.id]['host_profile'] = 'host_' + str(image.host_type.id)
335 image_filter[image.id]['name'] = image.name
337 return {'installer_filter': json.dumps(installer_filter),
338 'scenario_filter': json.dumps(scenario_filter),
339 'image_filter': json.dumps(image_filter)}