1 ##############################################################################
2 # Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, 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 ##############################################################################
11 from django.shortcuts import render
12 from django.contrib import messages
17 from workflow.forms import ConfirmationForm
18 from api.models import JobFactory
19 from dashboard.exceptions import ResourceAvailabilityException, ModelValidationException
20 from resource_inventory.models import Image, GenericInterface
21 from resource_inventory.resource_manager import ResourceManager
22 from notifier.manager import NotificationHandler
23 from booking.models import Booking
26 class BookingAuthManager():
27 LFN_PROJECTS = ["opnfv"] # TODO
29 def parse_github_url(self, url):
32 parts = url.split("/")
33 if "http" in parts[0]: # the url include http(s)://
35 if parts[-1] != "INFO.yaml":
37 if parts[0] not in ["github.com", "raw.githubusercontent.com"]:
39 if parts[1] not in self.LFN_PROJECTS:
41 # now to download and parse file
42 if parts[3] == "blob":
44 url = "https://" + "/".join(parts)
45 info_file = requests.get(url, timeout=15).text
46 info_parsed = yaml.load(info_file)
47 ptl = info_parsed.get('project_lead')
49 project_leads.append(ptl)
50 sub_ptl = info_parsed.get("subproject_lead")
52 project_leads.append(sub_ptl)
59 def parse_gerrit_url(self, url):
62 parts = url.split("/")
63 if "http" in parts[0]: # the url include http(s)://
65 if "f=INFO.yaml" not in parts[-1].split(";"):
67 if "gerrit.opnfv.org" not in parts[0]:
69 # now to download and parse file
70 url = "https://" + "/".join(parts)
71 info_file = requests.get(url, timeout=15).text
72 info_parsed = yaml.load(info_file)
73 ptl = info_parsed.get('project_lead')
75 project_leads.append(ptl)
76 sub_ptl = info_parsed.get("subproject_lead")
78 project_leads.append(sub_ptl)
85 def parse_opnfv_git_url(self, url):
88 parts = url.split("/")
89 if "http" in parts[0]: # the url include http(s)://
91 if "INFO.yaml" not in parts[-1]:
93 if "git.opnfv.org" not in parts[0]:
95 if parts[-2] == "tree":
97 # now to download and parse file
98 url = "https://" + "/".join(parts)
99 info_file = requests.get(url, timeout=15).text
100 info_parsed = yaml.load(info_file)
101 ptl = info_parsed.get('project_lead')
103 project_leads.append(ptl)
104 sub_ptl = info_parsed.get("subproject_lead")
106 project_leads.append(sub_ptl)
113 def parse_url(self, info_url):
115 will return the PTL in the INFO file on success, or None
117 if "github" in info_url:
118 return self.parse_github_url(info_url)
120 if "gerrit.opnfv.org" in info_url:
121 return self.parse_gerrit_url(info_url)
123 if "git.opnfv.org" in info_url:
124 return self.parse_opnfv_git_url(info_url)
126 def booking_allowed(self, booking, repo):
128 This is the method that will have to change whenever the booking policy changes in the Infra
129 group / LFN. This is a nice isolation of that administration crap
130 currently checks if the booking uses multiple servers. if it does, then the owner must be a PTL,
131 which is checked using the provided info file
133 if len(booking.resource.template.getHosts()) < 2:
134 return True # if they only have one server, we dont care
135 if booking.owner.userprofile.booking_privledge:
136 return True # admin override for this user
137 if repo.BOOKING_INFO_FILE not in repo.el:
138 return False # INFO file not provided
139 ptl_info = self.parse_url(repo.BOOKING_INFO_FILE)
140 return ptl_info and ptl_info == booking.owner.userprofile.email_addr
143 class WorkflowStep(object):
145 template = 'bad_request.html'
146 title = "Generic Step"
147 description = "You were led here by mistake"
148 short_title = "error"
151 def __init__(self, id, repo=None):
155 def get_context(self):
157 context['step_number'] = self.repo_get('steps')
158 context['active_step'] = self.repo_get('active_step')
159 context['render_correct'] = "true"
160 context['step_title'] = self.title
161 context['description'] = self.description
164 def render(self, request):
165 self.context = self.get_context()
166 return render(request, self.template, self.context)
168 def post_render(self, request):
169 return self.render(request)
171 def test_render(self, request):
172 if request.method == "POST":
173 return self.post_render(request)
174 return self.render(request)
176 def validate(self, request):
179 def repo_get(self, key, default=None):
180 return self.repo.get(key, default, self.id)
182 def repo_put(self, key, value):
183 return self.repo.put(key, value, self.id)
186 class Confirmation_Step(WorkflowStep):
187 template = 'workflow/confirm.html'
188 title = "Confirm Changes"
189 description = "Does this all look right?"
191 def get_vlan_warning(self):
192 grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB, False)
195 vlan_manager = grb.lab.vlan_manager
196 if vlan_manager is None:
198 hosts = grb.getHosts()
200 for interface in host.generic_interfaces.all():
201 for vlan in interface.vlans.all():
203 if not vlan_manager.public_vlan_is_available(vlan.vlan_id):
206 if not vlan_manager.is_available(vlan.vlan_id):
207 return 1 # There is a problem with these vlans
210 def get_context(self):
211 context = super(Confirmation_Step, self).get_context()
212 context['form'] = ConfirmationForm()
213 context['confirmation_info'] = yaml.dump(
214 self.repo_get(self.repo.CONFIRMATION),
215 default_flow_style=False
217 context['vlan_warning'] = self.get_vlan_warning()
221 def flush_to_db(self):
222 errors = self.repo.make_models()
226 def post_render(self, request):
227 form = ConfirmationForm(request.POST)
229 data = form.cleaned_data['confirm']
230 context = self.get_context()
232 context["bypassed"] = "true"
233 errors = self.flush_to_db()
235 messages.add_message(request, messages.ERROR, "ERROR OCCURRED: " + errors)
236 return render(request, self.template, context)
237 messages.add_message(request, messages.SUCCESS, "Confirmed")
238 return render(request, self.template, context)
239 elif data == "False":
240 context["bypassed"] = "true"
241 messages.add_message(request, messages.SUCCESS, "Canceled")
242 return render(request, self.template, context)
247 if "vlan_input" in request.POST:
248 if request.POST.get("vlan_input") == "True":
249 self.translate_vlans()
250 return self.render(request)
253 def translate_vlans(self):
254 grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB, False)
257 vlan_manager = grb.lab.vlan_manager
258 if vlan_manager is None:
260 hosts = grb.getHosts()
262 for interface in host.generic_interfaces.all():
263 for vlan in interface.vlans.all():
265 if not vlan_manager.is_available(vlan.vlan_id):
266 vlan.vlan_id = vlan_manager.get_vlan()
269 if not vlan_manager.public_vlan_is_available(vlan.vlan_id):
270 pub_vlan = vlan_manager.get_public_vlan()
271 vlan.vlan_id = pub_vlan.vlan
285 RESOURCE_SELECT = "resource_select"
286 CONFIRMATION = "confirmation"
287 SELECTED_GRESOURCE_BUNDLE = "selected generic bundle pk"
288 GRESOURCE_BUNDLE_MODELS = "generic_resource_bundle_models"
289 GRESOURCE_BUNDLE_INFO = "generic_resource_bundle_info"
292 GRB_LAST_HOSTLIST = "grb_network_previous_hostlist"
293 BOOKING_FORMS = "booking_forms"
294 SWCONF_HOSTS = "swconf_hosts"
295 SWCONF_SELECTED_GRB = "swconf_selected_grb_pk"
296 BOOKING_SELECTED_GRB = "booking_selected_grb_pk"
297 BOOKING_MODELS = "booking models"
298 CONFIG_MODELS = "configuration bundle models"
299 SESSION_USER = "session owner user account"
300 VALIDATED_MODEL_GRB = "valid grb config model instance in db"
301 VALIDATED_MODEL_CONFIG = "valid config model instance in db"
302 VALIDATED_MODEL_BOOKING = "valid booking model instance in db"
303 VLANS = "a list of vlans"
304 SNAPSHOT_MODELS = "the models for snapshotting"
305 SNAPSHOT_BOOKING_ID = "the booking id for snapshotting"
306 SNAPSHOT_NAME = "the name of the snapshot"
307 SNAPSHOT_DESC = "description of the snapshot"
308 BOOKING_INFO_FILE = "the INFO.yaml file for this user's booking"
310 def get(self, key, default, id):
311 self.add_get_history(key, id)
312 return self.el.get(key, default)
314 def put(self, key, val, id):
315 self.add_put_history(key, id)
318 def add_get_history(self, key, id):
319 self.add_history(key, id, self.get_history)
321 def add_put_history(self, key, id):
322 self.add_history(key, id, self.put_history)
324 def add_history(self, key, id, history):
325 if key not in history:
328 history[key].append(id)
330 def make_models(self):
331 if self.SNAPSHOT_MODELS in self.el:
332 errors = self.make_snapshot()
335 # if GRB WF, create it
336 if self.GRESOURCE_BUNDLE_MODELS in self.el:
337 errors = self.make_generic_resource_bundle()
341 if self.CONFIG_MODELS in self.el:
342 errors = self.make_software_config_bundle()
346 if self.BOOKING_MODELS in self.el:
347 errors = self.make_booking()
350 # create notification
351 booking = self.el[self.BOOKING_MODELS]['booking']
352 NotificationHandler.notify_new_booking(booking)
354 def make_snapshot(self):
355 owner = self.el[self.SESSION_USER]
356 models = self.el[self.SNAPSHOT_MODELS]
357 image = models.get('snapshot', Image())
358 booking_id = self.el.get(self.SNAPSHOT_BOOKING_ID)
360 return "SNAP, No booking ID provided"
361 booking = Booking.objects.get(pk=booking_id)
362 name = self.el.get(self.SNAPSHOT_NAME)
364 return "SNAP, no name provided"
365 host = models.get('host')
367 return "SNAP, no host provided"
368 description = self.el.get(self.SNAPSHOT_DESC, "")
369 image.from_lab = booking.lab
371 image.description = description
375 image.host_type = host.profile
378 def make_generic_resource_bundle(self):
379 owner = self.el[self.SESSION_USER]
380 if self.GRESOURCE_BUNDLE_MODELS in self.el:
381 models = self.el[self.GRESOURCE_BUNDLE_MODELS]
382 if 'hosts' in models:
383 hosts = models['hosts']
385 return "GRB has no hosts. CODE:0x0002"
386 if 'bundle' in models:
387 bundle = models['bundle']
389 return "GRB, no bundle in models. CODE:0x0003"
394 except Exception as e:
395 return "GRB, saving bundle generated exception: " + str(e) + " CODE:0x0004"
398 genericresource = host.resource
399 genericresource.bundle = bundle
400 genericresource.save()
401 host.resource = genericresource
403 except Exception as e:
404 return "GRB, saving hosts generated exception: " + str(e) + " CODE:0x0005"
406 if 'interfaces' in models:
407 for interface_set in models['interfaces'].values():
408 for interface in interface_set:
410 interface.host = interface.host
412 except Exception as e:
413 return "GRB, saving interface " + str(interface) + " failed. CODE:0x0019"
415 return "GRB, no interface set provided. CODE:0x001a"
417 if 'vlans' in models:
418 for resource_name, mapping in models['vlans'].items():
419 for profile_name, vlan_set in mapping.items():
420 interface = GenericInterface.objects.get(
421 profile__name=profile_name,
422 host__resource__name=resource_name,
423 host__resource__bundle=models['bundle']
425 for vlan in vlan_set:
428 interface.vlans.add(vlan)
429 except Exception as e:
430 return "GRB, saving vlan " + str(vlan) + " failed. Exception: " + str(e) + ". CODE:0x0017"
432 return "GRB, no vlan set provided. CODE:0x0018"
435 return "GRB no models given. CODE:0x0001"
437 self.el[self.VALIDATED_MODEL_GRB] = bundle
440 def make_software_config_bundle(self):
441 models = self.el[self.CONFIG_MODELS]
442 if 'bundle' in models:
443 bundle = models['bundle']
444 bundle.bundle = bundle.bundle
447 except Exception as e:
448 return "SWC, saving bundle generated exception: " + str(e) + "CODE:0x0007"
451 return "SWC, no bundle in models. CODE:0x0006"
452 if 'host_configs' in models:
453 host_configs = models['host_configs']
454 for host_config in host_configs:
455 host_config.bundle = host_config.bundle
456 host_config.host = host_config.host
459 except Exception as e:
460 return "SWC, saving host configs generated exception: " + str(e) + "CODE:0x0009"
462 return "SWC, no host configs in models. CODE:0x0008"
463 if 'opnfv' in models:
464 opnfvconfig = models['opnfv']
465 opnfvconfig.bundle = opnfvconfig.bundle
466 if opnfvconfig.scenario not in opnfvconfig.installer.sup_scenarios.all():
467 return "SWC, scenario not supported by installer. CODE:0x000d"
470 except Exception as e:
471 return "SWC, saving opnfv config generated exception: " + str(e) + "CODE:0x000b"
475 self.el[self.VALIDATED_MODEL_CONFIG] = bundle
478 def make_booking(self):
479 models = self.el[self.BOOKING_MODELS]
480 owner = self.el[self.SESSION_USER]
482 if self.BOOKING_SELECTED_GRB in self.el:
483 selected_grb = self.el[self.BOOKING_SELECTED_GRB]
485 return "BOOK, no selected resource. CODE:0x000e"
487 if not self.reserve_vlans(selected_grb):
488 return "BOOK, vlans not available"
490 if 'booking' in models:
491 booking = models['booking']
493 return "BOOK, no booking model exists. CODE:0x000f"
495 if not booking.start:
496 return "BOOK, booking has no start. CODE:0x0010"
498 return "BOOK, booking has no end. CODE:0x0011"
499 if booking.end <= booking.start:
500 return "BOOK, end before/same time as start. CODE:0x0012"
502 if 'collaborators' in models:
503 collaborators = models['collaborators']
505 return "BOOK, collaborators not defined. CODE:0x0013"
507 resource_bundle = ResourceManager.getInstance().convertResourceBundle(selected_grb, config=booking.config_bundle)
508 except ResourceAvailabilityException as e:
509 return "BOOK, requested resources are not available. Exception: " + str(e) + " CODE:0x0014"
510 except ModelValidationException as e:
511 return "Error encountered when saving bundle. " + str(e) + " CODE: 0x001b"
513 booking.resource = resource_bundle
514 booking.owner = owner
515 booking.config_bundle = booking.config_bundle
516 booking.lab = selected_grb.lab
518 is_allowed = BookingAuthManager().booking_allowed(booking, self)
520 return "BOOK, you are not allowed to book the requested resources"
524 except Exception as e:
525 return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0015"
527 for collaborator in collaborators:
528 booking.collaborators.add(collaborator)
531 booking.pdf = ResourceManager().makePDF(booking.resource)
533 except Exception as e:
534 return "BOOK, failed to create Pod Desriptor File: " + str(e)
537 JobFactory.makeCompleteJob(booking)
538 except Exception as e:
539 return "BOOK, serializing for api generated exception: " + str(e) + " CODE:0xFFFF"
543 except Exception as e:
544 return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0016"
546 def reserve_vlans(self, grb):
552 vlan_manager = grb.lab.vlan_manager
553 if vlan_manager is None:
555 for host in grb.getHosts():
556 for interface in host.generic_interfaces.all():
557 for vlan in interface.vlans.all():
561 vlans.append(vlan.vlan_id)
564 vlan_manager.reserve_vlans(vlans)
565 vlan_manager.reserve_public_vlan(public_vlan.vlan_id)
572 self.el[self.CONFIRMATION] = {}
573 self.get_history = {}
574 self.put_history = {}