Implement Segmented Workflows 61/65861/5
authorSawyer Bergeron <sawyerbergeron@gmail.com>
Fri, 14 Dec 2018 21:05:47 +0000 (16:05 -0500)
committerSawyer Bergeron <sawyerbergeron@gmail.com>
Thu, 3 Jan 2019 15:34:13 +0000 (10:34 -0500)
A major source of bugs has been how we've approached inlining
workflows. We no longer inline them as of this commit, and instead
use a stack structure. This commits the result of workflows to the
database before other workflows try to read them, so we don't have
to maintain a code path for when something is or isn't committed
to db.

This patchset allows for workflows to pass limited information
to preset selections

Change-Id: I3d040c7f3024c7420017ae4ec66a23219303dcb6
Signed-off-by: Sawyer Bergeron <sawyerbergeron@gmail.com>
13 files changed:
dashboard/src/templates/booking/steps/booking_meta.html
dashboard/src/templates/dashboard/searchable_select_multiple.html
dashboard/src/templates/workflow/confirm.html
dashboard/src/templates/workflow/exit_redirect.html [new file with mode: 0644]
dashboard/src/templates/workflow/no_workflow.html
dashboard/src/templates/workflow/viewport-base.html
dashboard/src/workflow/booking_workflow.py
dashboard/src/workflow/forms.py
dashboard/src/workflow/models.py
dashboard/src/workflow/sw_bundle_workflow.py
dashboard/src/workflow/views.py
dashboard/src/workflow/workflow_factory.py
dashboard/src/workflow/workflow_manager.py

index a42e158..e4881ae 100644 (file)
@@ -58,7 +58,6 @@
 {% block onleave %}
 var ajaxForm = $("#booking_meta_form");
 var formData = ajaxForm.serialize();
-console.log(formData);
 req = new XMLHttpRequest();
 req.open("POST", "/wf/workflow/", false);
 req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
index e7128b0..ee460dd 100644 (file)
@@ -1,6 +1,12 @@
 <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
 
 <div class="autocomplete" style="width:400px;">
+    <div id="warning_pane" style="background: #FFFFFF; color: #CC0000;">
+        {% if incompatible == "true" %}
+        <h3>Warning: Incompatible Configuration</h3>
+        <p>Please make a different selection, as the current config conflicts with the selected pod</p>
+        {% endif %}
+    </div>
     <input id="user_field" name="ignore_this" class="form-control" autocomplete="off" type="text" placeholder="{{placeholder}}" value="{{initial.name}}" oninput="search(this.value)"
     {% if disabled %} disabled {% endif %}
     >
index 4f2616e..277c305 100644 (file)
@@ -66,7 +66,9 @@
         req.open("POST", "/wf/workflow/finish/", false);
         req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
         req.onerror = function() { alert("problem with cleaning up session"); }
-        req.onreadystatechange = function() { if(req.readyState === 4 ) { parent.redirect_root(); } }
+        req.onreadystatechange = function() { if(req.readyState === 4 ) {
+                window.top.refresh_iframe();
+                }}
         req.send(formData);
     }
 
diff --git a/dashboard/src/templates/workflow/exit_redirect.html b/dashboard/src/templates/workflow/exit_redirect.html
new file mode 100644 (file)
index 0000000..b08df78
--- /dev/null
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+    <script>
+    top.window.location.href='/';
+    </script>
+</html>
index ff8aab3..0ac6549 100644 (file)
@@ -1,7 +1,3 @@
-{% extends "base.html" %}
-{% load staticfiles %}
-
-{% block content %}
-<h3>If you would like to create a booking or a resource, please use the links on the sidebar or from the homepage</h3>
-<a href="/">Go Home</a>
-{% endblock content %}
+<script>
+    top.window.location.href='/';
+</script>
index 37eff27..82c1324 100644 (file)
@@ -71,7 +71,7 @@
 
     .step_untouched
     {
-        background: #98B0AF;
+        background: #DDDDDD;
     }
 
     .step_invalid
 <button id="gob" onclick="go(step-1)" class="btn btn go_btn go_back">Go Back</button>
 
 <div class="options">
-    <button class="btn" onclick="cancel_wf()">Cancel</button>
+    <button id="cancel_btn" class="btn" onclick="cancel_wf()">Cancel</button>
 </div>
 <div class="btn_wrapper">
