From 2ec0d7b9f5c1354977b821c6b06c24a3ffa13142 Mon Sep 17 00:00:00 2001 From: Gergely Csatari Date: Thu, 26 Oct 2023 10:33:28 +0300 Subject: Removing project content and adding a note that the development continues in GitHub Change-Id: I25c58a679dbf92b2367d826429b7cda936bf9f0e Signed-off-by: Gergely Csatari --- src/workflow/README | 31 -- src/workflow/__init__.py | 8 - src/workflow/apps.py | 15 - src/workflow/booking_workflow.py | 182 -------- src/workflow/forms.py | 489 ---------------------- src/workflow/models.py | 693 ------------------------------- src/workflow/opnfv_workflow.py | 292 ------------- src/workflow/resource_bundle_workflow.py | 614 --------------------------- src/workflow/snapshot_workflow.py | 116 ------ src/workflow/tests/__init__.py | 8 - src/workflow/tests/constants.py | 198 --------- src/workflow/tests/test_fixtures.py | 2 - src/workflow/tests/test_steps.py | 269 ------------ src/workflow/tests/test_workflows.py | 99 ----- src/workflow/urls.py | 23 - src/workflow/views.py | 112 ----- src/workflow/workflow_factory.py | 126 ------ src/workflow/workflow_manager.py | 270 ------------ 18 files changed, 3547 deletions(-) delete mode 100644 src/workflow/README delete mode 100644 src/workflow/__init__.py delete mode 100644 src/workflow/apps.py delete mode 100644 src/workflow/booking_workflow.py delete mode 100644 src/workflow/forms.py delete mode 100644 src/workflow/models.py delete mode 100644 src/workflow/opnfv_workflow.py delete mode 100644 src/workflow/resource_bundle_workflow.py delete mode 100644 src/workflow/snapshot_workflow.py delete mode 100644 src/workflow/tests/__init__.py delete mode 100644 src/workflow/tests/constants.py delete mode 100644 src/workflow/tests/test_fixtures.py delete mode 100644 src/workflow/tests/test_steps.py delete mode 100644 src/workflow/tests/test_workflows.py delete mode 100644 src/workflow/urls.py delete mode 100644 src/workflow/views.py delete mode 100644 src/workflow/workflow_factory.py delete mode 100644 src/workflow/workflow_manager.py (limited to 'src/workflow') diff --git a/src/workflow/README b/src/workflow/README deleted file mode 100644 index fb4b949..0000000 --- a/src/workflow/README +++ /dev/null @@ -1,31 +0,0 @@ -This app creates "workflows", which are long and complex interactions from the user. -Workflows are composed of multiple steps. At each step the user inputs some information. -The content of one step may impact following steps. - -The WorkflowStep object is the abstract type for all the workflow steps. -Important attributes and methods: - -template - the django template to use when rendering this step -valid - the status code from WorkflowStepStatus - -get_context() - returns a dictionary that is used when rendering this step's template - You should always call super's get_context and add / overwrite any data into that - dictionary - -post(data, user) - this method is called when the step is POST'd to. - data is from the request object, suitable for a Form's constructor - - -Repository -Each step has a reference to a shared repository (self.repo). -The repo is a key-value store that allows the steps to share data - -Steps render based on the current state of the repo. For example, a step -may get information about each host the user said they want and ask for additional -input for each machine. -Because the steps render based on what is in the repo, a user can easily go back to -a previous step and change some data. This data will change in the repo and -affect later steps accordingly. - -Everything stored in the repo is temporary. After a workflow has been completed, the repo -is translated into Django models and saved to the database. diff --git a/src/workflow/__init__.py b/src/workflow/__init__.py deleted file mode 100644 index e0408fa..0000000 --- a/src/workflow/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# 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 -############################################################################## diff --git a/src/workflow/apps.py b/src/workflow/apps.py deleted file mode 100644 index adc2738..0000000 --- a/src/workflow/apps.py +++ /dev/null @@ -1,15 +0,0 @@ -############################################################################## -# 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.apps import AppConfig - - -class WorkflowConfig(AppConfig): - name = 'workflow' diff --git a/src/workflow/booking_workflow.py b/src/workflow/booking_workflow.py deleted file mode 100644 index ef89804..0000000 --- a/src/workflow/booking_workflow.py +++ /dev/null @@ -1,182 +0,0 @@ -############################################################################## -# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others. -# Copyright (c) 2020 Sawyer Bergeron, Sean Smith, 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.utils import timezone - -from datetime import timedelta - -from booking.models import Booking -from workflow.models import WorkflowStep, AbstractSelectOrCreate -from workflow.forms import ResourceSelectorForm, BookingMetaForm, OPNFVSelectForm -from resource_inventory.models import OPNFVConfig, ResourceTemplate -from django.db.models import Q - - -""" -subclassing notes: - subclasses have to define the following class attributes: - self.repo_key: main output of step, where the selected/created single selector - result is placed at the end - self.confirm_key: -""" - - -class Abstract_Resource_Select(AbstractSelectOrCreate): - form = ResourceSelectorForm - template = 'dashboard/genericselect.html' - title = "Select Resource" - description = "Select a resource template to use for your deployment" - short_title = "pod select" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.select_repo_key = self.repo.SELECTED_RESOURCE_TEMPLATE - self.confirm_key = self.workflow_type - - def alert_bundle_missing(self): - self.set_invalid("Please select a valid resource template") - - def get_form_queryset(self): - user = self.repo_get(self.repo.SESSION_USER) - return ResourceTemplate.objects.filter((Q(owner=user) | Q(public=True))) - - def get_page_context(self): - return { - 'select_type': 'resource', - 'select_type_title': 'Resource template', - 'addable_type_num': 1 - } - - def put_confirm_info(self, bundle): - confirm_dict = self.repo_get(self.repo.CONFIRMATION) - if self.confirm_key not in confirm_dict: - confirm_dict[self.confirm_key] = {} - confirm_dict[self.confirm_key]["Resource Template"] = bundle.name - self.repo_put(self.repo.CONFIRMATION, confirm_dict) - - -class Booking_Resource_Select(Abstract_Resource_Select): - workflow_type = "booking" - - -class OPNFV_EnablePicker(object): - pass - - -class OPNFV_Select(AbstractSelectOrCreate, OPNFV_EnablePicker): - title = "Choose an OPNFV Config" - description = "Choose or create a description of how you want to deploy OPNFV" - short_title = "opnfv config" - form = OPNFVSelectForm - enabled = False - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.select_repo_key = self.repo.SELECTED_OPNFV_CONFIG - self.confirm_key = "booking" - - def alert_bundle_missing(self): - self.set_invalid("Please select a valid OPNFV config") - - def get_form_queryset(self): - cb = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE) - qs = OPNFVConfig.objects.filter(bundle=cb) - return qs - - def put_confirm_info(self, config): - confirm_dict = self.repo_get(self.repo.CONFIRMATION) - if self.confirm_key not in confirm_dict: - confirm_dict[self.confirm_key] = {} - confirm_dict[self.confirm_key]["OPNFV Configuration"] = config.name - self.repo_put(self.repo.CONFIRMATION, confirm_dict) - - def get_page_context(self): - return { - 'select_type': 'opnfv', - 'select_type_title': 'OPNFV Config', - 'addable_type_num': 4 - } - - -class Booking_Meta(WorkflowStep): - template = 'booking/steps/booking_meta.html' - title = "Extra Info" - description = "Tell us how long you want your booking, what it is for, and who else should have access to it" - short_title = "booking info" - - def get_context(self): - context = super(Booking_Meta, self).get_context() - initial = {} - default = [] - try: - models = self.repo_get(self.repo.BOOKING_MODELS, {}) - booking = models.get("booking") - if booking: - initial['purpose'] = booking.purpose - initial['project'] = booking.project - initial['length'] = (booking.end - booking.start).days - info = self.repo_get(self.repo.BOOKING_INFO_FILE, False) - if info: - initial['info_file'] = info - users = models.get("collaborators", []) - for user in users: - default.append(user.userprofile) - except Exception: - pass - - owner = self.repo_get(self.repo.SESSION_USER) - - context['form'] = BookingMetaForm(initial=initial, user_initial=default, owner=owner) - return context - - def post(self, post_data, user): - form = BookingMetaForm(data=post_data, owner=user) - - forms = self.repo_get(self.repo.BOOKING_FORMS, {}) - - forms["meta_form"] = form - self.repo_put(self.repo.BOOKING_FORMS, forms) - - if form.is_valid(): - models = self.repo_get(self.repo.BOOKING_MODELS, {}) - if "booking" not in models: - models['booking'] = Booking() - models['collaborators'] = [] - confirm = self.repo_get(self.repo.CONFIRMATION) - if "booking" not in confirm: - confirm['booking'] = {} - - models['booking'].start = timezone.now() - models['booking'].end = timezone.now() + timedelta(days=int(form.cleaned_data['length'])) - models['booking'].purpose = form.cleaned_data['purpose'] - models['booking'].project = form.cleaned_data['project'] - for key in ['length', 'project', 'purpose']: - confirm['booking'][key] = form.cleaned_data[key] - - if form.cleaned_data["deploy_opnfv"]: - self.repo_get(self.repo.SESSION_MANAGER).set_step_statuses(OPNFV_EnablePicker, desired_enabled=True) - else: - self.repo_get(self.repo.SESSION_MANAGER).set_step_statuses(OPNFV_EnablePicker, desired_enabled=False) - - userprofile_list = form.cleaned_data['users'] - confirm['booking']['collaborators'] = [] - for userprofile in userprofile_list: - models['collaborators'].append(userprofile.user) - confirm['booking']['collaborators'].append(userprofile.user.username) - - info_file = form.cleaned_data.get("info_file", False) - if info_file: - self.repo_put(self.repo.BOOKING_INFO_FILE, info_file) - - self.repo_put(self.repo.BOOKING_MODELS, models) - self.repo_put(self.repo.CONFIRMATION, confirm) - self.set_valid("Step Completed") - else: - self.set_invalid("Please complete the fields highlighted in red to continue") diff --git a/src/workflow/forms.py b/src/workflow/forms.py deleted file mode 100644 index 62abad6..0000000 --- a/src/workflow/forms.py +++ /dev/null @@ -1,489 +0,0 @@ -############################################################################## -# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others. -# Copyright (c) 2020 Sawyer Bergeron, Sean Smith, 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 -############################################################################## - - -import django.forms as forms -from django.forms import widgets, ValidationError -from django.utils.safestring import mark_safe -from django.template.loader import render_to_string -from django.forms.widgets import NumberInput - -import json -import urllib - -from account.models import Lab -from account.models import UserProfile -from resource_inventory.models import ( - OPNFVRole, - Installer, - Scenario -) -from resource_inventory.resource_manager import ResourceManager -from booking.lib import get_user_items, get_user_field_opts - - -class SearchableSelectMultipleWidget(widgets.SelectMultiple): - template_name = 'dashboard/searchable_select_multiple.html' - - def __init__(self, attrs=None): - self.items = attrs['items'] - self.show_from_noentry = attrs['show_from_noentry'] - self.show_x_results = attrs['show_x_results'] - self.results_scrollable = attrs['results_scrollable'] - self.selectable_limit = attrs['selectable_limit'] - self.placeholder = attrs['placeholder'] - self.name = attrs['name'] - self.initial = attrs.get("initial", []) - - super(SearchableSelectMultipleWidget, self).__init__() - - def render(self, name, value, attrs=None, renderer=None): - - context = self.get_context(attrs) - return mark_safe(render_to_string(self.template_name, context)) - - def get_context(self, attrs): - return { - 'items': self.items, - 'name': self.name, - 'show_from_noentry': self.show_from_noentry, - 'show_x_results': self.show_x_results, - 'results_scrollable': self.results_scrollable, - 'selectable_limit': self.selectable_limit, - 'placeholder': self.placeholder, - 'initial': self.initial, - } - - -class SearchableSelectMultipleField(forms.Field): - def __init__(self, *args, required=True, widget=None, label=None, disabled=False, - items=None, queryset=None, show_from_noentry=True, show_x_results=-1, - results_scrollable=False, selectable_limit=-1, placeholder="search here", - name="searchable_select", initial=[], **kwargs): - """ - From the documentation. - - # required -- Boolean that specifies whether the field is required. - # True by default. - # widget -- A Widget class, or instance of a Widget class, that should - # be used for this Field when displaying it. Each Field has a - # default Widget that it'll use if you don't specify this. In - # most cases, the default widget is TextInput. - # label -- A verbose name for this field, for use in displaying this - # field in a form. By default, Django will use a "pretty" - # version of the form field name, if the Field is part of a - # Form. - # initial -- A value to use in this Field's initial display. This value - # is *not* used as a fallback if data isn't given. - # help_text -- An optional string to use as "help text" for this Field. - # error_messages -- An optional dictionary to override the default - # messages that the field will raise. - # show_hidden_initial -- Boolean that specifies if it is needed to render a - # hidden widget with initial value after widget. - # validators -- List of additional validators to use - # localize -- Boolean that specifies if the field should be localized. - # disabled -- Boolean that specifies whether the field is disabled, that - # is its widget is shown in the form but not editable. - # label_suffix -- Suffix to be added to the label. Overrides - # form's label_suffix. - """ - self.widget = widget - if self.widget is None: - self.widget = SearchableSelectMultipleWidget( - attrs={ - 'items': items, - 'initial': [obj.id for obj in initial], - 'show_from_noentry': show_from_noentry, - 'show_x_results': show_x_results, - 'results_scrollable': results_scrollable, - 'selectable_limit': selectable_limit, - 'placeholder': placeholder, - 'name': name, - 'disabled': disabled - } - ) - self.disabled = disabled - self.queryset = queryset - self.selectable_limit = selectable_limit - - super().__init__(disabled=disabled, **kwargs) - - self.required = required - - def clean(self, data): - data = data[0] - if not data: - if self.required: - raise ValidationError("Nothing was selected") - else: - return [] - try: - data_as_list = json.loads(data) - except json.decoder.JSONDecodeError: - data_as_list = None - if not data_as_list: - raise ValidationError("Contents Not JSON") - if self.selectable_limit != -1: - if len(data_as_list) > self.selectable_limit: - raise ValidationError("Too many items were selected") - - items = [] - for elem in data_as_list: - items.append(self.queryset.get(id=elem)) - - return items - - -class SearchableSelectAbstractForm(forms.Form): - def __init__(self, *args, queryset=None, initial=[], **kwargs): - self.queryset = queryset - items = self.generate_items(self.queryset) - options = self.generate_options() - - super(SearchableSelectAbstractForm, self).__init__(*args, **kwargs) - self.fields['searchable_select'] = SearchableSelectMultipleField( - initial=initial, - items=items, - queryset=self.queryset, - **options - ) - - def get_validated_bundle(self): - bundles = self.cleaned_data['searchable_select'] - if len(bundles) < 1: # don't need to check for >1, as field does that for us - raise ValidationError("No bundle was selected") - return bundles[0] - - def generate_items(self, queryset): - raise Exception("SearchableSelectAbstractForm does not implement concrete generate_items()") - - def generate_options(self, disabled=False): - return { - 'show_from_noentry': True, - 'show_x_results': -1, - 'results_scrollable': True, - 'selectable_limit': 1, - 'placeholder': 'Search for a Bundle', - 'name': 'searchable_select', - 'disabled': False - } - - -class SWConfigSelectorForm(SearchableSelectAbstractForm): - def generate_items(self, queryset): - items = {} - - for bundle in queryset: - items[bundle.id] = { - 'expanded_name': bundle.name, - 'small_name': bundle.owner.username, - 'string': bundle.description, - 'id': bundle.id - } - - return items - - -class OPNFVSelectForm(SearchableSelectAbstractForm): - def generate_items(self, queryset): - items = {} - - for config in queryset: - items[config.id] = { - 'expanded_name': config.name, - 'small_name': config.bundle.owner.username, - 'string': config.description, - 'id': config.id - } - - return items - - -class ResourceSelectorForm(SearchableSelectAbstractForm): - def generate_items(self, queryset): - items = {} - - for bundle in queryset: - items[bundle.id] = { - 'expanded_name': bundle.name, - 'small_name': bundle.owner.username, - 'string': bundle.description, - 'id': bundle.id - } - - return items - - -class BookingMetaForm(forms.Form): - # Django Form class for Book a Pod - length = forms.IntegerField( - widget=NumberInput( - attrs={ - "type": "range", - 'min': "1", - "max": "21", - "value": "1" - } - ) - ) - purpose = forms.CharField(max_length=1000) - project = forms.CharField(max_length=400) - info_file = forms.CharField(max_length=1000, required=False) - deploy_opnfv = forms.BooleanField(required=False) - - def __init__(self, *args, user_initial=[], owner=None, **kwargs): - super(BookingMetaForm, self).__init__(**kwargs) - - self.fields['users'] = SearchableSelectMultipleField( - queryset=UserProfile.objects.select_related('user').exclude(user=owner), - initial=user_initial, - items=get_user_items(exclude=owner), - required=False, - **get_user_field_opts() - ) - - -class MultipleSelectFilterWidget(forms.Widget): - def __init__(self, *args, display_objects=None, filter_items=None, neighbors=None, **kwargs): - super(MultipleSelectFilterWidget, self).__init__(*args, **kwargs) - self.display_objects = display_objects - self.filter_items = filter_items - self.neighbors = neighbors - self.template_name = "dashboard/multiple_select_filter_widget.html" - - def render(self, name, value, attrs=None, renderer=None): - context = self.get_context(name, value, attrs) - html = render_to_string(self.template_name, context=context) - return mark_safe(html) - - def get_context(self, name, value, attrs): - return { - 'display_objects': self.display_objects, - 'neighbors': self.neighbors, - 'filter_items': self.filter_items, - 'initial_value': value - } - - -class MultipleSelectFilterField(forms.Field): - - def __init__(self, **kwargs): - self.initial = kwargs.get("initial") - super().__init__(**kwargs) - - def to_python(self, value): - try: - return json.loads(value) - except json.decoder.JSONDecodeError: - pass - raise ValidationError("content is not valid JSON") - - -class FormUtils: - @staticmethod - def getLabData(multiple_hosts=False, user=None): - """ - Get all labs and thier host profiles, returns a serialized version the form can understand. - - Could be rewritten with a related query to make it faster - """ - # javascript truthy variables - true = 1 - false = 0 - if multiple_hosts: - multiple_hosts = true - else: - multiple_hosts = false - labs = {} - resources = {} - items = {} - neighbors = {} - for lab in Lab.objects.all(): - lab_node = { - 'id': "lab_" + str(lab.lab_user.id), - 'model_id': lab.lab_user.id, - 'name': lab.name, - 'description': lab.description, - 'selected': false, - 'selectable': true, - 'follow': multiple_hosts, - 'multiple': false, - 'class': 'lab', - 'available_resources': json.dumps(lab.get_available_resources()) - } - - items[lab_node['id']] = lab_node - neighbors[lab_node['id']] = [] - labs[lab_node['id']] = lab_node - - for template in ResourceManager.getInstance().getAvailableResourceTemplates(lab, user): - resource_node = { - 'form': {"name": "host_name", "type": "text", "placeholder": "hostname"}, - 'id': "resource_" + str(template.id), - 'model_id': template.id, - 'name': template.name, - 'description': template.description, - 'selected': false, - 'selectable': true, - 'follow': false, - 'multiple': multiple_hosts, - 'class': 'resource', - 'required_resources': json.dumps(template.get_required_resources()) - } - - if multiple_hosts: - resource_node['values'] = [] # place to store multiple values - - items[resource_node['id']] = resource_node - neighbors[lab_node['id']].append(resource_node['id']) - - if resource_node['id'] not in neighbors: - neighbors[resource_node['id']] = [] - - neighbors[resource_node['id']].append(lab_node['id']) - resources[resource_node['id']] = resource_node - - display_objects = [("lab", labs.values()), ("resource", resources.values())] - - context = { - 'display_objects': display_objects, - 'neighbors': neighbors, - 'filter_items': items - } - - return context - - -class HardwareDefinitionForm(forms.Form): - - def __init__(self, user, *args, **kwargs): - super(HardwareDefinitionForm, self).__init__(*args, **kwargs) - attrs = FormUtils.getLabData(multiple_hosts=True, user=user) - self.fields['filter_field'] = MultipleSelectFilterField( - widget=MultipleSelectFilterWidget(**attrs) - ) - - -class PodDefinitionForm(forms.Form): - - fields = ["xml"] - xml = forms.CharField() - - -class ResourceMetaForm(forms.Form): - - bundle_name = forms.CharField(label="POD Name") - bundle_description = forms.CharField(label="POD Description", widget=forms.Textarea, max_length=1000) - - -class GenericHostMetaForm(forms.Form): - - host_profile = forms.CharField(label="Host Type", disabled=True, required=False) - host_name = forms.CharField(label="Host Name") - - -class NetworkDefinitionForm(forms.Form): - def __init__(self, *args, **kwargs): - super(NetworkDefinitionForm, self).__init__(**kwargs) - - -class NetworkConfigurationForm(forms.Form): - def __init__(self, *args, **kwargs): - super(NetworkConfigurationForm).__init__(**kwargs) - - -class HostSoftwareDefinitionForm(forms.Form): - # Django Form class for Design a Pod - host_name = forms.CharField( - max_length=200, - disabled=False, - required=True - ) - headnode = forms.BooleanField(required=False, widget=forms.HiddenInput) - - 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): - fields = ['workflow'] - - empty_permitted = False - - workflow = forms.ChoiceField( - choices=( - (0, 'Booking'), - (1, 'Resource Bundle'), - (2, 'Software Configuration') - ), - label="Choose Workflow", - initial='booking', - required=True - ) - - -class SnapshotHostSelectForm(forms.Form): - host = forms.CharField() - - -class BasicMetaForm(forms.Form): - name = forms.CharField() - description = forms.CharField(widget=forms.Textarea) - - -class ConfirmationForm(forms.Form): - fields = ['confirm'] - - confirm = forms.ChoiceField( - choices=( - (False, "Cancel"), - (True, "Confirm") - ) - ) - - -def validate_step(value): - if value not in ["prev", "next", "current"]: - raise ValidationError(str(value) + " is not allowed") - - -def validate_step_form(value): - try: - urllib.parse.unquote_plus(value) - except Exception: - raise ValidationError("Value is not url encoded data") - - -class ManagerForm(forms.Form): - step = forms.CharField(widget=forms.widgets.HiddenInput, validators=[validate_step]) - step_form = forms.CharField(widget=forms.widgets.HiddenInput, validators=[validate_step_form]) - # other fields? - - -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 deleted file mode 100644 index e065202..0000000 --- a/src/workflow/models.py +++ /dev/null @@ -1,693 +0,0 @@ -############################################################################## -# 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.template.loader import get_template -from django.http import HttpResponse -from django.utils import timezone -from django.db import transaction - -import yaml -import requests - -from workflow.forms import ConfirmationForm -from api.models import JobFactory -from dashboard.exceptions import ResourceAvailabilityException, ModelValidationException -from resource_inventory.models import Image, OPNFVConfig, ResourceOPNFVConfig, NetworkRole -from resource_inventory.resource_manager import ResourceManager -from resource_inventory.pdf_templater import PDFTemplater -from notifier.manager import NotificationHandler -from booking.models import Booking - - -class BookingAuthManager(): - """ - Verifies Booking Authorization. - - Class to verify that the user is allowed to book the requested resource - The user must input a url to the INFO.yaml file to prove that they are the ptl of - an approved project if they are booking a multi-node pod. - This class parses the url and checks the logged in user against the info file. - """ - - LFN_PROJECTS = ["opnfv"] # TODO - - def parse_github_url(self, url): - project_leads = [] - try: - parts = url.split("/") - if "http" in parts[0]: # 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 ptl: - project_leads.append(ptl) - sub_ptl = info_parsed.get("subproject_lead") - if sub_ptl: - project_leads.append(sub_ptl) - - except Exception: - pass - - return project_leads - - def parse_gerrit_url(self, url): - project_leads = [] - try: - halfs = url.split("?") - parts = halfs[0].split("/") - args = halfs[1].split(";") - if "http" in parts[0]: # the url include http(s):// - parts = parts[2:] - if "f=INFO.yaml" not in args: - return None - if "gerrit.opnfv.org" not in parts[0]: - return None - try: - i = args.index("a=blob") - args[i] = "a=blob_plain" - except ValueError: - pass - # recreate url - halfs[1] = ";".join(args) - halfs[0] = "/".join(parts) - # now to download and parse file - url = "https://" + "?".join(halfs) - info_file = requests.get(url, timeout=15).text - info_parsed = yaml.load(info_file) - ptl = info_parsed.get('project_lead') - if ptl: - project_leads.append(ptl) - sub_ptl = info_parsed.get("subproject_lead") - if sub_ptl: - project_leads.append(sub_ptl) - - except Exception: - return None - - return project_leads - - def parse_opnfv_git_url(self, url): - project_leads = [] - try: - parts = url.split("/") - if "http" in parts[0]: # the url include http(s):// - parts = parts[2:] - if "INFO.yaml" not in parts[-1]: - return None - if "git.opnfv.org" not in parts[0]: - return None - if parts[-2] == "tree": - parts[-2] = "plain" - # now to download and parse file - 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 ptl: - project_leads.append(ptl) - sub_ptl = info_parsed.get("subproject_lead") - if sub_ptl: - project_leads.append(sub_ptl) - - except Exception: - return None - - return project_leads - - def parse_url(self, info_url): - """ - Parse the project URL. - - Gets the INFO.yaml file from the project and returns the PTL info. - """ - if "github" in info_url: - return self.parse_github_url(info_url) - - if "gerrit.opnfv.org" in info_url: - return self.parse_gerrit_url(info_url) - - if "git.opnfv.org" in info_url: - return self.parse_opnfv_git_url(info_url) - - def booking_allowed(self, booking, repo): - """ - Assert the current Booking Policy. - - 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 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.get_required_resources()) < 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)) - for ptl in ptl_info: - if ptl['email'] == booking.owner.userprofile.email_addr: - return True - return False - - -class WorkflowStepStatus(object): - """ - Poor man's enum for the status of a workflow step. - - The steps in a workflow are not completed (UNTOUCHED) - or they have been completed correctly (VALID) or they were filled out - incorrectly (INVALID) - """ - - UNTOUCHED = 0 - INVALID = 100 - VALID = 200 - - -class WorkflowStep(object): - template = 'bad_request.html' - title = "Generic Step" - description = "You were led here by mistake" - short_title = "error" - metastep = None - # phasing out metastep: - - valid = WorkflowStepStatus.UNTOUCHED - message = "" - - enabled = True - - def cleanup(self): - raise Exception("WorkflowStep subclass of type " + str(type(self)) + " has no concrete implemented cleanup() method") - - def enable(self): - if not self.enabled: - self.enabled = True - - def disable(self): - if self.enabled: - self.cleanup() - self.enabled = False - - def set_invalid(self, message, code=WorkflowStepStatus.INVALID): - self.valid = code - self.message = message - - def set_valid(self, message, code=WorkflowStepStatus.VALID): - self.valid = code - self.message = message - - def to_json(self): - return { - 'title': self.short_title, - 'enabled': self.enabled, - 'valid': self.valid, - 'message': self.message, - } - - 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): - return HttpResponse(self.render_to_string(request)) - - def render_to_string(self, request): - template = get_template(self.template) - return template.render(self.get_context(), request) - - def post(self, post_content, user): - raise Exception("WorkflowStep subclass of type " + str(type(self)) + " has no concrete post() method") - - 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) - - -""" -subclassing notes: - subclasses have to define the following class attributes: - self.select_repo_key: where the selected "object" or "bundle" is to be placed in the repo - self.form: the form to be used - alert_bundle_missing(): what message to display if a user does not select/selects an invalid object - get_form_queryset(): generate a queryset to be used to filter available items for the field - get_page_context(): return simple context such as page header and other info -""" - - -class AbstractSelectOrCreate(WorkflowStep): - template = 'dashboard/genericselect.html' - title = "Select a Bundle" - short_title = "select" - description = "Generic bundle selector step" - - select_repo_key = None - form = None # subclasses are expected to use a form that is a subclass of SearchableSelectGenericForm - - def alert_bundle_missing(self): # override in subclasses to change message if field isn't filled out - self.set_invalid("Please select a valid bundle") - - def post(self, post_data, user): - form = self.form(post_data, queryset=self.get_form_queryset()) - if form.is_valid(): - bundle = form.get_validated_bundle() - if not bundle: - self.alert_bundle_missing() - return - self.repo_put(self.select_repo_key, bundle) - self.put_confirm_info(bundle) - self.set_valid("Step Completed") - else: - self.alert_bundle_missing() - - def get_context(self): - default = [] - - bundle = self.repo_get(self.select_repo_key, False) - if bundle: - default.append(bundle) - - form = self.form(queryset=self.get_form_queryset(), initial=default) - - context = {'form': form, **self.get_page_context()} - context.update(super().get_context()) - - return context - - def get_page_context(): - return { - 'select_type': 'generic', - 'select_type_title': 'Generic Bundle' - } - - -class Confirmation_Step(WorkflowStep): - template = 'workflow/confirm.html' - title = "Confirm Changes" - description = "Does this all look right?" - - short_title = "confirm" - - def get_context(self): - context = super(Confirmation_Step, self).get_context() - context['form'] = ConfirmationForm() - # Summary of submitted form data shown on the 'confirm' step of the workflow - confirm_details = "\nPod:\n Name: '{name}'\n Description: '{desc}'\nLab: '{lab}'".format( - name=self.repo_get(self.repo.CONFIRMATION)['resource']['name'], - desc=self.repo_get(self.repo.CONFIRMATION)['resource']['description'], - lab=self.repo_get(self.repo.CONFIRMATION)['template']['lab']) - confirm_details += "\nResources:" - for i, device in enumerate(self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS)['resources']): - confirm_details += "\n " + str(device) + ": " + str(self.repo_get(self.repo.CONFIRMATION)['template']['resources'][i]['profile']) - context['confirmation_info'] = confirm_details - if self.valid == WorkflowStepStatus.VALID: - context["confirm_succeeded"] = "true" - - return context - - def flush_to_db(self): - errors = self.repo.make_models() - if errors: - return errors - - def post(self, post_data, user): - form = ConfirmationForm(post_data) - if form.is_valid(): - data = form.cleaned_data['confirm'] - if data == "True": - errors = self.flush_to_db() - if errors: - self.set_invalid("ERROR OCCURRED: " + errors) - else: - self.set_valid("Confirmed") - - elif data == "False": - self.repo.cancel() - self.set_valid("Canceled") - else: - self.set_invalid("Bad Form Contents") - - else: - self.set_invalid("Bad Form Contents") - - -class Repository(): - - EDIT = "editing" - MODELS = "models" - RESOURCE_SELECT = "resource_select" - CONFIRMATION = "confirmation" - SELECTED_RESOURCE_TEMPLATE = "selected resource template pk" - SELECTED_OPNFV_CONFIG = "selected opnfv deployment config" - RESOURCE_TEMPLATE_MODELS = "generic_resource_template_models" - RESOURCE_TEMPLATE_INFO = "generic_resource_template_info" - BOOKING = "booking" - LAB = "lab" - RCONFIG_LAST_HOSTLIST = "resource_configuration_network_previous_hostlist" - BOOKING_FORMS = "booking_forms" - SWCONF_HOSTS = "swconf_hosts" - BOOKING_MODELS = "booking models" - CONFIG_MODELS = "configuration bundle models" - OPNFV_MODELS = "opnfv configuration models" - SESSION_USER = "session owner user account" - SESSION_MANAGER = "session manager for current session" - 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" - - # new keys for migration to using ResourceTemplates: - RESOURCE_TEMPLATE_MODELS = "current working model of resource template" - - # migratory elements of segmented workflow - # each of these is the end result of a different workflow. - HAS_RESULT = "whether or not workflow has a result" - RESULT_KEY = "key for target index that result will be put into in parent" - RESULT = "result object from workflow" - - def get_child_defaults(self): - return_tuples = [] - for key in [self.SELECTED_RESOURCE_TEMPLATE, self.SESSION_USER]: - return_tuples.append((key, self.el.get(key))) - return return_tuples - - def set_defaults(self, defaults): - for key, value in defaults: - self.el[key] = value - - 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 key not in history: - history[key] = [id] - else: - history[key].append(id) - - def cancel(self): - if self.RESOURCE_TEMPLATE_MODELS in self.el: - models = self.el[self.RESOURCE_TEMPLATE_MODELS] - if models['template'].temporary: - models['template'].delete() - # deleting current template should cascade delete all - # necessary related models - - 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.RESOURCE_TEMPLATE_MODELS in self.el: - errors = self.make_generic_resource_bundle() - if errors: - return errors - else: - self.el[self.HAS_RESULT] = True - self.el[self.RESULT_KEY] = self.SELECTED_RESOURCE_TEMPLATE - 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: - return errors - # create notification - booking = self.el[self.BOOKING_MODELS]['booking'] - NotificationHandler.notify_new_booking(booking) - - 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) - if booking.start > timezone.now() or booking.end < timezone.now(): - return "Booking is not active" - 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() - try: - current_image = host.config.image - image.os = current_image.os - image.save() - except Exception: - pass - JobFactory.makeSnapshotTask(image, booking, host) - - self.el[self.RESULT] = image - self.el[self.HAS_RESULT] = True - - def make_generic_resource_bundle(self): - owner = self.el[self.SESSION_USER] - if self.RESOURCE_TEMPLATE_MODELS in self.el: - models = self.el[self.RESOURCE_TEMPLATE_MODELS] - models['template'].owner = owner - models['template'].temporary = False - models['template'].save() - self.el[self.RESULT] = models['template'] - self.el[self.HAS_RESULT] = True - return False - - else: - return "GRB no models given. CODE:0x0001" - - def make_software_config_bundle(self): - models = self.el[self.CONFIG_MODELS] - if 'bundle' in models: - bundle = models['bundle'] - bundle.bundle = self.el[self.SELECTED_RESOURCE_TEMPLATE] - 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.template = host_config.template - host_config.profile = host_config.profile - 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 opnfvconfig.scenario not 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.RESULT] = bundle - return False - - @transaction.atomic # TODO: Rewrite transactions with savepoints at user level for all workflows - def make_booking(self): - models = self.el[self.BOOKING_MODELS] - owner = self.el[self.SESSION_USER] - - if 'booking' in models: - booking = models['booking'] - else: - return "BOOK, no booking model exists. CODE:0x000f" - - selected_grb = None - - if self.SELECTED_RESOURCE_TEMPLATE in self.el: - selected_grb = self.el[self.SELECTED_RESOURCE_TEMPLATE] - else: - return "BOOK, no selected resource. CODE:0x000e" - - 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: - res_manager = ResourceManager.getInstance() - resource_bundle = res_manager.instantiateTemplate(selected_grb) - 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.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: - booking.pdf = PDFTemplater.makePDF(booking) - booking.save() - except Exception as e: - return "BOOK, failed to create Pod Desriptor File: " + str(e) - - 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" - - self.el[self.RESULT] = booking - self.el[self.HAS_RESULT] = True - - def make_opnfv_config(self): - opnfv_models = self.el[self.OPNFV_MODELS] - config_bundle = self.el[self.SELECTED_CONFIG_BUNDLE] - 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'] - ) - ResourceOPNFVConfig.objects.create( - role=host_role['role'], - host_config=config, - opnfv_config=opnfv_config - ) - - self.el[self.RESULT] = opnfv_config - self.el[self.HAS_RESULT] = True - - def __init__(self): - self.el = {} - self.el[self.CONFIRMATION] = {} - self.el["active_step"] = 0 - self.el[self.HAS_RESULT] = False - self.el[self.RESULT] = None - self.get_history = {} - self.put_history = {} diff --git a/src/workflow/opnfv_workflow.py b/src/workflow/opnfv_workflow.py deleted file mode 100644 index 6ffc91d..0000000 --- a/src/workflow/opnfv_workflow.py +++ /dev/null @@ -1,292 +0,0 @@ -############################################################################## -# 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 workflow.models import WorkflowStep, AbstractSelectOrCreate -from resource_inventory.models import ResourceTemplate, OPNFV_SETTINGS -from workflow.forms import OPNFVSelectionForm, OPNFVNetworkRoleForm, OPNFVHostRoleForm, SWConfigSelectorForm, BasicMetaForm - - -class OPNFV_Resource_Select(AbstractSelectOrCreate): - title = "Select Software Configuration" - description = "Choose the software bundle you wish to use as a base for your OPNFV configuration" - short_title = "software config" - form = SWConfigSelectorForm - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.select_repo_key = self.repo.SELECTED_CONFIG_BUNDLE - - def get_form_queryset(self): - user = self.repo_get(self.repo.SESSION_USER) - qs = ResourceTemplate.objects.filter(owner=user) - return qs - - def put_confirm_info(self, bundle): - confirm_dict = self.repo_get(self.repo.CONFIRMATION) - confirm_dict['software bundle'] = bundle.name - confirm_dict['hardware POD'] = bundle.bundle.name - self.repo_put(self.repo.CONFIRMATION, confirm_dict) - - def get_page_context(self): - return { - 'select_type': 'swconfig', - 'select_type_title': 'Software Config', - 'addable_type_num': 2 - } - - -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, {}) - initial = { - "installer": models.get("installer_chosen"), - "scenario": models.get("scenario_chosen") - } - - context["form"] = OPNFVSelectionForm(initial=initial) - return context - - def post(self, post_data, user): - form = OPNFVSelectionForm(post_data) - 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.set_valid("Step Completed") - else: - self.set_invalid("Please select an Installer and Scenario") - - -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": , - "network": - } - """ - 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.SELECTED_CONFIG_BUNDLE) - 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(self, post_data, user): - models = self.repo_get(self.repo.OPNFV_MODELS, {}) - config_bundle = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE) - roles = OPNFV_SETTINGS.NETWORK_ROLES - net_role_formset = self.create_netformset(roles, config_bundle, data=post_data) - 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.set_valid("Completed") - self.repo_put(self.repo.OPNFV_MODELS, models) - self.update_confirmation() - else: - self.set_invalid("Please complete all fields") - - -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() - config = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE) - if config is None: - context['error'] = "Please select a Configuration on the first step" - - formset = self.create_host_role_formset(hostlist=config.bundle.getResources()) - 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(self, post_data, user): - formset = self.create_host_role_formset(data=post_data) - - 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.set_invalid('Must have at least one "Jumphost" per POD') - else: - self.set_valid("Completed") - else: - self.set_invalid("Please complete all fields") - - -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(self, post_data, user): - models = self.repo_get(self.repo.OPNFV_MODELS, {}) - info = models.get("meta", {}) - - form = BasicMetaForm(post_data) - 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.set_valid("Complete") - else: - self.set_invalid("Please correct the errors shown below") - self.repo_put(self.repo.OPNFV_MODELS, models) diff --git a/src/workflow/resource_bundle_workflow.py b/src/workflow/resource_bundle_workflow.py deleted file mode 100644 index 4e288b5..0000000 --- a/src/workflow/resource_bundle_workflow.py +++ /dev/null @@ -1,614 +0,0 @@ -############################################################################## -# 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.conf import settings -from django.forms import formset_factory -from django.core.exceptions import ValidationError - -from typing import List - -import re -import json -from xml.dom import minidom -import traceback - -from workflow.models import WorkflowStep -from account.models import Lab -from workflow.forms import ( - HardwareDefinitionForm, - NetworkDefinitionForm, - ResourceMetaForm, - HostSoftwareDefinitionForm, -) -from resource_inventory.models import ( - ResourceTemplate, - ResourceConfiguration, - InterfaceConfiguration, - Network, - NetworkConnection, - Image, -) -from dashboard.exceptions import ( - InvalidVlanConfigurationException, - NetworkExistsException, - ResourceAvailabilityException -) - -import logging -logger = logging.getLogger(__name__) - - -class Define_Hardware(WorkflowStep): - - template = 'resource/steps/define_hardware.html' - title = "Define Hardware" - description = "Choose the type and amount of machines you want" - short_title = "hosts" - - def __init__(self, *args, **kwargs): - self.form = None - super().__init__(*args, **kwargs) - - def get_context(self): - context = super(Define_Hardware, self).get_context() - user = self.repo_get(self.repo.SESSION_USER) - context['form'] = self.form or HardwareDefinitionForm(user) - return context - - def update_models(self, data): - data = data['filter_field'] - models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}) - models['resources'] = [] # This will always clear existing data when this step changes - models['connections'] = [] - models['interfaces'] = {} - if "template" not in models: - template = ResourceTemplate.objects.create(temporary=True) - models['template'] = template - - resource_data = data['resource'] - - new_template = models['template'] - - public_network = Network.objects.create(name="public", bundle=new_template, is_public=True) - - all_networks = {public_network.id: public_network} - - for resource_template_dict in resource_data.values(): - id = resource_template_dict['id'] - old_template = ResourceTemplate.objects.get(id=id) - - # instantiate genericHost and store in repo - for _ in range(0, resource_template_dict['count']): - resource_configs = old_template.resourceConfigurations.all() - for config in resource_configs: - # need to save now for connections to refer to it later - new_config = ResourceConfiguration.objects.create( - profile=config.profile, - image=config.image, - name=config.name, - template=new_template) - - for interface_config in config.interface_configs.all(): - new_interface_config = InterfaceConfiguration.objects.create( - profile=interface_config.profile, - resource_config=new_config) - - for connection in interface_config.connections.all(): - network = None - if connection.network.is_public: - network = public_network - else: - # check if network is known - if connection.network.id not in all_networks: - # create matching one - new_network = Network( - name=connection.network.name + "_" + str(new_config.id), - bundle=new_template, - is_public=False) - new_network.save() - - all_networks[connection.network.id] = new_network - - network = all_networks[connection.network.id] - - new_connection = NetworkConnection( - network=network, - vlan_is_tagged=connection.vlan_is_tagged) - - new_interface_config.save() # can't do later because M2M on next line - new_connection.save() - - new_interface_config.connections.add(new_connection) - - unique_resource_ref = new_config.name + "_" + str(new_config.id) - if unique_resource_ref not in models['interfaces']: - models['interfaces'][unique_resource_ref] = [] - models['interfaces'][unique_resource_ref].append(interface_config) - - models['resources'].append(new_config) - - models['networks'] = all_networks - - # add selected lab to models - for lab_dict in data['lab'].values(): - if lab_dict['selected']: - models['template'].lab = Lab.objects.get(lab_user__id=lab_dict['id']) - models['template'].save() - break # if somehow we get two 'true' labs, we only use one - - # return to repo - self.repo_put(self.repo.RESOURCE_TEMPLATE_MODELS, models) - - def update_confirmation(self): - confirm = self.repo_get(self.repo.CONFIRMATION, {}) - if "template" not in confirm: - confirm['template'] = {} - confirm['template']['resources'] = [] - models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}) - if 'template' in models: - for resource in models['template'].getConfigs(): - host_dict = {"name": resource.name, "profile": resource.profile.name} - confirm['template']['resources'].append(host_dict) - if "template" in models: - confirm['template']['lab'] = models['template'].lab.lab_user.username - self.repo_put(self.repo.CONFIRMATION, confirm) - - def post(self, post_data, user): - try: - user = self.repo_get(self.repo.SESSION_USER) - self.form = HardwareDefinitionForm(user, post_data) - if self.form.is_valid(): - self.update_models(self.form.cleaned_data) - self.update_confirmation() - self.set_valid("Step Completed") - else: - self.set_invalid("Please complete the fields highlighted in red to continue") - except Exception as e: - print("Caught exception: " + str(e)) - traceback.print_exc() - self.form = None - self.set_invalid("Please select a lab.") - - -class Define_Software(WorkflowStep): - template = 'config_bundle/steps/define_software.html' - title = "Pick Software" - description = "Choose the opnfv and image of your machines" - short_title = "host config" - - def build_filter_data(self, hosts_data): - """ - Build list of Images to filter out. - - 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.RESOURCE_TEMPLATE_MODELS)['template'].lab - for i, host_data in enumerate(hosts_data): - host = ResourceConfiguration.objects.get(pk=host_data['host_id']) - wrong_owner = Image.objects.exclude(owner=user).exclude(public=True) - wrong_host = Image.objects.exclude(architecture=host.profile.architecture) - 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 = [] - configs = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}).get("resources") - if configs: - for i in range(len(configs)): - default_name = 'laas-node' - if i > 0: - default_name = default_name + "-" + str(i + 1) - hosts_initial.append({ - 'host_id': configs[i].id, - 'host_name': default_name, - 'headnode': False, - 'image': configs[i].image - }) - else: - for host in hostlist: - hosts_initial.append({ - 'host_id': host.id, - 'host_name': host.name - }) - - HostFormset = formset_factory(HostSoftwareDefinitionForm, extra=0) - filter_data = self.build_filter_data(hosts_initial) - - 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 - - if data: - return SpecialHostFormset(data, initial=hosts_initial) - return SpecialHostFormset(initial=hosts_initial) - - def get_host_list(self, grb=None): - return self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS).get("resources") - - def get_context(self): - context = super(Define_Software, self).get_context() - - context["formset"] = self.create_hostformset(self.get_host_list()) - - return context - - def post(self, post_data, user): - hosts = self.get_host_list() - formset = self.create_hostformset(hosts, data=post_data) - has_headnode = False - if formset.is_valid(): - for i, form in enumerate(formset): - host = hosts[i] - image = form.cleaned_data['image'] - hostname = form.cleaned_data['host_name'] - headnode = form.cleaned_data['headnode'] - if headnode: - has_headnode = True - host.is_head_node = headnode - host.name = hostname - host.image = image - # RFC921: They must start with a letter, end with a letter or digit and have only letters or digits or hyphen as interior characters - if bool(re.match("^[A-Za-z0-9-]*$", hostname)) is False: - self.set_invalid("Device names must only contain alphanumeric characters and dashes.") - return - if not hostname[0].isalpha() or not hostname[-1].isalnum(): - self.set_invalid("Device names must start with a letter and end with a letter or digit.") - return - for j in range(i): - if j != i and hostname == hosts[j].name: - self.set_invalid("Devices must have unique names. Please try again.") - return - host.save() - - if not has_headnode and len(hosts) > 0: - self.set_invalid("No headnode. Please set a headnode.") - return - - self.set_valid("Completed") - else: - self.set_invalid("Please complete all fields.") - - -class Define_Nets(WorkflowStep): - template = 'resource/steps/pod_definition.html' - title = "Define Networks" - description = "Use the tool below to draw the network topology of your POD" - short_title = "networking" - form = NetworkDefinitionForm - - def get_vlans(self): - vlans = self.repo_get(self.repo.VLANS) - if vlans: - return vlans - # try to grab some vlans from lab - models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}) - if "bundle" not in models: - return None - lab = models['bundle'].lab - if lab is None or lab.vlan_manager is None: - return None - try: - vlans = lab.vlan_manager.get_vlans(count=lab.vlan_manager.block_size) - self.repo_put(self.repo.VLANS, vlans) - return vlans - except Exception: - return None - - def make_mx_network_dict(self, network): - return { - 'id': network.id, - 'name': network.name, - 'public': network.is_public - } - - def make_mx_resource_dict(self, resource_config): - resource_dict = { - 'id': resource_config.id, - 'interfaces': [], - 'value': { - 'name': resource_config.name, - 'id': resource_config.id, - 'description': resource_config.profile.description - } - } - - for interface_config in resource_config.interface_configs.all(): - connections = [] - for connection in interface_config.connections.all(): - connections.append({'tagged': connection.vlan_is_tagged, 'network': connection.network.id}) - - interface_dict = { - "id": interface_config.id, - "name": interface_config.profile.name, - "description": "speed: " + str(interface_config.profile.speed) + "M\ntype: " + interface_config.profile.nic_type, - "connections": connections - } - - resource_dict['interfaces'].append(interface_dict) - - return resource_dict - - def make_mx_host_dict(self, generic_host): - host = { - 'id': generic_host.profile.name, - 'interfaces': [], - 'value': { - "name": generic_host.profile.name, - "description": generic_host.profile.description - } - } - for iface in generic_host.profile.interfaceprofile.all(): - host['interfaces'].append({ - "name": iface.name, - "description": "speed: " + str(iface.speed) + "M\ntype: " + iface.nic_type - }) - return host - - # first step guards this one, so can't get here without at least empty - # models being populated by step one - def get_context(self): - context = super(Define_Nets, self).get_context() - context.update({ - 'form': NetworkDefinitionForm(), - 'debug': settings.DEBUG, - 'resources': {}, - 'networks': {}, - 'vlans': [], - # remove others - 'hosts': [], - 'added_hosts': [], - 'removed_hosts': [] - }) - - models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS) # infallible, guarded by prior step - for resource in models['resources']: - d = self.make_mx_resource_dict(resource) - context['resources'][d['id']] = d - - for network in models['networks'].values(): - d = self.make_mx_network_dict(network) - context['networks'][d['id']] = d - - return context - - def post(self, post_data, user): - try: - xmlData = post_data.get("xml") - self.updateModels(xmlData) - # update model with xml - self.set_valid("Networks applied successfully") - except ResourceAvailabilityException: - self.set_invalid("Public network not availble") - except Exception as e: - traceback.print_exc() - self.set_invalid("An error occurred when applying networks: " + str(e)) - - def resetNetworks(self, networks: List[Network]): # potentially just pass template here? - for network in networks: - network.delete() - - def updateModels(self, xmlData): - models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}) - given_hosts = None - interfaces = None - networks = None - try: - given_hosts, interfaces, networks = self.parseXml(xmlData) - except Exception as e: - print("tried to parse Xml, got exception instead:") - print(e) - - existing_rconfig_list = models.get("resources", []) - existing_rconfigs = {} # maps id to host - for rconfig in existing_rconfig_list: - existing_rconfigs["host_" + str(rconfig.id)] = rconfig - - bundle = models.get("template") # hard fail if not in repo - - self.resetNetworks(models['networks'].values()) - models['networks'] = {} - - for net_id, net in networks.items(): - network = Network.objects.create( - name=net['name'], - bundle=bundle, - is_public=net['public']) - - models['networks'][net_id] = network - network.save() - - for hostid, given_host in given_hosts.items(): - for ifaceId in given_host['interfaces']: - iface = interfaces[ifaceId] - - iface_config = InterfaceConfiguration.objects.get(id=iface['config_id']) - if iface_config.resource_config.template.id != bundle.id: - raise ValidationError("User does not own the template they are editing") - - for connection in iface['connections']: - network_id = connection['network'] - net = models['networks'][network_id] - connection = NetworkConnection(vlan_is_tagged=connection['tagged'], network=net) - connection.save() - iface_config.connections.add(connection) - iface_config.save() - self.repo_put(self.repo.RESOURCE_TEMPLATE_MODELS, models) - - def decomposeXml(self, xmlString): - """ - Translate XML into useable data. - - 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 - 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"): - 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, "hostname": 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 = {"graph_id": cellId, "connections": [], "config_id": cell_json['id'], "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['config_id'] in untagged_ifaces: - raise InvalidVlanConfigurationException("More than one untagged vlan on an interface") - untagged_ifaces.add(interface['config_id']) - - # add connection to interface - interface['connections'].append({"tagged": tagged, "network": network['id']}) - - return hosts, interfaces, networks - - -class Resource_Meta_Info(WorkflowStep): - template = 'resource/steps/meta_info.html' - title = "Extra Info" - description = "Please fill out the rest of the information about your resource" - short_title = "pod info" - - def update_confirmation(self): - confirm = self.repo_get(self.repo.CONFIRMATION, {}) - if "template" not in confirm: - confirm['template'] = {} - models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}) - if "template" in models: - confirm['template']['description'] = models['template'].description - confirm['template']['name'] = models['template'].name - self.repo_put(self.repo.CONFIRMATION, confirm) - - def get_context(self): - context = super(Resource_Meta_Info, self).get_context() - name = "" - desc = "" - models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, None) - bundle = models['template'] - if bundle: - name = bundle.name - desc = bundle.description - context['form'] = ResourceMetaForm(initial={"bundle_name": name, "bundle_description": desc}) - return context - - def post(self, post_data, user): - form = ResourceMetaForm(post_data) - if form.is_valid(): - models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}) - name = form.cleaned_data['bundle_name'] - desc = form.cleaned_data['bundle_description'] - bundle = models['template'] # infallible - bundle.name = name - bundle.description = desc - bundle.save() - self.repo_put(self.repo.RESOURCE_TEMPLATE_MODELS, models) - confirm = self.repo_get(self.repo.CONFIRMATION) - if "resource" not in confirm: - confirm['resource'] = {} - confirm_info = confirm['resource'] - confirm_info["name"] = name - tmp = desc - if len(tmp) > 60: - tmp = tmp[:60] + "..." - confirm_info["description"] = tmp - self.repo_put(self.repo.CONFIRMATION, confirm) - self.set_valid("Step Completed") - else: - self.set_invalid("Please complete all fields.") diff --git a/src/workflow/snapshot_workflow.py b/src/workflow/snapshot_workflow.py deleted file mode 100644 index c0e2052..0000000 --- a/src/workflow/snapshot_workflow.py +++ /dev/null @@ -1,116 +0,0 @@ -############################################################################## -# 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.utils import timezone -import json - -from booking.models import Booking -from resource_inventory.models import ResourceQuery, Image -from workflow.models import WorkflowStep -from workflow.forms import BasicMetaForm, SnapshotHostSelectForm - - -class Select_Host_Step(WorkflowStep): - template = "snapshot_workflow/steps/select_host.html" - title = "Select Host" - description = "Choose which machine you want to snapshot" - short_title = "host" - - def get_context(self): - context = super(Select_Host_Step, self).get_context() - context['form'] = SnapshotHostSelectForm() - booking_hosts = {} - now = timezone.now() - user = self.repo_get(self.repo.SESSION_USER) - bookings = Booking.objects.filter(start__lt=now, end__gt=now, owner=user) - for booking in bookings: - booking_hosts[booking.id] = {} - booking_hosts[booking.id]['purpose'] = booking.purpose - booking_hosts[booking.id]['start'] = booking.start.strftime("%Y-%m-%d") - booking_hosts[booking.id]['end'] = booking.end.strftime("%Y-%m-%d") - booking_hosts[booking.id]['hosts'] = [] - for genericHost in booking.resource.template.getResources(): - booking_hosts[booking.id]['hosts'].append({"name": genericHost.resource.name}) - - context['booking_hosts'] = booking_hosts - - chosen_host = self.repo_get(self.repo.SNAPSHOT_MODELS, {}).get("host") - if chosen_host: - chosen = {} - chosen['booking_id'] = self.repo_get(self.repo.SNAPSHOT_BOOKING_ID) - chosen['hostname'] = chosen_host.template.resource.name - context['chosen'] = chosen - return context - - def post(self, post_data, user): - host_data = post_data.get("host") - if not host_data: - self.set_invalid("Please select a host") - return - host = json.loads(host_data) - if 'name' not in host or 'booking' not in host: - self.set_invalid("Invalid host selected") - return - name = host['name'] - booking_id = host['booking'] - booking = Booking.objects.get(pk=booking_id) - host = ResourceQuery.get(bundle=booking.resource, template__resource__name=name) - models = self.repo_get(self.repo.SNAPSHOT_MODELS, {}) - if "host" not in models: - models['host'] = host - if 'snapshot' not in models: - models['snapshot'] = Image() - self.repo_put(self.repo.SNAPSHOT_MODELS, models) - self.repo_put(self.repo.SNAPSHOT_BOOKING_ID, booking_id) - - confirm = self.repo_get(self.repo.CONFIRMATION, {}) - snap_confirm = confirm.get("snapshot", {}) - snap_confirm['host'] = name - confirm['snapshot'] = snap_confirm - self.repo_put(self.repo.CONFIRMATION, confirm) - self.set_valid("Success") - - -class Image_Meta_Step(WorkflowStep): - template = "snapshot_workflow/steps/meta.html" - title = "Additional Information" - description = "We need some more info" - short_title = "info" - - def get_context(self): - context = super(Image_Meta_Step, self).get_context() - name = self.repo_get(self.repo.SNAPSHOT_NAME, False) - desc = self.repo_get(self.repo.SNAPSHOT_DESC, False) - form = None - if name and desc: - form = BasicMetaForm(initial={"name": name, "description": desc}) - else: - form = BasicMetaForm() - context['form'] = form - return context - - def post(self, post_data, user): - form = BasicMetaForm(post_data) - if form.is_valid(): - name = form.cleaned_data['name'] - self.repo_put(self.repo.SNAPSHOT_NAME, name) - description = form.cleaned_data['description'] - self.repo_put(self.repo.SNAPSHOT_DESC, description) - - confirm = self.repo_get(self.repo.CONFIRMATION, {}) - snap_confirm = confirm.get("snapshot", {}) - snap_confirm['name'] = name - snap_confirm['description'] = description - confirm['snapshot'] = snap_confirm - self.repo_put(self.repo.CONFIRMATION, confirm) - - self.set_valid("Success") - else: - self.set_invalid("Please Fill out the Form") diff --git a/src/workflow/tests/__init__.py b/src/workflow/tests/__init__.py deleted file mode 100644 index 4f0437d..0000000 --- a/src/workflow/tests/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# 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 -############################################################################## diff --git a/src/workflow/tests/constants.py b/src/workflow/tests/constants.py deleted file mode 100644 index f94a949..0000000 --- a/src/workflow/tests/constants.py +++ /dev/null @@ -1,198 +0,0 @@ -############################################################################## -# 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 -############################################################################## -POD_XML = """ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -""" diff --git a/src/workflow/tests/test_fixtures.py b/src/workflow/tests/test_fixtures.py deleted file mode 100644 index fe16be7..0000000 --- a/src/workflow/tests/test_fixtures.py +++ /dev/null @@ -1,2 +0,0 @@ - -MX_GRAPH_MODEL = '' diff --git a/src/workflow/tests/test_steps.py b/src/workflow/tests/test_steps.py deleted file mode 100644 index ba27313..0000000 --- a/src/workflow/tests/test_steps.py +++ /dev/null @@ -1,269 +0,0 @@ -############################################################################## -# 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 -############################################################################## - -""" -This file tests basic functionality of each step class. - -More in depth case coverage of WorkflowStep.post() must happen elsewhere. -""" - -import json -from unittest import SkipTest, mock - -from django.test import TestCase, RequestFactory -from dashboard.testing_utils import make_lab, make_user, make_os, \ - make_complete_host_profile, make_opnfv_role, make_image, make_grb, \ - make_config_bundle, make_host, make_user_profile, make_generic_host -from workflow import resource_bundle_workflow -from workflow import booking_workflow -from workflow import sw_bundle_workflow -from workflow.models import Repository -from workflow.tests import test_fixtures - - -class TestConfig: - """ - Basic class to instantiate and hold reference. - - to models we will need often - """ - - def __init__(self, usr=None): - self.lab = make_lab() - self.user = usr or make_user() - self.os = make_os() - self.host_prof = make_complete_host_profile(self.lab) - self.host = make_host(self.host_prof, self.lab, name="host1") - - # pod description as required by testing lib - self.topology = { - "host1": { - "type": self.host_prof, - "role": make_opnfv_role(), - "image": make_image(self.lab, 3, self.user, self.os, self.host_prof), - "nets": [ - [{"name": "public", "tagged": True, "public": True}] - ] - } - } - self.grb = make_grb(self.topology, self.user, self.lab)[0] - self.generic_host = make_generic_host(self.grb, self.host_prof, "host1") - - -class StepTestCase(TestCase): - - # after setUp is called, this should be an instance of a step - step = None - - post_data = {} # subclasses will set this - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.factory = RequestFactory() - cls.user_prof = make_user_profile() - cls.user = cls.user_prof.user - - def setUp(self): - super().setUp() - if self.step is None: - raise SkipTest("Step instance not given") - repo = Repository() - self.add_to_repo(repo) - self.step = self.step(1, repo) - - def assertCorrectPostBehavior(self, post_data): - """ - Stub for validating step behavior on POST request. - - allows subclasses to override and make assertions about - the side effects of self.step.post() - post_data is the data passed into post() - """ - return - - def add_to_repo(self, repo): - """ - Stub for modifying the step's repo. - - This method is a hook that allows subclasses to modify - the contents of the repo before the step is created. - """ - return - - def assertValidHtml(self, html_str): - """ - Assert that html_str is a valid html fragment. - - However, I know of no good way of doing this in python - """ - self.assertTrue(isinstance(html_str, str)) - self.assertGreater(len(html_str), 0) - - def test_render_to_string(self): - request = self.factory.get("/workflow/manager/") - request.user = self.user - response_html = self.step.render_to_string(request) - self.assertValidHtml(response_html) - - def test_post(self, data=None): - post_data = data or self.post_data - self.step.post(post_data, self.user) - self.assertCorrectPostBehavior(data) - - -class SelectStepTestCase(StepTestCase): - # ID of model to be sent to the step's form - # can be an int or a list of ints - obj_id = -1 - - def setUp(self): - super().setUp() - - try: - iter(self.obj_id) - except TypeError: - self.obj_id = [self.obj_id] - - field_data = json.dumps(self.obj_id) - self.post_data = { - "searchable_select": [field_data] - } - - -class DefineHardwareTestCase(StepTestCase): - step = resource_bundle_workflow.Define_Hardware - post_data = { - "filter_field": { - "lab": { - "lab_35": {"selected": True, "id": 35}}, - "host": { - "host_1": {"selected": True, "id": 1}} - } - } - - -class DefineNetworkTestCase(StepTestCase): - step = resource_bundle_workflow.Define_Nets - post_data = {"xml": test_fixtures.MX_GRAPH_MODEL} - - -class ResourceMetaTestCase(StepTestCase): - step = resource_bundle_workflow.Resource_Meta_Info - post_data = { - "bundle_name": "my_bundle", - "bundle_description": "My Bundle" - } - - -class BookingResourceTestCase(SelectStepTestCase): - step = booking_workflow.Booking_Resource_Select - - def add_to_repo(self, repo): - repo.el[repo.SESSION_USER] = self.user - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - conf = TestConfig(usr=cls.user) - cls.obj_id = conf.grb.id - - -class SoftwareSelectTestCase(SelectStepTestCase): - step = booking_workflow.SWConfig_Select - - def add_to_repo(self, repo): - repo.el[repo.SESSION_USER] = self.user - repo.el[repo.SELECTED_RESOURCE_TEMPLATE] = self.conf.grb - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.conf = TestConfig(usr=cls.user) - host_map = {"host1": cls.conf.generic_host} - config_bundle = make_config_bundle(cls.conf.grb, cls.conf.user, cls.conf.topology, host_map)[0] - cls.obj_id = config_bundle.id - - -class OPNFVSelectTestCase(SelectStepTestCase): - step = booking_workflow.OPNFV_Select - - def add_to_repo(self, repo): - repo.el[repo.SELECTED_CONFIG_BUNDLE] = self.config_bundle - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - conf = TestConfig(usr=cls.user) - host_map = {"host1": conf.generic_host} - cls.config_bundle, opnfv_config = make_config_bundle(conf.grb, conf.user, conf.topology, host_map) - cls.obj_id = opnfv_config.id - - -class BookingMetaTestCase(StepTestCase): - step = booking_workflow.Booking_Meta - post_data = { - "length": 14, - "purpose": "Testing", - "project": "Lab as a Service", - "users": ["[-1]"] - } - - def add_to_repo(self, repo): - repo.el[repo.SESSION_MANAGER] = mock.MagicMock() - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - new_user = make_user(username="collaborator", email="different@mail.com") - new_user_prof = make_user_profile(user=new_user) - data = "[" + str(new_user_prof.id) + "]" # list of IDs - cls.post_data['users'] = [data] - - -class ConfigResourceSelectTestCase(SelectStepTestCase): - step = sw_bundle_workflow.SWConf_Resource_Select - - def add_to_repo(self, repo): - repo.el[repo.SESSION_USER] = self.user - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - conf = TestConfig(usr=cls.user) - cls.obj_id = conf.grb.id - - -class DefineSoftwareTestCase(StepTestCase): - step = sw_bundle_workflow.Define_Software - post_data = { - "form-0-image": 1, - "headnode": 1, - "form-0-headnode": "", - "form-TOTAL_FORMS": 1, - "form-INITIAL_FORMS": 1, - "form-MIN_NUM_FORMS": 0, - "form-MAX_NUM_FORMS": 1000, - } - - def add_to_repo(self, repo): - repo.el[repo.SELECTED_RESOURCE_TEMPLATE] = self.conf.grb - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.conf = TestConfig(usr=cls.user) - - -class ConfigSoftwareTestCase(StepTestCase): - step = sw_bundle_workflow.Config_Software - post_data = { - "name": "config_bundle", - "description": "My Config Bundle" - } diff --git a/src/workflow/tests/test_workflows.py b/src/workflow/tests/test_workflows.py deleted file mode 100644 index 995d699..0000000 --- a/src/workflow/tests/test_workflows.py +++ /dev/null @@ -1,99 +0,0 @@ -############################################################################## -# 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 unittest import SkipTest -from django.test import TestCase -from workflow.workflow_factory import WorkflowFactory - - -""" -To start a workflow: - POST to /wf/workflow {"add": - - types: - 0 - Booking - 1 - Resource - 2 - Config - -To remove a workflow: - POST to /wf/workflow {"cancel": ""} -""" - - -class WorkflowTestCase(TestCase): - - @classmethod - def setUpClass(cls): - super().setUpClass() - raise SkipTest("These tests are no good") - - def setUp(self): - self.clear_workflow() - self.create_workflow(self.wf_type) - - def create_workflow(self, wf_type): - self.clear_workflow() - - # creates workflow on backend - self.client.post("/", {"create": int(wf_type)}) # TODO: verify content type, etc - - def clear_workflow(self): - session = self.client.session - for k in session.keys(): - del session[k] - session.save() - - def render_steps(self): - """Retrieve each step individually at /wf/workflow/step=.""" - for i in range(self.step_count): - # renders the step itself, not in an iframe - exception = None - try: - response = self.client.get("/wf/workflow/", {"step": str(i)}) - self.assertLess(response.status_code, 300) - except Exception as e: - exception = e - - self.assertIsNone(exception) - - -class BookingWorkflowTestCase(WorkflowTestCase): - - @classmethod - def setUpClass(cls): - super(BookingWorkflowTestCase, cls).setUpClass() - cls.step_count = len(WorkflowFactory.booking_steps) - cls.wf_type = 0 - - def test_steps_render(self): - super(BookingWorkflowTestCase, self).render_steps() - - -class ResourceWorkflowTestCase(WorkflowTestCase): - - @classmethod - def setUpClass(cls): - super(ResourceWorkflowTestCase, cls).setUpClass() - cls.step_count = len(WorkflowFactory.resource_steps) - cls.wf_type = 1 - - def test_steps_render(self): - super(ResourceWorkflowTestCase, self).render_steps() - - -class ConfigWorkflowTestCase(WorkflowTestCase): - - @classmethod - def setUpClass(cls): - super(ConfigWorkflowTestCase, cls).setUpClass() - cls.step_count = len(WorkflowFactory.config_steps) - cls.wf_type = 2 - - def test_steps_render(self): - super(ConfigWorkflowTestCase, self).render_steps() diff --git a/src/workflow/urls.py b/src/workflow/urls.py deleted file mode 100644 index b1b95a7..0000000 --- a/src/workflow/urls.py +++ /dev/null @@ -1,23 +0,0 @@ -############################################################################## -# 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.conf.urls import url - -from workflow.views import manager_view, viewport_view, add_workflow, remove_workflow, create_workflow - -app_name = 'workflow' -urlpatterns = [ - - url(r'^manager/$', manager_view, name='manager'), - url(r'^add/$', add_workflow, name='add_workflow'), - url(r'^create/$', create_workflow, name='create_workflow'), - url(r'^pop/$', remove_workflow, name='remove_workflow'), - url(r'^$', viewport_view, name='viewport') -] diff --git a/src/workflow/views.py b/src/workflow/views.py deleted file mode 100644 index fb311b7..0000000 --- a/src/workflow/views.py +++ /dev/null @@ -1,112 +0,0 @@ -############################################################################## -# 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.http import HttpResponse -from django.shortcuts import render -from account.models import Lab - -import uuid - -from workflow.workflow_manager import ManagerTracker, SessionManager - -import logging -logger = logging.getLogger(__name__) - - -def attempt_auth(request): - try: - manager = ManagerTracker.managers[request.session['manager_session']] - - return manager - - except KeyError: - return None - - -def remove_workflow(request): - manager = attempt_auth(request) - - if not manager: - return no_workflow(request) - - has_more_workflows, result = manager.pop_workflow(discard=True) - - if not has_more_workflows: # this was the last workflow, so delete the reference to it in the tracker - del ManagerTracker.managers[request.session['manager_session']] - return manager.render(request) - - -def add_workflow(request): - manager = attempt_auth(request) - if not manager: - return no_workflow(request) - try: - workflow_type = int(request.POST.get('workflow_type')) - except ValueError: - return HttpResponse(status=400) - - manager.add_workflow(workflow_type=workflow_type) - return manager.render(request) # do we want this? - - -def manager_view(request): - manager = attempt_auth(request) - if not manager: - return no_workflow(request) - - return manager.handle_request(request) - - -def viewport_view(request): - if not request.user.is_authenticated: - return login(request) - - manager = attempt_auth(request) - if manager is None: - return no_workflow(request) - - if request.method != 'GET': - return HttpResponse(status=405) - - context = { - 'contact_email': Lab.objects.get(name="UNH_IOL").contact_email - } - - return render(request, 'workflow/viewport-base.html', context) - - -def create_workflow(request): - if request.method != 'POST': - return HttpResponse(status=405) - workflow_type = request.POST.get('workflow_type') - try: - workflow_type = int(workflow_type) - except Exception: - return HttpResponse(status=400) - mgr_uuid = create_session(workflow_type, request=request,) - request.session['manager_session'] = mgr_uuid - return HttpResponse() - - -def create_session(wf_type, request): - smgr = SessionManager(request=request) - smgr.add_workflow(workflow_type=wf_type, target_id=request.POST.get("target")) - manager_uuid = uuid.uuid4().hex - ManagerTracker.getInstance().managers[manager_uuid] = smgr - - return manager_uuid - - -def no_workflow(request): - return render(request, 'workflow/no_workflow.html', {'title': "Not Found"}, status=404) - - -def login(request): - return render(request, "dashboard/login.html", {'title': 'Authentication Required'}) diff --git a/src/workflow/workflow_factory.py b/src/workflow/workflow_factory.py deleted file mode 100644 index e688510..0000000 --- a/src/workflow/workflow_factory.py +++ /dev/null @@ -1,126 +0,0 @@ -############################################################################## -# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, Sean Smith, 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 workflow.booking_workflow import Booking_Resource_Select, Booking_Meta, OPNFV_Select -from workflow.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info, Define_Software -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 - -import logging -logger = logging.getLogger(__name__) - - -class MetaStep(object): - - UNTOUCHED = 0 - INVALID = 100 - VALID = 200 - - def set_invalid(self, message, code=100): - self.valid = code - self.message = message - - def set_valid(self, message, code=200): - self.valid = code - self.message = message - - def __init__(self, *args, **kwargs): - self.short_title = "error" - self.skip_step = 0 - self.valid = 0 - self.hidden = False - self.message = "" - self.id = uuid.uuid4() - - def to_json(self): - return { - 'title': self.short_title, - 'skip': self.skip_step, - 'valid': self.valid, - 'message': self.message, - } - - def __str__(self): - return "metastep: " + str(self.short_title) - - def __hash__(self): - return hash(self.id) - - def __eq__(self, other): - return self.id.int == other.id.int - - def __ne__(self, other): - return self.id.int != other.id.int - - -class Workflow(object): - def __init__(self, steps, repository): - self.repository = repository - self.steps = steps - self.active_index = 0 - - -class WorkflowFactory(): - booking_steps = [ - Booking_Resource_Select, - Booking_Meta, - OPNFV_Select, - ] - - resource_steps = [ - Define_Hardware, - Define_Software, - Define_Nets, - Resource_Meta_Info, - ] - - snapshot_steps = [ - Select_Host_Step, - 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.snapshot_steps, - self.opnfv_steps, - ] - - steps = self.make_steps(workflow_types[workflow_type], repository=repo) - return steps - - def create_workflow(self, workflow_type=None, repo=None): - steps = self.conjure(workflow_type, repo) - c_step = self.make_step(Confirmation_Step, repo) - steps.append(c_step) - return Workflow(steps, repo) - - def make_steps(self, step_types, repository): - steps = [] - for step_type in step_types: - steps.append(self.make_step(step_type, repository)) - - return steps - - def make_step(self, step_type, repository): - iden = step_type.description + step_type.title + step_type.template - return step_type(iden, repository) diff --git a/src/workflow/workflow_manager.py b/src/workflow/workflow_manager.py deleted file mode 100644 index 40be9d6..0000000 --- a/src/workflow/workflow_manager.py +++ /dev/null @@ -1,270 +0,0 @@ -############################################################################## -# 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.http import JsonResponse -from django.http.request import QueryDict -from django.urls import reverse - -from booking.models import Booking -from workflow.workflow_factory import WorkflowFactory -from workflow.models import Repository -from resource_inventory.models import ( - ResourceTemplate, - ResourceConfiguration, - OPNFVConfig -) -from workflow.forms import ManagerForm - -import logging -logger = logging.getLogger(__name__) - - -class SessionManager(): - def active_workflow(self): - return self.workflows[-1] - - def __init__(self, request=None): - self.workflows = [] - self.owner = request.user - self.factory = WorkflowFactory() - self.result = None - - def set_step_statuses(self, superclass_type, desired_enabled=True): - workflow = self.active_workflow() - steps = workflow.steps - for step in steps: - if isinstance(step, superclass_type): - if desired_enabled: - step.enable() - else: - step.disable() - - def add_workflow(self, workflow_type=None, **kwargs): - repo = Repository() - if (len(self.workflows) >= 1): - defaults = self.workflows[-1].repository.get_child_defaults() - repo.set_defaults(defaults) - repo.el[repo.HAS_RESULT] = False - repo.el[repo.SESSION_USER] = self.owner - repo.el[repo.SESSION_MANAGER] = self - self.workflows.append( - self.factory.create_workflow( - workflow_type=workflow_type, - repo=repo - ) - ) - - def get_redirect(self): - if isinstance(self.result, Booking): - return reverse('booking:booking_detail', kwargs={'booking_id': self.result.id}) - return "/" - - def pop_workflow(self, discard=False): - multiple_wfs = len(self.workflows) > 1 - if multiple_wfs: - if self.workflows[-1].repository.el[Repository.RESULT]: # move result - key = self.workflows[-1].repository.el[Repository.RESULT_KEY] - result = self.workflows[-1].repository.el[Repository.RESULT] - self.workflows[-2].repository.el[key] = result - prev_workflow = self.workflows.pop() - if self.workflows: - current_repo = self.workflows[-1].repository - else: - current_repo = prev_workflow.repository - self.result = current_repo.el[current_repo.RESULT] - if discard: - current_repo.cancel() - return multiple_wfs, self.result - - def status(self, request): - return { - "steps": [step.to_json() for step in self.active_workflow().steps], - "active": self.active_workflow().repository.el['active_step'], - "workflow_count": len(self.workflows) - } - - def handle_post(self, request): - form = ManagerForm(request.POST) - if form.is_valid(): - self.get_active_step().post( - QueryDict(form.cleaned_data['step_form']), - user=request.user - ) - # change step - if form.cleaned_data['step'] == 'prev': - self.go_prev() - if form.cleaned_data['step'] == 'next': - self.go_next() - else: - pass # Exception? - - def handle_request(self, request): - if request.method == 'POST': - self.handle_post(request) - return self.render(request) - - def render(self, request, **kwargs): - if self.workflows: - return JsonResponse({ - "meta": self.status(request), - "content": self.get_active_step().render_to_string(request), - }) - else: - return JsonResponse({ - "redirect": self.get_redirect() - }) - - def post_render(self, request): - return self.active_workflow().steps[self.active_workflow().active_index].post_render(request) - - def get_active_step(self): - return self.active_workflow().steps[self.active_workflow().active_index] - - def go_next(self, **kwargs): - # need to verify current step is valid to allow this - if self.get_active_step().valid < 200: - return - next_step = self.active_workflow().active_index + 1 - if next_step >= len(self.active_workflow().steps): - raise Exception("Out of bounds request for step") - while not self.active_workflow().steps[next_step].enabled: - next_step += 1 - self.active_workflow().repository.el['active_step'] = next_step - self.active_workflow().active_index = next_step - - def go_prev(self, **kwargs): - prev_step = self.active_workflow().active_index - 1 - if prev_step < 0: - raise Exception("Out of bounds request for step") - while not self.active_workflow().steps[prev_step].enabled: - prev_step -= 1 - self.active_workflow().repository.el['active_step'] = prev_step - self.active_workflow().active_index = prev_step - - def prefill_repo(self, target_id, workflow_type): - self.repository.el[self.repository.EDIT] = True - edit_object = None - if workflow_type == 0: - edit_object = Booking.objects.get(pk=target_id) - self.prefill_booking(edit_object) - elif workflow_type == 1: - edit_object = ResourceTemplate.objects.get(pk=target_id) - self.prefill_resource(edit_object) - elif workflow_type == 2: - edit_object = ResourceTemplate.objects.get(pk=target_id) - self.prefill_config(edit_object) - - def prefill_booking(self, booking): - models = self.make_booking_models(booking) - confirmation = self.make_booking_confirm(booking) - self.active_workflow().repository.el[self.active_workflow().repository.BOOKING_MODELS] = models - self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirmation - self.active_workflow().repository.el[self.active_workflow().repository.RESOURCE_TEMPLATE_MODELS] = self.make_grb_models(booking.resource.template) - self.active_workflow().repository.el[self.active_workflow().repository.SELECTED_RESOURCE_TEMPLATE] = self.make_grb_models(booking.resource.template)['bundle'] - self.active_workflow().repository.el[self.active_workflow().repository.CONFIG_MODELS] = self.make_config_models(booking.config_bundle) - - def prefill_resource(self, resource): - models = self.make_grb_models(resource) - confirm = self.make_grb_confirm(resource) - self.active_workflow().repository.el[self.active_workflow().repository.RESOURCE_TEMPLATE_MODELS] = models - self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirm - - def prefill_config(self, config): - models = self.make_config_models(config) - confirm = self.make_config_confirm(config) - self.active_workflow().repository.el[self.active_workflow().repository.CONFIG_MODELS] = models - self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirm - grb_models = self.make_grb_models(config.bundle) - self.active_workflow().repository.el[self.active_workflow().repository.RESOURCE_TEMPLATE_MODELS] = grb_models - - def make_grb_models(self, resource): - models = self.active_workflow().repository.el.get(self.active_workflow().repository.RESOURCE_TEMPLATE_MODELS, {}) - models['hosts'] = [] - models['bundle'] = resource - models['interfaces'] = {} - models['vlans'] = {} - for host in resource.getResources(): - models['hosts'].append(host) - models['interfaces'][host.resource.name] = [] - models['vlans'][host.resource.name] = {} - for interface in host.generic_interfaces.all(): - models['interfaces'][host.resource.name].append(interface) - models['vlans'][host.resource.name][interface.profile.name] = [] - for vlan in interface.vlans.all(): - models['vlans'][host.resource.name][interface.profile.name].append(vlan) - return models - - def make_grb_confirm(self, resource): - confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {}) - confirm['resource'] = {} - confirm['resource']['hosts'] = [] - confirm['resource']['lab'] = resource.lab.lab_user.username - for host in resource.getResources(): - confirm['resource']['hosts'].append({"name": host.resource.name, "profile": host.profile.name}) - return confirm - - def make_config_models(self, config): - models = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIG_MODELS, {}) - models['bundle'] = config - models['host_configs'] = [] - for host_conf in ResourceConfiguration.objects.filter(bundle=config): - models['host_configs'].append(host_conf) - models['opnfv'] = OPNFVConfig.objects.filter(bundle=config).last() - return models - - def make_config_confirm(self, config): - confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {}) - confirm['configuration'] = {} - confirm['configuration']['hosts'] = [] - confirm['configuration']['name'] = config.name - confirm['configuration']['description'] = config.description - opnfv = OPNFVConfig.objects.filter(bundle=config).last() - confirm['configuration']['installer'] = opnfv.installer.name - confirm['configuration']['scenario'] = opnfv.scenario.name - for host_conf in ResourceConfiguration.objects.filter(bundle=config): - h = {"name": host_conf.host.resource.name, "image": host_conf.image.name, "role": host_conf.opnfvRole.name} - confirm['configuration']['hosts'].append(h) - return confirm - - def make_booking_models(self, booking): - models = self.active_workflow().repository.el.get(self.active_workflow().repository.BOOKING_MODELS, {}) - models['booking'] = booking - models['collaborators'] = [] - for user in booking.collaborators.all(): - models['collaborators'].append(user) - return models - - def make_booking_confirm(self, booking): - confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {}) - confirm['booking'] = {} - confirm['booking']['length'] = (booking.end - booking.start).days - confirm['booking']['project'] = booking.project - confirm['booking']['purpose'] = booking.purpose - confirm['booking']['resource name'] = booking.resource.template.name - confirm['booking']['configuration name'] = booking.config_bundle.name - confirm['booking']['collaborators'] = [] - for user in booking.collaborators.all(): - confirm['booking']['collaborators'].append(user.username) - return confirm - - -class ManagerTracker(): - instance = None - - managers = {} - - def __init__(self): - pass - - @staticmethod - def getInstance(): - if ManagerTracker.instance is None: - ManagerTracker.instance = ManagerTracker() - return ManagerTracker.instance -- cgit 1.2.3-korg