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, OPNFVConfig, HostOPNFVConfig, NetworkRole
23 from resource_inventory.resource_manager import ResourceManager
24 from resource_inventory.pdf_templater import PDFTemplater
25 from notifier.manager import NotificationHandler
26 from booking.models import Booking
29 class BookingAuthManager():
30 LFN_PROJECTS = ["opnfv"] # TODO
32 def parse_github_url(self, url):
35 parts = url.split("/")
36 if "http" in parts[0]: # the url include http(s)://
38 if parts[-1] != "INFO.yaml":
40 if parts[0] not in ["github.com", "raw.githubusercontent.com"]:
42 if parts[1] not in self.LFN_PROJECTS:
44 # now to download and parse file
45 if parts[3] == "blob":
47 url = "https://" + "/".join(parts)
48 info_file = requests.get(url, timeout=15).text
49 info_parsed = yaml.load(info_file)
50 ptl = info_parsed.get('project_lead')
52 project_leads.append(ptl)
53 sub_ptl = info_parsed.get("subproject_lead")
55 project_leads.append(sub_ptl)
62 def parse_gerrit_url(self, url):
65 halfs = url.split("?")
66 parts = halfs[0].split("/")
67 args = halfs[1].split(";")
68 if "http" in parts[0]: # the url include http(s)://
70 if "f=INFO.yaml" not in args:
72 if "gerrit.opnfv.org" not in parts[0]:
75 i = args.index("a=blob")
76 args[i] = "a=blob_plain"
80 halfs[1] = ";".join(args)
81 halfs[0] = "/".join(parts)
82 # now to download and parse file
83 url = "https://" + "?".join(halfs)
84 info_file = requests.get(url, timeout=15).text
85 info_parsed = yaml.load(info_file)
86 ptl = info_parsed.get('project_lead')
88 project_leads.append(ptl)
89 sub_ptl = info_parsed.get("subproject_lead")
91 project_leads.append(sub_ptl)
98 def parse_opnfv_git_url(self, url):
101 parts = url.split("/")
102 if "http" in parts[0]: # the url include http(s)://
104 if "INFO.yaml" not in parts[-1]:
106 if "git.opnfv.org" not in parts[0]:
108 if parts[-2] == "tree":
110 # now to download and parse file
111 url = "https://" + "/".join(parts)
112 info_file = requests.get(url, timeout=15).text
113 info_parsed = yaml.load(info_file)
114 ptl = info_parsed.get('project_lead')
116 project_leads.append(ptl)
117 sub_ptl = info_parsed.get("subproject_lead")
119 project_leads.append(sub_ptl)
126 def parse_url(self, info_url):
128 will return the PTL in the INFO file on success, or None
130 if "github" in info_url:
131 return self.parse_github_url(info_url)
133 if "gerrit.opnfv.org" in info_url:
134 return self.parse_gerrit_url(info_url)
136 if "git.opnfv.org" in info_url:
137 return self.parse_opnfv_git_url(info_url)
139 def booking_allowed(self, booking, repo):
141 This is the method that will have to change whenever the booking policy changes in the Infra
142 group / LFN. This is a nice isolation of that administration crap
143 currently checks if the booking uses multiple servers. if it does, then the owner must be a PTL,
144 which is checked using the provided info file
146 if booking.owner.userprofile.booking_privledge:
147 return True # admin override for this user
148 if Booking.objects.filter(owner=booking.owner, end__gt=timezone.now()).count() >= 3:
150 if len(booking.resource.template.getHosts()) < 2:
151 return True # if they only have one server, we dont care
152 if repo.BOOKING_INFO_FILE not in repo.el:
153 return False # INFO file not provided
154 ptl_info = self.parse_url(repo.el.get(repo.BOOKING_INFO_FILE))
156 if ptl['email'] == booking.owner.userprofile.email_addr:
161 class WorkflowStep(object):
162 template = 'bad_request.html'
163 title = "Generic Step"
164 description = "You were led here by mistake"
165 short_title = "error"
168 def __init__(self, id, repo=None):
172 def get_context(self):
174 context['step_number'] = self.repo_get('steps')
175 context['active_step'] = self.repo_get('active_step')
176 context['render_correct'] = "true"
177 context['step_title'] = self.title
178 context['description'] = self.description
181 def render(self, request):
182 self.context = self.get_context()
183 return render(request, self.template, self.context)
185 def post_render(self, request):
186 return self.render(request)
188 def test_render(self, request):
189 if request.method == "POST":
190 return self.post_render(request)
191 return self.render(request)
193 def validate(self, request):
196 def repo_get(self, key, default=None):
197 return self.repo.get(key, default, self.id)
199 def repo_put(self, key, value):
200 return self.repo.put(key, value, self.id)
203 class Confirmation_Step(WorkflowStep):
204 template = 'workflow/confirm.html'
205 title = "Confirm Changes"
206 description = "Does this all look right?"
208 def get_context(self):
209 context = super(Confirmation_Step, self).get_context()
210 context['form'] = ConfirmationForm()
211 context['confirmation_info'] = yaml.dump(
212 self.repo_get(self.repo.CONFIRMATION),
213 default_flow_style=False
218 def flush_to_db(self):
219 errors = self.repo.make_models()
223 def post_render(self, request):
224 form = ConfirmationForm(request.POST)
226 data = form.cleaned_data['confirm']
227 context = self.get_context()
229 context["bypassed"] = "true"
230 errors = self.flush_to_db()
232 messages.add_message(request, messages.ERROR, "ERROR OCCURRED: " + errors)
234 messages.add_message(request, messages.SUCCESS, "Confirmed")
236 return HttpResponse('')
237 elif data == "False":
238 context["bypassed"] = "true"
239 messages.add_message(request, messages.SUCCESS, "Canceled")
240 return render(request, self.template, context)
258 RESOURCE_SELECT = "resource_select"
259 CONFIRMATION = "confirmation"
260 SELECTED_GRESOURCE_BUNDLE = "selected generic bundle pk"
261 SELECTED_CONFIG_BUNDLE = "selected config bundle pk"
262 SELECTED_OPNFV_CONFIG = "selected opnfv deployment config"
263 GRESOURCE_BUNDLE_MODELS = "generic_resource_bundle_models"
264 GRESOURCE_BUNDLE_INFO = "generic_resource_bundle_info"
267 GRB_LAST_HOSTLIST = "grb_network_previous_hostlist"
268 BOOKING_FORMS = "booking_forms"
269 SWCONF_HOSTS = "swconf_hosts"
270 BOOKING_MODELS = "booking models"
271 CONFIG_MODELS = "configuration bundle models"
272 OPNFV_MODELS = "opnfv configuration models"
273 SESSION_USER = "session owner user account"
274 VALIDATED_MODEL_GRB = "valid grb config model instance in db"
275 VALIDATED_MODEL_CONFIG = "valid config model instance in db"
276 VALIDATED_MODEL_BOOKING = "valid booking model instance in db"
277 VLANS = "a list of vlans"
278 SNAPSHOT_MODELS = "the models for snapshotting"
279 SNAPSHOT_BOOKING_ID = "the booking id for snapshotting"
280 SNAPSHOT_NAME = "the name of the snapshot"
281 SNAPSHOT_DESC = "description of the snapshot"
282 BOOKING_INFO_FILE = "the INFO.yaml file for this user's booking"
284 # migratory elements of segmented workflow
285 # each of these is the end result of a different workflow.
286 HAS_RESULT = "whether or not workflow has a result"
287 RESULT_KEY = "key for target index that result will be put into in parent"
288 RESULT = "result object from workflow"
290 def get_child_defaults(self):
292 for key in [self.SELECTED_GRESOURCE_BUNDLE, self.SESSION_USER]:
293 return_tuples.append((key, self.el.get(key)))
296 def set_defaults(self, defaults):
297 for key, value in defaults:
300 def get(self, key, default, id):
301 self.add_get_history(key, id)
302 return self.el.get(key, default)
304 def put(self, key, val, id):
305 self.add_put_history(key, id)
308 def add_get_history(self, key, id):
309 self.add_history(key, id, self.get_history)
311 def add_put_history(self, key, id):
312 self.add_history(key, id, self.put_history)
314 def add_history(self, key, id, history):
315 if key not in history:
318 history[key].append(id)
320 def make_models(self):
321 if self.SNAPSHOT_MODELS in self.el:
322 errors = self.make_snapshot()
325 # if GRB WF, create it
326 if self.GRESOURCE_BUNDLE_MODELS in self.el:
327 errors = self.make_generic_resource_bundle()
331 self.el[self.HAS_RESULT] = True
332 self.el[self.RESULT_KEY] = self.SELECTED_GRESOURCE_BUNDLE
335 if self.CONFIG_MODELS in self.el:
336 errors = self.make_software_config_bundle()
340 self.el[self.HAS_RESULT] = True
341 self.el[self.RESULT_KEY] = self.SELECTED_CONFIG_BUNDLE
344 if self.OPNFV_MODELS in self.el:
345 errors = self.make_opnfv_config()
349 self.el[self.HAS_RESULT] = True
350 self.el[self.RESULT_KEY] = self.SELECTED_OPNFV_CONFIG
352 if self.BOOKING_MODELS in self.el:
353 errors = self.make_booking()
356 # create notification
357 booking = self.el[self.BOOKING_MODELS]['booking']
358 NotificationHandler.notify_new_booking(booking)
360 def make_snapshot(self):
361 owner = self.el[self.SESSION_USER]
362 models = self.el[self.SNAPSHOT_MODELS]
363 image = models.get('snapshot', Image())
364 booking_id = self.el.get(self.SNAPSHOT_BOOKING_ID)
366 return "SNAP, No booking ID provided"
367 booking = Booking.objects.get(pk=booking_id)
368 if booking.start > timezone.now() or booking.end < timezone.now():
369 return "Booking is not active"
370 name = self.el.get(self.SNAPSHOT_NAME)
372 return "SNAP, no name provided"
373 host = models.get('host')
375 return "SNAP, no host provided"
376 description = self.el.get(self.SNAPSHOT_DESC, "")
377 image.from_lab = booking.lab
379 image.description = description
383 image.host_type = host.profile
386 current_image = host.config.image
387 image.os = current_image.os
391 JobFactory.makeSnapshotTask(image, booking, host)
393 def make_generic_resource_bundle(self):
394 owner = self.el[self.SESSION_USER]
395 if self.GRESOURCE_BUNDLE_MODELS in self.el:
396 models = self.el[self.GRESOURCE_BUNDLE_MODELS]
397 if 'hosts' in models:
398 hosts = models['hosts']
400 return "GRB has no hosts. CODE:0x0002"
401 if 'bundle' in models:
402 bundle = models['bundle']
404 return "GRB, no bundle in models. CODE:0x0003"
409 except Exception as e:
410 return "GRB, saving bundle generated exception: " + str(e) + " CODE:0x0004"
413 genericresource = host.resource
414 genericresource.bundle = bundle
415 genericresource.save()
416 host.resource = genericresource
418 except Exception as e:
419 return "GRB, saving hosts generated exception: " + str(e) + " CODE:0x0005"
421 if 'networks' in models:
422 for net in models['networks'].values():
426 if 'interfaces' in models:
427 for interface_set in models['interfaces'].values():
428 for interface in interface_set:
430 interface.host = interface.host
433 return "GRB, saving interface " + str(interface) + " failed. CODE:0x0019"
435 return "GRB, no interface set provided. CODE:0x001a"
437 if 'connections' in models:
438 for resource_name, mapping in models['connections'].items():
439 for profile_name, connection_set in mapping.items():
440 interface = GenericInterface.objects.get(
441 profile__name=profile_name,
442 host__resource__name=resource_name,
443 host__resource__bundle=models['bundle']
445 for connection in connection_set:
447 connection.network = connection.network
449 interface.connections.add(connection)
450 except Exception as e:
451 return "GRB, saving vlan " + str(connection) + " failed. Exception: " + str(e) + ". CODE:0x0017"
453 return "GRB, no vlan set provided. CODE:0x0018"
456 return "GRB no models given. CODE:0x0001"
458 self.el[self.RESULT] = bundle
461 def make_software_config_bundle(self):
462 models = self.el[self.CONFIG_MODELS]
463 if 'bundle' in models:
464 bundle = models['bundle']
465 bundle.bundle = bundle.bundle
468 except Exception as e:
469 return "SWC, saving bundle generated exception: " + str(e) + "CODE:0x0007"
472 return "SWC, no bundle in models. CODE:0x0006"
473 if 'host_configs' in models:
474 host_configs = models['host_configs']
475 for host_config in host_configs:
476 host_config.bundle = host_config.bundle
477 host_config.host = host_config.host
480 except Exception as e:
481 return "SWC, saving host configs generated exception: " + str(e) + "CODE:0x0009"
483 return "SWC, no host configs in models. CODE:0x0008"
484 if 'opnfv' in models:
485 opnfvconfig = models['opnfv']
486 opnfvconfig.bundle = opnfvconfig.bundle
487 if opnfvconfig.scenario not in opnfvconfig.installer.sup_scenarios.all():
488 return "SWC, scenario not supported by installer. CODE:0x000d"
491 except Exception as e:
492 return "SWC, saving opnfv config generated exception: " + str(e) + "CODE:0x000b"
496 self.el[self.RESULT] = bundle
499 def make_booking(self):
500 models = self.el[self.BOOKING_MODELS]
501 owner = self.el[self.SESSION_USER]
503 if self.SELECTED_GRESOURCE_BUNDLE in self.el:
504 selected_grb = self.el[self.SELECTED_GRESOURCE_BUNDLE]
506 return "BOOK, no selected resource. CODE:0x000e"
508 if 'booking' in models:
509 booking = models['booking']
511 return "BOOK, no booking model exists. CODE:0x000f"
513 if not booking.start:
514 return "BOOK, booking has no start. CODE:0x0010"
516 return "BOOK, booking has no end. CODE:0x0011"
517 if booking.end <= booking.start:
518 return "BOOK, end before/same time as start. CODE:0x0012"
520 if 'collaborators' in models:
521 collaborators = models['collaborators']
523 return "BOOK, collaborators not defined. CODE:0x0013"
525 resource_bundle = ResourceManager.getInstance().convertResourceBundle(selected_grb, config=booking.config_bundle)
526 except ResourceAvailabilityException as e:
527 return "BOOK, requested resources are not available. Exception: " + str(e) + " CODE:0x0014"
528 except ModelValidationException as e:
529 return "Error encountered when saving bundle. " + str(e) + " CODE: 0x001b"
531 booking.resource = resource_bundle
532 booking.owner = owner
533 booking.config_bundle = booking.config_bundle
534 booking.lab = selected_grb.lab
536 is_allowed = BookingAuthManager().booking_allowed(booking, self)
538 return "BOOK, you are not allowed to book the requested resources"
542 except Exception as e:
543 return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0015"
545 for collaborator in collaborators:
546 booking.collaborators.add(collaborator)
549 booking.pdf = PDFTemplater.makePDF(booking)
551 except Exception as e:
552 return "BOOK, failed to create Pod Desriptor File: " + str(e)
555 JobFactory.makeCompleteJob(booking)
556 except Exception as e:
557 return "BOOK, serializing for api generated exception: " + str(e) + " CODE:0xFFFF"
561 except Exception as e:
562 return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0016"
564 def make_opnfv_config(self):
565 opnfv_models = self.el[self.OPNFV_MODELS]
566 config_bundle = opnfv_models['configbundle']
567 if not config_bundle:
568 return "No Configuration bundle selected"
569 info = opnfv_models.get("meta", {})
570 name = info.get("name", False)
571 desc = info.get("description", False)
572 if not (name and desc):
573 return "No name or description given"
574 installer = opnfv_models['installer_chosen']
576 return "No OPNFV Installer chosen"
577 scenario = opnfv_models['scenario_chosen']
579 return "No OPNFV Scenario chosen"
581 opnfv_config = OPNFVConfig.objects.create(
582 bundle=config_bundle,
589 network_roles = opnfv_models['network_roles']
590 for net_role in network_roles:
591 opnfv_config.networks.add(
592 NetworkRole.objects.create(
593 name=net_role['role'],
594 network=net_role['network']
598 host_roles = opnfv_models['host_roles']
599 for host_role in host_roles:
600 config = config_bundle.hostConfigurations.get(
601 host__resource__name=host_role['host_name']
603 HostOPNFVConfig.objects.create(
604 role=host_role['role'],
606 opnfv_config=opnfv_config
609 self.el[self.RESULT] = opnfv_config
613 self.el[self.CONFIRMATION] = {}
614 self.el["active_step"] = 0
615 self.get_history = {}
616 self.put_history = {}