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 notifier.manager import NotificationHandler
37 from booking.models import Booking
38 from dashboard.exceptions import (
39 InvalidHostnameException,
40 ResourceAvailabilityException,
41 ModelValidationException
43 from api.models import JobFactory
46 # model validity exceptions
47 class IncompatibleInstallerForOS(Exception):
51 class IncompatibleScenarioForInstaller(Exception):
55 class IncompatibleImageForHost(Exception):
59 class ImageOwnershipInvalid(Exception):
63 class ImageNotAvailableAtLab(Exception):
67 class LabDNE(Exception):
71 class HostProfileDNE(Exception):
75 class HostNotAvailable(Exception):
79 class NoLabSelectedError(Exception):
83 class OPNFVRoleDNE(Exception):
87 class NoRemainingPublicNetwork(Exception):
91 def create_from_form(form, request):
92 quick_booking_id = str(uuid.uuid4())
94 host_field = form.cleaned_data['filter_field']
95 host_json = json.loads(host_field)
96 purpose_field = form.cleaned_data['purpose']
97 project_field = form.cleaned_data['project']
98 users_field = form.cleaned_data['users']
99 host_name = form.cleaned_data['hostname']
100 length = form.cleaned_data['length']
102 image = form.cleaned_data['image']
103 scenario = form.cleaned_data['scenario']
104 installer = form.cleaned_data['installer']
106 # get all initial info we need to validate
107 lab_dict = host_json['labs'][0]
108 lab_id = list(lab_dict.keys())[0]
109 lab_user_id = int(lab_id.split("_")[-1])
110 lab = Lab.objects.get(lab_user__id=lab_user_id)
112 host_dict = host_json['hosts'][0]
113 profile_id = list(host_dict.keys())[0]
114 profile_id = int(profile_id.split("_")[-1])
115 profile = HostProfile.objects.get(id=profile_id)
117 # check validity of field data before trying to apply to models
119 raise LabDNE("Lab with provided ID does not exist")
121 raise HostProfileDNE("Host type with provided ID does not exist")
123 # check that hostname is valid
124 if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})$", host_name):
125 raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
126 # check that image os is compatible with installer
127 if installer in image.os.sup_installers.all():
128 # if installer not here, we can omit that and not check for scenario
130 raise IncompatibleScenarioForInstaller("An OPNFV Installer needs a scenario to be chosen to work properly")
131 if scenario not in installer.sup_scenarios.all():
132 raise IncompatibleScenarioForInstaller("The chosen installer does not support the chosen scenario")
133 if image.from_lab != lab:
134 raise ImageNotAvailableAtLab("The chosen image is not available at the chosen hosting lab")
135 if image.host_type != profile:
136 raise IncompatibleImageForHost("The chosen image is not available for the chosen host type")
137 if not image.public and image.owner != request.user:
138 raise ImageOwnershipInvalid("You are not the owner of the chosen private image")
140 # check if host type is available
141 # ResourceManager.getInstance().acquireHost(ghost, lab.name)
142 available_host_types = ResourceManager.getInstance().getAvailableHostTypes(lab)
143 if profile not in available_host_types:
144 # TODO: handle deleting generic resource in this instance along with grb
145 raise HostNotAvailable("Could not book selected host due to changed availability. Try again later")
147 # check if any hosts with profile at lab are still available
148 hostset = Host.objects.filter(lab=lab, profile=profile).filter(booked=False).filter(working=True)
149 if not hostset.first():
150 raise HostNotAvailable("Couldn't find any matching unbooked hosts")
152 # generate GenericResourceBundle
153 if len(host_json['labs']) != 1:
154 raise NoLabSelectedError("No lab was selected")
156 grbundle = GenericResourceBundle(owner=request.user)
158 grbundle.name = "grbundle for quick booking with uid " + quick_booking_id
159 grbundle.description = "grbundle created for quick-deploy booking"
162 # generate GenericResource, GenericHost
163 gresource = GenericResource(bundle=grbundle, name=host_name)
166 ghost = GenericHost()
167 ghost.resource = gresource
168 ghost.profile = profile
171 # generate config bundle
172 cbundle = ConfigBundle()
173 cbundle.owner = request.user
174 cbundle.name = "configbundle for quick booking with uid " + quick_booking_id
175 cbundle.description = "configbundle created for quick-deploy booking"
176 cbundle.bundle = grbundle
179 # generate OPNFVConfig pointing to cbundle
181 opnfvconfig = OPNFVConfig()
182 opnfvconfig.scenario = scenario
183 opnfvconfig.installer = installer
184 opnfvconfig.bundle = cbundle
187 # generate HostConfiguration pointing to cbundle
188 hconf = HostConfiguration()
191 hconf.opnfvRole = OPNFVRole.objects.get(name="Jumphost")
192 if not hconf.opnfvRole:
193 raise OPNFVRoleDNE("No jumphost role was found")
194 hconf.bundle = cbundle
197 # construct generic interfaces
198 for interface_profile in profile.interfaceprofile.all():
199 generic_interface = GenericInterface.objects.create(profile=interface_profile, host=ghost)
200 generic_interface.save()
203 # get vlan, assign to first interface
204 publicnetwork = lab.vlan_manager.get_public_vlan()
205 publicvlan = publicnetwork.vlan
206 if not publicnetwork:
207 raise NoRemainingPublicNetwork("No public networks were available for your pod")
208 lab.vlan_manager.reserve_public_vlan(publicvlan)
210 vlan = Vlan.objects.create(vlan_id=publicvlan, tagged=False, public=True)
212 ghost.generic_interfaces.first().vlans.add(vlan)
213 ghost.generic_interfaces.first().save()
215 # generate resource bundle
217 resource_bundle = ResourceManager.getInstance().convertResourceBundle(grbundle, config=cbundle)
218 except ResourceAvailabilityException:
219 raise ResourceAvailabilityException("Requested resources not available")
220 except ModelValidationException:
221 raise ModelValidationException("Encountered error while saving grbundle")
225 booking.purpose = purpose_field
226 booking.project = project_field
228 booking.owner = request.user
229 booking.start = timezone.now()
230 booking.end = timezone.now() + timedelta(days=int(length))
231 booking.resource = resource_bundle
232 booking.pdf = ResourceManager().makePDF(booking.resource)
233 booking.config_bundle = cbundle
235 users_field = users_field[2:-2]
236 if users_field: # may be empty after split, if no collaborators entered
237 users_field = json.loads(users_field)
238 for collaborator in users_field:
239 user = User.objects.get(id=collaborator['id'])
240 booking.collaborators.add(user)
244 JobFactory.makeCompleteJob(booking)
245 NotificationHandler.notify_new_booking(booking)
248 def drop_filter(user):
249 installer_filter = {}
250 for image in Image.objects.all():
251 installer_filter[image.id] = {}
252 for installer in image.os.sup_installers.all():
253 installer_filter[image.id][installer.id] = 1
256 for installer in Installer.objects.all():
257 scenario_filter[installer.id] = {}
258 for scenario in installer.sup_scenarios.all():
259 scenario_filter[installer.id][scenario.id] = 1
261 images = Image.objects.filter(Q(public=True) | Q(owner=user))
264 image_filter[image.id] = {}
265 image_filter[image.id]['lab'] = 'lab_' + str(image.from_lab.lab_user.id)
266 image_filter[image.id]['host_profile'] = 'host_' + str(image.host_type.id)
267 image_filter[image.id]['name'] = image.name
269 return {'installer_filter': json.dumps(installer_filter),
270 'scenario_filter': json.dumps(scenario_filter),
271 'image_filter': json.dumps(image_filter)}