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,
86 for old_network in old_template.networks.all():
87 Network.objects.create(
88 name=old_network.name,
92 # We are assuming there is only one opnfv config per public resource template
93 old_opnfv = template.opnfv_config.first()
95 opnfv_config = OPNFVConfig.objects.create(
96 installer=old_opnfv.installer,
97 scenario=old_opnfv.installer,
99 name=old_opnfv.installer,
101 # I am explicitly leaving opnfv_config.networks empty to avoid
102 # problems with duplicated / shared networks. In the quick deploy,
103 # there is never multiple networks anyway. This may have to change in the future
105 for old_config in old_template.getConfigs():
106 config = ResourceConfiguration.objects.create(
107 profile=old_config.profile,
110 is_head_node=old_config.is_head_node
113 for old_iface_config in old_config.interface_configs.all():
114 iface_config = InterfaceConfiguration.objects.create(
115 profile=old_iface_config.profile,
116 resource_config=config
119 for old_connection in old_iface_config.connections.all():
120 iface_config.connections.add(NetworkConnection.objects.create(
121 network=template.networks.get(name=old_connection.network.name),
122 vlan_is_tagged=old_connection.vlan_is_tagged
125 for old_res_opnfv in old_config.resource_opnfv_config.all():
127 ResourceOPNFVConfig.objects.create(
129 resource_config=config,
130 opnfv_config=opnfv_config
135 def generate_opnfvconfig(scenario, installer, template):
136 return OPNFVConfig.objects.create(
143 def generate_hostopnfv(hostconfig, opnfvconfig):
146 role = OPNFVRole.objects.get(name="Jumphost")
148 role = OPNFVRole.objects.create(
150 description="Single server jumphost role"
152 return ResourceOPNFVConfig.objects.create(
154 host_config=hostconfig,
155 opnfv_config=opnfvconfig
159 def generate_resource_bundle(template):
160 resource_manager = ResourceManager.getInstance()
161 resource_bundle = resource_manager.instantiateTemplate(template)
162 return resource_bundle
165 def check_invariants(request, **kwargs):
166 # TODO: This should really happen in the BookingForm validation methods
167 installer = kwargs['installer']
168 image = kwargs['image']
169 scenario = kwargs['scenario']
171 length = kwargs['length']
172 # check that image os is compatible with installer
173 if installer in image.os.sup_installers.all():
174 # if installer not here, we can omit that and not check for scenario
176 raise ValidationError("An OPNFV Installer needs a scenario to be chosen to work properly")
177 if scenario not in installer.sup_scenarios.all():
178 raise ValidationError("The chosen installer does not support the chosen scenario")
179 if image.from_lab != lab:
180 raise ValidationError("The chosen image is not available at the chosen hosting lab")
182 # if image.host_type != host_profile:
183 # raise ValidationError("The chosen image is not available for the chosen host type")
184 if not image.public and image.owner != request.user:
185 raise ValidationError("You are not the owner of the chosen private image")
186 if length < 1 or length > 21:
187 raise BookingLengthException("Booking must be between 1 and 21 days long")
190 def create_from_form(form, request):
192 Create a Booking from the user's form.
194 Large, nasty method to create a booking or return a useful error
195 based on the form from the frontend
197 resource_field = form.cleaned_data['filter_field']
198 purpose_field = form.cleaned_data['purpose']
199 project_field = form.cleaned_data['project']
200 users_field = form.cleaned_data['users']
201 hostname = form.cleaned_data['hostname']
202 length = form.cleaned_data['length']
204 image = form.cleaned_data['image']
205 scenario = form.cleaned_data['scenario']
206 installer = form.cleaned_data['installer']
208 lab, resource_template = parse_resource_field(resource_field)
209 data = form.cleaned_data
211 data['resource_template'] = resource_template
212 check_invariants(request, **data)
214 # check booking privileges
215 # TODO: use the canonical booking_allowed method because now template might have multiple
217 if Booking.objects.filter(owner=request.user, end__gt=timezone.now()).count() >= 3 and not request.user.userprofile.booking_privledge:
218 raise PermissionError("You do not have permission to have more than 3 bookings at a time.")
220 ResourceManager.getInstance().templateIsReservable(resource_template)
222 resource_template = update_template(resource_template, image, hostname, request.user)
224 # if no installer provided, just create blank host
227 hconf = resource_template.getConfigs()[0]
228 opnfv_config = generate_opnfvconfig(scenario, installer, resource_template)
229 generate_hostopnfv(hconf, opnfv_config)
231 # generate resource bundle
232 resource_bundle = generate_resource_bundle(resource_template)
235 booking = Booking.objects.create(
236 purpose=purpose_field,
237 project=project_field,
240 start=timezone.now(),
241 end=timezone.now() + timedelta(days=int(length)),
242 resource=resource_bundle,
243 opnfv_config=opnfv_config
245 booking.pdf = PDFTemplater.makePDF(booking)
247 for collaborator in users_field: # list of UserProfiles
248 booking.collaborators.add(collaborator.user)
253 JobFactory.makeCompleteJob(booking)
254 NotificationHandler.notify_new_booking(booking)
259 def drop_filter(user):
261 Return a dictionary that contains filters.
263 Only certain installlers are supported on certain images, etc
264 so the image filter indexed at [imageid][installerid] is truthy if
265 that installer is supported on that image
267 installer_filter = {}
268 for image in Image.objects.all():
269 installer_filter[image.id] = {}
270 for installer in image.os.sup_installers.all():
271 installer_filter[image.id][installer.id] = 1
274 for installer in Installer.objects.all():
275 scenario_filter[installer.id] = {}
276 for scenario in installer.sup_scenarios.all():
277 scenario_filter[installer.id][scenario.id] = 1
279 images = Image.objects.filter(Q(public=True) | Q(owner=user))
282 image_filter[image.id] = {
283 'lab': 'lab_' + str(image.from_lab.lab_user.id),
284 'host_profile': str(image.host_type.id),
289 templates = ResourceTemplate.objects.filter(Q(public=True) | Q(owner=user))
291 profiles = [conf.profile for conf in rt.getConfigs()]
292 resource_filter["resource_" + str(rt.id)] = [str(p.id) for p in profiles]
295 'installer_filter': json.dumps(installer_filter),
296 'scenario_filter': json.dumps(scenario_filter),
297 'image_filter': json.dumps(image_filter),
298 'resource_profile_map': json.dumps(resource_filter),