Start fixing workflow for model changes 54/70154/7
authorSawyer Bergeron <sawyerbergeron@gmail.com>
Thu, 2 Apr 2020 18:05:26 +0000 (14:05 -0400)
committerSawyer Bergeron <sbergeron@iol.unh.edu>
Fri, 15 May 2020 18:19:19 +0000 (14:19 -0400)
Change-Id: I79df975ef45abf2e6e69594d358bbd205938828f
Signed-off-by: Sawyer Bergeron <sbergeron@iol.unh.com>
Signed-off-by: Sawyer Bergeron <sbergeron@iol.unh.edu>
14 files changed:
src/booking/forms.py
src/booking/views.py
src/resource_inventory/models.py
src/resource_inventory/pdf_templater.py
src/static/js/dashboard.js
src/templates/base/base.html
src/templates/base/resource/steps/pod_definition.html
src/workflow/forms.py
src/workflow/models.py
src/workflow/resource_bundle_workflow.py
src/workflow/sw_bundle_workflow.py [deleted file]
src/workflow/views.py
src/workflow/workflow_factory.py
src/workflow/workflow_manager.py

index 2d3ef0f..886f0f6 100644 (file)
@@ -11,8 +11,7 @@ from django.forms.widgets import NumberInput
 
 from workflow.forms import (
     MultipleSelectFilterField,
-    MultipleSelectFilterWidget,
-    FormUtils)
+    MultipleSelectFilterWidget)
 from account.models import UserProfile
 from resource_inventory.models import Image, Installer, Scenario
 from workflow.forms import SearchableSelectMultipleField
index e767be1..3c95e07 100644 (file)
@@ -19,18 +19,16 @@ from django.db.models import Q
 from django.urls import reverse
 
 from resource_inventory.models import ResourceBundle, ResourceProfile, Image, ResourceQuery
-from resource_inventory.resource_manager import ResourceManager
-from account.models import Lab, Downtime
+from account.models import Downtime
 from booking.models import Booking
 from booking.stats import StatisticsManager
 from booking.forms import HostReImageForm
-from booking.forms import FormUtils
+from workflow.forms import FormUtils
 from api.models import JobFactory
 from workflow.views import login
 from booking.forms import QuickBookingForm
 from booking.quick_deployer import create_from_form, drop_filter
-from workflow.forms import (MultipleSelectFilterField,
-        MultipleSelectFilterWidget)
+
 
 def quick_create_clear_fields(request):
     request.session['quick_create_forminfo'] = None
index 1a4b75c..5b26ce1 100644 (file)
@@ -155,7 +155,7 @@ class ResourceTemplate(models.Model):
 
     # TODO: template might not be a good name because this is a collection of lots of configured resources
     id = models.AutoField(primary_key=True)
-    name = models.CharField(max_length=300, unique=True)
+    name = models.CharField(max_length=300)
     xml = models.TextField()
     owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
     lab = models.ForeignKey(Lab, null=True, on_delete=models.SET_NULL, related_name="resourcetemplates")
@@ -201,7 +201,7 @@ class ResourceConfiguration(models.Model):
     image = models.ForeignKey("Image", on_delete=models.PROTECT)
     template = models.ForeignKey(ResourceTemplate, related_name="resourceConfigurations", null=True, on_delete=models.CASCADE)
     is_head_node = models.BooleanField(default=False)
-    # name = models.CharField(max_length=300)
+    name = models.CharField(max_length=3000, default="<Hostname>")
 
     def __str__(self):
         return "config with " + str(self.template) + " and image " + str(self.image)
@@ -429,7 +429,7 @@ class InterfaceConfiguration(models.Model):
     connections = models.ManyToManyField(NetworkConnection)
 
     def __str__(self):
