aboutsummaryrefslogtreecommitdiffstats
path: root/src/workflow
diff options
context:
space:
mode:
authorJustin Choquette <jchoquette@iol.unh.edu>2023-06-08 12:46:53 -0400
committerJustin Choquette <jchoquette@iol.unh.edu>2023-07-21 13:17:51 -0400
commita09db9f287a02873c0226759f8ea444bb304cd59 (patch)
tree59e744e4b998973a808abbae2d21fbdd6201d829 /src/workflow
parent8ddc7e820e120f1dde4e901d3cb6f1dd3f281e65 (diff)
LaaS 3.0 Almost MVP
Change-Id: Ided9a43cf3088bb58a233dc459711c03f43e11b8 Signed-off-by: Justin Choquette <jchoquette@iol.unh.edu>
Diffstat (limited to 'src/workflow')
-rw-r--r--src/workflow/README32
-rw-r--r--src/workflow/booking_workflow.py182
-rw-r--r--src/workflow/forms.py263
-rw-r--r--src/workflow/models.py687
-rw-r--r--src/workflow/opnfv_workflow.py292
-rw-r--r--src/workflow/resource_bundle_workflow.py614
-rw-r--r--src/workflow/snapshot_workflow.py116
-rw-r--r--src/workflow/tests/__init__.py8
-rw-r--r--src/workflow/tests/constants.py198
-rw-r--r--src/workflow/tests/test_fixtures.py2
-rw-r--r--src/workflow/tests/test_steps.py269
-rw-r--r--src/workflow/tests/test_workflows.py99
-rw-r--r--src/workflow/urls.py11
-rw-r--r--src/workflow/views.py137
-rw-r--r--src/workflow/workflow_factory.py126
-rw-r--r--src/workflow/workflow_manager.py270
16 files changed, 60 insertions, 3246 deletions
diff --git a/src/workflow/README b/src/workflow/README
index fb4b949..565d1c2 100644
--- a/src/workflow/README
+++ b/src/workflow/README
@@ -1,31 +1 @@
-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.
+TODO: Document how new workflows work
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
index 62abad6..da36e83 100644
--- a/src/workflow/forms.py
+++ b/src/workflow/forms.py
@@ -20,12 +20,7 @@ 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
@@ -206,50 +201,6 @@ class OPNFVSelectForm(SearchableSelectAbstractForm):
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)
@@ -284,206 +235,16 @@ class MultipleSelectFilterField(forms.Field):
except json.decoder.JSONDecodeError:
pass
raise ValidationError("content is not valid JSON")
+
+class BookingMetaForm(forms.Form):
+ def __init__(self, *args, user_initial=[], owner=None, **kwargs):
+ super(BookingMetaForm, self).__init__(**kwargs)
-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"))
+ 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()
+ ) \ No newline at end of file
diff --git a/src/workflow/models.py b/src/workflow/models.py
index e065202..f69ee85 100644
--- a/src/workflow/models.py
+++ b/src/workflow/models.py
@@ -5,689 +5,4 @@
# 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 = {}
+############################################################################## \ No newline at end of file
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": <NetworkRole object ref>,
- "network": <Network object ref>
- }
- """
- def create_netformset(self, roles, config_bundle, data=None):
- roles_initial = []
- set_roles = self.repo_get(self.repo.OPNFV_MODELS, {}).get("network_roles")
- if set_roles:
- roles_initial = set_roles
- else:
- for role in OPNFV_SETTINGS.NETWORK_ROLES:
- roles_initial.append({"role": role})
-
- Formset = formset_factory(OPNFVNetworkRoleForm, extra=0)
- kwargs = {
- "initial": roles_initial,
- "form_kwargs": {"config_bundle": config_bundle}
- }
- formset = None
- if data:
- formset = Formset(data, **kwargs)
- else:
- formset = Formset(**kwargs)
- return formset
-
- def get_context(self):
- context = super(Assign_Network_Roles, self).get_context()
- config_bundle = self.repo_get(self.repo.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('&quot;', '"')
- 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('&quot;', '"')
- 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 = """<mxGraphModel>
-<root>
-<mxCell id="0"/>
-<mxCell id="1" parent="0"/>
-<mxCell id="host_null" value="Test profile 0" style="editable=0" vertex="1" connectable="0" parent="1">
-<mxGeometry x="75" y="150" width="110" height="90" as="geometry"/>
-</mxCell>
-<mxCell id="2" value="eno0" style="fillColor=blue;editable=0" vertex="1" parent="host_null">
-<mxGeometry x="90" y="5" width="20" height="20" as="geometry"/>
-</mxCell>
-<mxCell id="3" value="eno1" style="fillColor=blue;editable=0" vertex="1" parent="host_null">
-<mxGeometry x="90" y="30" width="20" height="20" as="geometry"/>
-</mxCell>
-<mxCell id="4" value="eno2" style="fillColor=blue;editable=0" vertex="1" parent="host_null">
-<mxGeometry x="90" y="55" width="20" height="20" as="geometry"/>
-</mxCell>
-<mxCell id="5" value="Test profile 3" style="editable=0" vertex="1" connectable="0" parent="1">
-<mxGeometry x="75" y="290" width="110" height="90" as="geometry"/>
-</mxCell>
-<mxCell id="6" value="eno0" style="fillColor=blue;editable=0" vertex="1" parent="5">
-<mxGeometry x="90" y="5" width="20" height="20" as="geometry"/>
-</mxCell>
-<mxCell id="7" value="eno1" style="fillColor=blue;editable=0" vertex="1" parent="5">
-<mxGeometry x="90" y="30" width="20" height="20" as="geometry"/>
-</mxCell>
-<mxCell id="8" value="eno2" style="fillColor=blue;editable=0" vertex="1" parent="5">
-<mxGeometry x="90" y="55" width="20" height="20" as="geometry"/>
-</mxCell>
-<mxCell id="network_0" value="{&quot;vlan_id&quot;:&quot;500&quot;,&quot;name&quot;:&quot;net&quot;}" style="fillColor=red" vertex="1" parent="1">
-<mxGeometry x="400" y="-20" width="10" height="2000" as="geometry"/>
-</mxCell>
-<mxCell id="9" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="10" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.02" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="11" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.04" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="12" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.06" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="13" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.08" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="14" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.1" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="15" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.12" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="16" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.14" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="17" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.16" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="18" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.18" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="19" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.2" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="20" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.22" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="21" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.24" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="22" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.26" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="23" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.28" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="24" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.3" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="25" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.32" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="26" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.34" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="27" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.36" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="28" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.38" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="29" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.4" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="30" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.42" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="31" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.44" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="32" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.46" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="33" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.48" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="34" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.5" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="35" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.52" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="36" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.54" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="37" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.56" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="38" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.58" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="39" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.6" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="40" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.62" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="41" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.64" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="42" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.66" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="43" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.68" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="44" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.7000000000000001" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="45" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.72" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="46" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.74" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="47" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.76" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="48" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.78" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="49" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.8" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="50" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.8200000000000001" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="51" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.84" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="52" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.86" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="53" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.88" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="54" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.9" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="55" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.92" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="56" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.9400000000000001" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="57" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.96" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="58" value="" style="fillColor=black;opacity=0" vertex="1" parent="network_0">
-<mxGeometry y="0.98" width="10" height="40" relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="59" value="{&quot;tagged&quot;:true}" style="strokeColor=red" edge="1" parent="1" source="2" target="13">
-<mxGeometry relative="1" as="geometry"/>
-</mxCell>
-<mxCell id="60" value="{&quot;tagged&quot;:false}" style="strokeColor=red" edge="1" parent="1" source="7" target="17">
-<mxGeometry relative="1" as="geometry"/>
-</mxCell>
-</root>
-</mxGraphModel>
-"""
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 = '<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="host_c" value="{&quot;name&quot;:&quot;c&quot;,&quot;description&quot;:&quot;Intel based ProLiant server from HPE&quot;}" style="editable=0" parent="1" vertex="1" connectable="0"><mxGeometry x="75" y="150" width="110" height="175" as="geometry"><mxPoint x="-50" as="offset"/></mxGeometry></mxCell><mxCell id="2" value="{&quot;name&quot;:&quot;ens4f1&quot;,&quot;description&quot;:&quot;speed: 10000M type: onboard&quot;}" style="fillColor=blue;editable=0" parent="host_c" vertex="1"><mxGeometry x="90" y="12" width="20" height="20" as="geometry"><mxPoint x="-26" as="offset"/></mxGeometry></mxCell><mxCell id="3" value="{&quot;name&quot;:&quot;ens4f0&quot;,&quot;description&quot;:&quot;speed: 10000M type: onboard&quot;}" style="fillColor=blue;editable=0" parent="host_c" vertex="1"><mxGeometry x="90" y="37" width="20" height="20" as="geometry"><mxPoint x="-26" as="offset"/></mxGeometry></mxCell><mxCell id="4" value="{&quot;name&quot;:&quot;ens1f2&quot;,&quot;description&quot;:&quot;speed: 10000M type: onboard&quot;}" style="fillColor=blue;editable=0" parent="host_c" vertex="1"><mxGeometry x="90" y="62" width="20" height="20" as="geometry"><mxPoint x="-26" as="offset"/></mxGeometry></mxCell><mxCell id="5" value="{&quot;name&quot;:&quot;ens1f1&quot;,&quot;description&quot;:&quot;speed: 10000M type: onboard&quot;}" style="fillColor=blue;editable=0" parent="host_c" vertex="1"><mxGeometry x="90" y="87" width="20" height="20" as="geometry"><mxPoint x="-26" as="offset"/></mxGeometry></mxCell><mxCell id="6" value="{&quot;name&quot;:&quot;ens1f0&quot;,&quot;description&quot;:&quot;speed: 10000M type: onboard&quot;}" style="fillColor=blue;editable=0" parent="host_c" vertex="1"><mxGeometry x="90" y="112" width="20" height="20" as="geometry"><mxPoint x="-26" as="offset"/></mxGeometry></mxCell><mxCell id="7" value="{&quot;name&quot;:&quot;eno49&quot;,&quot;description&quot;:&quot;speed: 10000M type: onboard&quot;}" style="fillColor=blue;editable=0" parent="host_c" vertex="1"><mxGeometry x="90" y="137" width="20" height="20" as="geometry"><mxPoint x="-22" as="offset"/></mxGeometry></mxCell><mxCell id="network_0" value="{&quot;name&quot;:&quot;public&quot;,&quot;public&quot;:true}" style="fillColor=red" parent="1" vertex="1"><mxGeometry x="400" y="-10" width="10" height="1700" as="geometry"/></mxCell><mxCell id="8" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="9" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.022222222222222223" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="10" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.044444444444444446" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="11" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.06666666666666667" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="12" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.08888888888888889" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="13" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.11111111111111112" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="14" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.13333333333333333" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="15" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.15555555555555556" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="16" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.17777777777777778" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="17" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.2" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="18" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.22222222222222224" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="19" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.24444444444444446" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="20" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.26666666666666666" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="21" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.2888888888888889" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="22" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.3111111111111111" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="23" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.33333333333333337" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="24" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.35555555555555557" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="25" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.37777777777777777" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="26" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.4" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="27" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.4222222222222222" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="28" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.4444444444444445" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="29" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.4666666666666667" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="30" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.48888888888888893" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="31" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.5111111111111112" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="32" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.5333333333333333" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="33" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.5555555555555556" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="34" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.5777777777777778" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="35" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.6" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="36" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.6222222222222222" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="37" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.6444444444444445" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="38" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.6666666666666667" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="39" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.6888888888888889" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="40" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.7111111111111111" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="41" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.7333333333333334" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="42" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.7555555555555555" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="43" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.7777777777777778" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="44" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.8" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="45" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.8222222222222223" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="46" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.8444444444444444" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="47" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.8666666666666667" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="48" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.888888888888889" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="49" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.9111111111111111" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="50" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.9333333333333333" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="51" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.9555555555555556" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="52" value="" style="fillColor=black;opacity=0" parent="network_0" vertex="1"><mxGeometry y="0.9777777777777779" width="10" height="37.77777777777778" relative="1" as="geometry"/></mxCell><mxCell id="53" value="{&quot;tagged&quot;:false}" style="strokeColor=red" parent="1" source="2" target="13" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell></root></mxGraphModel>'
diff --git a/src/workflow/tests/test_steps.py b/src/workflow/tests/test_steps.py
deleted file mode 100644
index 57bf6a3..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": <wf_type_int>
-
- 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=<index>."""
- 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
index b1b95a7..e0ee41d 100644
--- a/src/workflow/urls.py
+++ b/src/workflow/urls.py
@@ -9,15 +9,10 @@
from django.conf.urls import url
-
-from workflow.views import manager_view, viewport_view, add_workflow, remove_workflow, create_workflow
+from workflow.views import design_a_pod_view, book_a_pod_view
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')
+ url(r'^design/$', design_a_pod_view, name='design_a_pod'),
+ url(r'^book/$', book_a_pod_view, name='book_a_pod'),
]
diff --git a/src/workflow/views.py b/src/workflow/views.py
index fb311b7..08ed22b 100644
--- a/src/workflow/views.py
+++ b/src/workflow/views.py
@@ -7,101 +7,13 @@
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
-
-from django.http import HttpResponse
+import json
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
+from laas_dashboard.settings import TEMPLATE_OVERRIDE
+from django.http import HttpResponse
+from django.http.response import JsonResponse
+from workflow.forms import BookingMetaForm
+from api.views import liblaas_request, make_booking
def no_workflow(request):
@@ -110,3 +22,40 @@ def no_workflow(request):
def login(request):
return render(request, "dashboard/login.html", {'title': 'Authentication Required'})
+
+def design_a_pod_view(request):
+ if request.method == "GET":
+ if not request.user.is_authenticated:
+ return login(request)
+ template = "workflow/design_a_pod.html"
+ context = {
+ "dashboard": str(TEMPLATE_OVERRIDE)
+ }
+ return render(request, template, context)
+
+ if request.method == "POST":
+ print("forwarding request to liblaas...")
+ return liblaas_request(request)
+
+ return HttpResponse(status=405)
+
+def book_a_pod_view(request):
+ if request.method == "GET":
+ if not request.user.is_authenticated:
+ return login(request)
+ template = "workflow/book_a_pod.html"
+ context = {
+ "dashboard": str(TEMPLATE_OVERRIDE),
+ "form": BookingMetaForm(initial={}, user_initial=[], owner=request.user),
+ }
+ return render(request, template, context)
+
+ if request.method == "POST":
+ print("forwarding request to liblaas...")
+ return liblaas_request(request)
+
+ # Using PUT to signal that we do not want to talk to liblaas
+ if request.method == "PUT":
+ return make_booking(request)
+
+ return HttpResponse(status=405)
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