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,
90 is_public=old_network.is_public
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():
108 image_to_set = old_config.image
110 config = ResourceConfiguration.objects.create(
111 profile=old_config.profile,
114 is_head_node=old_config.is_head_node,
115 name=hostname if len(old_template.getConfigs()) == 1 else old_config.name
118 for old_iface_config in old_config.interface_configs.all():
119 iface_config = InterfaceConfiguration.objects.create(
120 profile=old_iface_config.profile,
121 resource_config=config
124 for old_connection in old_iface_config.connections.all():
125 iface_config.connections.add(NetworkConnection.objects.create(
126 network=template.networks.get(name=old_connection.network.name),
127 vlan_is_tagged=old_connection.vlan_is_tagged
130 for old_res_opnfv in old_config.resource_opnfv_config.all():
132 ResourceOPNFVConfig.objects.create(
134 resource_config=config,
135 opnfv_config=opnfv_config
140 def generate_opnfvconfig(scenario, installer, template):
141 return OPNFVConfig.objects.create(
148 def generate_hostopnfv(hostconfig, opnfvconfig):
151 role = OPNFVRole.objects.get(name="Jumphost")
153 role = OPNFVRole.objects.create(
155 description="Single server jumphost role"
157 return ResourceOPNFVConfig.objects.create(
159 host_config=hostconfig,
160 opnfv_config=opnfvconfig
164 def generate_resource_bundle(template):
165 resource_manager = ResourceManager.getInstance()
166 resource_bundle = resource_manager.instantiateTemplate(template)
167 return resource_bundle
170 def check_invariants(request, **kwargs):
171 # TODO: This should really happen in the BookingForm validation methods
172 installer = kwargs['installer']
173 image = kwargs['image']
174 scenario = kwargs['scenario']
176 length = kwargs['length']
177 # check that image os is compatible with installer
179 if installer or scenario:
180 if installer in image.os.sup_installers.all():
181 # if installer not here, we can omit that and not check for scenario
183 raise ValidationError("An OPNFV Installer needs a scenario to be chosen to work properly")
184 if scenario not in installer.sup_scenarios.all():
185 raise ValidationError("The chosen installer does not support the chosen scenario")
186 if image.from_lab != lab:
187 raise ValidationError("The chosen image is not available at the chosen hosting lab")
189 # if image.host_type != host_profile:
190 # raise ValidationError("The chosen image is not available for the chosen host type")
191 if not image.public and image.owner != request.user:
192 raise ValidationError("You are not the owner of the chosen private image")
193 if length < 1 or length > 21:
194 raise BookingLengthException("Booking must be between 1 and 21 days long")
197 def create_from_form(form, request):
199 Create a Booking from the user's form.
201 Large, nasty method to create a booking or return a useful error
202 based on the form from the frontend
204 resource_field = form.cleaned_data['filter_field']
205 purpose_field = form.cleaned_data['purpose']
206 project_field = form.cleaned_data['project']
207 users_field = form.cleaned_data['users']
208 hostname = 'opnfv_host' if not form.cleaned_data['hostname'] else form.cleaned_data['hostname']
209 length = form.cleaned_data['length']
211 image = form.cleaned_data['image']
212 scenario = form.cleaned_data['scenario']
213 installer = form.cleaned_data['installer']
215 lab, resource_template = parse_resource_field(resource_field)
216 data = form.cleaned_data
218 data['resource_template'] = resource_template
219 check_invariants(request, **data)
221 # check booking privileges
222 # TODO: use the canonical booking_allowed method because now template might have multiple
224 if Booking.objects.filter(owner=request.user, end__gt=timezone.now()).count() >= 3 and not request.user.userprofile.booking_privledge:
225 raise PermissionError("You do not have permission to have more than 3 bookings at a time.")
227 ResourceManager.getInstance().templateIsReservable(resource_template)
229 resource_template = update_template(resource_template, image, hostname, request.user)
231 # if no installer provided, just create blank host
234 hconf = resource_template.getConfigs()[0]
235 opnfv_config = generate_opnfvconfig(scenario, installer, resource_template)
236 generate_hostopnfv(hconf, opnfv_config)
238 # generate resource bundle
239 resource_bundle = generate_resource_bundle(resource_template)
242 booking = Booking.objects.create(
243 purpose=purpose_field,
244 project=project_field,
247 start=timezone.now(),
248 end=timezone.now() + timedelta(days=int(length)),
249 resource=resource_bundle,
250 opnfv_config=opnfv_config
252 booking.pdf = PDFTemplater.makePDF(booking)
254 for collaborator in users_field: # list of UserProfiles
255 booking.collaborators.add(collaborator.user)
260 JobFactory.makeCompleteJob(booking)
261 NotificationHandler.notify_new_booking(booking)
266 def drop_filter(user):
268 Return a dictionary that contains filters.
270 Only certain installlers are supported on certain images, etc
271 so the image filter indexed at [imageid][installerid] is truthy if
272 that installer is supported on that image
274 installer_filter = {}
275 for image in Image.objects.all():
276 installer_filter[image.id] = {}
277 for installer in image.os.sup_installers.all():
278 installer_filter[image.id][installer.id] = 1
281 for installer in Installer.objects.all():
282 scenario_filter[installer.id] = {}
283 for scenario in installer.sup_scenarios.all():
284 scenario_filter[installer.id][scenario.id] = 1
286 images = Image.objects.filter(Q(public=True) | Q(owner=user))
289 image_filter[image.id] = {
290 'lab': 'lab_' + str(image.from_lab.lab_user.id),
291 'host_profile': str(image.host_type.id),
296 templates = ResourceTemplate.objects.filter(Q(public=True) | Q(owner=user))
298 profiles = [conf.profile for conf in rt.getConfigs()]
299 resource_filter["resource_" + str(rt.id)] = [str(p.id) for p in profiles]
302 'installer_filter': json.dumps(installer_filter),
303 'scenario_filter': json.dumps(scenario_filter),
304 'image_filter': json.dumps(image_filter),
305 'resource_profile_map': json.dumps(resource_filter),