-<div id="breadcrumbs">
+<div id="breadcrumbs" class="btn-group">
+    <div class="btn-group" id="breadcrumb-wrapper">
+    </div>
 </div>
 </div>
 {% csrf_token %}
     {
         context_data = data;
         update_breadcrumbs(data);
+        if(data["workflow_count"] == 1)
+        {
+                document.getElementById("cancel_btn").innerText = "Exit Workflow";
+        }
+        else
+        {
+                document.getElementById("cancel_btn").innerText = "Return to Parent";
+        }
     }
 
     function update_breadcrumbs(meta_json) {
         while(container.firstChild){
             container.removeChild(container.firstChild);
         }
-        //draw enough rows for all steps
-        var depth = meta_json['max_depth'];
-        for(var i=0; i<=depth; i++){
-            var div = document.createElement("DIV");
-            div.id = "row"+i;
-            if(i<depth){
-                div.style['margin-bottom'] = "7px";
-            }
-            if(i>0){
-                div.style['margin-top'] = "7px";
-            }
-            container.appendChild(div);
-        }
+
         draw_steps(meta_json);
     }
 
     function draw_steps(meta_json){
-        var all_relations = meta_json['relations'];
-        var relations = [];
-        var active_steps = [];
-        var active_step = step;
-        while(active_step < meta_json['steps'].length){
-            active_steps.push(active_step);
-            var index = meta_json['parents'][active_step];
-            var relation = all_relations[index];
-            relations.push(relation);
-            active_step = relation['parent'];
-        }
-        var child_index = meta_json['children'][step];
-        var my_children = all_relations[child_index];
-        if(my_children){
-            relations.push(my_children);
-        }
-        draw_relations(relations, meta_json, active_steps);
-    }
-
-    function draw_relations(relations, meta_json, active_steps){
-        for(var i=0; i<relations.length; i++){
-            var relation = relations[i];
-            var children_container = document.createElement("DIV");
-            children_container.style['display'] = "inline";
-            children_container.style['margin'] = "3px";
-            children_container.style['padding'] = "3px";
-            console.log("meta_json: ");
-            console.log(meta_json);
-            for(var j=0; j<relation['children'].length; j++){
-                var step_json = meta_json['steps'][relation['children'][j]];
-                step_json['index'] = relation['children'][j];
-                var active = active_steps.indexOf(step_json['index']) > -1;
-                var step_button = create_step(meta_json['steps'][relation['children'][j]], active);
-                children_container.appendChild(step_button);
-            }
-            var parent_div = document.getElementById("row" + relation['depth']);
-            parent_div.appendChild(children_container);
+        for( var i = 0; i < meta_json["steps"].length; i++ )
+        {
+            meta_json["steps"][i]["index"] = i;
+            var step_btn = create_step(meta_json["steps"][i], i == meta_json["active"]);
+            document.getElementById("breadcrumbs").appendChild(step_btn);
         }
     }
 
         var step_dom = document.createElement("DIV");
         if(active){
             step_dom.className = "step_active";
-            console.log(step_json['message']);
 
         } else{
             step_dom.className = "step";
         {
             update_message(msg, stat);
         }
+        step_dom.classList.add("btn");
 
         var step_number = step_json['index'];
         step_dom.onclick = function(){ go(step_number); }
-        //TODO: background color and other style
         return step_dom;
     }
 
     function cancel_wf(){
-        $.ajax({
-            type: "POST",
-            url: "/wf/manager/",
-            data: {"cancel":"",},
-            beforeSend: function(request) {
-                request.setRequestHeader("X-CSRFToken",
-                $('input[name="csrfmiddlewaretoken"]').val()
-                );
-            },
-            success: redirect_root()
-        });
+        var form = $("#workflow_pop_form");
+        var formData = form.serialize();
+        var req = new XMLHttpRequest();
+        req.open("POST", "/wf/workflow/finish/", false);
+        req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+        req.onerror = function() { alert("problem occurred while trying to cancel current workflow"); }
+        req.onreadystatechange = function() { if(req.readyState === 4){
+            refresh_iframe();
+        }};
+        req.send(formData);
+    }
+
+    function refresh_iframe() {
+        req = new XMLHttpRequest();
+        url = "/wf/workflow/";
+        req.open("GET", url, true);
+        req.onload = function(e) {
+            var doc = document.getElementById("viewport-iframe").contentWindow.document;
+            doc.open(); doc.write(this.responseText); doc.close();
+        }
+        req.send();
+    }
+
+    function write_iframe(contents)
+    {
+        document.getElementById("viewport-iframe").contentWindow.document.innerHTML= contents;
     }
 
     function redirect_root()
     {
-        window.location.replace('/');
+        window.location.replace('/wf/');
     }
 
     function add_wf(type){
     </script>
     <!-- /.col-lg-12 -->
 </div>
+<div style="display: none;" id="workflow_pop_form_div">
+<form id="workflow_pop_form" action="/wf/workflow/finish/" method="post">
+    {% csrf_token %}
+</form>
+</div>
 
 <iframe src="/wf/workflow" style="position: absolute; left: 351px; right: 105px; width: calc(100% - 450px); border-style: none; border-width: 1px; border-color: #888888;" scrolling="yes" id="viewport-iframe" onload="resize_iframe();"></iframe>
 {% endblock content %}
index cd12ab6..76950b8 100644 (file)
@@ -33,32 +33,15 @@ class Resource_Select(WorkflowStep):
         self.repo_check_key = False
         self.confirm_key = "booking"
 
-    def get_default_entry(self):
-        return None
-
     def get_context(self):
         context = super(Resource_Select, self).get_context()
         default = []
-        chosen_bundle = None
-        default_bundle = self.get_default_entry()
-        if default_bundle:
-            context['disabled'] = True
-            chosen_bundle = default_bundle
-            if chosen_bundle.id:
-                default.append(chosen_bundle.id)
-            else:
-                default.append("repo bundle")
-        else:
-            chosen_bundle = self.repo_get(self.repo_key, False)
-            if chosen_bundle:
-                if chosen_bundle.id:
-                    default.append(chosen_bundle.id)
-                else:
-                    default.append("repo bundle")
-
-        bundle = default_bundle
-        if not bundle:
-            bundle = chosen_bundle
+
+        chosen_bundle = self.repo_get(self.repo_key, False)
+        if chosen_bundle:
+            default.append(chosen_bundle.id)
+
+        bundle = chosen_bundle
         edit = self.repo_get(self.repo.EDIT, False)
         user = self.repo_get(self.repo.SESSION_USER)
         context['form'] = ResourceSelectorForm(
@@ -79,6 +62,9 @@ class Resource_Select(WorkflowStep):
                 self.metastep.set_invalid("Please select a valid bundle")
                 return render(request, self.template, context)
             selected_bundle = json.loads(data)
+            if len(selected_bundle) < 1:
+                self.metastep.set_invalid("Please select a valid bundle")
+                return render(request, self.template, context)
             selected_id = selected_bundle[0]['id']
             gresource_bundle = None
             try:
@@ -112,23 +98,9 @@ class Booking_Resource_Select(Resource_Select):
 
     def __init__(self, *args, **kwargs):
         super(Booking_Resource_Select, self).__init__(*args, **kwargs)
-        self.repo_key = self.repo.BOOKING_SELECTED_GRB
+        self.repo_key = self.repo.SELECTED_GRESOURCE_BUNDLE
         self.confirm_key = "booking"
 
-    def get_default_entry(self):
-        default = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}).get("bundle")
-        mine = self.repo_get(self.repo_key)
-        if mine:
-            return None
-        try:
-            config_bundle = self.repo_get(self.repo.BOOKING_MODELS)['booking'].config_bundle
-            if default:
-                return default  # select created grb, even if preselected config bundle
-            return config_bundle.bundle
-        except:
-            pass
-        return default
-
     def get_context(self):
         context = super(Booking_Resource_Select, self).get_context()
         return context
@@ -166,12 +138,20 @@ class SWConfig_Select(WorkflowStep):
                 self.metastep.set_invalid("Please select a valid config")
                 return self.render(request)
             bundle_json = json.loads(bundle_json)
+            if len(bundle_json) < 1:
+                self.metastep.set_invalid("Please select a valid config")
+                return self.render(request)
             bundle = None
-            try:
-                id = int(bundle_json[0]['id'])
-                bundle = ConfigBundle.objects.get(id=id)
-            except ValueError:
-                bundle = self.repo_get(self.repo.CONFIG_MODELS).get("bundle")
+            id = int(bundle_json[0]['id'])
+            bundle = ConfigBundle.objects.get(id=id)
+
+            grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE)
+
+            if grb and bundle.bundle != grb:
+                self.metastep.set_invalid("Incompatible config selected for resource bundle")
+                return self.render(request)
+            if not grb:
+                self.repo_set(self.repo.SELECTED_GRESOURCE_BUNDLE, bundle.bundle)
 
             models = self.repo_get(self.repo.BOOKING_MODELS, {})
             if "booking" not in models:
