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
13 from django.http import HttpResponse
14 from django.utils import timezone
19 from workflow.forms import ConfirmationForm
20 from api.models import JobFactory
21 from dashboard.exceptions import ResourceAvailabilityException, ModelValidationException
22 from resource_inventory.models import Image, GenericInterface
23 from resource_inventory.resource_manager import ResourceManager
24 from notifier.manager import NotificationHandler
25 from booking.models import Booking
28 class BookingAuthManager():
29 LFN_PROJECTS = ["opnfv"] # TODO
31 def parse_github_url(self, url):
34 parts = url.split("/")
35 if "http" in parts[0]: # the url include http(s)://
37 if parts[-1] != "INFO.yaml":
39 if parts[0] not in ["github.com", "raw.githubusercontent.com"]:
41 if parts[1] not in self.LFN_PROJECTS:
43 # now to download and parse file
44 if parts[3] == "blob":
46 url = "https://" + "/".join(parts)
47 info_file = requests.get(url, timeout=15).text
48 info_parsed = yaml.load(info_file)
49 ptl = info_parsed.get('project_lead')
51 project_leads.append(ptl)
52 sub_ptl = info_parsed.get("subproject_lead")
54 project_leads.append(sub_ptl)
61 def parse_gerrit_url(self, url):
64 halfs = url.split("?")
65 parts = halfs[0].split("/")
66 args = halfs[1].split(";")
67 if "http" in parts[0]: # the url include http(s)://
69 if "f=INFO.yaml" not in args:
71 if "gerrit.opnfv.org" not in parts[0]:
74 i = args.index("a=blob")
75 args[i] = "a=blob_plain"
79 halfs[1] = ";".join(args)
80 halfs[0] = "/".join(parts)
81 # now to download and parse file
82 url = "https://" + "?".join(halfs)
83 info_file = requests.get(url, timeout=15).text
84 info_parsed = yaml.load(info_file)
85 ptl = info_parsed.get('project_lead')
87 project_leads.append(ptl)
88 sub_ptl = info_parsed.get("subproject_lead")
90 project_leads.append(sub_ptl)
97 def parse_opnfv_git_url(self, url):
100 parts = url.split("/")
101 if "http" in parts[0]: # the url include http(s)://
103 if "INFO.yaml" not in parts[-1]:
105 if "git.opnfv.org" not in parts[0]:
107 if parts[-2] == "tree":
109 # now to download and parse file
110 url = "https://" + "/".join(parts)
111 info_file = requests.get(url, timeout=15).text
112 info_parsed = yaml.load(info_file)
113 ptl = info_parsed.get('project_lead')
115 project_leads.append(ptl)
116 sub_ptl = info_parsed.get("subproject_lead")
118 project_leads.append(sub_ptl)
125 def parse_url(self, info_url):
127 will return the PTL in the INFO file on success, or None
129 if "github" in info_url:
130 return self.parse_github_url(info_url)
132 if "gerrit.opnfv.org" in info_url:
133 return self.parse_gerrit_url(info_url)
135 if "git.opnfv.org" in info_url:
136 return self.parse_opnfv_git_url(info_url)
138 def booking_allowed(self, booking, repo):
140 This is the method that will have to change whenever the booking policy changes in the Infra
141 group / LFN. This is a nice isolation of that administration crap
142 currently checks if the booking uses multiple servers. if it does, then the owner must be a PTL,
143 which is checked using the provided info file
145 if len(booking.resource.template.getHosts()) < 2:
146 return True # if they only have one server, we dont care
147 if booking.owner.userprofile.booking_privledge:
148 return True # admin override for this user
149 if repo.BOOKING_INFO_FILE not in repo.el:
150 return False # INFO file not provided
151 ptl_info = self.parse_url(repo.el.get(repo.BOOKING_INFO_FILE))
153 if ptl['email'] == booking.owner.userprofile.email_addr:
158 class WorkflowStep(object):
159 template = 'bad_request.html'
160 title = "Generic Step"
161 description = "You were led here by mistake"
162 short_title = "error"
165 def __init__(self, id, repo=None):
169 def get_context(self):
171 context['step_number'] = self.repo_get('steps')
172 context['active_step'] = self.repo_get('active_step')
173 context['render_correct'] = "true"
174 context['step_title'] = self.title
175 context['description'] = self.description
178 def render(self, request):
179 self.context = self.get_context()
180 return render(request, self.template, self.context)
182 def post_render(self, request):
183 return self.render(request)
185 def test_render(self, request):
186 if request.method == "POST":
187 return self.post_render(request)
188 return self.render(request)
190 def validate(self, request):
193 def repo_get(self, key, default=None):
194 return self.repo.get(key, default, self.id)
196 def repo_put(self, key, value):
197 return self.repo.put(key, value, self.id)
200 class Confirmation_Step(WorkflowStep):
201 template = 'workflow/confirm.html'
202 title = "Confirm Changes"
203 description = "Does this all look right?"
205 def get_vlan_warning(self):
206 grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False)
209 if self.repo.BOOKING_MODELS not in self.repo.el:
211 vlan_manager = grb.lab.vlan_manager
212 if vlan_manager is None:
214 hosts = grb.getHosts()
216 for interface in host.generic_interfaces.all():
217 for vlan in interface.vlans.all():
219 if not vlan_manager.public_vlan_is_available(vlan.vlan_id):
222 if not vlan_manager.is_available(vlan.vlan_id):
223 return 1 # There is a problem with these vlans
226 def get_context(self):
227 context = super(Confirmation_Step, self).get_context()
228 context['form'] = ConfirmationForm()
229 context['confirmation_info'] = yaml.dump(
230 self.repo_get(self.repo.CONFIRMATION),
231 default_flow_style=False
233 context['vlan_warning'] = self.get_vlan_warning()
237 def flush_to_db(self):
238 errors = self.repo.make_models()
242 def post_render(self, request):
243 form = ConfirmationForm(request.POST)
245 data = form.cleaned_data['confirm']
246 context = self.get_context()
248 context["bypassed"] = "true"
249 errors = self.flush_to_db()
251 messages.add_message(request, messages.ERROR, "ERROR OCCURRED: " + errors)
253 messages.add_message(request, messages.SUCCESS, "Confirmed")
255 return HttpResponse('')
256 elif data == "False":
257 context["bypassed"] = "true"
258 messages.add_message(request, messages.SUCCESS, "Canceled")
259 return render(request, self.template, context)
264 if "vlan_input" in request.POST:
265 if request.POST.get("vlan_input") == "True":
266 self.translate_vlans()
267 return self.render(request)
270 def translate_vlans(self):
271 grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False)
274 vlan_manager = grb.lab.vlan_manager
275 if vlan_manager is None:
277 hosts = grb.getHosts()
279 for interface in host.generic_interfaces.all():
280 for vlan in interface.vlans.all():
282 if not vlan_manager.is_available(vlan.vlan_id):
283 vlan.vlan_id = vlan_manager.get_vlan()
286 if not vlan_manager.public_vlan_is_available(vlan.vlan_id):
287 pub_vlan = vlan_manager.get_public_vlan()
288 vlan.vlan_id = pub_vlan.vlan
302 RESOURCE_SELECT = "resource_select"
303 CONFIRMATION = "confirmation"
304 SELECTED_GRESOURCE_BUNDLE = "selected generic bundle pk"
305 SELECTED_CONFIG_BUNDLE = "selected config bundle pk"
306 GRESOURCE_BUNDLE_MODELS = "generic_resource_bundle_models"
307 GRESOURCE_BUNDLE_INFO = "generic_resource_bundle_info"
310 GRB_LAST_HOSTLIST = "grb_network_previous_hostlist"
311 BOOKING_FORMS = "booking_forms"
312 SWCONF_HOSTS = "swconf_hosts"
313 BOOKING_MODELS = "booking models"
314 CONFIG_MODELS = "configuration bundle models"
315 SESSION_USER = "session owner user account"
316 VALIDATED_MODEL_GRB = "valid grb config model instance in db"
317 VALIDATED_MODEL_CONFIG = "valid config model instance in db"
318 VALIDATED_MODEL_BOOKING = "valid booking model instance in db"
319 VLANS = "a list of vlans"
320 SNAPSHOT_MODELS = "the models for snapshotting"
321 SNAPSHOT_BOOKING_ID = "the booking id for snapshotting"
322 SNAPSHOT_NAME = "the name of the snapshot"
323 SNAPSHOT_DESC = "description of the snapshot"
324 BOOKING_INFO_FILE = "the INFO.yaml file for this user's booking"
326 # migratory elements of segmented workflow
327 # each of these is the end result of a different workflow.
328 HAS_RESULT = "whether or not workflow has a result"
329 RESULT_KEY = "key for target index that result will be put into in parent"
330 RESULT = "result object from workflow"
332 def get_child_defaults(self):
334 for key in [self.SELECTED_GRESOURCE_BUNDLE, self.SESSION_USER]:
335 return_tuples.append((key, self.el.get(key)))
338 def set_defaults(self, defaults):
339 for key, value in defaults:
342 def get(self, key, default, id):
343 self.add_get_history(key, id)
344 return self.el.get(key, default)
346 def put(self, key, val, id):
347 self.add_put_history(key, id)
350 def add_get_history(self, key, id):
351 self.add_history(key, id, self.get_history)
353 def add_put_history(self, key, id):
354 self.add_history(key, id, self.put_history)
356 def add_history(self, key, id, history):
357 if key not in history:
360 history[key].append(id)
362 def make_models(self):
363 if self.SNAPSHOT_MODELS in self.el:
364 errors = self.make_snapshot()
367 # if GRB WF, create it
368 if self.GRESOURCE_BUNDLE_MODELS in self.el:
369 errors = self.make_generic_resource_bundle()
373 self.el[self.HAS_RESULT] = True
374 self.el[self.RESULT_KEY] = self.SELECTED_GRESOURCE_BUNDLE
377 if self.CONFIG_MODELS in self.el:
378 errors = self.make_software_config_bundle()
382 self.el[self.HAS_RESULT] = True
383 self.el[self.RESULT_KEY] = self.SELECTED_CONFIG_BUNDLE
386 if self.BOOKING_MODELS in self.el:
387 errors = self.make_booking()
390 # create notification
391 booking = self.el[self.BOOKING_MODELS]['booking']
392 NotificationHandler.notify_new_booking(booking)
394 def make_snapshot(self):
395 owner = self.el[self.SESSION_USER]
396 models = self.el[self.SNAPSHOT_MODELS]
397 image = models.get('snapshot', Image())
398 booking_id = self.el.get(self.SNAPSHOT_BOOKING_ID)
400 return "SNAP, No booking ID provided"
401 booking = Booking.objects.get(pk=booking_id)
402 if booking.start > timezone.now() or booking.end < timezone.now():
403 return "Booking is not active"
404 name = self.el.get(self.SNAPSHOT_NAME)
406 return "SNAP, no name provided"
407 host = models.get('host')
409 return "SNAP, no host provided"
410 description = self.el.get(self.SNAPSHOT_DESC, "")
411 image.from_lab = booking.lab
413 image.description = description
417 image.host_type = host.profile
420 current_image = host.config.image
421 image.os = current_image.os
425 JobFactory.makeSnapshotTask(image, booking, host)
427 def make_generic_resource_bundle(self):
428 owner = self.el[self.SESSION_USER]
429 if self.GRESOURCE_BUNDLE_MODELS in self.el:
430 models = self.el[self.GRESOURCE_BUNDLE_MODELS]
431 if 'hosts' in models:
432 hosts = models['hosts']
434 return "GRB has no hosts. CODE:0x0002"
435 if 'bundle' in models:
436 bundle = models['bundle']
438 return "GRB, no bundle in models. CODE:0x0003"
443 except Exception as e:
444 return "GRB, saving bundle generated exception: " + str(e) + " CODE:0x0004"
447 genericresource = host.resource
448 genericresource.bundle = bundle
449 genericresource.save()
450 host.resource = genericresource
452 except Exception as e:
453 return "GRB, saving hosts generated exception: " + str(e) + " CODE:0x0005"
455 if 'interfaces' in models:
456 for interface_set in models['interfaces'].values():
457 for interface in interface_set:
459 interface.host = interface.host
462 return "GRB, saving interface " + str(interface) + " failed. CODE:0x0019"
464 return "GRB, no interface set provided. CODE:0x001a"
466 if 'vlans' in models:
467 for resource_name, mapping in models['vlans'].items():
468 for profile_name, vlan_set in mapping.items():
469 interface = GenericInterface.objects.get(
470 profile__name=profile_name,
471 host__resource__name=resource_name,
472 host__resource__bundle=models['bundle']
474 for vlan in vlan_set:
477 interface.vlans.add(vlan)
478 except Exception as e:
479 return "GRB, saving vlan " + str(vlan) + " failed. Exception: " + str(e) + ". CODE:0x0017"
481 return "GRB, no vlan set provided. CODE:0x0018"
484 return "GRB no models given. CODE:0x0001"
486 self.el[self.RESULT] = bundle
489 def make_software_config_bundle(self):
490 models = self.el[self.CONFIG_MODELS]
491 if 'bundle' in models:
492 bundle = models['bundle']
493 bundle.bundle = bundle.bundle
496 except Exception as e:
497 return "SWC, saving bundle generated exception: " + str(e) + "CODE:0x0007"
500 return "SWC, no bundle in models. CODE:0x0006"
501 if 'host_configs' in models:
502 host_configs = models['host_configs']
503 for host_config in host_configs:
504 host_config.bundle = host_config.bundle
505 host_config.host = host_config.host
508 except Exception as e:
509 return "SWC, saving host configs generated exception: " + str(e) + "CODE:0x0009"
511 return "SWC, no host configs in models. CODE:0x0008"
512 if 'opnfv' in models:
513 opnfvconfig = models['opnfv']
514 opnfvconfig.bundle = opnfvconfig.bundle
515 if opnfvconfig.scenario not in opnfvconfig.installer.sup_scenarios.all():
516 return "SWC, scenario not supported by installer. CODE:0x000d"
519 except Exception as e:
520 return "SWC, saving opnfv config generated exception: " + str(e) + "CODE:0x000b"
524 self.el[self.RESULT] = bundle
527 def make_booking(self):
528 models = self.el[self.BOOKING_MODELS]
529 owner = self.el[self.SESSION_USER]
531 if self.SELECTED_GRESOURCE_BUNDLE in self.el:
532 selected_grb = self.el[self.SELECTED_GRESOURCE_BUNDLE]
534 return "BOOK, no selected resource. CODE:0x000e"
536 if not self.reserve_vlans(selected_grb):
537 return "BOOK, vlans not available"
539 if 'booking' in models:
540 booking = models['booking']
542 return "BOOK, no booking model exists. CODE:0x000f"
544 if not booking.start:
545 return "BOOK, booking has no start. CODE:0x0010"
547 return "BOOK, booking has no end. CODE:0x0011"
548 if booking.end <= booking.start:
549 return "BOOK, end before/same time as start. CODE:0x0012"
551 if 'collaborators' in models:
552 collaborators = models['collaborators']
554 return "BOOK, collaborators not defined. CODE:0x0013"
556 resource_bundle = ResourceManager.getInstance().convertResourceBundle(selected_grb, config=booking.config_bundle)
557 except ResourceAvailabilityException as e:
558 return "BOOK, requested resources are not available. Exception: " + str(e) + " CODE:0x0014"
559 except ModelValidationException as e:
560 return "Error encountered when saving bundle. " + str(e) + " CODE: 0x001b"
562 booking.resource = resource_bundle
563 booking.owner = owner
564 booking.config_bundle = booking.config_bundle
565 booking.lab = selected_grb.lab
567 is_allowed = BookingAuthManager().booking_allowed(booking, self)
569 return "BOOK, you are not allowed to book the requested resources"
573 except Exception as e:
574 return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0015"
576 for collaborator in collaborators:
577 booking.collaborators.add(collaborator)
580 booking.pdf = ResourceManager().makePDF(booking.resource)
582 except Exception as e:
583 return "BOOK, failed to create Pod Desriptor File: " + str(e)
586 JobFactory.makeCompleteJob(booking)
587 except Exception as e:
588 return "BOOK, serializing for api generated exception: " + str(e) + " CODE:0xFFFF"
592 except Exception as e:
593 return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0016"
595 def reserve_vlans(self, grb):
601 vlan_manager = grb.lab.vlan_manager
602 if vlan_manager is None:
604 for host in grb.getHosts():
605 for interface in host.generic_interfaces.all():
606 for vlan in interface.vlans.all():
610 vlans.append(vlan.vlan_id)
613 vlan_manager.reserve_vlans(vlans)
614 vlan_manager.reserve_public_vlan(public_vlan.vlan_id)
621 self.el[self.CONFIRMATION] = {}
622 self.el["active_step"] = 0
623 self.get_history = {}
624 self.put_history = {}