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
44 from api.models import JobFactory
47 # model validity exceptions
48 class IncompatibleInstallerForOS(Exception):
52 class IncompatibleScenarioForInstaller(Exception):
56 class IncompatibleImageForHost(Exception):
60 class ImageOwnershipInvalid(Exception):
64 class ImageNotAvailableAtLab(Exception):
68 class LabDNE(Exception):
72 class HostProfileDNE(Exception):
76 class HostNotAvailable(Exception):
80 class NoLabSelectedError(Exception):
84 class OPNFVRoleDNE(Exception):
88 class NoRemainingPublicNetwork(Exception):
92 def create_from_form(form, request):
93 quick_booking_id = str(uuid.uuid4())
95 host_field = form.cleaned_data['filter_field']
96 host_json = json.loads(host_field)
97 purpose_field = form.cleaned_data['purpose']
98 project_field = form.cleaned_data['project']
99 users_field = form.cleaned_data['users']
100 host_name = form.cleaned_data['hostname']
101 length = form.cleaned_data['length']
103 image = form.cleaned_data['image']
104 scenario = form.cleaned_data['scenario']
105 installer = form.cleaned_data['installer']
107 # get all initial info we need to validate
108 lab_dict = host_json['labs'][0]
109 lab_id = list(lab_dict.keys())[0]
110 lab_user_id = int(lab_id.split("_")[-1])
111 lab = Lab.objects.get(lab_user__id=lab_user_id)
113 host_dict = host_json['hosts'][0]
114 profile_id = list(host_dict.keys())[0]
115 profile_id = int(profile_id.split("_")[-1])
116 profile = HostProfile.objects.get(id=profile_id)
118 # check validity of field data before trying to apply to models
120 raise LabDNE("Lab with provided ID does not exist")
122 raise HostProfileDNE("Host type with provided ID does not exist")
124 # check that hostname is valid
125 if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})$", host_name):
126 raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
127 # check that image os is compatible with installer
128 if installer in image.os.sup_installers.all():
129 # if installer not here, we can omit that and not check for scenario
131 raise IncompatibleScenarioForInstaller("An OPNFV Installer needs a scenario to be chosen to work properly")
132 if scenario not in installer.sup_scenarios.all():
133 raise IncompatibleScenarioForInstaller("The chosen installer does not support the chosen scenario")
134 if image.from_lab != lab:
135 raise ImageNotAvailableAtLab("The chosen image is not available at the chosen hosting lab")
136 if image.host_type != profile:
137 raise IncompatibleImageForHost("The chosen image is not available for the chosen host type")
138 if not image.public and image.owner != request.user:
139 raise ImageOwnershipInvalid("You are not the owner of the chosen private image")
141 # check if host type is available
142 # ResourceManager.getInstance().acquireHost(ghost, lab.name)
143 available_host_types = ResourceManager.getInstance().getAvailableHostTypes(lab)
144 if profile not in available_host_types:
145 # TODO: handle deleting generic resource in this instance along with grb
146 raise HostNotAvailable("Could not book selected host due to changed availability. Try again later")
148 # check if any hosts with profile at lab are still available
149 hostset = Host.objects.filter(lab=lab, profile=profile).filter(booked=False).filter(working=True)
150 if not hostset.first():
151 raise HostNotAvailable("Couldn't find any matching unbooked hosts")
153 # generate GenericResourceBundle
154 if len(host_json['labs']) != 1:
155 raise NoLabSelectedError("No lab was selected")
157 grbundle = GenericResourceBundle(owner=request.user)
159 grbundle.name = "grbundle for quick booking with uid " + quick_booking_id
160 grbundle.description = "grbundle created for quick-deploy booking"
163 # generate GenericResource, GenericHost
164 gresource = GenericResource(bundle=grbundle, name=host_name)
167 ghost = GenericHost()
168 ghost.resource = gresource
169 ghost.profile = profile
172 # generate config bundle
173 cbundle = ConfigBundle()
174 cbundle.owner = request.user
175 cbundle.name = "configbundle for quick booking with uid " + quick_booking_id
176 cbundle.description = "configbundle created for quick-deploy booking"
177 cbundle.bundle = grbundle
180 # generate OPNFVConfig pointing to cbundle
182 opnfvconfig = OPNFVConfig()
183 opnfvconfig.scenario = scenario
184 opnfvconfig.installer = installer
185 opnfvconfig.bundle = cbundle
188 # generate HostConfiguration pointing to cbundle
189 hconf = HostConfiguration()
192 hconf.opnfvRole = OPNFVRole.objects.get(name="Jumphost")
193 if not hconf.opnfvRole:
194 raise OPNFVRoleDNE("No jumphost role was found")
195 hconf.bundle = cbundle
198 # construct generic interfaces
199 for interface_profile in profile.interfaceprofile.all():
200 generic_interface = GenericInterface.objects.create(profile=interface_profile, host=ghost)
201 generic_interface.save()
204 # get vlan, assign to first interface
205 publicnetwork = lab.vlan_manager.get_public_vlan()
206 publicvlan = publicnetwork.vlan
207 if not publicnetwork:
208 raise NoRemainingPublicNetwork("No public networks were available for your pod")
209 lab.vlan_manager.reserve_public_vlan(publicvlan)
211 vlan = Vlan.objects.create(vlan_id=publicvlan, tagged=False, public=True)
213 ghost.generic_interfaces.first().vlans.add(vlan)
214 ghost.generic_interfaces.first().save()
216 # generate resource bundle
218 resource_bundle = ResourceManager.getInstance().convertResourceBundle(grbundle, config=cbundle)
219 except ResourceAvailabilityException:
220 raise ResourceAvailabilityException("Requested resources not available")
221 except ModelValidationException:
222 raise ModelValidationException("Encountered error while saving grbundle")
226 booking.purpose = purpose_field
227 booking.project = project_field
229 booking.owner = request.user
230 booking.start = timezone.now()
231 booking.end = timezone.now() + timedelta(days=int(length))
232 booking.resource = resource_bundle
233 booking.pdf = PDFTemplater.makePDF(booking.resource)
234 booking.config_bundle = cbundle
236 users_field = users_field[2:-2]
237 if users_field: # may be empty after split, if no collaborators entered
238 users_field = json.loads(users_field)
239 for collaborator in users_field:
240 user = User.objects.get(id=collaborator['id'])
241 booking.collaborators.add(user)
245 JobFactory.makeCompleteJob(booking)
246 NotificationHandler.notify_new_booking(booking)
249 def drop_filter(user):
250 installer_filter = {}
251 for image in Image.objects.all():
252 installer_filter[image.id] = {}
253 for installer in image.os.sup_installers.all():
254 installer_filter[image.id][installer.id] = 1
257 for installer in Installer.objects.all():
258 scenario_filter[installer.id] = {}
259 for scenario in installer.sup_scenarios.all():
260 scenario_filter[installer.id][scenario.id] = 1
262 images = Image.objects.filter(Q(public=True) | Q(owner=user))
265 image_filter[image.id] = {}
266 image_filter[image.id]['lab'] = 'lab_' + str(image.from_lab.lab_user.id)
267 image_filter[image.id]['host_profile'] = 'host_' + str(image.host_type.id)
268 image_filter[image.id]['name'] = image.name
270 return {'installer_filter': json.dumps(installer_filter),
271 'scenario_filter': json.dumps(scenario_filter),
272 'image_filter': json.dumps(image_filter)}