@@ -196,7 +176,7 @@ class SWConfig_Select(WorkflowStep):
         default = []
         bundle = None
         chosen_bundle = None
-        created_bundle = self.repo_get(self.repo.CONFIG_MODELS, {}).get("bundle", False)
+        created_bundle = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
         booking = self.repo_get(self.repo.BOOKING_MODELS, {}).get("booking", False)
         try:
             chosen_bundle = booking.config_bundle
@@ -204,11 +184,10 @@ class SWConfig_Select(WorkflowStep):
             bundle = chosen_bundle
         except:
             if created_bundle:
-                default.append("repo bundle")
+                default.append(created_bundle.id)
                 bundle = created_bundle
-                context['disabled'] = True
         edit = self.repo_get(self.repo.EDIT, False)
-        grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB)
+        grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE)
         context['form'] = SWConfigSelectorForm(chosen_software=default, bundle=bundle, edit=edit, resource=grb)
         return context
 
index feb32f2..f781663 100644 (file)
@@ -41,6 +41,7 @@ class SearchableSelectMultipleWidget(widgets.SelectMultiple):
         self.default_entry = attrs.get("default_entry", "")
         self.edit = attrs.get("edit", False)
         self.wf_type = attrs.get("wf_type")
+        self.incompatible = attrs.get("incompatible", "false")
 
         super(SearchableSelectMultipleWidget, self).__init__(attrs)
 
