From 1f3a770d2547848590f39e9d9b9bdffeb94eec14 Mon Sep 17 00:00:00 2001 From: Parker Berberian Date: Wed, 10 Oct 2018 16:06:47 -0400 Subject: Lab as a Service 2.0 See changes here: https://wiki.opnfv.org/display/INF/Pharos+Laas Change-Id: I59ada5f98e70a28d7f8c14eab3239597e236ca26 Signed-off-by: Sawyer Bergeron Signed-off-by: Parker Berberian --- src/workflow/models.py | 508 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 508 insertions(+) create mode 100644 src/workflow/models.py (limited to 'src/workflow/models.py') diff --git a/src/workflow/models.py b/src/workflow/models.py new file mode 100644 index 0000000..e862957 --- /dev/null +++ b/src/workflow/models.py @@ -0,0 +1,508 @@ +############################################################################## +# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.contrib.auth.models import User +from django.db import models +from django.shortcuts import render +from django.contrib import messages + +import yaml +import json +import traceback +import requests + +from workflow.forms import ConfirmationForm +from api.models import * +from dashboard.exceptions import * +from resource_inventory.models import * +from resource_inventory.resource_manager import ResourceManager + + +class BookingAuthManager(): + LFN_PROJECTS = ["opnfv"] # TODO + + def parse_url(self, info_url): + """ + will return the PTL in the INFO file on success, or None + """ + try: + parts = info_url.split("/") + if parts[0].find("http") > -1: # the url include http(s):// + parts = parts[2:] + if parts[-1] != "INFO.yaml": + return None + if parts[0] not in ["github.com", "raw.githubusercontent.com"]: + return None + if parts[1] not in self.LFN_PROJECTS: + return None + # now to download and parse file + if parts[3] == "blob": + parts[3] = "raw" + url = "https://" + "/".join(parts) + info_file = requests.get(url, timeout=15).text + info_parsed = yaml.load(info_file) + ptl = info_parsed.get('project_lead') + if not ptl: + return None + return ptl + + except Exception as e: + return None + + + def booking_allowed(self, booking, repo): + """ + This is the method that will have to change whenever the booking policy changes in the Infra + group / LFN. This is a nice isolation of that administration crap + currently checks if the booking uses multiple servers. if it does, then the owner must be a PTL, + which is checked using the provided info file + """ + if len(booking.resource.template.getHosts()) < 2: + return True #if they only have one server, we dont care + if booking.owner.userprofile.booking_privledge: + return True # admin override for this user + if repo.BOOKING_INFO_FILE not in repo.el: + return False # INFO file not provided + ptl_info = self.parse_url(repo.BOOKING_INFO_FILE) + return ptl_info and ptl_info == booking.owner.userprofile.email_addr + + + +class WorkflowStep(object): + + template = 'bad_request.html' + title = "Generic Step" + description = "You were led here by mistake" + short_title = "error" + metastep = None + + def __init__(self, id, repo=None): + self.repo = repo + self.id = id + + def get_context(self): + context = {} + context['step_number'] = self.repo_get('steps') + context['active_step'] = self.repo_get('active_step') + context['render_correct'] = "true" + context['step_title'] = self.title + context['description'] = self.description + return context + + def render(self, request): + self.context = self.get_context() + return render(request, self.template, self.context) + + def post_render(self, request): + return self.render(request) + + def test_render(self, request): + if request.method == "POST": + return self.post_render(request) + return self.render(request) + + def validate(self, request): + pass + + def repo_get(self, key, default=None): + return self.repo.get(key, default, self.id) + + def repo_put(self, key, value): + return self.repo.put(key, value, self.id) + +class Confirmation_Step(WorkflowStep): + template = 'workflow/confirm.html' + title = "Confirm Changes" + description = "Does this all look right?" + + def get_vlan_warning(self): + grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB, False) + if not grb: + return 0 + vlan_manager = grb.lab.vlan_manager + if vlan_manager is None: + return 0 + hosts = grb.getHosts() + for host in hosts: + for interface in host.generic_interfaces.all(): + for vlan in interface.vlans.all(): + if vlan.public: + if not vlan_manager.public_vlan_is_available(vlan.vlan_id): + return 1 + else: + if not vlan_manager.is_available(vlan.vlan_id): + return 1 # There is a problem with these vlans + return 0 + + + def get_context(self): + context = super(Confirmation_Step, self).get_context() + context['form'] = ConfirmationForm() + context['confirmation_info'] = yaml.dump( + self.repo_get(self.repo.CONFIRMATION), + default_flow_style=False + ).strip() + context['vlan_warning'] = self.get_vlan_warning() + + return context + + def flush_to_db(self): + errors = self.repo.make_models() + if errors: + return errors + + def post_render(self, request): + form = ConfirmationForm(request.POST) + if form.is_valid(): + data = form.cleaned_data['confirm'] + context = self.get_context() + if data == "True": + context["bypassed"] = "true" + errors = self.flush_to_db() + if errors: + messages.add_message(request, messages.ERROR, "ERROR OCCURRED: " + errors) + return render(request, self.template, context) + messages.add_message(request, messages.SUCCESS, "Confirmed") + return render(request, self.template, context) + elif data == "False": + context["bypassed"] = "true" + messages.add_message(request, messages.SUCCESS, "Canceled") + return render(request, self.template, context) + else: + pass + + else: + if "vlan_input" in request.POST: + if request.POST.get("vlan_input") == "True": + self.translate_vlans() + return self.render(request) + pass + + def translate_vlans(self): + grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB, False) + if not grb: + return 0 + vlan_manager = grb.lab.vlan_manager + if vlan_manager is None: + return 0 + hosts = grb.getHosts() + for host in hosts: + for interface in host.generic_interfaces.all(): + for vlan in interface.vlans.all(): + if not vlan.public: + if not vlan_manager.is_available(vlan.vlan_id): + vlan.vlan_id = vlan_manager.get_vlan() + vlan.save() + else: + if not vlan_manager.public_vlan_is_available(vlan.vlan_id): + pub_vlan = vlan_manager.get_public_vlan() + vlan.vlan_id = pub_vlan.vlan + vlan.save() + + +class Workflow(): + + steps = [] + active_index = 0 + +class Repository(): + + EDIT = "editing" + MODELS = "models" + RESOURCE_SELECT = "resource_select" + CONFIRMATION = "confirmation" + SELECTED_GRESOURCE_BUNDLE = "selected generic bundle pk" + GRESOURCE_BUNDLE_MODELS = "generic_resource_bundle_models" + GRESOURCE_BUNDLE_INFO = "generic_resource_bundle_info" + BOOKING = "booking" + LAB = "lab" + GRB_LAST_HOSTLIST = "grb_network_previous_hostlist" + BOOKING_FORMS = "booking_forms" + SWCONF_HOSTS = "swconf_hosts" + SWCONF_SELECTED_GRB = "swconf_selected_grb_pk" + BOOKING_SELECTED_GRB = "booking_selected_grb_pk" + BOOKING_MODELS = "booking models" + CONFIG_MODELS = "configuration bundle models" + SESSION_USER = "session owner user account" + VALIDATED_MODEL_GRB = "valid grb config model instance in db" + VALIDATED_MODEL_CONFIG = "valid config model instance in db" + VALIDATED_MODEL_BOOKING = "valid booking model instance in db" + VLANS = "a list of vlans" + SNAPSHOT_MODELS = "the models for snapshotting" + SNAPSHOT_BOOKING_ID = "the booking id for snapshotting" + SNAPSHOT_NAME = "the name of the snapshot" + SNAPSHOT_DESC = "description of the snapshot" + BOOKING_INFO_FILE = "the INFO.yaml file for this user's booking" + + + def get(self, key, default, id): + self.add_get_history(key, id) + return self.el.get(key, default) + + def put(self,key,val, id): + self.add_put_history(key, id) + self.el[key] = val + + def add_get_history(self, key, id): + self.add_history(key, id, self.get_history) + + def add_put_history(self, key, id): + self.add_history(key, id, self.put_history) + + def add_history(self, key, id, history): + if not key in history: + history[key] = [id] + else: + history[key].append(id) + + def make_models(self): + if self.SNAPSHOT_MODELS in self.el: + errors = self.make_snapshot() + if errors: + return errors + #if GRB WF, create it + if self.GRESOURCE_BUNDLE_MODELS in self.el: + errors = self.make_generic_resource_bundle() + if errors: + return errors + + if self.CONFIG_MODELS in self.el: + errors = self.make_software_config_bundle() + if errors: + return errors + + if self.BOOKING_MODELS in self.el: + errors = self.make_booking() + if errors: + return errors + + + def make_snapshot(self): + owner = self.el[self.SESSION_USER] + models = self.el[self.SNAPSHOT_MODELS] + image = models.get('snapshot', Image()) + booking_id = self.el.get(self.SNAPSHOT_BOOKING_ID) + if not booking_id: + return "SNAP, No booking ID provided" + booking = Booking.objects.get(pk=booking_id) + name = self.el.get(self.SNAPSHOT_NAME) + if not name: + return "SNAP, no name provided" + host = models.get('host') + if not host: + return "SNAP, no host provided" + description = self.el.get(self.SNAPSHOT_DESC, "") + image.from_lab = booking.lab + image.name = name + image.description = description + image.public = False + image.lab_id = -1 + image.owner = owner + image.host_type = host.profile + image.save() + + + def make_generic_resource_bundle(self): + owner = self.el[self.SESSION_USER] + if self.GRESOURCE_BUNDLE_MODELS in self.el: + models = self.el[self.GRESOURCE_BUNDLE_MODELS] + if 'hosts' in models: + hosts = models['hosts'] + else: + return "GRB has no hosts. CODE:0x0002" + if 'bundle' in models: + bundle = models['bundle'] + else: + return "GRB, no bundle in models. CODE:0x0003" + + try: + bundle.owner = owner + bundle.save() + except Exception as e: + return "GRB, saving bundle generated exception: " + str(e) + " CODE:0x0004" + try: + for host in hosts: + genericresource = host.resource + genericresource.bundle = bundle + genericresource.save() + host.resource = genericresource + host.save() + except Exception as e: + return "GRB, saving hosts generated exception: " + str(e) + " CODE:0x0005" + + if 'interfaces' in models: + for interface_set in models['interfaces'].values(): + for interface in interface_set: + try: + interface.host = interface.host + interface.save() + except Exception as e: + return "GRB, saving interface " + str(interface) + " failed. CODE:0x0019" + else: + return "GRB, no interface set provided. CODE:0x001a" + + if 'vlans' in models: + for resource_name, mapping in models['vlans'].items(): + for profile_name, vlan_set in mapping.items(): + interface = GenericInterface.objects.get( + profile__name=profile_name, + host__resource__name=resource_name, + host__resource__bundle=models['bundle'] + ) + for vlan in vlan_set: + try: + vlan.save() + interface.vlans.add(vlan) + except Exception as e: + return "GRB, saving vlan " + str(vlan) + " failed. Exception: " + str(e) + ". CODE:0x0017" + else: + return "GRB, no vlan set provided. CODE:0x0018" + + + else: + return "GRB no models given. CODE:0x0001" + + self.el[self.VALIDATED_MODEL_GRB] = bundle + return False + + + def make_software_config_bundle(self): + owner = self.el[self.SESSION_USER] + models = self.el[self.CONFIG_MODELS] + if 'bundle' in models: + bundle = models['bundle'] + bundle.bundle = bundle.bundle + try: + bundle.save() + except Exception as e: + return "SWC, saving bundle generated exception: " + str(e) + "CODE:0x0007" + + else: + return "SWC, no bundle in models. CODE:0x0006" + if 'host_configs' in models: + host_configs = models['host_configs'] + for host_config in host_configs: + host_config.bundle = host_config.bundle + host_config.host = host_config.host + try: + host_config.save() + except Exception as e: + return "SWC, saving host configs generated exception: " + str(e) + "CODE:0x0009" + else: + return "SWC, no host configs in models. CODE:0x0008" + if 'opnfv' in models: + opnfvconfig = models['opnfv'] + opnfvconfig.bundle = opnfvconfig.bundle + if not opnfvconfig.scenario in opnfvconfig.installer.sup_scenarios.all(): + return "SWC, scenario not supported by installer. CODE:0x000d" + try: + opnfvconfig.save() + except Exception as e: + return "SWC, saving opnfv config generated exception: " + str(e) + "CODE:0x000b" + else: + pass + + self.el[self.VALIDATED_MODEL_CONFIG] = bundle + return False + + + def make_booking(self): + models = self.el[self.BOOKING_MODELS] + owner = self.el[self.SESSION_USER] + + if self.BOOKING_SELECTED_GRB in self.el: + selected_grb = self.el[self.BOOKING_SELECTED_GRB] + else: + return "BOOK, no selected resource. CODE:0x000e" + + if not self.reserve_vlans(selected_grb): + return "BOOK, vlans not available" + + if 'booking' in models: + booking = models['booking'] + else: + return "BOOK, no booking model exists. CODE:0x000f" + + if not booking.start: + return "BOOK, booking has no start. CODE:0x0010" + if not booking.end: + return "BOOK, booking has no end. CODE:0x0011" + if booking.end <= booking.start: + return "BOOK, end before/same time as start. CODE:0x0012" + + if 'collaborators' in models: + collaborators = models['collaborators'] + else: + return "BOOK, collaborators not defined. CODE:0x0013" + try: + resource_bundle = ResourceManager.getInstance().convertResourceBundle(selected_grb, config=booking.config_bundle) + except ResourceAvailabilityException as e: + return "BOOK, requested resources are not available. Exception: " + str(e) + " CODE:0x0014" + except ModelValidationException as e: + return "Error encountered when saving bundle. " + str(e) + " CODE: 0x001b" + + booking.resource = resource_bundle + booking.owner = owner + booking.config_bundle = booking.config_bundle + booking.lab = selected_grb.lab + + is_allowed = BookingAuthManager().booking_allowed(booking, self) + if not is_allowed: + return "BOOK, you are not allowed to book the requested resources" + + try: + booking.save() + except Exception as e: + return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0015" + + for collaborator in collaborators: + booking.collaborators.add(collaborator) + + + try: + JobFactory.makeCompleteJob(booking) + except Exception as e: + return "BOOK, serializing for api generated exception: " + str(e) + " CODE:0xFFFF" + + try: + booking.save() + except Exception as e: + return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0016" + + def reserve_vlans(self, grb): + """ + True is success + """ + vlans = [] + public_vlan = None + vlan_manager = grb.lab.vlan_manager + if vlan_manager is None: + return True + for host in grb.getHosts(): + for interface in host.generic_interfaces.all(): + for vlan in interface.vlans.all(): + if vlan.public: + public_vlan = vlan + else: + vlans.append(vlan.vlan_id) + + try: + vlan_manager.reserve_vlans(vlans) + vlan_manager.reserve_public_vlan(public_vlan.vlan_id) + return True + except Exception as e: + return False + + + def __init__(self): + self.el = {} + self.el[self.CONFIRMATION] = {} + self.get_history = {} + self.put_history = {} -- cgit 1.2.3-korg