diff options
Diffstat (limited to 'src/workflow')
-rw-r--r-- | src/workflow/forms.py | 53 | ||||
-rw-r--r-- | src/workflow/models.py | 159 | ||||
-rw-r--r-- | src/workflow/opnfv_workflow.py | 327 | ||||
-rw-r--r-- | src/workflow/resource_bundle_workflow.py | 214 | ||||
-rw-r--r-- | src/workflow/snapshot_workflow.py | 8 | ||||
-rw-r--r-- | src/workflow/sw_bundle_workflow.py | 193 | ||||
-rw-r--r-- | src/workflow/workflow_factory.py | 15 |
7 files changed, 661 insertions, 308 deletions
diff --git a/src/workflow/forms.py b/src/workflow/forms.py index b40713f..6d26b5c 100644 --- a/src/workflow/forms.py +++ b/src/workflow/forms.py @@ -20,9 +20,8 @@ from resource_inventory.models import ( GenericResourceBundle, ConfigBundle, OPNFVRole, - Image, Installer, - Scenario + Scenario, ) @@ -125,6 +124,7 @@ class SWConfigSelectorForm(forms.Form): bundle = None edit = False resource = None + user = None if "chosen_software" in kwargs: chosen_software = kwargs.pop("chosen_software") @@ -134,18 +134,25 @@ class SWConfigSelectorForm(forms.Form): edit = kwargs.pop("edit") if "resource" in kwargs: resource = kwargs.pop("resource") + if "user" in kwargs: + user = kwargs.pop("user") super(SWConfigSelectorForm, self).__init__(*args, **kwargs) - attrs = self.build_search_widget_attrs(chosen_software, bundle, edit, resource) + attrs = self.build_search_widget_attrs(chosen_software, bundle, edit, resource, user) self.fields['software_bundle'] = forms.CharField( widget=SearchableSelectMultipleWidget(attrs=attrs) ) - def build_search_widget_attrs(self, chosen, bundle, edit, resource): + def build_search_widget_attrs(self, chosen, bundle, edit, resource, user): configs = {} queryset = ConfigBundle.objects.select_related('owner').all() if resource: + if user is None: + user = resource.owner queryset = queryset.filter(bundle=resource) + if user: + queryset = queryset.filter(owner=user) + for config in queryset: displayable = {} displayable['small_name'] = config.name @@ -424,20 +431,14 @@ class NetworkConfigurationForm(forms.Form): class HostSoftwareDefinitionForm(forms.Form): - fields = ["host_name", "role", "image"] host_name = forms.CharField(max_length=200, disabled=True, required=False) - role = forms.ModelChoiceField(queryset=OPNFVRole.objects.all()) - image = forms.ModelChoiceField(queryset=Image.objects.all()) - + headnode = forms.BooleanField(required=False, widget=forms.HiddenInput) -class SoftwareConfigurationForm(forms.Form): - - name = forms.CharField(max_length=200) - description = forms.CharField(widget=forms.Textarea) - opnfv = forms.BooleanField(disabled=True, required=False) - installer = forms.ModelChoiceField(queryset=Installer.objects.all(), disabled=True, required=False) - scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), disabled=True, required=False) + def __init__(self, *args, **kwargs): + imageQS = kwargs.pop("imageQS") + super(HostSoftwareDefinitionForm, self).__init__(*args, **kwargs) + self.fields['image'] = forms.ModelChoiceField(queryset=imageQS) class WorkflowSelectionForm(forms.Form): @@ -461,7 +462,7 @@ class SnapshotHostSelectForm(forms.Form): host = forms.CharField() -class SnapshotMetaForm(forms.Form): +class BasicMetaForm(forms.Form): name = forms.CharField() description = forms.CharField(widget=forms.Textarea) @@ -475,3 +476,23 @@ class ConfirmationForm(forms.Form): (False, "Cancel") ) ) + + +class OPNFVSelectionForm(forms.Form): + installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=True) + scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=True) + + +class OPNFVNetworkRoleForm(forms.Form): + role = forms.CharField(max_length=200, disabled=True, required=False) + + def __init__(self, *args, config_bundle, **kwargs): + super(OPNFVNetworkRoleForm, self).__init__(*args, **kwargs) + self.fields['network'] = forms.ModelChoiceField( + queryset=config_bundle.bundle.networks.all() + ) + + +class OPNFVHostRoleForm(forms.Form): + host_name = forms.CharField(max_length=200, disabled=True, required=False) + role = forms.ModelChoiceField(queryset=OPNFVRole.objects.all().order_by("name").distinct("name")) diff --git a/src/workflow/models.py b/src/workflow/models.py index cdfddef..bf5751d 100644 --- a/src/workflow/models.py +++ b/src/workflow/models.py @@ -19,7 +19,7 @@ import requests from workflow.forms import ConfirmationForm from api.models import JobFactory from dashboard.exceptions import ResourceAvailabilityException, ModelValidationException -from resource_inventory.models import Image, GenericInterface +from resource_inventory.models import Image, GenericInterface, OPNFVConfig, HostOPNFVConfig, NetworkRole from resource_inventory.resource_manager import ResourceManager from resource_inventory.pdf_templater import PDFTemplater from notifier.manager import NotificationHandler @@ -143,10 +143,12 @@ class BookingAuthManager(): 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 Booking.objects.filter(owner=booking.owner, end__gt=timezone.now()).count() >= 3: + return False + if len(booking.resource.template.getHosts()) < 2: + return True # if they only have one server, we dont care if repo.BOOKING_INFO_FILE not in repo.el: return False # INFO file not provided ptl_info = self.parse_url(repo.el.get(repo.BOOKING_INFO_FILE)) @@ -203,27 +205,6 @@ class Confirmation_Step(WorkflowStep): title = "Confirm Changes" description = "Does this all look right?" - def get_vlan_warning(self): - grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False) - if not grb: - return 0 - if self.repo.BOOKING_MODELS not in self.repo.el: - 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() @@ -231,7 +212,6 @@ class Confirmation_Step(WorkflowStep): self.repo_get(self.repo.CONFIRMATION), default_flow_style=False ).strip() - context['vlan_warning'] = self.get_vlan_warning() return context @@ -262,33 +242,8 @@ class Confirmation_Step(WorkflowStep): 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.SELECTED_GRESOURCE_BUNDLE, 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(): @@ -304,6 +259,7 @@ class Repository(): CONFIRMATION = "confirmation" SELECTED_GRESOURCE_BUNDLE = "selected generic bundle pk" SELECTED_CONFIG_BUNDLE = "selected config bundle pk" + SELECTED_OPNFV_CONFIG = "selected opnfv deployment config" GRESOURCE_BUNDLE_MODELS = "generic_resource_bundle_models" GRESOURCE_BUNDLE_INFO = "generic_resource_bundle_info" BOOKING = "booking" @@ -313,6 +269,7 @@ class Repository(): SWCONF_HOSTS = "swconf_hosts" BOOKING_MODELS = "booking models" CONFIG_MODELS = "configuration bundle models" + OPNFV_MODELS = "opnfv configuration 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" @@ -384,6 +341,14 @@ class Repository(): self.el[self.RESULT_KEY] = self.SELECTED_CONFIG_BUNDLE return + if self.OPNFV_MODELS in self.el: + errors = self.make_opnfv_config() + if errors: + return errors + else: + self.el[self.HAS_RESULT] = True + self.el[self.RESULT_KEY] = self.SELECTED_OPNFV_CONFIG + if self.BOOKING_MODELS in self.el: errors = self.make_booking() if errors: @@ -453,6 +418,11 @@ class Repository(): except Exception as e: return "GRB, saving hosts generated exception: " + str(e) + " CODE:0x0005" + if 'networks' in models: + for net in models['networks'].values(): + net.bundle = bundle + net.save() + if 'interfaces' in models: for interface_set in models['interfaces'].values(): for interface in interface_set: @@ -464,20 +434,21 @@ class Repository(): 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(): + if 'connections' in models: + for resource_name, mapping in models['connections'].items(): + for profile_name, connection_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: + for connection in connection_set: try: - vlan.save() - interface.vlans.add(vlan) + connection.network = connection.network + connection.save() + interface.connections.add(connection) except Exception as e: - return "GRB, saving vlan " + str(vlan) + " failed. Exception: " + str(e) + ". CODE:0x0017" + return "GRB, saving vlan " + str(connection) + " failed. Exception: " + str(e) + ". CODE:0x0017" else: return "GRB, no vlan set provided. CODE:0x0018" @@ -534,9 +505,6 @@ class Repository(): 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: @@ -578,7 +546,7 @@ class Repository(): booking.collaborators.add(collaborator) try: - booking.pdf = PDFTemplater.makePDF(booking.resource) + booking.pdf = PDFTemplater.makePDF(booking) booking.save() except Exception as e: return "BOOK, failed to create Pod Desriptor File: " + str(e) @@ -593,29 +561,52 @@ class Repository(): 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: - return False + def make_opnfv_config(self): + opnfv_models = self.el[self.OPNFV_MODELS] + config_bundle = opnfv_models['configbundle'] + if not config_bundle: + return "No Configuration bundle selected" + info = opnfv_models.get("meta", {}) + name = info.get("name", False) + desc = info.get("description", False) + if not (name and desc): + return "No name or description given" + installer = opnfv_models['installer_chosen'] + if not installer: + return "No OPNFV Installer chosen" + scenario = opnfv_models['scenario_chosen'] + if not scenario: + return "No OPNFV Scenario chosen" + + opnfv_config = OPNFVConfig.objects.create( + bundle=config_bundle, + name=name, + description=desc, + installer=installer, + scenario=scenario + ) + + network_roles = opnfv_models['network_roles'] + for net_role in network_roles: + opnfv_config.networks.add( + NetworkRole.objects.create( + name=net_role['role'], + network=net_role['network'] + ) + ) + + host_roles = opnfv_models['host_roles'] + for host_role in host_roles: + config = config_bundle.hostConfigurations.get( + host__resource__name=host_role['host_name'] + ) + HostOPNFVConfig.objects.create( + role=host_role['role'], + host_config=config, + opnfv_config=opnfv_config + ) + + self.el[self.RESULT] = opnfv_config def __init__(self): self.el = {} diff --git a/src/workflow/opnfv_workflow.py b/src/workflow/opnfv_workflow.py new file mode 100644 index 0000000..26e1d7c --- /dev/null +++ b/src/workflow/opnfv_workflow.py @@ -0,0 +1,327 @@ +############################################################################## +# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, 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.forms import formset_factory +from django.contrib import messages + +import json + +from workflow.models import WorkflowStep +from resource_inventory.models import ConfigBundle, OPNFV_SETTINGS +from workflow.forms import OPNFVSelectionForm, OPNFVNetworkRoleForm, OPNFVHostRoleForm, SWConfigSelectorForm, BasicMetaForm + + +class OPNFV_Resource_Select(WorkflowStep): + template = 'booking/steps/swconfig_select.html' + title = "Select Software Configuration" + description = "Choose the software and related configurations you want to use to configure OPNFV" + short_title = "software configuration" + modified_key = "configbundle_step" + + def update_confirmation(self): + confirm = self.repo_get(self.repo.CONFIRMATION, {}) + config_bundle = self.repo_get(self.repo.OPNFV_MODELS, {}).get("configbundle") + if not config_bundle: + return + confirm['software bundle'] = config_bundle.name + confirm['hardware POD'] = config_bundle.bundle.name + self.repo_put(self.repo.CONFIRMATION, confirm) + + def post_render(self, request): + models = self.repo_get(self.repo.OPNFV_MODELS, {}) + form = SWConfigSelectorForm(request.POST) + if form.is_valid(): + bundle_json = form.cleaned_data['software_bundle'] + bundle_json = bundle_json[2:-2] # Stupid django string bug + if not bundle_json: + self.metastep.set_invalid("Please select a valid config") + return self.render(request) + bundle_json = json.loads(bundle_json) + if len(bundle_json) < 1: + self.metastep.set_invalid("Please select a valid config") + return self.render(request) + bundle = None + id = int(bundle_json[0]['id']) + bundle = ConfigBundle.objects.get(id=id) + + models['configbundle'] = bundle + self.repo_put(self.repo.OPNFV_MODELS, models) + self.metastep.set_valid("Step Completed") + messages.add_message(request, messages.SUCCESS, 'Form Validated Successfully', fail_silently=True) + self.update_confirmation() + else: + self.metastep.set_invalid("Please select or create a valid config") + messages.add_message(request, messages.ERROR, "Form Didn't Validate", fail_silently=True) + + return self.render(request) + + def get_context(self): + context = super(OPNFV_Resource_Select, self).get_context() + default = [] + user = self.repo_get(self.repo.SESSION_USER) + + context['form'] = SWConfigSelectorForm(chosen_software=default, bundle=None, edit=True, resource=None, user=user) + return context + + +class Pick_Installer(WorkflowStep): + template = 'config_bundle/steps/pick_installer.html' + title = 'Pick OPNFV Installer' + description = 'Choose which OPNFV installer to use' + short_title = "opnfv installer" + modified_key = "installer_step" + + def update_confirmation(self): + confirm = self.repo_get(self.repo.CONFIRMATION, {}) + models = self.repo_get(self.repo.OPNFV_MODELS, {}) + installer = models.get("installer_chosen") + scenario = models.get("scenario_chosen") + if not (installer and scenario): + return + confirm['installer'] = installer.name + confirm['scenario'] = scenario.name + self.repo_put(self.repo.CONFIRMATION, confirm) + + def get_context(self): + context = super(Pick_Installer, self).get_context() + + models = self.repo_get(self.repo.OPNFV_MODELS, None) + initial = { + "installer": models.get("installer_chosen"), + "scenario": models.get("scenario_chosen") + } + + context["form"] = OPNFVSelectionForm(initial=initial) + return context + + def post_render(self, request): + form = OPNFVSelectionForm(request.POST) + if form.is_valid(): + installer = form.cleaned_data['installer'] + scenario = form.cleaned_data['scenario'] + models = self.repo_get(self.repo.OPNFV_MODELS, {}) + models['installer_chosen'] = installer + models['scenario_chosen'] = scenario + self.repo_put(self.repo.OPNFV_MODELS, models) + self.update_confirmation() + self.metastep.set_valid("Step Completed") + else: + self.metastep.set_invalid("Please select an Installer and Scenario") + + return self.render(request) + + +class Assign_Network_Roles(WorkflowStep): + template = 'config_bundle/steps/assign_network_roles.html' + title = 'Pick Network Roles' + description = 'Choose what role each network should get' + short_title = "network roles" + modified_key = "net_roles_step" + + """ + to do initial filling, repo should have a "network_roles" array with the following structure for each element: + { + "role": <NetworkRole object ref>, + "network": <Network object ref> + } + """ + def create_netformset(self, roles, config_bundle, data=None): + roles_initial = [] + set_roles = self.repo_get(self.repo.OPNFV_MODELS, {}).get("network_roles") + if set_roles: + roles_initial = set_roles + else: + for role in OPNFV_SETTINGS.NETWORK_ROLES: + roles_initial.append({"role": role}) + + Formset = formset_factory(OPNFVNetworkRoleForm, extra=0) + kwargs = { + "initial": roles_initial, + "form_kwargs": {"config_bundle": config_bundle} + } + formset = None + if data: + formset = Formset(data, **kwargs) + else: + formset = Formset(**kwargs) + return formset + + def get_context(self): + context = super(Assign_Network_Roles, self).get_context() + config_bundle = self.repo_get(self.repo.OPNFV_MODELS, {}).get("configbundle") + if config_bundle is None: + context["unavailable"] = True + return context + + roles = OPNFV_SETTINGS.NETWORK_ROLES + formset = self.create_netformset(roles, config_bundle) + context['formset'] = formset + + return context + + def update_confirmation(self): + confirm = self.repo_get(self.repo.CONFIRMATION, {}) + models = self.repo_get(self.repo.OPNFV_MODELS, {}) + roles = models.get("network_roles") + if not roles: + return + confirm['network roles'] = {} + for role in roles: + confirm['network roles'][role['role']] = role['network'].name + self.repo_put(self.repo.CONFIRMATION, confirm) + + def post_render(self, request): + models = self.repo_get(self.repo.OPNFV_MODELS, {}) + config_bundle = models.get("configbundle") + roles = OPNFV_SETTINGS.NETWORK_ROLES + net_role_formset = self.create_netformset(roles, config_bundle, data=request.POST) + if net_role_formset.is_valid(): + results = [] + for form in net_role_formset: + results.append({ + "role": form.cleaned_data['role'], + "network": form.cleaned_data['network'] + }) + models['network_roles'] = results + self.metastep.set_valid("Completed") + self.repo_put(self.repo.OPNFV_MODELS, models) + self.update_confirmation() + else: + self.metastep.set_invalid("Please complete all fields") + return self.render(request) + + +class Assign_Host_Roles(WorkflowStep): # taken verbatim from Define_Software in sw workflow, merge the two? + template = 'config_bundle/steps/assign_host_roles.html' + title = 'Pick Host Roles' + description = "Choose the role each machine will have in your OPNFV pod" + short_title = "host roles" + modified_key = "host_roles_step" + + def create_host_role_formset(self, hostlist=[], data=None): + models = self.repo_get(self.repo.OPNFV_MODELS, {}) + host_roles = models.get("host_roles", []) + if not host_roles: + for host in hostlist: + initial = {"host_name": host.resource.name} + host_roles.append(initial) + models['host_roles'] = host_roles + self.repo_put(self.repo.OPNFV_MODELS, models) + + HostFormset = formset_factory(OPNFVHostRoleForm, extra=0) + + kwargs = {"initial": host_roles} + formset = None + if data: + formset = HostFormset(data, **kwargs) + else: + formset = HostFormset(**kwargs) + + return formset + + def get_context(self): + context = super(Assign_Host_Roles, self).get_context() + models = self.repo_get(self.repo.OPNFV_MODELS, {}) + config = models.get("configbundle") + if config is None: + context['error'] = "Please select a Configuration on the first step" + + formset = self.create_host_role_formset(hostlist=config.bundle.getHosts()) + context['formset'] = formset + + return context + + def get_host_role_mapping(self, host_roles, hostname): + for obj in host_roles: + if hostname == obj['host_name']: + return obj + return None + + def update_confirmation(self): + confirm = self.repo_get(self.repo.CONFIRMATION, {}) + models = self.repo_get(self.repo.OPNFV_MODELS, {}) + roles = models.get("host_roles") + if not roles: + return + confirm['host roles'] = {} + for role in roles: + confirm['host roles'][role['host_name']] = role['role'].name + self.repo_put(self.repo.CONFIRMATION, confirm) + + def post_render(self, request): + formset = self.create_host_role_formset(data=request.POST) + + models = self.repo_get(self.repo.OPNFV_MODELS, {}) + host_roles = models.get("host_roles", []) + + has_jumphost = False + if formset.is_valid(): + for form in formset: + hostname = form.cleaned_data['host_name'] + role = form.cleaned_data['role'] + mapping = self.get_host_role_mapping(host_roles, hostname) + mapping['role'] = role + if "jumphost" in role.name.lower(): + has_jumphost = True + + models['host_roles'] = host_roles + self.repo_put(self.repo.OPNFV_MODELS, models) + self.update_confirmation() + + if not has_jumphost: + self.metastep.set_invalid('Must have at least one "Jumphost" per POD') + else: + self.metastep.set_valid("Completed") + else: + self.metastep.set_invalid("Please complete all fields") + + return self.render(request) + + +class MetaInfo(WorkflowStep): + template = 'config_bundle/steps/config_software.html' + title = "Other Info" + description = "Give your software config a name, description, and other stuff" + short_title = "config info" + + def get_context(self): + context = super(MetaInfo, self).get_context() + + initial = self.repo_get(self.repo.OPNFV_MODELS, {}).get("meta", {}) + context["form"] = BasicMetaForm(initial=initial) + return context + + def update_confirmation(self): + confirm = self.repo_get(self.repo.CONFIRMATION, {}) + models = self.repo_get(self.repo.OPNFV_MODELS, {}) + meta = models.get("meta") + if not meta: + return + confirm['name'] = meta['name'] + confirm['description'] = meta['description'] + self.repo_put(self.repo.CONFIRMATION, confirm) + + def post_render(self, request): + models = self.repo_get(self.repo.OPNFV_MODELS, {}) + info = models.get("meta", {}) + + form = BasicMetaForm(request.POST) + if form.is_valid(): + info['name'] = form.cleaned_data['name'] + info['description'] = form.cleaned_data['description'] + models['meta'] = info + self.repo_put(self.repo.OPNFV_MODELS, models) + self.update_confirmation() + self.metastep.set_valid("Complete") + else: + self.metastep.set_invalid("Please correct the errors shown below") + + self.repo_put(self.repo.OPNFV_MODELS, models) + return self.render(request) diff --git a/src/workflow/resource_bundle_workflow.py b/src/workflow/resource_bundle_workflow.py index 4858ebe..536187f 100644 --- a/src/workflow/resource_bundle_workflow.py +++ b/src/workflow/resource_bundle_workflow.py @@ -10,6 +10,7 @@ from django.shortcuts import render from django.forms import formset_factory +from django.conf import settings import json import re @@ -25,11 +26,12 @@ from workflow.forms import ( ) from resource_inventory.models import ( GenericResourceBundle, - Vlan, GenericInterface, GenericHost, GenericResource, - HostProfile + HostProfile, + Network, + NetworkConnection ) from dashboard.exceptions import ( InvalidVlanConfigurationException, @@ -185,6 +187,7 @@ class Define_Nets(WorkflowStep): hostlist = self.repo_get(self.repo.GRB_LAST_HOSTLIST, None) added_list = [] added_dict = {} + context['debug'] = settings.DEBUG context['added_hosts'] = [] if hostlist is not None: new_hostlist = [] @@ -239,15 +242,15 @@ class Define_Nets(WorkflowStep): self.metastep.set_valid("Networks applied successfully") except ResourceAvailabilityException: self.metastep.set_invalid("Public network not availble") - except Exception: - self.metastep.set_invalid("An error occurred when applying networks") + except Exception as e: + self.metastep.set_invalid("An error occurred when applying networks: " + str(e)) return self.render(request) def updateModels(self, xmlData): models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}) - models["vlans"] = {} - given_hosts, interfaces = self.parseXml(xmlData) - vlan_manager = models['bundle'].lab.vlan_manager + models["connections"] = {} + models['networks'] = {} + given_hosts, interfaces, networks = self.parseXml(xmlData) existing_host_list = models.get("hosts", []) existing_hosts = {} # maps id to host for host in existing_host_list: @@ -255,104 +258,133 @@ class Define_Nets(WorkflowStep): bundle = models.get("bundle", GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER))) + for net_id, net in networks.items(): + network = Network() + network.name = net['name'] + network.bundle = bundle + network.is_public = net['public'] + models['networks'][net_id] = network + for hostid, given_host in given_hosts.items(): existing_host = existing_hosts[hostid[5:]] for ifaceId in given_host['interfaces']: iface = interfaces[ifaceId] - if existing_host.resource.name not in models['vlans']: - models['vlans'][existing_host.resource.name] = {} - models['vlans'][existing_host.resource.name][iface['profile_name']] = [] - for network in iface['networks']: - vlan_id = network['network']['vlan'] - is_public = network['network']['public'] - if is_public: - public_net = vlan_manager.get_public_vlan() - if public_net is None: - raise ResourceAvailabilityException("No public networks available") - vlan_id = vlan_manager.get_public_vlan().vlan - vlan = Vlan(vlan_id=vlan_id, tagged=network['tagged'], public=is_public) - models['vlans'][existing_host.resource.name][iface['profile_name']].append(vlan) + if existing_host.resource.name not in models['connections']: + models['connections'][existing_host.resource.name] = {} + models['connections'][existing_host.resource.name][iface['profile_name']] = [] + for connection in iface['connections']: + network_id = connection['network'] + net = models['networks'][network_id] + connection = NetworkConnection(vlan_is_tagged=connection['tagged'], network=net) + models['connections'][existing_host.resource.name][iface['profile_name']].append(connection) bundle.xml = xmlData self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models) - # serialize and deserialize xml from mxGraph - def parseXml(self, xmlString): - parent_nets = {} # map network ports to networks - networks = {} # maps net id to network object - hosts = {} # cotains id -> hosts, each containing interfaces, referencing networks - interfaces = {} # maps id -> interface + def decomposeXml(self, xmlString): + """ + This function takes in an xml doc from our front end + and returns dictionaries that map cellIds to the xml + nodes themselves. There is no unpacking of the + xml objects, just grouping and organizing + """ + + connections = {} + networks = {} + hosts = {} + interfaces = {} + network_ports = {} + xmlDom = minidom.parseString(xmlString) root = xmlDom.documentElement.firstChild - netids = {} - untagged_ints = {} for cell in root.childNodes: cellId = cell.getAttribute('id') + group = cellId.split("_")[0] + parentGroup = cell.getAttribute("parent").split("_")[0] + # place cell into correct group if cell.getAttribute("edge"): - # cell is a network connection - escaped_json_str = cell.getAttribute("value") - json_str = escaped_json_str.replace('"', '"') - attributes = json.loads(json_str) - tagged = attributes['tagged'] - interface = None - network = None - src = cell.getAttribute("source") - tgt = cell.getAttribute("target") - if src in parent_nets: - # src is a network port - network = networks[parent_nets[src]] - if tgt in untagged_ints and not tagged: - raise InvalidVlanConfigurationException("More than one untagged vlan on an interface") - interface = interfaces[tgt] - untagged_ints[tgt] = True - else: - network = networks[parent_nets[tgt]] - if src in untagged_ints and not tagged: - raise InvalidVlanConfigurationException("More than one untagged vlan on an interface") - interface = interfaces[src] - untagged_ints[src] = True - interface['networks'].append({"network": network, "tagged": tagged}) - - elif "network" in cellId: # cell is a network - escaped_json_str = cell.getAttribute("value") - json_str = escaped_json_str.replace('"', '"') - net_info = json.loads(json_str) - nid = net_info['vlan_id'] - public = net_info['public'] - try: - int_netid = int(nid) - assert public or int_netid > 1, "Net id is 1 or lower" - assert int_netid < 4095, "Net id is 4095 or greater" - except Exception: - raise InvalidVlanConfigurationException("VLAN ID is not an integer more than 1 and less than 4095") - if nid in netids: - raise NetworkExistsException("Non unique network id found") - else: - pass - network = {"name": net_info['name'], "vlan": net_info['vlan_id'], "public": public} - netids[net_info['vlan_id']] = True - networks[cellId] = network - - elif "host" in cellId: # cell is a host/machine - # TODO gather host info - cell_json_str = cell.getAttribute("value") - cell_json = json.loads(cell_json_str) - host = {"interfaces": [], "name": cellId, "profile_name": cell_json['name']} - hosts[cellId] = host - - elif cell.hasAttribute("parent"): - parentId = cell.getAttribute('parent') - if "network" in parentId: - parent_nets[cellId] = parentId - elif "host" in parentId: - # TODO gather iface info - cell_json_str = cell.getAttribute("value") - cell_json = json.loads(cell_json_str) - iface = {"name": cellId, "networks": [], "profile_name": cell_json['name']} - hosts[parentId]['interfaces'].append(cellId) - interfaces[cellId] = iface - return hosts, interfaces + connections[cellId] = cell + + elif "network" in group: + networks[cellId] = cell + + elif "host" in group: + hosts[cellId] = cell + + elif "host" in parentGroup: + interfaces[cellId] = cell + + # make network ports also map to thier network + elif "network" in parentGroup: + network_ports[cellId] = cell.getAttribute("parent") # maps port ID to net ID + + return connections, networks, hosts, interfaces, network_ports + + # serialize and deserialize xml from mxGraph + def parseXml(self, xmlString): + networks = {} # maps net name to network object + hosts = {} # cotains id -> hosts, each containing interfaces, referencing networks + interfaces = {} # maps id -> interface + untagged_ifaces = set() # used to check vlan config + network_names = set() # used to check network names + xml_connections, xml_nets, xml_hosts, xml_ifaces, xml_ports = self.decomposeXml(xmlString) + + # parse Hosts + for cellId, cell in xml_hosts.items(): + cell_json_str = cell.getAttribute("value") + cell_json = json.loads(cell_json_str) + host = {"interfaces": [], "name": cellId, "profile_name": cell_json['name']} + hosts[cellId] = host + + # parse networks + for cellId, cell in xml_nets.items(): + escaped_json_str = cell.getAttribute("value") + json_str = escaped_json_str.replace('"', '"') + net_info = json.loads(json_str) + net_name = net_info['name'] + public = net_info['public'] + if net_name in network_names: + raise NetworkExistsException("Non unique network name found") + network = {"name": net_name, "public": public, "id": cellId} + networks[cellId] = network + network_names.add(net_name) + + # parse interfaces + for cellId, cell in xml_ifaces.items(): + parentId = cell.getAttribute('parent') + cell_json_str = cell.getAttribute("value") + cell_json = json.loads(cell_json_str) + iface = {"name": cellId, "connections": [], "profile_name": cell_json['name']} + hosts[parentId]['interfaces'].append(cellId) + interfaces[cellId] = iface + + # parse connections + for cellId, cell in xml_connections.items(): + escaped_json_str = cell.getAttribute("value") + json_str = escaped_json_str.replace('"', '"') + attributes = json.loads(json_str) + tagged = attributes['tagged'] + interface = None + network = None + src = cell.getAttribute("source") + tgt = cell.getAttribute("target") + if src in interfaces: + interface = interfaces[src] + network = networks[xml_ports[tgt]] + else: + interface = interfaces[tgt] + network = networks[xml_ports[src]] + + if not tagged: + if interface['name'] in untagged_ifaces: + raise InvalidVlanConfigurationException("More than one untagged vlan on an interface") + untagged_ifaces.add(interface['name']) + + # add connection to interface + interface['connections'].append({"tagged": tagged, "network": network['id']}) + + return hosts, interfaces, networks class Resource_Meta_Info(WorkflowStep): diff --git a/src/workflow/snapshot_workflow.py b/src/workflow/snapshot_workflow.py index 002aee5..34ac3a5 100644 --- a/src/workflow/snapshot_workflow.py +++ b/src/workflow/snapshot_workflow.py @@ -14,7 +14,7 @@ import json from booking.models import Booking from resource_inventory.models import Host, Image from workflow.models import WorkflowStep -from workflow.forms import SnapshotMetaForm, SnapshotHostSelectForm +from workflow.forms import BasicMetaForm, SnapshotHostSelectForm class Select_Host_Step(WorkflowStep): @@ -91,14 +91,14 @@ class Image_Meta_Step(WorkflowStep): desc = self.repo_get(self.repo.SNAPSHOT_DESC, False) form = None if name and desc: - form = SnapshotMetaForm(initial={"name": name, "description": desc}) + form = BasicMetaForm(initial={"name": name, "description": desc}) else: - form = SnapshotMetaForm() + form = BasicMetaForm() context['form'] = form return context def post_render(self, request): - form = SnapshotMetaForm(request.POST) + form = BasicMetaForm(request.POST) if form.is_valid(): name = form.cleaned_data['name'] self.repo_put(self.repo.SNAPSHOT_NAME, name) diff --git a/src/workflow/sw_bundle_workflow.py b/src/workflow/sw_bundle_workflow.py index fd41018..a6a7464 100644 --- a/src/workflow/sw_bundle_workflow.py +++ b/src/workflow/sw_bundle_workflow.py @@ -11,9 +11,9 @@ from django.forms import formset_factory from workflow.models import WorkflowStep -from workflow.forms import SoftwareConfigurationForm, HostSoftwareDefinitionForm +from workflow.forms import BasicMetaForm, HostSoftwareDefinitionForm from workflow.booking_workflow import Resource_Select -from resource_inventory.models import Image, GenericHost, ConfigBundle, HostConfiguration, Installer, OPNFVConfig +from resource_inventory.models import Image, GenericHost, ConfigBundle, HostConfiguration # resource selection step is reused from Booking workflow @@ -39,48 +39,57 @@ class Define_Software(WorkflowStep): description = "Choose the opnfv and image of your machines" short_title = "host config" - def create_hostformset(self, hostlist): + def build_filter_data(self, hosts_data): + """ + returns a 2D array of images to exclude + based on the ordering of the passed + hosts_data + """ + filter_data = [] + user = self.repo_get(self.repo.SESSION_USER) + lab = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE).lab + for i, host_data in enumerate(hosts_data): + host = GenericHost.objects.get(pk=host_data['host_id']) + wrong_owner = Image.objects.exclude(owner=user).exclude(public=True) + wrong_host = Image.objects.exclude(host_type=host.profile) + wrong_lab = Image.objects.exclude(from_lab=lab) + excluded_images = wrong_owner | wrong_host | wrong_lab + filter_data.append([]) + for image in excluded_images: + filter_data[i].append(image.pk) + return filter_data + + def create_hostformset(self, hostlist, data=None): hosts_initial = [] host_configs = self.repo_get(self.repo.CONFIG_MODELS, {}).get("host_configs", False) if host_configs: for config in host_configs: - host_initial = {'host_id': config.host.id, 'host_name': config.host.resource.name} - host_initial['role'] = config.opnfvRole - host_initial['image'] = config.image - hosts_initial.append(host_initial) - + hosts_initial.append({ + 'host_id': config.host.id, + 'host_name': config.host.resource.name, + 'headnode': config.is_head_node, + 'image': config.image + }) else: for host in hostlist: - host_initial = {'host_id': host.id, 'host_name': host.resource.name} - - hosts_initial.append(host_initial) + hosts_initial.append({ + 'host_id': host.id, + 'host_name': host.resource.name + }) HostFormset = formset_factory(HostSoftwareDefinitionForm, extra=0) - host_formset = HostFormset(initial=hosts_initial) + filter_data = self.build_filter_data(hosts_initial) - filter_data = {} - user = self.repo_get(self.repo.SESSION_USER) - i = 0 - for host_data in hosts_initial: - host_profile = None - try: - host = GenericHost.objects.get(pk=host_data['host_id']) - host_profile = host.profile - except Exception: - for host in hostlist: - if host.resource.name == host_data['host_name']: - host_profile = host.profile - break - excluded_images = Image.objects.exclude(owner=user).exclude(public=True) - excluded_images = excluded_images | Image.objects.exclude(host_type=host_profile) - lab = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE).lab - excluded_images = excluded_images | Image.objects.exclude(from_lab=lab) - filter_data["id_form-" + str(i) + "-image"] = [] - for image in excluded_images: - filter_data["id_form-" + str(i) + "-image"].append(image.name) - i += 1 + class SpecialHostFormset(HostFormset): + def get_form_kwargs(self, index): + kwargs = super(SpecialHostFormset, self).get_form_kwargs(index) + if index is not None: + kwargs['imageQS'] = Image.objects.exclude(pk__in=filter_data[index]) + return kwargs - return host_formset, filter_data + if data: + return SpecialHostFormset(data, initial=hosts_initial) + return SpecialHostFormset(initial=hosts_initial) def get_host_list(self, grb=None): if grb is None: @@ -99,9 +108,9 @@ class Define_Software(WorkflowStep): if grb: context["grb"] = grb - formset, filter_data = self.create_hostformset(self.get_host_list(grb)) + formset = self.create_hostformset(self.get_host_list(grb)) context["formset"] = formset - context["filter_data"] = filter_data + context['headnode'] = self.repo_get(self.repo.CONFIG_MODELS, {}).get("headnode_index", 1) else: context["error"] = "Please select a resource first" self.metastep.set_invalid("Step requires information that is not yet provided by previous step") @@ -115,47 +124,35 @@ class Define_Software(WorkflowStep): confirm = self.repo_get(self.repo.CONFIRMATION, {}) - HostFormset = formset_factory(HostSoftwareDefinitionForm, extra=0) - formset = HostFormset(request.POST) hosts = self.get_host_list() - has_jumphost = False + models['headnode_index'] = request.POST.get("headnode", 1) + formset = self.create_hostformset(hosts, data=request.POST) + has_headnode = False if formset.is_valid(): models['host_configs'] = [] - i = 0 confirm_hosts = [] - for form in formset: + for i, form in enumerate(formset): host = hosts[i] - i += 1 image = form.cleaned_data['image'] - # checks image compatability - grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE) - lab = None - if grb: - lab = grb.lab - try: - owner = self.repo_get(self.repo.SESSION_USER) - q = Image.objects.filter(owner=owner) | Image.objects.filter(public=True) - q.filter(host_type=host.profile) - q.filter(from_lab=lab) - q.get(id=image.id) # will throw exception if image is not in q - except Exception: - self.metastep.set_invalid("Image " + image.name + " is not compatible with host " + host.resource.name) - role = form.cleaned_data['role'] - if "jumphost" in role.name.lower(): - has_jumphost = True + headnode = form.cleaned_data['headnode'] + if headnode: + has_headnode = True bundle = models['bundle'] hostConfig = HostConfiguration( host=host, image=image, bundle=bundle, - opnfvRole=role + is_head_node=headnode ) models['host_configs'].append(hostConfig) - confirm_host = {"name": host.resource.name, "image": image.name, "role": role.name} - confirm_hosts.append(confirm_host) - - if not has_jumphost: - self.metastep.set_invalid('Must have at least one "Jumphost" per POD') + confirm_hosts.append({ + "name": host.resource.name, + "image": image.name, + "headnode": headnode + }) + + if not has_headnode: + self.metastep.set_invalid('Must have one "Headnode" per POD') return self.render(request) self.repo_put(self.repo.CONFIG_MODELS, models) @@ -172,8 +169,6 @@ class Define_Software(WorkflowStep): class Config_Software(WorkflowStep): template = 'config_bundle/steps/config_software.html' - form = SoftwareConfigurationForm - context = {'workspace_form': form} title = "Other Info" description = "Give your software config a name, description, and other stuff" short_title = "config info" @@ -187,58 +182,30 @@ class Config_Software(WorkflowStep): if bundle: initial['name'] = bundle.name initial['description'] = bundle.description - opnfv = models.get("opnfv", False) - if opnfv: - initial['installer'] = opnfv.installer - initial['scenario'] = opnfv.scenario - else: - initial['opnfv'] = False - supported = {} - for installer in Installer.objects.all(): - supported[str(installer)] = [] - for scenario in installer.sup_scenarios.all(): - supported[str(installer)].append(str(scenario)) - - context["form"] = SoftwareConfigurationForm(initial=initial) - context['supported'] = supported - + context["form"] = BasicMetaForm(initial=initial) return context def post_render(self, request): - try: - models = self.repo_get(self.repo.CONFIG_MODELS, {}) - if "bundle" not in models: - models['bundle'] = ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER)) + models = self.repo_get(self.repo.CONFIG_MODELS, {}) + if "bundle" not in models: + models['bundle'] = ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER)) - confirm = self.repo_get(self.repo.CONFIRMATION, {}) - if "configuration" not in confirm: - confirm['configuration'] = {} + confirm = self.repo_get(self.repo.CONFIRMATION, {}) + if "configuration" not in confirm: + confirm['configuration'] = {} - form = self.form(request.POST) - if form.is_valid(): - models['bundle'].name = form.cleaned_data['name'] - models['bundle'].description = form.cleaned_data['description'] - if form.cleaned_data['opnfv']: - installer = form.cleaned_data['installer'] - scenario = form.cleaned_data['scenario'] - opnfv = OPNFVConfig( - bundle=models['bundle'], - installer=installer, - scenario=scenario - ) - models['opnfv'] = opnfv - confirm['configuration']['installer'] = form.cleaned_data['installer'].name - confirm['configuration']['scenario'] = form.cleaned_data['scenario'].name - - confirm['configuration']['name'] = form.cleaned_data['name'] - confirm['configuration']['description'] = form.cleaned_data['description'] - self.metastep.set_valid("Complete") - else: - self.metastep.set_invalid("Please correct the errors shown below") + form = BasicMetaForm(request.POST) + if form.is_valid(): + models['bundle'].name = form.cleaned_data['name'] + models['bundle'].description = form.cleaned_data['description'] - self.repo_put(self.repo.CONFIG_MODELS, models) - self.repo_put(self.repo.CONFIRMATION, confirm) + confirm['configuration']['name'] = form.cleaned_data['name'] + confirm['configuration']['description'] = form.cleaned_data['description'] + self.metastep.set_valid("Complete") + else: + self.metastep.set_invalid("Please correct the errors shown below") + + self.repo_put(self.repo.CONFIG_MODELS, models) + self.repo_put(self.repo.CONFIRMATION, confirm) - except Exception: - pass return self.render(request) diff --git a/src/workflow/workflow_factory.py b/src/workflow/workflow_factory.py index f5e2ad1..db2bba1 100644 --- a/src/workflow/workflow_factory.py +++ b/src/workflow/workflow_factory.py @@ -12,6 +12,7 @@ from workflow.booking_workflow import Booking_Resource_Select, SWConfig_Select, from workflow.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info from workflow.sw_bundle_workflow import Config_Software, Define_Software, SWConf_Resource_Select from workflow.snapshot_workflow import Select_Host_Step, Image_Meta_Step +from workflow.opnfv_workflow import Pick_Installer, Assign_Network_Roles, Assign_Host_Roles, OPNFV_Resource_Select, MetaInfo from workflow.models import Confirmation_Step import uuid @@ -36,6 +37,11 @@ class ConfigMetaWorkflow(object): color = "#00ffcc" +class OPNFVMetaWorkflow(object): + workflow_type = 3 + color = "000000" + + class MetaStep(object): UNTOUCHED = 0 @@ -110,12 +116,21 @@ class WorkflowFactory(): Image_Meta_Step, ] + opnfv_steps = [ + OPNFV_Resource_Select, + Pick_Installer, + Assign_Network_Roles, + Assign_Host_Roles, + MetaInfo + ] + def conjure(self, workflow_type=None, repo=None): workflow_types = [ self.booking_steps, self.resource_steps, self.config_steps, self.snapshot_steps, + self.opnfv_steps, ] steps = self.make_steps(workflow_types[workflow_type], repository=repo) |