@@ -61,7 +62,8 @@ class SearchableSelectMultipleWidget(widgets.SelectMultiple):
             'initial': self.initial,
             'default_entry': self.default_entry,
             'edit': self.edit,
-            'wf_type': self.wf_type
+            'wf_type': self.wf_type,
+            'incompatible': self.incompatible
         }
 
 
@@ -101,13 +103,6 @@ class ResourceSelectorForm(forms.Form):
             displayable['id'] = res.id
             resources[res.id] = displayable
 
-            if bundle:
-                displayable = {}
-                displayable['small_name'] = bundle.name
-                displayable['expanded_name'] = "Current bundle"
-                displayable['string'] = bundle.description
-                displayable['id'] = "repo bundle"
-                resources["repo bundle"] = displayable
         attrs = {
             'set': resources,
             'show_from_noentry': "true",
@@ -159,13 +154,15 @@ class SWConfigSelectorForm(forms.Form):
             displayable['id'] = config.id
             configs[config.id] = displayable
 
-        if bundle:
+        incompatible_choice = "false"
+        if bundle and bundle.id not in configs:
             displayable = {}
             displayable['small_name'] = bundle.name
-            displayable['expanded_name'] = "Current configuration"
+            displayable['expanded_name'] = bundle.owner.username
             displayable['string'] = bundle.description
-            displayable['id'] = "repo bundle"
-            configs['repo bundle'] = displayable
+            displayable['id'] = bundle.id
+            configs[bundle.id] = displayable
+            incompatible_choice = "true"
 
         attrs = {
             'set': configs,
@@ -177,7 +174,8 @@ class SWConfigSelectorForm(forms.Form):
             'placeholder': "config",
             'initial': chosen,
             'edit': edit,
-            'wf_type': 2
+            'wf_type': 2,
+            'incompatible': incompatible_choice
         }
         return attrs
 
index 966582c..66b1739 100644 (file)
@@ -10,6 +10,7 @@
 
 from django.shortcuts import render
 from django.contrib import messages
+from django.http import HttpResponse
 
 import yaml
 import requests
@@ -72,7 +73,6 @@ class BookingAuthManager():
 
 
 class WorkflowStep(object):
-
     template = 'bad_request.html'
     title = "Generic Step"
     description = "You were led here by mistake"
@@ -120,9 +120,11 @@ class Confirmation_Step(WorkflowStep):
     description = "Does this all look right?"
 
     def get_vlan_warning(self):
-        grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB, False)
+        grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False)
         if not grb:
             return 0
+        if self.repo.BOOKING_MODELS not in self.repo.el:
+            return 0
         vlan_manager = grb.lab.vlan_manager
         if vlan_manager is None:
             return 0
@@ -164,9 +166,10 @@ class Confirmation_Step(WorkflowStep):
                 errors = self.flush_to_db()
                 if errors:
                     messages.add_message(request, messages.ERROR, "ERROR OCCURRED: " + errors)
-                    return render(request, self.template, context)
-                messages.add_message(request, messages.SUCCESS, "Confirmed")
-                return render(request, self.template, context)
+                else:
+                    messages.add_message(request, messages.SUCCESS, "Confirmed")
+
+                return HttpResponse('')
             elif data == "False":
                 context["bypassed"] = "true"
                 messages.add_message(request, messages.SUCCESS, "Canceled")
@@ -182,7 +185,7 @@ class Confirmation_Step(WorkflowStep):
             pass
 
     def translate_vlans(self):
-        grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB, False)
+        grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False)
         if not grb:
             return 0
         vlan_manager = grb.lab.vlan_manager
@@ -216,6 +219,7 @@ class Repository():
     RESOURCE_SELECT = "resource_select"
     CONFIRMATION = "confirmation"
     SELECTED_GRESOURCE_BUNDLE = "selected generic bundle pk"
+    SELECTED_CONFIG_BUNDLE = "selected config bundle pk"
     GRESOURCE_BUNDLE_MODELS = "generic_resource_bundle_models"
     GRESOURCE_BUNDLE_INFO = "generic_resource_bundle_info"
     BOOKING = "booking"
@@ -223,8 +227,6 @@ class Repository():
     GRB_LAST_HOSTLIST = "grb_network_previous_hostlist"
     BOOKING_FORMS = "booking_forms"
     SWCONF_HOSTS = "swconf_hosts"
-    SWCONF_SELECTED_GRB = "swconf_selected_grb_pk"
-    BOOKING_SELECTED_GRB = "booking_selected_grb_pk"
     BOOKING_MODELS = "booking models"
     CONFIG_MODELS = "configuration bundle models"
     SESSION_USER = "session owner user account"
