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 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
189 opnfvrole = OPNFVRole.objects.get(name="Jumphost")
191 raise OPNFVRoleDNE("No jumphost role was found.")
193 hconf.opnfvRole = opnfvrole
194 hconf.bundle = config_bundle
200 def generate_resource_bundle(generic_resource_bundle, config_bundle): # warning: requires cleanup
202 resource_manager = ResourceManager.getInstance()
203 resource_bundle = resource_manager.convertResourceBundle(generic_resource_bundle, config=config_bundle)
204 return resource_bundle
205 except ResourceAvailabilityException:
206 raise ResourceAvailabilityException("Requested resources not available")
207 except ModelValidationException:
208 raise ModelValidationException("Encountered error while saving grbundle")
211 def check_invariants(request, **kwargs):
212 installer = kwargs['installer']
213 image = kwargs['image']
214 scenario = kwargs['scenario']
216 host_profile = kwargs['host_profile']
217 length = kwargs['length']
218 # check that image os is compatible with installer
219 if installer in image.os.sup_installers.all():
220 # if installer not here, we can omit that and not check for scenario
222 raise IncompatibleScenarioForInstaller("An OPNFV Installer needs a scenario to be chosen to work properly")
223 if scenario not in installer.sup_scenarios.all():
224 raise IncompatibleScenarioForInstaller("The chosen installer does not support the chosen scenario")
225 if image.from_lab != lab:
226 raise ImageNotAvailableAtLab("The chosen image is not available at the chosen hosting lab")
227 if image.host_type != host_profile:
228 raise IncompatibleImageForHost("The chosen image is not available for the chosen host type")
229 if not image.public and image.owner != request.user:
230 raise ImageOwnershipInvalid("You are not the owner of the chosen private image")
231 if length < 1 or length > 21:
232 raise BookingLengthException("Booking must be between 1 and 21 days long")
235 def configure_networking(grb, config):
237 net = Network.objects.create(name="public", bundle=grb, is_public=True)
238 # connect network to generic host
239 grb.getHosts()[0].generic_interfaces.first().connections.add(
240 NetworkConnection.objects.create(network=net, vlan_is_tagged=False)
243 role = NetworkRole.objects.create(name="public", network=net)
244 opnfv_config = config.opnfv_config.first()
246 opnfv_config.networks.add(role)
249 def create_from_form(form, request):
250 quick_booking_id = str(uuid.uuid4())
252 host_field = form.cleaned_data['filter_field']
253 purpose_field = form.cleaned_data['purpose']
254 project_field = form.cleaned_data['project']
255 users_field = form.cleaned_data['users']
256 hostname = form.cleaned_data['hostname']
257 length = form.cleaned_data['length']
259 image = form.cleaned_data['image']
260 scenario = form.cleaned_data['scenario']
261 installer = form.cleaned_data['installer']
263 lab, host_profile = parse_host_field(host_field)
264 data = form.cleaned_data
266 data['host_profile'] = host_profile
267 check_invariants(request, **data)
269 # check booking privileges
270 if Booking.objects.filter(owner=request.user, end__gt=timezone.now()).count() >= 3 and not request.user.userprofile.booking_privledge:
271 raise BookingPermissionException("You do not have permission to have more than 3 bookings at a time.")
273 check_available_matching_host(lab, host_profile) # requires cleanup if failure after this point
275 grbundle = generate_grb(request.user, lab, quick_booking_id)
277 gresource = generate_gresource(grbundle, hostname)
279 ghost = generate_ghost(gresource, host_profile)
281 cbundle = generate_config_bundle(request.user, quick_booking_id, grbundle)
283 # if no installer provided, just create blank host
285 generate_opnfvconfig(scenario, installer, cbundle)
287 generate_hostconfig(ghost, image, cbundle)
289 # construct generic interfaces
290 for interface_profile in host_profile.interfaceprofile.all():
291 generic_interface = GenericInterface.objects.create(profile=interface_profile, host=ghost)
292 generic_interface.save()
294 configure_networking(grbundle, cbundle)
296 # generate resource bundle
297 resource_bundle = generate_resource_bundle(grbundle, cbundle)
301 booking.purpose = purpose_field
302 booking.project = project_field
304 booking.owner = request.user
305 booking.start = timezone.now()
306 booking.end = timezone.now() + timedelta(days=int(length))
307 booking.resource = resource_bundle
308 booking.pdf = PDFTemplater.makePDF(booking.resource)
309 booking.config_bundle = cbundle
311 users_field = users_field[2:-2]
312 if users_field: # may be empty after split, if no collaborators entered
313 users_field = json.loads(users_field)
314 for collaborator in users_field:
315 user = User.objects.get(id=collaborator['id'])
316 booking.collaborators.add(user)
320 JobFactory.makeCompleteJob(booking)
321 NotificationHandler.notify_new_booking(booking)
324 def drop_filter(user):
325 installer_filter = {}
326 for image in Image.objects.all():
327 installer_filter[image.id] = {}
328 for installer in image.os.sup_installers.all():
329 installer_filter[image.id][installer.id] = 1
332 for installer in Installer.objects.all():
333 scenario_filter[installer.id] = {}
334 for scenario in installer.sup_scenarios.all():
335 scenario_filter[installer.id][scenario.id] = 1
337 images = Image.objects.filter(Q(public=True) | Q(owner=user))
340 image_filter[image.id] = {}
341 image_filter[image.id]['lab'] = 'lab_' + str(image.from_lab.lab_user.id)
342 image_filter[image.id]['host_profile'] = 'host_' + str(image.host_type.id)
343 image_filter[image.id]['name'] = image.name
345 return {'installer_filter': json.dumps(installer_filter),
346 'scenario_filter': json.dumps(scenario_filter),
347 'image_filter': json.dumps(image_filter)}