summaryrefslogtreecommitdiffstats
path: root/src/workflow
diff options
context:
space:
mode:
Diffstat (limited to 'src/workflow')
-rw-r--r--src/workflow/booking_workflow.py8
-rw-r--r--src/workflow/forms.py32
-rw-r--r--src/workflow/models.py49
-rw-r--r--src/workflow/opnfv_workflow.py23
-rw-r--r--src/workflow/resource_bundle_workflow.py75
-rw-r--r--src/workflow/snapshot_workflow.py15
-rw-r--r--src/workflow/sw_bundle_workflow.py16
-rw-r--r--src/workflow/tests/test_fixtures.py2
-rw-r--r--src/workflow/tests/test_steps.py437
-rw-r--r--src/workflow/tests/test_steps_render.py43
-rw-r--r--src/workflow/tests/test_workflows.py7
-rw-r--r--src/workflow/urls.py19
-rw-r--r--src/workflow/views.py101
-rw-r--r--src/workflow/workflow_manager.py76
14 files changed, 382 insertions, 521 deletions
diff --git a/src/workflow/booking_workflow.py b/src/workflow/booking_workflow.py
index 42372ce..3698164 100644
--- a/src/workflow/booking_workflow.py
+++ b/src/workflow/booking_workflow.py
@@ -7,7 +7,6 @@
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
-from django.contrib import messages
from django.utils import timezone
from datetime import timedelta
@@ -171,8 +170,8 @@ class Booking_Meta(WorkflowStep):
context['form'] = BookingMetaForm(initial=initial, user_initial=default, owner=owner)
return context
- def post_render(self, request):
- form = BookingMetaForm(data=request.POST, owner=request.user)
+ def post(self, post_data, user):
+ form = BookingMetaForm(data=post_data, owner=user)
forms = self.repo_get(self.repo.BOOKING_FORMS, {})
@@ -212,9 +211,6 @@ class Booking_Meta(WorkflowStep):
self.repo_put(self.repo.BOOKING_MODELS, models)
self.repo_put(self.repo.CONFIRMATION, confirm)
- messages.add_message(request, messages.SUCCESS, 'Form Validated', fail_silently=True)
self.set_valid("Step Completed")
else:
- messages.add_message(request, messages.ERROR, "Form didn't validate", fail_silently=True)
self.set_invalid("Please complete the fields highlighted in red to continue")
- return self.render(request)
diff --git a/src/workflow/forms.py b/src/workflow/forms.py
index ee44ecd..4d5e9e2 100644
--- a/src/workflow/forms.py
+++ b/src/workflow/forms.py
@@ -15,6 +15,7 @@ from django.template.loader import render_to_string
from django.forms.widgets import NumberInput
import json
+import urllib
from account.models import Lab
from account.models import UserProfile
@@ -120,7 +121,12 @@ class SearchableSelectMultipleField(forms.Field):
raise ValidationError("Nothing was selected")
else:
return []
- data_as_list = json.loads(data)
+ try:
+ data_as_list = json.loads(data)
+ except json.decoder.JSONDecodeError:
+ data_as_list = None
+ if not data_as_list:
+ raise ValidationError("Contents Not JSON")
if self.selectable_limit != -1:
if len(data_as_list) > self.selectable_limit:
raise ValidationError("Too many items were selected")
@@ -270,7 +276,11 @@ class MultipleSelectFilterField(forms.Field):
super().__init__(**kwargs)
def to_python(self, value):
- return json.loads(value)
+ try:
+ return json.loads(value)
+ except json.decoder.JSONDecodeError:
+ pass
+ raise ValidationError("content is not valid JSON")
class FormUtils:
@@ -428,6 +438,24 @@ class ConfirmationForm(forms.Form):
)
+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)
diff --git a/src/workflow/models.py b/src/workflow/models.py
index 6c6bd9a..9d1fac2 100644
--- a/src/workflow/models.py
+++ b/src/workflow/models.py
@@ -8,8 +8,7 @@
##############################################################################
-from django.shortcuts import render
-from django.contrib import messages
+from django.template.loader import get_template
from django.http import HttpResponse
from django.utils import timezone
@@ -219,16 +218,14 @@ class WorkflowStep(object):
return context
def render(self, request):
- self.context = self.get_context()
- return render(request, self.template, self.context)
+ return HttpResponse(self.render_to_string(request))
- def post_render(self, request):
- return self.render(request)
+ def render_to_string(self, request):
+ template = get_template(self.template)
+ return template.render(self.get_context(), request)
- def test_render(self, request):
- if request.method == "POST":
- return self.post_render(request)
- return self.render(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
@@ -263,22 +260,18 @@ class AbstractSelectOrCreate(WorkflowStep):
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_render(self, request):
- context = self.get_context()
- form = self.form(request.POST, queryset=self.get_form_queryset())
+ 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 render(request, self.template, context)
+ return
self.repo_put(self.select_repo_key, bundle)
self.put_confirm_info(bundle)
self.set_valid("Step Completed")
else:
self.alert_bundle_missing()
- messages.add_message(request, messages.ERROR, "Form Didn't Validate", fail_silently=True)
-
- return self.render(request)
def get_context(self):
default = []
@@ -316,6 +309,9 @@ class Confirmation_Step(WorkflowStep):
default_flow_style=False
).strip()
+ if self.valid == WorkflowStepStatus.VALID:
+ context["confirm_succeeded"] = "true"
+
return context
def flush_to_db(self):
@@ -323,29 +319,24 @@ class Confirmation_Step(WorkflowStep):
if errors:
return errors
- def post_render(self, request):
- form = ConfirmationForm(request.POST)
+ def post(self, post_data, user):
+ form = ConfirmationForm(post_data)
if form.is_valid():
data = form.cleaned_data['confirm']
- context = self.get_context()
if data == "True":
- context["bypassed"] = "true"
errors = self.flush_to_db()
if errors:
- messages.add_message(request, messages.ERROR, "ERROR OCCURRED: " + errors)
+ self.set_invalid("ERROR OCCURRED: " + errors)
else:
- messages.add_message(request, messages.SUCCESS, "Confirmed")
+ self.set_valid("Confirmed")
- return HttpResponse('')
elif data == "False":
- context["bypassed"] = "true"
- messages.add_message(request, messages.SUCCESS, "Canceled")
- return render(request, self.template, context)
+ self.set_valid("Canceled")
else:
- pass
+ self.set_invalid("Bad Form Contents")
else:
- pass
+ self.set_invalid("Bad Form Contents")
class Repository():
diff --git a/src/workflow/opnfv_workflow.py b/src/workflow/opnfv_workflow.py
index 7d499ec..a192d6e 100644
--- a/src/workflow/opnfv_workflow.py
+++ b/src/workflow/opnfv_workflow.py
@@ -74,8 +74,8 @@ class Pick_Installer(WorkflowStep):
context["form"] = OPNFVSelectionForm(initial=initial)
return context
- def post_render(self, request):
- form = OPNFVSelectionForm(request.POST)
+ def post(self, post_data, user):
+ form = OPNFVSelectionForm(post_data)
if form.is_valid():
installer = form.cleaned_data['installer']
scenario = form.cleaned_data['scenario']
@@ -88,8 +88,6 @@ class Pick_Installer(WorkflowStep):
else:
self.set_invalid("Please select an Installer and Scenario")
- return self.render(request)
-
class Assign_Network_Roles(WorkflowStep):
template = 'config_bundle/steps/assign_network_roles.html'
@@ -150,11 +148,11 @@ class Assign_Network_Roles(WorkflowStep):
confirm['network roles'][role['role']] = role['network'].name
self.repo_put(self.repo.CONFIRMATION, confirm)
- def post_render(self, request):
+ 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=request.POST)
+ 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:
@@ -168,7 +166,6 @@ class Assign_Network_Roles(WorkflowStep):
self.update_confirmation()
else:
self.set_invalid("Please complete all fields")
- return self.render(request)
class Assign_Host_Roles(WorkflowStep): # taken verbatim from Define_Software in sw workflow, merge the two?
@@ -227,8 +224,8 @@ class Assign_Host_Roles(WorkflowStep): # taken verbatim from Define_Software in
confirm['host roles'][role['host_name']] = role['role'].name
self.repo_put(self.repo.CONFIRMATION, confirm)
- def post_render(self, request):
- formset = self.create_host_role_formset(data=request.POST)
+ 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", [])
@@ -254,8 +251,6 @@ class Assign_Host_Roles(WorkflowStep): # taken verbatim from Define_Software in
else:
self.set_invalid("Please complete all fields")
- return self.render(request)
-
class MetaInfo(WorkflowStep):
template = 'config_bundle/steps/config_software.html'
@@ -280,11 +275,11 @@ class MetaInfo(WorkflowStep):
confirm['description'] = meta['description']
self.repo_put(self.repo.CONFIRMATION, confirm)
- def post_render(self, request):
+ def post(self, post_data, user):
models = self.repo_get(self.repo.OPNFV_MODELS, {})
info = models.get("meta", {})
- form = BasicMetaForm(request.POST)
+ form = BasicMetaForm(post_data)
if form.is_valid():
info['name'] = form.cleaned_data['name']
info['description'] = form.cleaned_data['description']
@@ -294,6 +289,4 @@ class MetaInfo(WorkflowStep):
self.set_valid("Complete")
else:
self.set_invalid("Please correct the errors shown below")
-
self.repo_put(self.repo.OPNFV_MODELS, models)
- return self.render(request)
diff --git a/src/workflow/resource_bundle_workflow.py b/src/workflow/resource_bundle_workflow.py
index 06737d2..2f4aa5d 100644
--- a/src/workflow/resource_bundle_workflow.py
+++ b/src/workflow/resource_bundle_workflow.py
@@ -8,8 +8,6 @@
##############################################################################
-from django.shortcuts import render
-from django.forms import formset_factory
from django.conf import settings
import json
@@ -22,7 +20,6 @@ from workflow.forms import (
HardwareDefinitionForm,
NetworkDefinitionForm,
ResourceMetaForm,
- GenericHostMetaForm
)
from resource_inventory.models import (
GenericResourceBundle,
@@ -111,9 +108,9 @@ class Define_Hardware(WorkflowStep):
confirm['resource']['lab'] = models['lab'].lab_user.username
self.repo_put(self.repo.CONFIRMATION, confirm)
- def post_render(self, request):
+ def post(self, post_data, user):
try:
- self.form = HardwareDefinitionForm(request.POST)
+ self.form = HardwareDefinitionForm(post_data)
if self.form.is_valid():
self.update_models(self.form.cleaned_data)
self.update_confirmation()
@@ -122,8 +119,6 @@ class Define_Hardware(WorkflowStep):
self.set_invalid("Please complete the fields highlighted in red to continue")
except Exception as e:
self.set_invalid(str(e))
- self.context = self.get_context()
- return render(request, self.template, self.context)
class Define_Nets(WorkflowStep):
@@ -206,13 +201,13 @@ class Define_Nets(WorkflowStep):
return context
- def post_render(self, request):
+ def post(self, post_data, user):
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
if 'hosts' in models:
host_set = set([h.resource.name + "*" + h.profile.name for h in models['hosts']])
self.repo_put(self.repo.GRB_LAST_HOSTLIST, host_set)
try:
- xmlData = request.POST.get("xml")
+ xmlData = post_data.get("xml")
self.updateModels(xmlData)
# update model with xml
self.set_valid("Networks applied successfully")
@@ -220,7 +215,6 @@ class Define_Nets(WorkflowStep):
self.set_invalid("Public network not availble")
except Exception as e:
self.set_invalid("An error occurred when applying networks: " + str(e))
- return self.render(request)
def updateModels(self, xmlData):
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
@@ -380,8 +374,8 @@ class Resource_Meta_Info(WorkflowStep):
context['form'] = ResourceMetaForm(initial={"bundle_name": name, "bundle_description": desc})
return context
- def post_render(self, request):
- form = ResourceMetaForm(request.POST)
+ def post(self, post_data, user):
+ form = ResourceMetaForm(post_data)
if form.is_valid():
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
name = form.cleaned_data['bundle_name']
@@ -402,62 +396,5 @@ class Resource_Meta_Info(WorkflowStep):
confirm_info["description"] = tmp
self.repo_put(self.repo.CONFIRMATION, confirm)
self.set_valid("Step Completed")
-
else:
self.set_invalid("Please correct the fields highlighted in red to continue")
- pass
- return self.render(request)
-
-
-class Host_Meta_Info(WorkflowStep):
- template = "resource/steps/host_info.html"
- title = "Host Info"
- description = "We need a little bit of information about your chosen machines"
- short_title = "host info"
-
- def __init__(self, *args, **kwargs):
- super(Host_Meta_Info, self).__init__(*args, **kwargs)
- self.formset = formset_factory(GenericHostMetaForm, extra=0)
-
- def get_context(self):
- context = super(Host_Meta_Info, self).get_context()
- GenericHostFormset = self.formset
- models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
- initial_data = []
- if "hosts" not in models:
- context['error'] = "Please go back and select your hosts"
- else:
- for host in models['hosts']:
- profile = host.profile.name
- name = host.resource.name
- if not name:
- name = ""
- initial_data.append({"host_profile": profile, "host_name": name})
- context['formset'] = GenericHostFormset(initial=initial_data)
- return context
-
- def post_render(self, request):
- models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
- if 'hosts' not in models:
- models['hosts'] = []
- hosts = models['hosts']
- i = 0
- confirm_hosts = []
- GenericHostFormset = self.formset
- formset = GenericHostFormset(request.POST)
- if formset.is_valid():
- for form in formset:
- host = hosts[i]
- host.resource.name = form.cleaned_data['host_name']
- i += 1
- confirm_hosts.append({"name": host.resource.name, "profile": host.profile.name})
- models['hosts'] = hosts
- self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
- if "resource" not in confirm:
- confirm['resource'] = {}
- confirm['resource']['hosts'] = confirm_hosts
- self.repo_put(self.repo.CONFIRMATION, confirm)
- else:
- pass
- return self.render(request)
diff --git a/src/workflow/snapshot_workflow.py b/src/workflow/snapshot_workflow.py
index 5414784..4266587 100644
--- a/src/workflow/snapshot_workflow.py
+++ b/src/workflow/snapshot_workflow.py
@@ -49,15 +49,15 @@ class Select_Host_Step(WorkflowStep):
context['chosen'] = chosen
return context
- def post_render(self, request):
- host_data = request.POST.get("host")
+ def post(self, post_data, user):
+ host_data = post_data.get("host")
if not host_data:
self.set_invalid("Please select a host")
- return self.render(request)
+ return
host = json.loads(host_data)
if 'name' not in host or 'booking' not in host:
self.set_invalid("Invalid host selected")
- return self.render(request)
+ return
name = host['name']
booking_id = host['booking']
booking = Booking.objects.get(pk=booking_id)
@@ -76,7 +76,6 @@ class Select_Host_Step(WorkflowStep):
confirm['snapshot'] = snap_confirm
self.repo_put(self.repo.CONFIRMATION, confirm)
self.set_valid("Success")
- return self.render(request)
class Image_Meta_Step(WorkflowStep):
@@ -97,8 +96,8 @@ class Image_Meta_Step(WorkflowStep):
context['form'] = form
return context
- def post_render(self, request):
- form = BasicMetaForm(request.POST)
+ 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)
@@ -115,5 +114,3 @@ class Image_Meta_Step(WorkflowStep):
self.set_valid("Success")
else:
self.set_invalid("Please Fill out the Form")
-
- return self.render(request)
diff --git a/src/workflow/sw_bundle_workflow.py b/src/workflow/sw_bundle_workflow.py
index 0c558fc..4dc0b8e 100644
--- a/src/workflow/sw_bundle_workflow.py
+++ b/src/workflow/sw_bundle_workflow.py
@@ -104,7 +104,7 @@ class Define_Software(WorkflowStep):
return context
- def post_render(self, request):
+ def post(self, post_data, user):
models = self.repo_get(self.repo.CONFIG_MODELS, {})
if "bundle" not in models:
models['bundle'] = ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER))
@@ -112,8 +112,8 @@ class Define_Software(WorkflowStep):
confirm = self.repo_get(self.repo.CONFIRMATION, {})
hosts = self.get_host_list()
- models['headnode_index'] = request.POST.get("headnode", 1)
- formset = self.create_hostformset(hosts, data=request.POST)
+ models['headnode_index'] = post_data.get("headnode", 1)
+ formset = self.create_hostformset(hosts, data=post_data)
has_headnode = False
if formset.is_valid():
models['host_configs'] = []
@@ -140,7 +140,7 @@ class Define_Software(WorkflowStep):
if not has_headnode:
self.set_invalid('Must have one "Headnode" per POD')
- return self.render(request)
+ return
self.repo_put(self.repo.CONFIG_MODELS, models)
if "configuration" not in confirm:
@@ -151,8 +151,6 @@ class Define_Software(WorkflowStep):
else:
self.set_invalid("Please complete all fields")
- return self.render(request)
-
class Config_Software(WorkflowStep):
template = 'config_bundle/steps/config_software.html'
@@ -172,7 +170,7 @@ class Config_Software(WorkflowStep):
context["form"] = BasicMetaForm(initial=initial)
return context
- def post_render(self, request):
+ def post(self, post_data, user):
models = self.repo_get(self.repo.CONFIG_MODELS, {})
if "bundle" not in models:
models['bundle'] = ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER))
@@ -181,7 +179,7 @@ class Config_Software(WorkflowStep):
if "configuration" not in confirm:
confirm['configuration'] = {}
- form = BasicMetaForm(request.POST)
+ form = BasicMetaForm(post_data)
if form.is_valid():
models['bundle'].name = form.cleaned_data['name']
models['bundle'].description = form.cleaned_data['description']
@@ -194,5 +192,3 @@ class Config_Software(WorkflowStep):
self.repo_put(self.repo.CONFIG_MODELS, models)
self.repo_put(self.repo.CONFIRMATION, confirm)
-
- return self.render(request)
diff --git a/src/workflow/tests/test_fixtures.py b/src/workflow/tests/test_fixtures.py
new file mode 100644
index 0000000..fe16be7
--- /dev/null
+++ b/src/workflow/tests/test_fixtures.py
@@ -0,0 +1,2 @@
+
+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
index 380102a..39b1f86 100644
--- a/src/workflow/tests/test_steps.py
+++ b/src/workflow/tests/test_steps.py
@@ -7,275 +7,256 @@
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
-from django.test import TestCase
-from dashboard.populate_db import Populator
-from workflow.tests import constants
-from workflow.workflow_factory import WorkflowFactory
+"""
+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.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info, Host_Meta_Info
-from workflow.sw_bundle_workflow import SWConf_Resource_Select, Define_Software, Config_Software
-from workflow.booking_workflow import Booking_Resource_Select, SWConfig_Select, Booking_Meta
-from django.http import QueryDict, HttpRequest
-from django.contrib.auth.models import User
-from resource_inventory.models import (
- Scenario,
- Installer,
- OPNFVRole,
- Image,
- GenericResourceBundle,
- GenericHost,
- HostProfile,
- GenericResource,
- ConfigBundle
-)
-
-
-class BaseStepTestCase(TestCase):
+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):
- Populator().populate()
-
- def makeRepo(self):
+ 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()
- repo.el[repo.SESSION_USER] = User.objects.filter(username="user 1").first()
- return repo
-
- def step_test(self, step_type, data):
- step = WorkflowFactory().make_step(step_type, self.makeRepo())
- formData = QueryDict(mutable=True)
- formData.update(data)
- request = HttpRequest()
- request.POST = formData
- response = step.post_render(request)
- context = step.get_context()
- return response, context
-
-
-class BookingResourceSelectTestCase(BaseStepTestCase):
-
- def test_step_with_good_data(self):
- grb_model = GenericResourceBundle.objects.filter(owner__username="user 1").first()
- grb = [{"small_name": grb_model.name, "expanded_name": "user 1", "id": grb_model.id, "string": ""}]
- grb = str(grb).replace("'", '"')
- data = {"generic_resource_bundle": grb}
- response, context = self.step_test(Booking_Resource_Select, data)
- self.assertTrue(True)
-
- def test_step_with_bad_data(self): # TODO
- data = {}
- response, context = self.step_test(Booking_Resource_Select, data)
-
- def test_step_with_empty_data(self):
- data = {}
- response, context = self.step_test(SWConfig_Select, data)
-
+ self.add_to_repo(repo)
+ self.step = self.step(1, repo)
-class SoftwareConfigSelectTestCase(BaseStepTestCase):
+ def assertCorrectPostBehavior(self, post_data):
+ """
+ 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 test_step_with_good_data(self):
- config_model = ConfigBundle.objects.filter(owner__username="user 1").first()
- config = [{"expanded_name": "user 1", "small_name": config_model.name, "id": config_model.id, "string": ""}]
- config = str(config).replace("'", '"')
- data = {"software_bundle": config}
- response, context = self.step_test(SWConfig_Select, data)
+ def add_to_repo(self, repo):
+ """
+ This method is a hook that allows subclasses to modify
+ the contents of the repo before the step is created.
+ """
+ return
- def test_step_with_bad_data(self): # TODO
- data = {}
- response, context = self.step_test(SWConfig_Select, data)
+ def assertValidHtml(self, html_str):
+ """
+ This method should make sure 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_step_with_empty_data(self):
- data = {}
- response, context = self.step_test(SWConfig_Select, data)
+ 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 BookingMetaTestCase(BaseStepTestCase):
- def test_step_with_good_data(self):
- data = {"length": 7, "project": "LaaS", "purpose": "testing"}
- user2 = User.objects.get(username="user 2")
- john = User.objects.get(username="johnsmith")
- users = [
- {"expanded_name": "", "id": user2.id, "small_name": user2.username, "string": user2.email},
- {"expanded_name": "", "id": john.id, "small_name": john.username, "string": john.email}
- ]
- users = str(users).replace("'", '"')
- data['users'] = users
- response, context = self.step_test(Booking_Meta, 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 test_step_with_bad_data(self): # TODO
- data = {}
- response, context = self.step_test(Booking_Meta, data)
+ def setUp(self):
+ super().setUp()
- def test_step_with_empty_data(self):
- data = {}
- response, context = self.step_test(Booking_Meta, data)
+ 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(BaseStepTestCase):
- def test_step_with_good_data(self):
- hosts = {"host_4": 1, "host_1": 1}
- labs = {"lab_1": "true"}
- data = {"hosts": hosts, "labs": labs}
- response, context = self.step_test(Define_Hardware, 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}}
+ }
+ }
- def test_step_with_bad_data(self): # TODO
- data = {}
- response, context = self.step_test(Define_Hardware, data)
- def test_step_with_empty_data(self):
- data = {}
- response, context = self.step_test(Define_Hardware, data)
+class DefineNetworkTestCase(StepTestCase):
+ step = resource_bundle_workflow.Define_Nets
+ post_data = {"xml": test_fixtures.MX_GRAPH_MODEL}
-class HostMetaInfoTestCase(BaseStepTestCase):
+class ResourceMetaTestCase(StepTestCase):
+ step = resource_bundle_workflow.Resource_Meta_Info
+ post_data = {
+ "bundle_name": "my_bundle",
+ "bundle_description": "My Bundle"
+ }
- def makeRepo(self):
- """
- override to provide step with needed host info
- """
- repo = super(HostMetaInfoTestCase, self).makeRepo()
- # get models
- models = {}
- models['bundle'] = GenericResourceBundle()
- # make generic hosts
- gres1 = GenericResource(bundle=models['bundle'])
- prof1 = HostProfile.objects.get(name="Test profile 0")
- ghost1 = GenericHost(profile=prof1, resource=gres1)
- gres2 = GenericResource(bundle=models['bundle'])
- prof2 = HostProfile.objects.get(name="Test profile 3")
- ghost2 = GenericHost(profile=prof2, resource=gres2)
- models['hosts'] = [ghost1, ghost2]
- repo.el[repo.GRESOURCE_BUNDLE_MODELS] = models
- return repo
+class BookingResourceTestCase(SelectStepTestCase):
+ step = booking_workflow.Booking_Resource_Select
- def test_step_with_good_data(self):
- data = {"form-INITIAL_FORMS": 2, "form-MAX_NUM_FORMS": 1000}
- data["form-MIN_NUM_FORMS"] = 0
- data["form-TOTAL_FORMS"] = 2
- data['form-0-host_name'] = "first host"
- data['form-1-host_name'] = "second host"
- response, context = self.step_test(Host_Meta_Info, data)
+ def add_to_repo(self, repo):
+ repo.el[repo.SESSION_USER] = self.user
- def test_step_with_bad_data(self): # TODO
- data = {"form-INITIAL_FORMS": 0, "form-MAX_NUM_FORMS": 1000}
- data["form-MIN_NUM_FORMS"] = 0
- data["form-TOTAL_FORMS"] = 0
- response, context = self.step_test(Host_Meta_Info, data)
+ @classmethod
+ def setUpTestData(cls):
+ super().setUpTestData()
+ conf = TestConfig(usr=cls.user)
+ cls.obj_id = conf.grb.id
- def test_step_with_empty_data(self):
- data = {"form-INITIAL_FORMS": 0, "form-MAX_NUM_FORMS": 1000}
- data["form-MIN_NUM_FORMS"] = 0
- data["form-TOTAL_FORMS"] = 0
- response, context = self.step_test(Host_Meta_Info, data)
+class SoftwareSelectTestCase(SelectStepTestCase):
+ step = booking_workflow.SWConfig_Select
-class DefineNetsTestCase(BaseStepTestCase):
+ def add_to_repo(self, repo):
+ repo.el[repo.SESSION_USER] = self.user
+ repo.el[repo.SELECTED_GRESOURCE_BUNDLE] = self.conf.grb
- def test_step_with_good_data(self):
- xml = constants.POD_XML
- data = {"xml": xml}
- response, context = self.step_test(Define_Nets, data)
+ @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
- def test_step_with_bad_data(self): # TODO
- data = {}
- response, context = self.step_test(Define_Nets, data)
- def test_step_with_empty_data(self):
- data = {}
- response, context = self.step_test(Define_Nets, data)
+class OPNFVSelectTestCase(SelectStepTestCase):
+ step = booking_workflow.OPNFV_Select
+ def add_to_repo(self, repo):
+ repo.el[repo.SELECTED_CONFIG_BUNDLE] = self.config_bundle
-class ResourceMetaInfoTestCase(BaseStepTestCase):
+ @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
- def test_step_with_good_data(self):
- data = {"bundle_description": "description", "bundle_name": "my testing bundle"}
- response, context = self.step_test(Resource_Meta_Info, data)
- def test_step_with_bad_data(self): # TODO
- data = {}
- response, context = self.step_test(Resource_Meta_Info, data)
+class BookingMetaTestCase(StepTestCase):
+ step = booking_workflow.Booking_Meta
+ post_data = {
+ "length": 14,
+ "purpose": "Testing",
+ "project": "Lab as a Service",
+ "users": ["[-1]"]
+ }
- def test_step_with_empty_data(self):
- data = {}
- response, context = self.step_test(Resource_Meta_Info, data)
+ 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 SWConfResourceSelectTestCase(BaseStepTestCase):
- def test_step_with_good_data(self):
- grb_model = GenericResourceBundle.objects.filter(owner__username="user 1").first()
- grb = [{"small_name": grb_model.name, "expanded_name": "user 1", "id": grb_model.id, "string": ""}]
- grb = str(grb).replace("'", '"')
- data = {"generic_resource_bundle": grb}
- response, context = self.step_test(SWConf_Resource_Select, data)
+class ConfigResourceSelectTestCase(SelectStepTestCase):
+ step = sw_bundle_workflow.SWConf_Resource_Select
- def test_step_with_bad_data(self): # TODO
- data = {}
- response, context = self.step_test(SWConf_Resource_Select, data)
+ def add_to_repo(self, repo):
+ repo.el[repo.SESSION_USER] = self.user
- def test_step_with_empty_data(self):
- data = {}
- response, context = self.step_test(SWConf_Resource_Select, data)
+ @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_GRESOURCE_BUNDLE] = self.conf.grb
+ @classmethod
+ def setUpTestData(cls):
+ super().setUpTestData()
+ cls.conf = TestConfig(usr=cls.user)
-class DefineSoftwareTestCase(BaseStepTestCase):
- def makeRepo(self):
- """
- put selected grb in repo for step
- """
- repo = super(DefineSoftwareTestCase, self).makeRepo()
- grb = GenericResourceBundle.objects.filter(owner__username="user 1").first()
- repo.el[repo.SWCONF_SELECTED_GRB] = grb
- return repo
-
- def test_step_with_good_data(self):
- data = {"form-INITIAL_FORMS": 3, "form-MAX_NUM_FORMS": 1000}
- data["form-MIN_NUM_FORMS"] = 0
- data["form-TOTAL_FORMS"] = 3
- an_image_id = Image.objects.get(name="a host image").id
- another_image_id = Image.objects.get(name="another host image").id
- control = OPNFVRole.objects.get(name="Controller")
- compute = OPNFVRole.objects.get(name="Compute")
- jumphost = OPNFVRole.objects.get(name="Jumphost")
- data['form-0-image'] = an_image_id
- data['form-1-image'] = an_image_id
- data['form-2-image'] = another_image_id
- data['form-0-role'] = compute.id
- data['form-1-role'] = control.id
- data['form-2-role'] = jumphost.id
- response, context = self.step_test(Define_Software, data)
-
- def test_step_with_bad_data(self): # TODO
- data = {"form-INITIAL_FORMS": 0, "form-MAX_NUM_FORMS": 1000}
- data["form-MIN_NUM_FORMS"] = 0
- data["form-TOTAL_FORMS"] = 0
- response, context = self.step_test(Define_Software, data)
-
- def test_step_with_empty_data(self):
- data = {"form-INITIAL_FORMS": 0, "form-MAX_NUM_FORMS": 1000}
- data["form-MIN_NUM_FORMS"] = 0
- data["form-TOTAL_FORMS"] = 0
- response, context = self.step_test(Define_Software, data)
-
-
-class ConfigSoftwareTestCase(BaseStepTestCase):
-
- def test_step_with_good_data(self):
- data = {"description": "description", "name": "namey"}
- installer = Installer.objects.get(name="Fuel")
- scenario = Scenario.objects.get(name="os-nosdn-nofeature-noha")
- data['installer'] = installer.id
- data['scenario'] = scenario.id
- response, context = self.step_test(Config_Software, data)
-
- def test_step_with_bad_data(self): # TODO
- data = {}
- response, context = self.step_test(Config_Software, data)
-
- def test_step_with_empty_data(self):
- data = {}
- response, context = self.step_test(Config_Software, data)
+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_steps_render.py b/src/workflow/tests/test_steps_render.py
deleted file mode 100644
index f3df8f2..0000000
--- a/src/workflow/tests/test_steps_render.py
+++ /dev/null
@@ -1,43 +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.test import TestCase, Client
-
-
-class SuperViewTestCase(TestCase):
- url = "/"
- client = Client()
-
- def test_get(self):
- response = self.client.get(self.url)
- self.assertLess(response.status_code, 300)
-
-
-class DefineHardwareViewTestCase(SuperViewTestCase):
- url = "/wf/workflow/step/define_hardware"
-
-
-class DefineNetworkViewTestCase(SuperViewTestCase):
- url = "/wf/workflow/step/define_net"
-
-
-class ResourceMetaViewTestCase(SuperViewTestCase):
- url = "/wf/workflow/step/resource_meta"
-
-
-class BookingMetaViewTestCase(SuperViewTestCase):
- url = "/wf/workflow/step/booking_meta"
-
-
-class SoftwareSelectViewTestCase(SuperViewTestCase):
- url = "/wf/workflow/step/software_select"
-
-
-class ResourceSelectViewTestCase(SuperViewTestCase):
- url = "/wf/workflow/step/resource_select"
diff --git a/src/workflow/tests/test_workflows.py b/src/workflow/tests/test_workflows.py
index 7a53521..293e43d 100644
--- a/src/workflow/tests/test_workflows.py
+++ b/src/workflow/tests/test_workflows.py
@@ -7,9 +7,9 @@
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
+from unittest import SkipTest
from django.test import TestCase
from workflow.workflow_factory import WorkflowFactory
-from dashboard.populate_db import Populator
"""
@@ -29,8 +29,9 @@ To remove a workflow:
class WorkflowTestCase(TestCase):
@classmethod
- def setUpTestData(cls):
- Populator().populate()
+ def setUpClass(cls):
+ super().setUpClass()
+ raise SkipTest("These tests are no good")
def setUp(self):
self.clear_workflow()
diff --git a/src/workflow/urls.py b/src/workflow/urls.py
index 5a97904..b1b95a7 100644
--- a/src/workflow/urls.py
+++ b/src/workflow/urls.py
@@ -9,26 +9,15 @@
from django.conf.urls import url
-from django.conf import settings
-from workflow.views import step_view, delete_session, manager_view, viewport_view
-from workflow.models import Repository
-from workflow.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info
-from workflow.booking_workflow import SWConfig_Select, Booking_Resource_Select, Booking_Meta
+from workflow.views import manager_view, viewport_view, add_workflow, remove_workflow, create_workflow
app_name = 'workflow'
urlpatterns = [
- url(r'^workflow/$', step_view, name='workflow'),
- url(r'^workflow/finish/$', delete_session, name='delete_session'),
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')
]
-
-if settings.TESTING:
- urlpatterns.append(url(r'^workflow/step/define_hardware$', Define_Hardware("", Repository()).test_render))
- urlpatterns.append(url(r'^workflow/step/define_net$', Define_Nets("", Repository()).test_render))
- urlpatterns.append(url(r'^workflow/step/resource_meta$', Resource_Meta_Info("", Repository()).test_render))
- urlpatterns.append(url(r'^workflow/step/booking_meta$', Booking_Meta("", Repository()).test_render))
- urlpatterns.append(url(r'^workflow/step/software_select$', SWConfig_Select("", Repository()).test_render))
- urlpatterns.append(url(r'^workflow/step/resource_select$', Booking_Resource_Select("", Repository()).test_render))
diff --git a/src/workflow/views.py b/src/workflow/views.py
index 7ed9031..9ff444d 100644
--- a/src/workflow/views.py
+++ b/src/workflow/views.py
@@ -8,14 +8,12 @@
##############################################################################
-from django.http import HttpResponseGone, JsonResponse
+from django.http import HttpResponse
from django.shortcuts import render
-from django.urls import reverse
import uuid
from workflow.workflow_manager import ManagerTracker, SessionManager
-from booking.models import Booking
import logging
logger = logging.getLogger(__name__)
@@ -31,77 +29,38 @@ def attempt_auth(request):
return None
-def get_redirect_response(result):
- if not result:
- return {}
-
- # need to get type of result, and switch on the type
- # since has_result, result must be populated with a valid object
- if isinstance(result, Booking):
- return {
- 'redir_url': reverse('booking:booking_detail', kwargs={'booking_id': result.id})
- }
- else:
- return {}
-
-
-def delete_session(request):
+def remove_workflow(request):
manager = attempt_auth(request)
if not manager:
- return HttpResponseGone("No session found that relates to current request")
+ return no_workflow(request)
- not_last_workflow, result = manager.pop_workflow()
+ has_more_workflows, result = manager.pop_workflow()
- if not_last_workflow: # this was not the last workflow, so don't redirect away
- return JsonResponse({})
- else:
+ 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 JsonResponse(get_redirect_response(result))
+ return manager.render(request)
-def step_view(request):
+def add_workflow(request):
manager = attempt_auth(request)
if not manager:
- # no manager found, redirect to "lost" page
return no_workflow(request)
- if request.GET.get('step') is not None:
- if request.GET.get('step') == 'next':
- manager.go_next()
- elif request.GET.get('step') == 'prev':
- manager.go_prev()
- else:
- raise Exception("requested action for new step had malformed contents: " + request.GET.get('step'))
- return manager.render(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 HttpResponseGone("No session found that relates to current request")
-
- if request.method == 'GET':
- # no need for this statement if only intercepting post requests
-
- # return general context for viewport page
- return manager.status(request)
-
- if request.method == 'POST':
- if request.POST.get('add') is not None:
- logger.debug("add found")
- target_id = None
- if 'target' in request.POST:
- target_id = int(request.POST.get('target'))
- manager.add_workflow(workflow_type=int(request.POST.get('add')), target_id=target_id)
- elif request.POST.get('edit') is not None and request.POST.get('edit_id') is not None:
- logger.debug("edit found")
- manager.add_workflow(workflow_type=request.POST.get('edit'), edit_object=int(request.POST.get('edit_id')))
- elif request.POST.get('cancel') is not None:
- if not manager.pop_workflow():
- del ManagerTracker.managers[request.session['manager_session']]
+ return no_workflow(request)
- return manager.status(request)
+ return manager.handle_request(request)
def viewport_view(request):
@@ -112,16 +71,27 @@ def viewport_view(request):
if manager is None:
return no_workflow(request)
- if request.method == 'GET':
- return render(request, 'workflow/viewport-base.html')
- else:
- pass
+ if request.method != 'GET':
+ return HttpResponse(status=405)
+ return render(request, 'workflow/viewport-base.html')
+
+
+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):
- wf = int(wf_type)
smgr = SessionManager(request=request)
- smgr.add_workflow(workflow_type=wf, target_id=request.POST.get("target"))
+ smgr.add_workflow(workflow_type=wf_type, target_id=request.POST.get("target"))
manager_uuid = uuid.uuid4().hex
ManagerTracker.getInstance().managers[manager_uuid] = smgr
@@ -129,10 +99,7 @@ def create_session(wf_type, request):
def no_workflow(request):
-
- logger.debug("There is no active workflow")
-
- return render(request, 'workflow/no_workflow.html', {'title': "Not Found"})
+ return render(request, 'workflow/no_workflow.html', {'title': "Not Found"}, status=404)
def login(request):
diff --git a/src/workflow/workflow_manager.py b/src/workflow/workflow_manager.py
index 80b8a67..4677829 100644
--- a/src/workflow/workflow_manager.py
+++ b/src/workflow/workflow_manager.py
@@ -9,6 +9,8 @@
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
@@ -19,6 +21,7 @@ from resource_inventory.models import (
HostConfiguration,
OPNFVConfig
)
+from workflow.forms import ManagerForm
import logging
logger = logging.getLogger(__name__)
@@ -30,10 +33,9 @@ class SessionManager():
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()
@@ -45,10 +47,7 @@ class SessionManager():
else:
step.disable()
- def add_workflow(self, workflow_type=None, target_id=None, **kwargs):
- if target_id is not None:
- self.prefill_repo(target_id, workflow_type)
-
+ def add_workflow(self, workflow_type=None, **kwargs):
repo = Repository()
if(len(self.workflows) >= 1):
defaults = self.workflows[-1].repository.get_child_defaults()
@@ -63,6 +62,11 @@ class SessionManager():
)
)
+ 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):
multiple_wfs = len(self.workflows) > 1
if multiple_wfs:
@@ -70,29 +74,51 @@ class SessionManager():
key = self.workflows[-1].repository.el[Repository.RESULT_KEY]
result = self.workflows[-1].repository.el[Repository.RESULT]
self.workflows[-2].repository.el[key] = result
- self.workflows.pop()
- current_repo = self.workflows[-1].repository
- return (multiple_wfs, current_repo.el[current_repo.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]
+ return multiple_wfs, self.result
def status(self, request):
- try:
- meta_json = []
- for step in self.active_workflow().steps:
- meta_json.append(step.to_json())
- responsejson = {}
- responsejson["steps"] = meta_json
- responsejson["active"] = self.active_workflow().repository.el['active_step']
- responsejson["workflow_count"] = len(self.workflows)
- return JsonResponse(responsejson, safe=False)
- except Exception:
- pass
+ 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):
- # filter out when a step needs to handle post/form data
- # if 'workflow' in post data, this post request was meant for me, not step
- if request.method == 'POST' and request.POST.get('workflow', None) is None:
- return self.active_workflow().steps[self.active_workflow().active_index].post_render(request)
- return self.active_workflow().steps[self.active_workflow().active_index].render(request)
+ 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)