@@ -238,6 +240,22 @@ class Repository():
     SNAPSHOT_DESC = "description of the snapshot"
     BOOKING_INFO_FILE = "the INFO.yaml file for this user's booking"
 
+    #migratory elements of segmented workflow
+    #each of these is the end result of a different workflow.
+    HAS_RESULT = "whether or not workflow has a result"
+    RESULT_KEY = "key for target index that result will be put into in parent"
+    RESULT = "result object from workflow"
+
+    def get_child_defaults(self):
+        return_tuples = []
+        for key in [self.SELECTED_GRESOURCE_BUNDLE, self.SESSION_USER]:
+            return_tuples.append((key, self.el.get(key)))
+        return return_tuples
+
+    def set_defaults(self, defaults):
+        for key, value in defaults:
+            self.el[key] = value
+
     def get(self, key, default, id):
         self.add_get_history(key, id)
         return self.el.get(key, default)
@@ -268,11 +286,19 @@ class Repository():
             errors = self.make_generic_resource_bundle()
             if errors:
                 return errors
+            else:
+                self.el[self.HAS_RESULT] = True
+                self.el[self.RESULT_KEY] = self.SELECTED_GRESOURCE_BUNDLE
+                return
 
         if self.CONFIG_MODELS in self.el:
             errors = self.make_software_config_bundle()
             if errors:
                 return errors
+            else:
+                self.el[self.HAS_RESULT] = True
+                self.el[self.RESULT_KEY] = self.SELECTED_CONFIG_BUNDLE
+                return
 
         if self.BOOKING_MODELS in self.el:
             errors = self.make_booking()
@@ -365,7 +391,7 @@ class Repository():
         else:
             return "GRB no models given. CODE:0x0001"
 
-        self.el[self.VALIDATED_MODEL_GRB] = bundle
+        self.el[self.RESULT] = bundle
         return False
 
     def make_software_config_bundle(self):
@@ -403,15 +429,15 @@ class Repository():
         else:
             pass
 
-        self.el[self.VALIDATED_MODEL_CONFIG] = bundle
+        self.el[self.RESULT] = bundle
         return False
 
     def make_booking(self):
         models = self.el[self.BOOKING_MODELS]
         owner = self.el[self.SESSION_USER]
 
-        if self.BOOKING_SELECTED_GRB in self.el:
-            selected_grb = self.el[self.BOOKING_SELECTED_GRB]
+        if self.SELECTED_GRESOURCE_BUNDLE in self.el:
+            selected_grb = self.el[self.SELECTED_GRESOURCE_BUNDLE]
         else:
             return "BOOK, no selected resource. CODE:0x000e"
 
@@ -495,5 +521,6 @@ class Repository():
     def __init__(self):
         self.el = {}
         self.el[self.CONFIRMATION] = {}
+        self.el["active_step"] = 0
         self.get_history = {}
         self.put_history = {}
index 26ade22..a9be4ea 100644 (file)
@@ -20,16 +20,9 @@ from resource_inventory.models import Image, GenericHost, ConfigBundle, HostConf
 class SWConf_Resource_Select(Resource_Select):
     def __init__(self, *args, **kwargs):
         super(SWConf_Resource_Select, self).__init__(*args, **kwargs)
-        self.repo_key = self.repo.SWCONF_SELECTED_GRB
+        self.repo_key = self.repo.SELECTED_GRESOURCE_BUNDLE
         self.confirm_key = "configuration"
 
-    def get_default_entry(self):
-        booking_grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB)
-        if booking_grb:
-            return booking_grb
-        created_grb = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}).get("bundle", None)
-        return created_grb
-
     def post_render(self, request):
         response = super(SWConf_Resource_Select, self).post_render(request)
         models = self.repo_get(self.repo.CONFIG_MODELS, {})
@@ -80,7 +73,7 @@ class Define_Software(WorkflowStep):
                         break
             excluded_images = Image.objects.exclude(owner=user).exclude(public=True)
             excluded_images = excluded_images | Image.objects.exclude(host_type=host.profile)
-            lab = self.repo_get(self.repo.SWCONF_SELECTED_GRB).lab
+            lab = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE).lab
             excluded_images = excluded_images | Image.objects.exclude(from_lab=lab)
             filter_data["id_form-" + str(i) + "-image"] = []
             for image in excluded_images:
@@ -91,7 +84,7 @@ class Define_Software(WorkflowStep):
 
     def get_host_list(self, grb=None):
         if grb is None:
-            grb = self.repo_get(self.repo.SWCONF_SELECTED_GRB, False)
+            grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False)
             if not grb:
                 return []
         if grb.id:
@@ -102,7 +95,7 @@ class Define_Software(WorkflowStep):
     def get_context(self):
         context = super(Define_Software, self).get_context()
 
