diff options
Diffstat (limited to 'src/workflow')
-rw-r--r-- | src/workflow/booking_workflow.py | 8 | ||||
-rw-r--r-- | src/workflow/forms.py | 32 | ||||
-rw-r--r-- | src/workflow/models.py | 49 | ||||
-rw-r--r-- | src/workflow/opnfv_workflow.py | 23 | ||||
-rw-r--r-- | src/workflow/resource_bundle_workflow.py | 75 | ||||
-rw-r--r-- | src/workflow/snapshot_workflow.py | 15 | ||||
-rw-r--r-- | src/workflow/sw_bundle_workflow.py | 16 | ||||
-rw-r--r-- | src/workflow/tests/test_fixtures.py | 2 | ||||
-rw-r--r-- | src/workflow/tests/test_steps.py | 437 | ||||
-rw-r--r-- | src/workflow/tests/test_steps_render.py | 43 | ||||
-rw-r--r-- | src/workflow/tests/test_workflows.py | 7 | ||||
-rw-r--r-- | src/workflow/urls.py | 19 | ||||
-rw-r--r-- | src/workflow/views.py | 101 | ||||
-rw-r--r-- | src/workflow/workflow_manager.py | 76 |
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="{"name":"c","description":"Intel based ProLiant server from HPE"}" 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="{"name":"ens4f1","description":"speed: 10000M type: onboard"}" 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="{"name":"ens4f0","description":"speed: 10000M type: onboard"}" 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="{"name":"ens1f2","description":"speed: 10000M type: onboard"}" 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="{"name":"ens1f1","description":"speed: 10000M type: onboard"}" 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="{"name":"ens1f0","description":"speed: 10000M type: onboard"}" 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="{"name":"eno49","description":"speed: 10000M type: onboard"}" 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="{"name":"public","public":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="{"tagged":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) |