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 ##############################################################################
12 from django.db.models import Q
13 from datetime import timedelta
14 from django.utils import timezone
15 from django.core.exceptions import ValidationError
16 from account.models import Lab
18 from resource_inventory.models import (
25 ResourceConfiguration,
27 InterfaceConfiguration,
30 from resource_inventory.resource_manager import ResourceManager
31 from resource_inventory.pdf_templater import PDFTemplater
32 from notifier.manager import NotificationHandler
33 from booking.models import Booking
34 from dashboard.exceptions import BookingLengthException
35 from api.models import JobFactory
38 def parse_resource_field(resource_json):
40 Parse the json from the frontend.
42 returns a reference to the selected Lab and ResourceTemplate objects
44 lab, template = (None, None)
45 lab_dict = resource_json['lab']
46 for lab_info in lab_dict.values():
47 if lab_info['selected']:
48 lab = Lab.objects.get(lab_user__id=lab_info['id'])
50 resource_dict = resource_json['resource']
51 for resource_info in resource_dict.values():
52 if resource_info['selected']:
53 template = ResourceTemplate.objects.get(pk=resource_info['id'])
56 raise ValidationError("No lab was selected")
58 raise ValidationError("No Host was selected")
63 def update_template(old_template, image, hostname, user):
65 Duplicate a template to the users account and update configured fields.
67 The dashboard presents users with preconfigured resource templates,
68 but the user might want to make small modifications, e.g hostname and
69 linux distro. So we copy the public template and create a private version
70 to the user's profile, and mark it temporary. When the booking ends the
71 new template is deleted
73 name = user.username + "'s Copy of '" + old_template.name + "'"
74 num_copies = ResourceTemplate.objects.filter(name__startswith=name).count()
75 template = ResourceTemplate.objects.create(
76 name=name if num_copies == 0 else name + " (" + str(num_copies) + ")",
80 description=old_template.description,
85 for old_network in old_template.networks.all():
86 Network.objects.create(
87 name=old_network.name,
91 # We are assuming there is only one opnfv config per public resource template
92 old_opnfv = template.opnfv_config.first()
94 opnfv_config = OPNFVConfig.objects.create(
95 installer=old_opnfv.installer,
96 scenario=old_opnfv.installer,
98 name=old_opnfv.installer,
100 # I am explicitly leaving opnfv_config.networks empty to avoid
101 # problems with duplicated / shared networks. In the quick deploy,
102 # there is never multiple networks anyway. This may have to change in the future
104 for old_config in old_template.getConfigs():
105 config = ResourceConfiguration.objects.create(
106 profile=old_config.profile,
111 for old_iface_config in old_config.interface_configs.all():
112 iface_config = InterfaceConfiguration.objects.create(
113 profile=old_iface_config.profile,
114 resource_config=config
117 for old_connection in old_iface_config.connections.all():
118 iface_config.connections.add(NetworkConnection.objects.create(
119 network=template.networks.get(name=old_connection.network.name),
120 vlan_is_tagged=old_connection.vlan_is_tagged
123 for old_res_opnfv in old_config.resource_opnfv_config.all():
125 ResourceOPNFVConfig.objects.create(
127 resource_config=config,
128 opnfv_config=opnfv_config
132 def generate_opnfvconfig(scenario, installer, template):
133 return OPNFVConfig.objects.create(
140 def generate_hostopnfv(hostconfig, opnfvconfig):
143 role = OPNFVRole.objects.get(name="Jumphost")
145 role = OPNFVRole.objects.create(
147 description="Single server jumphost role"
149 return ResourceOPNFVConfig.objects.create(
151 host_config=hostconfig,
152 opnfv_config=opnfvconfig
156 def generate_resource_bundle(template):
157 resource_manager = ResourceManager.getInstance()
158 resource_bundle = resource_manager.instantiateTemplate(template)
159 return resource_bundle
162 def check_invariants(request, **kwargs):
163 # TODO: This should really happen in the BookingForm validation methods
164 installer = kwargs['installer']
165 image = kwargs['image']
166 scenario = kwargs['scenario']
168 resource_template = kwargs['resource_template']
169 length = kwargs['length']
170 # check that image os is compatible with installer
171 if installer in image.os.sup_installers.all():
172 # if installer not here, we can omit that and not check for scenario
174 raise ValidationError("An OPNFV Installer needs a scenario to be chosen to work properly")
175 if scenario not in installer.sup_scenarios.all():
176 raise ValidationError("The chosen installer does not support the chosen scenario")
177 if image.from_lab != lab:
178 raise ValidationError("The chosen image is not available at the chosen hosting lab")
180 #if image.host_type != host_profile:
181 # raise ValidationError("The chosen image is not available for the chosen host type")
182 if not image.public and image.owner != request.user:
183 raise ValidationError("You are not the owner of the chosen private image")
184 if length < 1 or length > 21:
185 raise BookingLengthException("Booking must be between 1 and 21 days long")
188 def create_from_form(form, request):
190 Create a Booking from the user's form.
192 Large, nasty method to create a booking or return a useful error
193 based on the form from the frontend
195 resource_field = form.cleaned_data['filter_field']
196 purpose_field = form.cleaned_data['purpose']
197 project_field = form.cleaned_data['project']
198 users_field = form.cleaned_data['users']
199 hostname = form.cleaned_data['hostname']
200 length = form.cleaned_data['length']
202 image = form.cleaned_data['image']
203 scenario = form.cleaned_data['scenario']
204 installer = form.cleaned_data['installer']
206 lab, resource_template = parse_resource_field(resource_field)
207 data = form.cleaned_data
209 data['resource_template'] = resource_template
210 check_invariants(request, **data)
212 # check booking privileges
213 # TODO: use the canonical booking_allowed method because now template might have multiple
215 if Booking.objects.filter(owner=request.user, end__gt=timezone.now()).count() >= 3 and not request.user.userprofile.booking_privledge:
216 raise PermissionError("You do not have permission to have more than 3 bookings at a time.")
218 ResourceManager.getInstance().templateIsReservable(resource_template)
220 hconf = update_template(resource_template, image, hostname, request.user)
222 # if no installer provided, just create blank host
225 opnfv_config = generate_opnfvconfig(scenario, installer, resource_template)
226 generate_hostopnfv(hconf, opnfv_config)
228 # generate resource bundle
229 resource_bundle = generate_resource_bundle(resource_template)
232 booking = Booking.objects.create(
233 purpose=purpose_field,
234 project=project_field,
237 start=timezone.now(),
238 end=timezone.now() + timedelta(days=int(length)),
239 resource=resource_bundle,
240 opnfv_config=opnfv_config
242 booking.pdf = PDFTemplater.makePDF(booking)
244 for collaborator in users_field: # list of UserProfiles
245 booking.collaborators.add(collaborator.user)
250 JobFactory.makeCompleteJob(booking)
251 NotificationHandler.notify_new_booking(booking)
256 def drop_filter(user):
258 Return a dictionary that contains filters.
260 Only certain installlers are supported on certain images, etc
261 so the image filter indexed at [imageid][installerid] is truthy if
262 that installer is supported on that image
264 installer_filter = {}
265 for image in Image.objects.all():
266 installer_filter[image.id] = {}
267 for installer in image.os.sup_installers.all():
268 installer_filter[image.id][installer.id] = 1
271 for installer in Installer.objects.all():
272 scenario_filter[installer.id] = {}
273 for scenario in installer.sup_scenarios.all():
274 scenario_filter[installer.id][scenario.id] = 1
276 images = Image.objects.filter(Q(public=True) | Q(owner=user))
279 image_filter[image.id] = {
280 'lab': 'lab_' + str(image.from_lab.lab_user.id),
281 'host_profile': str(image.host_type.id),
286 templates = ResourceTemplate.objects.filter(Q(public=True) | Q(owner=user))
288 profiles = [conf.profile for conf in rt.getConfigs()]
289 resource_filter["resource_" + str(rt.id)] = [str(p.id) for p in profiles]
292 'installer_filter': json.dumps(installer_filter),
293 'scenario_filter': json.dumps(scenario_filter),
294 'image_filter': json.dumps(image_filter),
295 'resource_profile_map': json.dumps(resource_filter),