-        grb = self.repo_get(self.repo.SWCONF_SELECTED_GRB, False)
+        grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False)
 
         if grb:
             context["grb"] = grb
@@ -134,7 +127,7 @@ class Define_Software(WorkflowStep):
                 i += 1
                 image = form.cleaned_data['image']
                 # checks image compatability
-                grb = self.repo_get(self.repo.SWCONF_SELECTED_GRB)
+                grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE)
                 lab = None
                 if grb:
                     lab = grb.lab
index e5ef5c6..6d59b1c 100644 (file)
@@ -30,6 +30,17 @@ def attempt_auth(request):
 
 
 def delete_session(request):
+    manager = attempt_auth(request)
+
+    if not manager:
+        return HttpResponseGone("No session found that relates to current request")
+
+    if manager.pop_workflow():
+        return HttpResponse('')
+    else:
+        del ManagerTracker.managers[request.session['manager_session']]
+        return render(request, 'workflow/exit_redirect.html')
+
     try:
         del ManagerTracker.managers[request.session['manager_session']]
         return HttpResponse('')
@@ -70,7 +81,8 @@ def manager_view(request):
             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:
-            del ManagerTracker.managers[request.session['manager_session']]
+            if not manager.pop_workflow():
+                del ManagerTracker.managers[request.session['manager_session']]
 
     return manager.status(request)
 
index 9a42d86..1f4a28a 100644 (file)
@@ -12,6 +12,7 @@ from workflow.booking_workflow import Booking_Resource_Select, SWConfig_Select,
 from workflow.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info
 from workflow.sw_bundle_workflow import Config_Software, Define_Software, SWConf_Resource_Select
 from workflow.snapshot_workflow import Select_Host_Step, Image_Meta_Step
+from workflow.models import Repository, Confirmation_Step
 
 import uuid
 
@@ -34,23 +35,6 @@ class ConfigMetaWorkflow(object):
     workflow_type = 2
     color = "#00ffcc"
 
-
-class MetaRelation(object):
-    def __init__(self, *args, **kwargs):
-        self.color = "#cccccc"
-        self.parent = 0
-        self.children = []
-        self.depth = -1
-
-    def to_json(self):
-        return {
-            'color': self.color,
-            'parent': self.parent,
-            'children': self.children,
-            'depth': self.depth,
-        }
-
-
 class MetaStep(object):
 
     UNTOUCHED = 0
@@ -92,12 +76,18 @@ class MetaStep(object):
     def __ne__(self, other):
         return self.id.int != other.id.int
 
