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('Requested host type is not available. Please try again later. Host availability can be viewed in the "Hosts" tab to the left.')
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)}