-        return "type " + str(self.profile.name) + " on host " + str(self.profile.host.name)
+        return "type " + str(self.profile) + " on host " + str(self.resource_config)
 
 
 """
index 367ba43..27a264e 100644 (file)
@@ -10,7 +10,7 @@
 
 from django.template.loader import render_to_string
 import booking
-from resource_inventory.models import Server, InterfaceProfile
+from resource_inventory.models import Server
 
 
 class PDFTemplater:
index 8e1250a..76d660d 100644 (file)
@@ -390,11 +390,8 @@ class MultipleSelectFilterWidget {
         this.dropdown_count++;
         const label = document.createElement("H5")
         label.appendChild(document.createTextNode(node['name']))
-        label.classList.add("p-1", "m-1");
+        label.classList.add("p-1", "m-1", "flex-grow-1");
         div.appendChild(label);
-        let input = this.make_input(div, node, prepopulate);
-        input.classList.add("flex-grow-1", "p-1", "m-1");
-        div.appendChild(input);
         let remove_btn = this.make_remove_button(div, node);
         remove_btn.classList.add("p-1", "m-1");
         div.appendChild(remove_btn);
@@ -407,10 +404,10 @@ class MultipleSelectFilterWidget {
         const node = this.filter_items[node_id]
         const parent = div.parentNode;
         div.parentNode.removeChild(div);
-        delete this.result[node.class][node.id]['values'][div.id];
+        this.result[node.class][node.id]['count']--;
 
         //checks if we have removed last item in class
-        if(jQuery.isEmptyObject(this.result[node.class][node.id]['values'])){
+        if(this.result[node.class][node.id]['count'] == 0){
             delete this.result[node.class][node.id];
             this.clear(node);
         }
@@ -426,9 +423,9 @@ class MultipleSelectFilterWidget {
 
     updateObjectResult(node, childKey, childValue){
         if(!this.result[node.class][node.id])
-            this.result[node.class][node.id] = {selected: true, id: node.model_id, values: {}}
+            this.result[node.class][node.id] = {selected: true, id: node.model_id, count: 0}
 
-        this.result[node.class][node.id]['values'][childKey] = childValue;
+        this.result[node.class][node.id]['count']++;
     }
 
     finish(){
@@ -437,9 +434,41 @@ class MultipleSelectFilterWidget {
 }
 
 class NetworkStep {
-    constructor(debug, xml, hosts, added_hosts, removed_host_ids, graphContainer, overviewContainer, toolbarContainer){
-        if(!this.check_support())
+    // expects:
+    //
+    // debug: bool
+    // resources: {
+    //     id: {
+    //         id: int,
+    //         value: {
+    //             description: string,
+    //         },
+    //         interfaces: [
+    //             id: int,
+    //             name: str,
+    //             description: str,
+    //             connections: [
+    //                 {
+    //                     network: int, [networks.id]
+    //                     tagged: bool
+    //                 }
+    //             ],
+    //         ],
+    //     }
+    // }
+    // networks: {
+    //     id: {
+    //         id: int,
+    //         name: str,
+    //         public: bool,
+    //     }
+    // }
+    //
+    constructor(debug, resources, networks, graphContainer, overviewContainer, toolbarContainer){
+        if(!this.check_support()) {
+            console.log("Aborting, browser is not supported");
             return;
+        }
 
         this.currentWindow = null;
         this.netCount = 0;
@@ -452,9 +481,22 @@ class NetworkStep {
         this.editor = new mxEditor();
         this.graph = this.editor.graph;
 
+        window.global_graph = this.graph;
+        window.network_rr_index = 5;
+
         this.editor.setGraphContainer(graphContainer);
         this.doGlobalConfig();
-        this.prefill(xml, hosts, added_hosts, removed_host_ids);
+
+        let mx_networks = {}
+
+        for(const network_id in networks) {
+            let network = networks[network_id];
+
+            mx_networks[network_id] = this.populateNetwork(network);
+        }
+
+        this.prefillHosts(resources, mx_networks);
+
         this.addToolbarButton(this.editor, toolbarContainer, 'zoomIn', '', "/static/img/mxgraph/zoom_in.png", true);
         this.addToolbarButton(this.editor, toolbarContainer, 'zoomOut', '', "/static/img/mxgraph/zoom_out.png", true);
 
@@ -471,10 +513,6 @@ class NetworkStep {
         this.graph.addListener(mxEvent.CELL_CONNECTED, function(sender, event) {this.cellConnectionHandler(sender, event)}.bind(this));
         //hooks up double click functionality
         this.graph.dblClick = function(evt, cell) {this.doubleClickHandler(evt, cell);}.bind(this);
-
-        if(!this.has_public_net){
-            this.addPublicNetwork();
-        }
     }
 
     check_support(){
@@ -485,22 +523,84 @@ class NetworkStep {
         return true;
     }
 
-    prefill(xml, hosts, added_hosts, removed_host_ids){
-        //populate existing data
-        if(xml){
-            this.restoreFromXml(xml, this.editor);
-        } else if(hosts){
-            for(const host of hosts)
-                this.makeHost(host);
-        }
+    /**
+     * Expects
+     * mx_interface: mxCell for the interface itself
+     * network: mxCell for the outer network
+     * tagged: bool
+     */
+    connectNetwork(mx_interface, network, tagged) {
+        var cell = new mxCell(
+            "connection from " + network + " to " + mx_interface,
+            new mxGeometry(0, 0, 50, 50));
+        cell.edge = true;
+        cell.geometry.relative = true;
+        cell.setValue(JSON.stringify({tagged: tagged}));
+
+        let terminal = this.getClosestNetworkCell(mx_interface.geometry.y, network);
+        let edge = this.graph.addEdge(cell, null, mx_interface, terminal);
+        this.colorEdge(edge, terminal, true);
+        this.graph.refresh(edge);
+    }
 
-        //apply any changes
-        if(added_hosts){
-            for(const host of added_hosts)
-                this.makeHost(host);
-            this.updateHosts([]); //TODO: why?
+    /**
+     * Expects:
+     *
+     * to: desired y axis position of the matching cell
+     * within: graph cell for a full network, with all child cells
+     *
+     * Returns:
+     * an mx cell, the one vertically closest to the desired value
+     *
+     * Side effect:
+     * modifies the <rr_index> on the <within> parameter
+     */
+    getClosestNetworkCell(to, within) {
+        if(window.network_rr_index === undefined) {
+            window.network_rr_index = 5;
+        }
+
+        let child_keys = within.children.keys();
+        let children = Array.from(within.children);
+        let index = (window.network_rr_index++) % children.length;
+
+        let child = within.children[child_keys[index]];
+
+        return children[index];
+    }
+
+    /** Expects
+     *
+     * hosts: {
+     *     id: {
+     *         id: int,
+     *         value: {
+     *             description: string,
+     *         },
+     *         interfaces: [
+     *             id: int,
+     *             name: str,
+     *             description: str,
+     *             connections: [
+     *                 {
+     *                     network: int, [networks.id]
+     *                     tagged: bool 
+     *                 }
+     *             ],
+     *         ],
+     *     }
+     * }
+     *
+     * network_mappings: {
+     *     <django network id>: <mxnetwork id>
+     * }
+     *
+     * draws given hosts into the mxgraph
+     */
+    prefillHosts(hosts, network_mappings){
+        for(const host_id in hosts) {
+            this.makeHost(hosts[host_id], network_mappings);
         }
-        this.updateHosts(removed_host_ids);
     }
 
     cellConnectionHandler(sender, event){
@@ -607,7 +707,10 @@ class NetworkStep {
                     color = kvp[1];
                 }
             }
+
             edge.setStyle('strokeColor=' + color);
+        } else {
+            console.log("Failed to color " + edge + ", " + terminal + ", " + source);
         }
     }
 
@@ -839,6 +942,7 @@ class NetworkStep {
                 return true;
             }
         }
+
         return false;
     };
 
@@ -917,6 +1021,27 @@ class NetworkStep {
         return ret_val;
     }
 
+    // expects:
+    //
+    // {
+    //     id: int,
+    //     name: str,
+    //     public: bool,
+    // }
+    //
+    // returns:
+    // mxgraph id of network
+    populateNetwork(network) {
+        let mxNet = this.makeMxNetwork(network.name, network.public);
+        this.makeSidebarNetwork(network.name, mxNet.color, mxNet.element_id);
+
+        if( network.public ) {
+            this.has_public_net = true;
+        }
+
+        return mxNet.element_id;
+    }
+
     addPublicNetwork() {
         const net = this.makeMxNetwork("public", true);
         this.makeSidebarNetwork("public", net['color'], net['element_id']);
@@ -977,7 +1102,33 @@ class NetworkStep {
         document.getElementById("network_list").appendChild(newNet);
     }
 
-    makeHost(hostInfo) {
+    /** 
+     * Expects format:
+     * {
+     *     'id': int,
+     *     'value': {
+     *         'description': string,
+     *     },
+     *     'interfaces': [
+     *          {
+     *              id: int,
+     *              name: str,
+     *              description: str,
+     *              connections: [
+     *                  {
+     *                      network: int, <django network id>,
+     *                      tagged: bool
+     *                  }
+     *              ]
+     *          }
+     *      ]
+     * }
+     *
+     * network_mappings: {
+     *     <django network id>: <mxnetwork id>
+     * }
+     */
+    makeHost(hostInfo, network_mappings) {
         const value = JSON.stringify(hostInfo['value']);
         const interfaces = hostInfo['interfaces'];
         const width = 100;
@@ -1013,6 +1164,15 @@ class NetworkStep {
                 false
             );
             port.getGeometry().offset = new mxPoint(-4*interfaces[i].name.length -2,0);
+            const iface = interfaces[i];
+            for( const connection of iface.connections ) {
+                const network = this
+                    .graph
+                    .getModel()
+                    .getCell(network_mappings[connection.network]);
+
+                this.connectNetwork(port, network, connection.tagged);
+            }
             this.graph.refresh(port);
         }
         this.graph.refresh(host);
index 4011739..f163c19 100644 (file)
                                     Design a Pod
                                 </a>
                                 <a href="#" onclick="create_workflow(2)" class="list-group-item list-group-item-action list-group-item-secondary">
-                                    Configure a Pod
-                                </a>
-                                <a href="#" onclick="create_workflow(3)" class="list-group-item list-group-item-action list-group-item-secondary">
                                     Create a Snapshot
                                 </a>
-                                <a href="#" onclick="create_workflow(4)" class="list-group-item list-group-item-action list-group-item-secondary">
+                                <a href="#" onclick="create_workflow(3)" class="list-group-item list-group-item-action list-group-item-secondary">
                                     Configure OPNFV
                                 </a>
                             </div>
index a6810de..e4d02f5 100644 (file)
     debug = true;
     {% endif %}
 
-    let xml = '';
-    {% if xml %}
-    xml = '{{xml|safe}}';
-    {% endif %}
-
-    let hosts = [];
-    {% for host in hosts %}
-    hosts.push({{host|safe}});
-    {% endfor %}
-
-    let added_hosts = [];
-    {% for host in added_hosts %}
-    added_hosts.push({{host|safe}});
-    {% endfor %}
+    const False = false;
+    const True = true;
 
-    let removed_host_ids = {{removed_hosts|safe}};
+    let resources = {{resources|safe}};
+    let networks = {{networks|safe}};
 
     network_step = new NetworkStep(
         debug,
-        xml,
-        hosts,
-        added_hosts,
-        removed_host_ids,
+        resources,
+        networks,
         document.getElementById('graphContainer'),
         document.getElementById('outlineContainer'),
         document.getElementById('toolbarContainer'),
index c86f9da..4220dea 100644 (file)
@@ -321,7 +321,6 @@ class FormUtils:
             labs[lab_node['id']] = lab_node
 
             for template in ResourceManager.getInstance().getAvailableResourceTemplates(lab, user):
-
                 resource_node = {
                     'form': {"name": "host_name", "type": "text", "placeholder": "hostname"},
                     'id': "resource_" + str(template.id),
@@ -355,9 +354,9 @@ class FormUtils:
 
 class HardwareDefinitionForm(forms.Form):
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, user, *args, **kwargs):
         super(HardwareDefinitionForm, self).__init__(*args, **kwargs)
-        attrs = FormUtils.getLabData(multiple_hosts=True)
+        attrs = FormUtils.getLabData(multiple_hosts=True, user=user)
         self.fields['filter_field'] = MultipleSelectFilterField(
             widget=MultipleSelectFilterWidget(**attrs)
         )
@@ -393,7 +392,7 @@ class NetworkConfigurationForm(forms.Form):
 
 class HostSoftwareDefinitionForm(forms.Form):
 
-    host_name = forms.CharField(max_length=200, disabled=True, required=False)
+    host_name = forms.CharField(max_length=200, disabled=False, required=True)
     headnode = forms.BooleanField(required=False, widget=forms.HiddenInput)
 
     def __init__(self, *args, **kwargs):
index c8b09f2..173fdba 100644 (file)
@@ -18,7 +18,7 @@ import requests
 from workflow.forms import ConfirmationForm
 from api.models import JobFactory
 from dashboard.exceptions import ResourceAvailabilityException, ModelValidationException
-from resource_inventory.models import Image, InterfaceConfiguration, OPNFVConfig, ResourceOPNFVConfig, NetworkRole
+from resource_inventory.models import Image, OPNFVConfig, ResourceOPNFVConfig, NetworkRole
 from resource_inventory.resource_manager import ResourceManager
 from resource_inventory.pdf_templater import PDFTemplater
 from notifier.manager import NotificationHandler
@@ -352,6 +352,7 @@ class Confirmation_Step(WorkflowStep):
                     self.set_valid("Confirmed")
 
             elif data == "False":
+                self.repo.cancel()
                 self.set_valid("Canceled")
             else:
                 self.set_invalid("Bad Form Contents")
@@ -391,6 +392,9 @@ class Repository():
     SNAPSHOT_DESC = "description of the snapshot"
     BOOKING_INFO_FILE = "the INFO.yaml file for this user's booking"
 
+    # new keys for migration to using ResourceTemplates:
+    RESOURCE_TEMPLATE_MODELS = "current working model of resource template"
+
     # migratory elements of segmented workflow
     # each of these is the end result of a different workflow.
     HAS_RESULT = "whether or not workflow has a result"
@@ -428,6 +432,14 @@ class Repository():
         else:
             history[key].append(id)
 
+    def cancel(self):
+        if self.RESOURCE_TEMPLATE_MODELS in self.el:
+            models = self.el[self.RESOURCE_TEMPLATE_MODELS]
+            if models['template'].temporary:
+                models['template'].delete()
+                # deleting current template should cascade delete all
+                # necessary related models
+
     def make_models(self):
         if self.SNAPSHOT_MODELS in self.el:
             errors = self.make_snapshot()
@@ -509,71 +521,16 @@ class Repository():
         owner = self.el[self.SESSION_USER]
         if self.RESOURCE_TEMPLATE_MODELS in self.el:
             models = self.el[self.RESOURCE_TEMPLATE_MODELS]
-            if 'hosts' in models:
-                hosts = models['hosts']
-            else:
-                return "GRB has no hosts. CODE:0x0002"
-            if 'bundle' in models:
-                bundle = models['bundle']
-            else:
-                return "GRB, no bundle in models. CODE:0x0003"
-
-            try:
-                bundle.owner = owner
-                bundle.save()
-            except Exception as e:
-                return "GRB, saving bundle generated exception: " + str(e) + " CODE:0x0004"
-            try:
-                for host in hosts:
-                    genericresource = host.resource
-                    genericresource.bundle = bundle
-                    genericresource.save()
-                    host.resource = genericresource
-                    host.save()
-            except Exception as e:
-                return "GRB, saving hosts generated exception: " + str(e) + " CODE:0x0005"
-
-            if 'networks' in models:
-                for net in models['networks'].values():
-                    net.bundle = bundle
-                    net.save()
-
-            if 'interfaces' in models:
-                for interface_set in models['interfaces'].values():
-                    for interface in interface_set:
-                        try:
-                            interface.host = interface.host
-                            interface.save()
-                        except Exception:
-                            return "GRB, saving interface " + str(interface) + " failed. CODE:0x0019"
-            else:
-                return "GRB, no interface set provided. CODE:0x001a"
-
-            if 'connections' in models:
-                for resource_name, mapping in models['connections'].items():
-                    for profile_name, connection_set in mapping.items():
-                        interface = InterfaceConfiguration.objects.get(
-                            profile__name=profile_name,
-                            host__resource__name=resource_name,
-                            host__resource__bundle=models['bundle']
-                        )
-                        for connection in connection_set:
-                            try:
-                                connection.network = connection.network
-                                connection.save()
-                                interface.connections.add(connection)
-                            except Exception as e:
-                                return "GRB, saving vlan " + str(connection) + " failed. Exception: " + str(e) + ". CODE:0x0017"
-            else:
-                return "GRB, no vlan set provided. CODE:0x0018"
+            models['template'].owner = owner
+            models['template'].temporary = False
+            models['template'].save()
+            self.el[self.RESULT] = models['template']
+            self.el[self.HAS_RESULT] = True
+            return False
 
         else:
             return "GRB no models given. CODE:0x0001"
 
-        self.el[self.RESULT] = bundle
-        self.el[self.HAS_RESULT] = True
-        return False
-
     def make_software_config_bundle(self):
         models = self.el[self.CONFIG_MODELS]
         if 'bundle' in models:
index 2cf5459..391d33e 100644 (file)
@@ -9,10 +9,13 @@
 
 
 from django.conf import settings
+from django.forms import formset_factory
+
+from typing import List
 
 import json
-import re
 from xml.dom import minidom
+import traceback
 
 from workflow.models import WorkflowStep
 from account.models import Lab
@@ -20,20 +23,19 @@ from workflow.forms import (
     HardwareDefinitionForm,
     NetworkDefinitionForm,
     ResourceMetaForm,
+    HostSoftwareDefinitionForm,
 )
 from resource_inventory.models import (
-    ResourceProfile,
     ResourceTemplate,
     ResourceConfiguration,
     InterfaceConfiguration,
     Network,
-    NetworkConnection
+    NetworkConnection,
+    Image,
 )
 from dashboard.exceptions import (
     InvalidVlanConfigurationException,
     NetworkExistsException,
-    InvalidHostnameException,
-    NonUniqueHostnameException,
     ResourceAvailabilityException
 )
 
@@ -54,40 +56,89 @@ class Define_Hardware(WorkflowStep):
 
     def get_context(self):
         context = super(Define_Hardware, self).get_context()
-        context['form'] = self.form or HardwareDefinitionForm()
+        user = self.repo_get(self.repo.SESSION_USER)
+        context['form'] = self.form or HardwareDefinitionForm(user)
         return context
 
     def update_models(self, data):
         data = data['filter_field']
         models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
-        models['hosts'] = []  # This will always clear existing data when this step changes
+        models['resources'] = []  # This will always clear existing data when this step changes
+        models['connections'] = []
         models['interfaces'] = {}
-        if "bundle" not in models:
-            models['bundle'] = ResourceTemplate(owner=self.repo_get(self.repo.SESSION_USER))
-        host_data = data['host']
-        names = {}
-        for host_profile_dict in host_data.values():
-            id = host_profile_dict['id']
-            profile = ResourceProfile.objects.get(id=id)
+        if "template" not in models:
+            template = ResourceTemplate.objects.create(temporary=True)
+            models['template'] = template
+
+        resource_data = data['resource']
+
+        new_template = models['template']
+
+        public_network = Network.objects.create(name="public", bundle=new_template, is_public=True)
+
+        all_networks = {public_network.id: public_network}
+
+        for resource_template_dict in resource_data.values():
+            id = resource_template_dict['id']
+            old_template = ResourceTemplate.objects.get(id=id)
+
             # instantiate genericHost and store in repo
-            for name in host_profile_dict['values'].values():
-                if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})", name):
-                    raise InvalidHostnameException("Invalid hostname: '" + name + "'")
-                if name in names:
-                    raise NonUniqueHostnameException("All hosts must have unique names")
-                names[name] = True
-                resourceConfig = ResourceConfiguration(profile=profile, template=models['bundle'])
-                models['hosts'].append(resourceConfig)
-                for interface_profile in profile.interfaceprofile.all():
-                    genericInterface = InterfaceConfiguration(profile=interface_profile, resource_config=resourceConfig)
-                    if resourceConfig.name not in models['interfaces']:
-                        models['interfaces'][resourceConfig.name] = []
-                    models['interfaces'][resourceConfig.name].append(genericInterface)
+            for _ in range(0, resource_template_dict['count']):
+                resource_configs = old_template.resourceConfigurations.all()
+                for config in resource_configs:
+                    # need to save now for connections to refer to it later
+                    new_config = ResourceConfiguration.objects.create(
+                        profile=config.profile,
+                        image=config.image,
+                        name=config.name,
+                        template=new_template)
+
+                    for interface_config in config.interface_configs.all():
+                        new_interface_config = InterfaceConfiguration.objects.create(
+                            profile=interface_config.profile,
+                            resource_config=new_config)
+
+                        for connection in interface_config.connections.all():
+                            network = None
+                            if connection.network.is_public:
+                                network = public_network
+                            else:
+                                # check if network is known
+                                if connection.network.id not in all_networks:
+                                    # create matching one
+                                    new_network = Network(
+                                        name=connection.network.name + "_" + str(new_config.id),
+                                        bundle=new_template,
+                                        is_public=False)
+                                    new_network.save()
+
+                                    all_networks[connection.network.id] = new_network
+
+                                network = all_networks[connection.network.id]
+
+                            new_connection = NetworkConnection(
+                                network=network,
+                                vlan_is_tagged=connection.vlan_is_tagged)
+
+                            new_interface_config.save()  # can't do later because M2M on next line
+                            new_connection.save()
+
+                            new_interface_config.connections.add(new_connection)
+
+                        unique_resource_ref = new_config.name + "_" + str(new_config.id)
+                        if unique_resource_ref not in models['interfaces']:
+                            models['interfaces'][unique_resource_ref] = []
+                        models['interfaces'][unique_resource_ref].append(interface_config)
+
+                    models['resources'].append(new_config)
+
+            models['networks'] = all_networks
 
         # add selected lab to models
         for lab_dict in data['lab'].values():
             if lab_dict['selected']:
-                models['bundle'].lab = Lab.objects.get(lab_user__id=lab_dict['id'])
+                models['template'].lab = Lab.objects.get(lab_user__id=lab_dict['id'])
+                models['template'].save()
                 break  # if somehow we get two 'true' labs, we only use one
 
         # return to repo
@@ -95,20 +146,22 @@ class Define_Hardware(WorkflowStep):
 
     def update_confirmation(self):
         confirm = self.repo_get(self.repo.CONFIRMATION, {})
-        if "resource" not in confirm:
-            confirm['resource'] = {}
-        confirm['resource']['hosts'] = []
-        models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {"hosts": []})
-        for host in models['hosts']:
-            host_dict = {"name": host.resource.name, "profile": host.profile.name}
-            confirm['resource']['hosts'].append(host_dict)
-        if "lab" in models:
-            confirm['resource']['lab'] = models['lab'].lab_user.username
+        if "template" not in confirm:
+            confirm['template'] = {}
+        confirm['template']['resources'] = []
+        models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
+        if 'template' in models:
+            for resource in models['template'].getConfigs():
+                host_dict = {"name": resource.name, "profile": resource.profile.name}
+                confirm['template']['resources'].append(host_dict)
+        if "template" in models:
+            confirm['template']['lab'] = models['template'].lab.lab_user.username
         self.repo_put(self.repo.CONFIRMATION, confirm)
 
     def post(self, post_data, user):
         try:
-            self.form = HardwareDefinitionForm(post_data)
+            user = self.repo_get(self.repo.SESSION_USER)
+            self.form = HardwareDefinitionForm(user, post_data)
             if self.form.is_valid():
                 self.update_models(self.form.cleaned_data)
                 self.update_confirmation()
@@ -116,9 +169,107 @@ class Define_Hardware(WorkflowStep):
             else:
                 self.set_invalid("Please complete the fields highlighted in red to continue")
         except Exception as e:
+            print("Caught exception: " + str(e))
+            traceback.print_exc()
             self.set_invalid(str(e))
 
 
+class Define_Software(WorkflowStep):
+    template = 'config_bundle/steps/define_software.html'
+    title = "Pick Software"
+    description = "Choose the opnfv and image of your machines"
+    short_title = "host config"
+
+    def build_filter_data(self, hosts_data):
+        """
+        Build list of Images to filter out.
+
+        returns a 2D array of images to exclude
+        based on the ordering of the passed
+        hosts_data
+        """
+
+        filter_data = []
+        user = self.repo_get(self.repo.SESSION_USER)
+        lab = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS)['template'].lab
+        for i, host_data in enumerate(hosts_data):
+            host = ResourceConfiguration.objects.get(pk=host_data['host_id'])
+            wrong_owner = Image.objects.exclude(owner=user).exclude(public=True)
+            wrong_host = Image.objects.exclude(host_type=host.profile)
+            wrong_lab = Image.objects.exclude(from_lab=lab)
+            excluded_images = wrong_owner | wrong_host | wrong_lab
+            filter_data.append([])
+            for image in excluded_images:
+                filter_data[i].append(image.pk)
+        return filter_data
+
+    def create_hostformset(self, hostlist, data=None):
+        hosts_initial = []
+        configs = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}).get("resources")
+        if configs:
+            for config in configs:
+                hosts_initial.append({
+                    'host_id': config.id,
+                    'host_name': config.name,
+                    'headnode': config.is_head_node,
+                    'image': config.image
+                })
+        else:
+            for host in hostlist:
+                hosts_initial.append({
+                    'host_id': host.id,
+                    'host_name': host.name
+                })
+
+        HostFormset = formset_factory(HostSoftwareDefinitionForm, extra=0)
+        filter_data = self.build_filter_data(hosts_initial)
+
+        class SpecialHostFormset(HostFormset):
+            def get_form_kwargs(self, index):
+                kwargs = super(SpecialHostFormset, self).get_form_kwargs(index)
+                if index is not None:
+                    kwargs['imageQS'] = Image.objects.exclude(pk__in=filter_data[index])
+                return kwargs
+
+        if data:
+            return SpecialHostFormset(data, initial=hosts_initial)
+        return SpecialHostFormset(initial=hosts_initial)
+
+    def get_host_list(self, grb=None):
+        return self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS).get("resources")
+
+    def get_context(self):
+        context = super(Define_Software, self).get_context()
+
+        context["formset"] = self.create_hostformset(self.get_host_list())
+
+        return context
+
+    def post(self, post_data, user):
+        hosts = self.get_host_list()
+
+        # TODO: fix headnode in form, currently doesn't return a selected one
+        # models['headnode_index'] = post_data.get("headnode", 1)
+        formset = self.create_hostformset(hosts, data=post_data)
+        has_headnode = False
+        if formset.is_valid():
+            for i, form in enumerate(formset):
+                host = hosts[i]
+                image = form.cleaned_data['image']
+                hostname = form.cleaned_data['host_name']
+                headnode = form.cleaned_data['headnode']
+                if headnode:
+                    has_headnode = True
+                host.is_head_node = headnode
+                host.name = hostname
+                host.image = image
+                host.save()
+
+            self.set_valid("Completed")
+        else:
+            self.set_invalid("Please complete all fields")
+
+
 class Define_Nets(WorkflowStep):
     template = 'resource/steps/pod_definition.html'
     title = "Define Networks"
@@ -144,6 +295,40 @@ class Define_Nets(WorkflowStep):
         except Exception:
             return None
 
+    def make_mx_network_dict(self, network):
+        return {
+            'id': network.id,
+            'name': network.name,
+            'public': network.is_public
+        }
+
+    def make_mx_resource_dict(self, resource_config):
+        resource_dict = {
+            'id': resource_config.id,
+            'interfaces': [],
+            'value': {
+                'name': resource_config.name,
+                'id': resource_config.id,
+                'description': resource_config.profile.description
+            }
+        }
+
+        for interface_config in resource_config.interface_configs.all():
+            connections = []
+            for connection in interface_config.connections.all():
+                connections.append({'tagged': connection.vlan_is_tagged, 'network': connection.network.id})
+
+            interface_dict = {
+                "id": interface_config.id,
+                "name": interface_config.profile.name,
+                "description": "speed: " + str(interface_config.profile.speed) + "M\ntype: " + interface_config.profile.nic_type,
+                "connections": connections
+            }
+
+            resource_dict['interfaces'].append(interface_dict)
+
+        return resource_dict
+
     def make_mx_host_dict(self, generic_host):
         host = {
             'id': generic_host.profile.name,
@@ -160,50 +345,34 @@ class Define_Nets(WorkflowStep):
             })
         return host
 
+    # first step guards this one, so can't get here without at least empty
+    # models being populated by step one
     def get_context(self):
         context = super(Define_Nets, self).get_context()
         context.update({
             'form': NetworkDefinitionForm(),
             'debug': settings.DEBUG,
+            'resources': {},
+            'networks': {},
+            'vlans': [],
+            # remove others
             'hosts': [],
             'added_hosts': [],
             'removed_hosts': []
         })
-        vlans = self.get_vlans()
-        if vlans:
-            context['vlans'] = vlans
-        try:
-            models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
-            hosts = models.get("hosts", [])
-            # calculate if the selected hosts have changed
-            added_hosts = set()
-            host_set = set(self.repo_get(self.repo.RCONFIG_LAST_HOSTLIST, []))
-            if len(host_set):
-                new_host_set = set([h.resource.name + "*" + h.profile.name for h in models['hosts']])
-                context['removed_hosts'] = [h.split("*")[0] for h in (host_set - new_host_set)]
-                added_hosts.update([h.split("*")[0] for h in (new_host_set - host_set)])
-
-            # add all host info to context
-            for generic_host in hosts:
-                host = self.make_mx_host_dict(generic_host)
-                host_serialized = json.dumps(host)
-                context['hosts'].append(host_serialized)
-                if host['id'] in added_hosts:
-                    context['added_hosts'].append(host_serialized)
-            bundle = models.get("bundle", False)
-            if bundle:
-                context['xml'] = bundle.xml or False
 
-        except Exception:
-            pass
+        models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS)  # infallible, guarded by prior step
+        for resource in models['resources']:
+            d = self.make_mx_resource_dict(resource)
+            context['resources'][d['id']] = d
+
+        for network in models['networks'].values():
+            d = self.make_mx_network_dict(network)
+            context['networks'][d['id']] = d
 
         return context
 
     def post(self, post_data, user):
-        models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
-        if 'hosts' in models:
-            host_set = set([h.resource.name + "*" + h.profile.name for h in models['hosts']])
-            self.repo_put(self.repo.RCONFIG_LAST_HOSTLIST, host_set)
         try:
             xmlData = post_data.get("xml")
             self.updateModels(xmlData)
@@ -212,41 +381,58 @@ class Define_Nets(WorkflowStep):
         except ResourceAvailabilityException:
             self.set_invalid("Public network not availble")
         except Exception as e:
+            traceback.print_exc()
             self.set_invalid("An error occurred when applying networks: " + str(e))
 
+    def resetNetworks(self, networks: List[Network]):  # potentially just pass template here?
+        for network in networks:
+            network.delete()
+
     def updateModels(self, xmlData):
         models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
-        models["connections"] = {}
-        models['networks'] = {}
-        given_hosts, interfaces, networks = self.parseXml(xmlData)
-        existing_host_list = models.get("hosts", [])
-        existing_hosts = {}  # maps id to host
-        for host in existing_host_list:
-            existing_hosts[host.resource.name] = host
+        given_hosts = None
+        interfaces = None
+        networks = None
+        try:
+            given_hosts, interfaces, networks = self.parseXml(xmlData)
+        except Exception as e:
+            print("tried to parse Xml, got exception instead:")
+            print(e)
+
+        existing_rconfig_list = models.get("resources", [])
+        existing_rconfigs = {}  # maps id to host
+        for rconfig in existing_rconfig_list:
+            existing_rconfigs["host_" + str(rconfig.id)] = rconfig
 
-        bundle = models.get("bundle", ResourceTemplate(owner=self.repo_get(self.repo.SESSION_USER)))
+        bundle = models.get("template")  # hard fail if not in repo
+
+        self.resetNetworks(models['networks'].values())
+        models['networks'] = {}
 
         for net_id, net in networks.items():
-            network = Network()
-            network.name = net['name']
-            network.bundle = bundle
-            network.is_public = net['public']
+            network = Network.objects.create(
+                name=net['name'],
+                bundle=bundle,
+                is_public=net['public'])
+
             models['networks'][net_id] = network
+            network.save()
 
         for hostid, given_host in given_hosts.items():
-            existing_host = existing_hosts[hostid[5:]]
-
             for ifaceId in given_host['interfaces']:
                 iface = interfaces[ifaceId]
-                if existing_host.resource.name not in models['connections']:
-                    models['connections'][existing_host.resource.name] = {}
-                models['connections'][existing_host.resource.name][iface['profile_name']] = []
+
+                iface_config = InterfaceConfiguration.objects.get(id=iface['config_id'])
+                if iface_config.resource_config.template.id != bundle.id:
+                    raise ValidationError("User does not own the template they are editing")
+
                 for connection in iface['connections']:
                     network_id = connection['network']
                     net = models['networks'][network_id]
                     connection = NetworkConnection(vlan_is_tagged=connection['tagged'], network=net)
-                    models['connections'][existing_host.resource.name][iface['profile_name']].append(connection)
-        bundle.xml = xmlData
+                    connection.save()
+                    iface_config.connections.add(connection)
+                    iface_config.save()
         self.repo_put(self.repo.RESOURCE_TEMPLATE_MODELS, models)
 
     def decomposeXml(self, xmlString):
@@ -303,7 +489,7 @@ class Define_Nets(WorkflowStep):
         for cellId, cell in xml_hosts.items():
             cell_json_str = cell.getAttribute("value")
             cell_json = json.loads(cell_json_str)
-            host = {"interfaces": [], "name": cellId, "profile_name": cell_json['name']}
+            host = {"interfaces": [], "name": cellId, "hostname": cell_json['name']}
             hosts[cellId] = host
 
         # parse networks
@@ -324,7 +510,7 @@ class Define_Nets(WorkflowStep):
             parentId = cell.getAttribute('parent')
             cell_json_str = cell.getAttribute("value")
             cell_json = json.loads(cell_json_str)
-            iface = {"name": cellId, "connections": [], "profile_name": cell_json['name']}
+            iface = {"graph_id": cellId, "connections": [], "config_id": cell_json['id'], "profile_name": cell_json['name']}
             hosts[parentId]['interfaces'].append(cellId)
             interfaces[cellId] = iface
 
@@ -346,9 +532,9 @@ class Define_Nets(WorkflowStep):
                 network = networks[xml_ports[src]]
 
             if not tagged:
-                if interface['name'] in untagged_ifaces:
+                if interface['config_id'] in untagged_ifaces:
                     raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
-                untagged_ifaces.add(interface['name'])
+                untagged_ifaces.add(interface['config_id'])
 
             # add connection to interface
             interface['connections'].append({"tagged": tagged, "network": network['id']})
@@ -362,12 +548,23 @@ class Resource_Meta_Info(WorkflowStep):
     description = "Please fill out the rest of the information about your resource"
     short_title = "pod info"
 
+    def update_confirmation(self):
+        confirm = self.repo_get(self.repo.CONFIRMATION, {})
+        if "template" not in confirm:
+            confirm['template'] = {}
+        models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
+        if "template" in models:
+            confirm['template']['description'] = models['template'].description
+            confirm['template']['name'] = models['template'].name
+        self.repo_put(self.repo.CONFIRMATION, confirm)
+
     def get_context(self):
         context = super(Resource_Meta_Info, self).get_context()
         name = ""
         desc = ""
-        bundle = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}).get("bundle", False)
-        if bundle and bundle.name:
+        models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, None)
+        bundle = models['template']
+        if bundle:
             name = bundle.name
             desc = bundle.description
         context['form'] = ResourceMetaForm(initial={"bundle_name": name, "bundle_description": desc})
@@ -379,10 +576,10 @@ class Resource_Meta_Info(WorkflowStep):
             models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
             name = form.cleaned_data['bundle_name']
             desc = form.cleaned_data['bundle_description']
-            bundle = models.get("bundle", ResourceTemplate(owner=self.repo_get(self.repo.SESSION_USER)))
+            bundle = models['template']  # infallible
             bundle.name = name
             bundle.description = desc
-            models['bundle'] = bundle
+            bundle.save()
             self.repo_put(self.repo.RESOURCE_TEMPLATE_MODELS, models)
             confirm = self.repo_get(self.repo.CONFIRMATION)
             if "resource" not in confirm:
diff --git a/src/workflow/sw_bundle_workflow.py b/src/workflow/sw_bundle_workflow.py
deleted file mode 100644 (file)
index 4f872c0..0000000
+++ /dev/null
@@ -1,188 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, Sean Smith, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-from django.forms import formset_factory
-
-from workflow.models import WorkflowStep
-from workflow.forms import BasicMetaForm, HostSoftwareDefinitionForm
-from workflow.booking_workflow import Abstract_Resource_Select
-from resource_inventory.models import Image, ResourceConfiguration, ResourceTemplate
-
-
-class SWConf_Resource_Select(Abstract_Resource_Select):
-    workflow_type = "configuration"
-
-
-class Define_Software(WorkflowStep):
-    template = 'config_bundle/steps/define_software.html'
-    title = "Pick Software"
-    description = "Choose the opnfv and image of your machines"
-    short_title = "host config"
-
-    def build_filter_data(self, hosts_data):
-        """
-        Build list of Images to filter out.
-
-        returns a 2D array of images to exclude
-        based on the ordering of the passed
-        hosts_data
-        """
-
-        filter_data = []
-        user = self.repo_get(self.repo.SESSION_USER)
-        lab = self.repo_get(self.repo.SELECTED_RESOURCE_TEMPLATE).lab
-        for i, host_data in enumerate(hosts_data):
-            host = ResourceConfiguration.objects.get(pk=host_data['host_id'])
-            wrong_owner = Image.objects.exclude(owner=user).exclude(public=True)
-            wrong_host = Image.objects.exclude(host_type=host.profile)
-            wrong_lab = Image.objects.exclude(from_lab=lab)
-            excluded_images = wrong_owner | wrong_host | wrong_lab
-            filter_data.append([])
-            for image in excluded_images:
-                filter_data[i].append(image.pk)
-        return filter_data
-
-    def create_hostformset(self, hostlist, data=None):
-        hosts_initial = []
-        host_configs = self.repo_get(self.repo.CONFIG_MODELS, {}).get("host_configs", False)
-        if host_configs:
-            for config in host_configs:
-                hosts_initial.append({
-                    'host_id': config.id,
-                    'host_name': config.profile.name,
-                    'headnode': config.is_head_node,
-                    'image': config.image
-                })
-        else:
-            for host in hostlist:
-                hosts_initial.append({
-                    'host_id': host.id,
-                    'host_name': host.profile.name
-                })
-
-        HostFormset = formset_factory(HostSoftwareDefinitionForm, extra=0)
-        filter_data = self.build_filter_data(hosts_initial)
-
-        class SpecialHostFormset(HostFormset):
-            def get_form_kwargs(self, index):
-                kwargs = super(SpecialHostFormset, self).get_form_kwargs(index)
-                if index is not None:
-                    kwargs['imageQS'] = Image.objects.exclude(pk__in=filter_data[index])
-                return kwargs
-
-        if data:
-            return SpecialHostFormset(data, initial=hosts_initial)
-        return SpecialHostFormset(initial=hosts_initial)
-
-    def get_host_list(self, grb=None):
-        if grb is None:
-            grb = self.repo_get(self.repo.SELECTED_RESOURCE_TEMPLATE, False)
-            if not grb:
-                return []
-        if grb.id:
-            return ResourceConfiguration.objects.filter(template=grb)
-        generic_hosts = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}).get("hosts", [])
-        return generic_hosts
-
-    def get_context(self):
-        context = super(Define_Software, self).get_context()
-
-        grb = self.repo_get(self.repo.SELECTED_RESOURCE_TEMPLATE, False)
-
-        if grb:
-            context["grb"] = grb
-            formset = self.create_hostformset(self.get_host_list(grb))
-            context["formset"] = formset
-            context['headnode'] = self.repo_get(self.repo.CONFIG_MODELS, {}).get("headnode_index", 1)
-        else:
-            context["error"] = "Please select a resource first"
-            self.set_invalid("Step requires information that is not yet provided by previous step")
-
-        return context
-
-    def post(self, post_data, user):
-        models = self.repo_get(self.repo.CONFIG_MODELS, {})
-        if "bundle" not in models:
-            models['bundle'] = ResourceTemplate(owner=self.repo_get(self.repo.SESSION_USER))
-
-        confirm = self.repo_get(self.repo.CONFIRMATION, {})
-
-        hosts = self.get_host_list()
-        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'] = []
-            confirm_hosts = []
-            for i, form in enumerate(formset):
-                host = hosts[i]
-                if host.is_head_node:
-                    has_headnode = True
-                models['host_configs'].append(host)
-                confirm_hosts.append({
-                    "name": host.profile.name,
-                    "image": host.image.name,
-                    "headnode": host.is_head_node
-                })
-
-            if not has_headnode:
-                self.set_invalid('Must have one "Headnode" per POD')
-                return
-
-            self.repo_put(self.repo.CONFIG_MODELS, models)
-            if "configuration" not in confirm:
-                confirm['configuration'] = {}
-            confirm['configuration']['hosts'] = confirm_hosts
-            self.repo_put(self.repo.CONFIRMATION, confirm)
-            self.set_valid("Completed")
-        else:
-            self.set_invalid("Please complete all fields")
-
-
-class Config_Software(WorkflowStep):
-    template = 'config_bundle/steps/config_software.html'
-    title = "Other Info"
-    description = "Give your software config a name, description, and other stuff"
-    short_title = "config info"
-
-    def get_context(self):
-        context = super(Config_Software, self).get_context()
-
-        initial = {}
-        models = self.repo_get(self.repo.CONFIG_MODELS, {})
-        bundle = models.get("bundle", False)
-        if bundle:
-            initial['name'] = bundle.name
-            initial['description'] = bundle.description
-        context["form"] = BasicMetaForm(initial=initial)
-        return context
-
-    def post(self, post_data, user):
-        models = self.repo_get(self.repo.CONFIG_MODELS, {})
-        if "bundle" not in models:
-            models['bundle'] = ResourceTemplate(owner=self.repo_get(self.repo.SESSION_USER))
-
-        confirm = self.repo_get(self.repo.CONFIRMATION, {})
-        if "configuration" not in confirm:
-            confirm['configuration'] = {}
-
-        form = BasicMetaForm(post_data)
-        if form.is_valid():
-            models['bundle'].name = form.cleaned_data['name']
-            models['bundle'].description = form.cleaned_data['description']
-
-            confirm['configuration']['name'] = form.cleaned_data['name']
-            confirm['configuration']['description'] = form.cleaned_data['description']
-            self.set_valid("Complete")
-        else:
-            self.set_invalid("Please correct the errors shown below")
-
-        self.repo_put(self.repo.CONFIG_MODELS, models)
-        self.repo_put(self.repo.CONFIRMATION, confirm)
index 9ff444d..9666d72 100644 (file)
@@ -35,7 +35,7 @@ def remove_workflow(request):
     if not manager:
         return no_workflow(request)
 
-    has_more_workflows, result = manager.pop_workflow()
+    has_more_workflows, result = manager.pop_workflow(discard=True)
 
     if not has_more_workflows:  # this was the last workflow, so delete the reference to it in the tracker
         del ManagerTracker.managers[request.session['manager_session']]
index 7d498e8..04ed280 100644 (file)
@@ -9,8 +9,7 @@
 
 
 from workflow.booking_workflow import Booking_Resource_Select, SWConfig_Select, Booking_Meta, OPNFV_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.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info, Define_Software
 from workflow.snapshot_workflow import Select_Host_Step, Image_Meta_Step
 from workflow.opnfv_workflow import Pick_Installer, Assign_Network_Roles, Assign_Host_Roles, OPNFV_Resource_Select, MetaInfo
 from workflow.models import Confirmation_Step
@@ -81,23 +80,11 @@ class WorkflowFactory():
 
     resource_steps = [
         Define_Hardware,
+        Define_Software,
         Define_Nets,
         Resource_Meta_Info,
     ]
 
-    config_steps = [
-        SWConf_Resource_Select,
-        Define_Software,
-        Config_Software,
-    ]
-
-    # resource_steps = [
-    #     Define_Hardware,
-    #     Define_Nets,
-    #     Define_Software,
-    #     Software,
-    # ]
-
     snapshot_steps = [
         Select_Host_Step,
         Image_Meta_Step,
@@ -115,7 +102,6 @@ class WorkflowFactory():
         workflow_types = [
             self.booking_steps,
             self.resource_steps,
-            self.config_steps,
             self.snapshot_steps,
             self.opnfv_steps,
         ]
index b5cb815..a48efe5 100644 (file)
@@ -66,7 +66,7 @@ class SessionManager():
             return reverse('booking:booking_detail', kwargs={'booking_id': self.result.id})
         return "/"
 
-    def pop_workflow(self):
+    def pop_workflow(self, discard=False):
         multiple_wfs = len(self.workflows) > 1
         if multiple_wfs:
             if self.workflows[-1].repository.el[Repository.RESULT]:  # move result
@@ -79,6 +79,8 @@ class SessionManager():
         else:
             current_repo = prev_workflow.repository
         self.result = current_repo.el[current_repo.RESULT]
+        if discard:
+            current_repo.cancel()
         return multiple_wfs, self.result
 
     def status(self, request):