+class Workflow(object):
+    def __init__(self, steps, metasteps, repository):
+        self.repository = repository
+        self.steps = steps
+        self.metasteps = metasteps
+        self.active_index = 0
 
 class WorkflowFactory():
     booking_steps = [
         Booking_Resource_Select,
         SWConfig_Select,
-        Booking_Meta
+        Booking_Meta,
     ]
 
     resource_steps = [
@@ -114,7 +104,7 @@ class WorkflowFactory():
 
     snapshot_steps = [
         Select_Host_Step,
-        Image_Meta_Step
+        Image_Meta_Step,
     ]
 
     def conjure(self, workflow_type=None, repo=None):
@@ -129,8 +119,17 @@ class WorkflowFactory():
         meta_steps = self.metaize(steps=steps, wf_type=workflow_type)
         return steps, meta_steps
 
+    def create_workflow(self, workflow_type=None, repo=None):
+        steps, meta_steps = self.conjure(workflow_type, repo)
+        c_step = self.make_step(Confirmation_Step, repo)
+        metaconfirm = MetaStep()
+        metaconfirm.short_title = "confirm"
+        metaconfirm.index = len(steps)
+        steps.append(c_step)
+        meta_steps.append(metaconfirm)
+        return Workflow(steps, meta_steps, repo)
+
     def make_steps(self, step_types, repository):
-        repository.el['steps'] += len(step_types)
         steps = []
         for step_type in step_types:
             steps.append(self.make_step(step_type, repository))
index 95fefbf..1d672cf 100644 (file)
@@ -13,8 +13,8 @@ from django.http import JsonResponse
 import random
 
 from booking.models import Booking
-from workflow.workflow_factory import WorkflowFactory, MetaStep, MetaRelation
-from workflow.models import Repository, Confirmation_Step
+from workflow.workflow_factory import WorkflowFactory, MetaStep
+from workflow.models import Repository
 from resource_inventory.models import (
     GenericResourceBundle,
     ConfigBundle,
@@ -27,103 +27,65 @@ logger = logging.getLogger(__name__)
 
 
 class SessionManager():
+    def active_workflow(self):
+        return self.workflows[-1]
 
     def __init__(self, request=None):
-        self.repository = Repository()
-        self.repository.el[self.repository.SESSION_USER] = request.user
-        self.repository.el['active_step'] = 0
-        self.steps = []
+        self.workflows = []
+
+        self.owner = request.user
+
         self.factory = WorkflowFactory()
-        c_step = WorkflowFactory().make_step(Confirmation_Step, self.repository)
-        self.steps.append(c_step)
-        metaconfirm = MetaStep()
-        metaconfirm.index = 0
-        metaconfirm.short_title = "confirm"
-        self.repository.el['steps'] = 1
-        self.metaworkflow = None
-        self.metaworkflows = []
-        self.metarelations = []
-        self.relationreverselookup = {}
-        self.initialized = False
-        self.active_index = 0
-        self.step_meta = [metaconfirm]
-        self.relation_depth = 0
 
     def add_workflow(self, workflow_type=None, target_id=None, **kwargs):
         if target_id is not None:
             self.prefill_repo(target_id, workflow_type)
-        factory_steps, meta_info = self.factory.conjure(workflow_type=workflow_type, repo=self.repository)
-        offset = len(meta_info)
-        for relation in self.metarelations:
-            if relation.depth > self.relation_depth:
-                self.relation_depth = relation.depth
-            if relation.parent >= self.repository.el['active_step']:
-                relation.parent += offset
-                for i in range(0, len(relation.children)):
-                    if relation.children[i] >= self.repository.el['active_step']:
-                        relation.children[i] += offset
-        self.step_meta[self.active_index:self.active_index] = meta_info
-        self.steps[self.active_index:self.active_index] = factory_steps
-
-        if self.initialized:
-            relation = MetaRelation()
-            relation.parent = self.repository.el['active_step'] + offset
-            relation.depth = self.relationreverselookup[self.step_meta[relation.parent]].depth + 1
-            if relation.depth > self.relation_depth:
-                self.relation_depth = relation.depth
-            for i in range(self.repository.el['active_step'], offset + self.repository.el['active_step']):
-                relation.children.append(i)
-                self.relationreverselookup[self.step_meta[i]] = relation
-            relation.color = "#%06x" % random.randint(0, 0xFFFFFF)
-            self.metarelations.append(relation)
-        else:
-            relation = MetaRelation()
-            relation.depth = 0
-            relation.parent = 500000000000
-            for i in range(0, len(self.step_meta)):
-                relation.children.append(i)
-                self.relationreverselookup[self.step_meta[i]] = relation
-            self.metarelations.append(relation)
-            self.initialized = True
+
+        repo = Repository()
+        if(len(self.workflows) >= 1):
+            defaults = self.workflows[-1].repository.get_child_defaults()
+            repo.set_defaults(defaults)
+            repo.el[repo.HAS_RESULT] = False
+        repo.el[repo.SESSION_USER] = self.owner
+        self.workflows.append(self.factory.create_workflow(workflow_type=workflow_type, repo = repo))
+
+    def pop_workflow(self):
+        if( len(self.workflows) <= 1 ):
+            return False
+
+        if self.workflows[-1].repository.el[self.workflows[-1].repository.HAS_RESULT]:
+            key = self.workflows[-1].repository.el[self.workflows[-1].repository.RESULT_KEY]
+            result = self.workflows[-1].repository.el[self.workflows[-1].repository.RESULT]
+            self.workflows[-2].repository.el[key] = result
+        self.workflows.pop()
+        return True
 
     def status(self, request):
         try:
-            steps = []
-            for step in self.step_meta:
-                steps.append(step.to_json())
-            parents = {}
-            children = {}
+            meta_steps = []
+            for step in self.active_workflow().metasteps:
+                meta_steps.append(step.to_json())
             responsejson = {}
-            responsejson["steps"] = steps
-            responsejson["active"] = self.repository.el['active_step']
-            responsejson["relations"] = []
-            i = 0
-            for relation in self.metarelations:
-                responsejson["relations"].append(relation.to_json())
-                children[relation.parent] = i
-                for child in relation.children:
-                    parents[child] = i
-                i += 1
-            responsejson['max_depth'] = self.relation_depth
-            responsejson['parents'] = parents
-            responsejson['children'] = children
+            responsejson["steps"] = meta_steps
+            responsejson["active"] = self.active_workflow().repository.el['active_step']
+            responsejson["workflow_count"] = len(self.workflows)
             return JsonResponse(responsejson, safe=False)
-        except Exception:
+        except Exception as e:
             pass
 
     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.steps[self.active_index].post_render(request)
-        return self.steps[self.active_index].render(request)
+            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)
 
     def post_render(self, request):
-        return self.steps[self.active_index].post_render(request)
+        return self.active_workflow().steps[self.active_workflow().active_index].post_render(request)
 
     def goto(self, num, **kwargs):
-        self.repository.el['active_step'] = int(num)
-        self.active_index = int(num)
+        self.active_workflow().repository.el['active_step'] = int(num)
+        self.active_workflow().active_index = int(num)
         # TODO: change to include some checking
 
     def prefill_repo(self, target_id, workflow_type):
@@ -142,29 +104,28 @@ class SessionManager():
     def prefill_booking(self, booking):
         models = self.make_booking_models(booking)
         confirmation = self.make_booking_confirm(booking)
-        self.repository.el[self.repository.BOOKING_MODELS] = models
-        self.repository.el[self.repository.CONFIRMATION] = confirmation
-        self.repository.el[self.repository.GRESOURCE_BUNDLE_MODELS] = self.make_grb_models(booking.resource.template)
-        self.repository.el[self.repository.BOOKING_SELECTED_GRB] = self.make_grb_models(booking.resource.template)['bundle']
-        self.repository.el[self.repository.CONFIG_MODELS] = self.make_config_models(booking.config_bundle)
+        self.active_workflow().repository.el[self.active_workflow().repository.BOOKING_MODELS] = models
+        self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirmation
+        self.active_workflow().repository.el[self.active_workflow().repository.GRESOURCE_BUNDLE_MODELS] = self.make_grb_models(booking.resource.template)
+        self.active_workflow().repository.el[self.active_workflow().repository.SELECTED_GRESOURCE_BUNDLE] = self.make_grb_models(booking.resource.template)['bundle']
+        self.active_workflow().repository.el[self.active_workflow().repository.CONFIG_MODELS] = self.make_config_models(booking.config_bundle)
 
     def prefill_resource(self, resource):
         models = self.make_grb_models(resource)
         confirm = self.make_grb_confirm(resource)
-        self.repository.el[self.repository.GRESOURCE_BUNDLE_MODELS] = models
-        self.repository.el[self.repository.CONFIRMATION] = confirm
+        self.active_workflow().repository.el[self.active_workflow().repository.GRESOURCE_BUNDLE_MODELS] = models
+        self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirm
 
     def prefill_config(self, config):
         models = self.make_config_models(config)
         confirm = self.make_config_confirm(config)
-        self.repository.el[self.repository.CONFIG_MODELS] = models
-        self.repository.el[self.repository.CONFIRMATION] = confirm
+        self.active_workflow().repository.el[self.active_workflow().repository.CONFIG_MODELS] = models
+        self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirm
         grb_models = self.make_grb_models(config.bundle)
-        self.repository.el[self.repository.GRESOURCE_BUNDLE_MODELS] = grb_models
-        self.repository.el[self.repository.SWCONF_SELECTED_GRB] = config.bundle
+        self.active_workflow().repository.el[self.active_workflow().repository.GRESOURCE_BUNDLE_MODELS] = grb_models
 
     def make_grb_models(self, resource):
-        models = self.repository.el.get(self.repository.GRESOURCE_BUNDLE_MODELS, {})
+        models = self.active_workflow().repository.el.get(self.active_workflow().repository.GRESOURCE_BUNDLE_MODELS, {})
         models['hosts'] = []
         models['bundle'] = resource
         models['interfaces'] = {}
@@ -181,7 +142,7 @@ class SessionManager():
         return models
 
     def make_grb_confirm(self, resource):
-        confirm = self.repository.el.get(self.repository.CONFIRMATION, {})
+        confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {})
         confirm['resource'] = {}
         confirm['resource']['hosts'] = []
         confirm['resource']['lab'] = resource.lab.lab_user.username
@@ -190,7 +151,7 @@ class SessionManager():
         return confirm
 
     def make_config_models(self, config):
-        models = self.repository.el.get(self.repository.CONFIG_MODELS, {})
+        models = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIG_MODELS, {})
         models['bundle'] = config
         models['host_configs'] = []
         for host_conf in HostConfiguration.objects.filter(bundle=config):
@@ -199,7 +160,7 @@ class SessionManager():
         return models
 
     def make_config_confirm(self, config):
-        confirm = self.repository.el.get(self.repository.CONFIRMATION, {})
+        confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {})
         confirm['configuration'] = {}
         confirm['configuration']['hosts'] = []
         confirm['configuration']['name'] = config.name
@@ -213,7 +174,7 @@ class SessionManager():
         return confirm
 
     def make_booking_models(self, booking):
-        models = self.repository.el.get(self.repository.BOOKING_MODELS, {})
+        models = self.active_workflow().repository.el.get(self.active_workflow().repository.BOOKING_MODELS, {})
         models['booking'] = booking
         models['collaborators'] = []
         for user in booking.collaborators.all():
@@ -221,7 +182,7 @@ class SessionManager():
         return models
 
     def make_booking_confirm(self, booking):
-        confirm = self.repository.el.get(self.repository.CONFIRMATION, {})
+        confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {})
         confirm['booking'] = {}
         confirm['booking']['length'] = (booking.end - booking.start).days
         confirm['booking']['project'] = booking.project