Merge "Limit total number of active bookings per user"
authorParker Berberian <pberberian@iol.unh.edu>
Tue, 16 Apr 2019 16:53:08 +0000 (16:53 +0000)
committerGerrit Code Review <gerrit@opnfv.org>
Tue, 16 Apr 2019 16:53:08 +0000 (16:53 +0000)
src/booking/forms.py
src/booking/quick_deployer.py
src/resource_inventory/migrations/0009_auto_20190315_1757.py [new file with mode: 0644]
src/resource_inventory/models.py
src/resource_inventory/resource_manager.py
src/templates/booking/quick_deploy.html
src/templates/resource/steps/pod_definition.html
src/workflow/models.py
src/workflow/resource_bundle_workflow.py

index 7ba5af0..9349ac1 100644 (file)
@@ -8,7 +8,6 @@
 ##############################################################################
 import django.forms as forms
 from django.forms.widgets import NumberInput
-from django.db.models import Q
 
 from workflow.forms import (
     SearchableSelectMultipleWidget,
@@ -22,7 +21,6 @@ from resource_inventory.models import Image, Installer, Scenario
 class QuickBookingForm(forms.Form):
     purpose = forms.CharField(max_length=1000)
     project = forms.CharField(max_length=400)
-    image = forms.ModelChoiceField(queryset=Image.objects.all())
     hostname = forms.CharField(max_length=400)
 
     installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=False)
@@ -40,14 +38,14 @@ class QuickBookingForm(forms.Form):
         elif data and "users" in data:
             chosen_users = data.getlist("users")
 
-        if user:
-            self.image = forms.ModelChoiceField(queryset=Image.objects.filter(
-                Q(public=True) | Q(owner=user)), required=False)
-        else:
-            self.image = forms.ModelChoiceField(queryset=Image.objects.all(), required=False)
-
         super(QuickBookingForm, self).__init__(data=data, **kwargs)
 
+        self.fields["image"] = forms.ModelChoiceField(
+            queryset=Image.objects.difference(
+                Image.objects.filter(public=False).difference(Image.objects.filter(owner=user))
+            )
+        )
+
         self.fields['users'] = forms.CharField(
             widget=SearchableSelectMultipleWidget(
                 attrs=self.build_search_widget_attrs(chosen_users, default_user=default_user)
index 7059313..640ded9 100644 (file)
@@ -22,7 +22,6 @@ from resource_inventory.models import (
     Image,
     GenericResourceBundle,
     ConfigBundle,
-    Vlan,
     Host,
     HostProfile,
     HostConfiguration,
@@ -30,7 +29,10 @@ from resource_inventory.models import (
     GenericHost,
     GenericInterface,
     OPNFVRole,
-    OPNFVConfig
+    OPNFVConfig,
+    Network,
+    NetworkConnection,
+    NetworkRole
 )
 from resource_inventory.resource_manager import ResourceManager
 from resource_inventory.pdf_templater import PDFTemplater
@@ -230,6 +232,20 @@ def check_invariants(request, **kwargs):
         raise BookingLengthException("Booking must be between 1 and 21 days long")
 
 
+def configure_networking(grb, config):
+    # create network
+    net = Network.objects.create(name="public", bundle=grb, is_public=True)
+    # connect network to generic host
+    grb.getHosts()[0].generic_interfaces.first().connections.add(
+        NetworkConnection.objects.create(network=net, vlan_is_tagged=False)
+    )
+    # asign network role
+    role = NetworkRole.objects.create(name="public", network=net)
+    opnfv_config = config.opnfv_config.first()
+    if opnfv_config:
+        opnfv_config.networks.add(role)
+
+
 def create_from_form(form, request):
     quick_booking_id = str(uuid.uuid4())
 
@@ -275,18 +291,7 @@ def create_from_form(form, request):
         generic_interface = GenericInterface.objects.create(profile=interface_profile, host=ghost)
         generic_interface.save()
 
-    # get vlan, assign to first interface
-    publicnetwork = lab.vlan_manager.get_public_vlan()
-    if not publicnetwork:
-        raise NoRemainingPublicNetwork("No public networks were available for your pod")
-    publicvlan = publicnetwork.vlan
-    lab.vlan_manager.reserve_public_vlan(publicvlan)
-
-    vlan = Vlan.objects.create(vlan_id=publicvlan, tagged=False, public=True)
-    vlan.save()
-
-    ghost.generic_interfaces.first().vlans.add(vlan)
-    ghost.generic_interfaces.first().save()
+    configure_networking(grbundle, cbundle)
 
     # generate resource bundle
     resource_bundle = generate_resource_bundle(grbundle, cbundle)
diff --git a/src/resource_inventory/migrations/0009_auto_20190315_1757.py b/src/resource_inventory/migrations/0009_auto_20190315_1757.py
new file mode 100644 (file)
index 0000000..92ed0e9
--- /dev/null
@@ -0,0 +1,73 @@
+# Generated by Django 2.1 on 2019-03-15 17:57
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('resource_inventory', '0008_host_remote_management'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='NetworkConnection',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('vlan_is_tagged', models.BooleanField()),
+            ],
+        ),
+        migrations.CreateModel(
+            name='NetworkRole',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100)),
+            ],
+        ),
+        migrations.RemoveField(
+            model_name='genericinterface',
+            name='vlans',
+        ),
+        migrations.RemoveField(
+            model_name='network',
+            name='vlan_id',
+        ),
+        migrations.AddField(
+            model_name='network',
+            name='bundle',
+            field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='networks', to='resource_inventory.GenericResourceBundle'),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='network',
+            name='is_public',
+            field=models.BooleanField(default=False),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='vlan',
+            name='network',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='resource_inventory.Network'),
+        ),
+        migrations.AddField(
+            model_name='networkrole',
+            name='network',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Network'),
+        ),
+        migrations.AddField(
+            model_name='networkconnection',
+            name='network',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Network'),
+        ),
+        migrations.AddField(
+            model_name='genericinterface',
+            name='connections',
+            field=models.ManyToManyField(to='resource_inventory.NetworkConnection'),
+        ),
+        migrations.AddField(
+            model_name='opnfvconfig',
+            name='networks',
+            field=models.ManyToManyField(to='resource_inventory.NetworkRole'),
+        ),
+    ]
index bdc1f5d..d3f47d4 100644 (file)
@@ -105,26 +105,6 @@ class RamProfile(models.Model):
         return str(self.amount) + "G for " + str(self.host)
 
 
-# Networking -- located here due to import order requirements
-class Network(models.Model):
-    id = models.AutoField(primary_key=True)
-    vlan_id = models.IntegerField()
-    name = models.CharField(max_length=100)
-
-    def __str__(self):
-        return self.name
-
-
-class Vlan(models.Model):
-    id = models.AutoField(primary_key=True)
-    vlan_id = models.IntegerField()
-    tagged = models.BooleanField()
-    public = models.BooleanField(default=False)
-
-    def __str__(self):
-        return str(self.vlan_id) + ("_T" if self.tagged else "")
-
-
 # Generic resource templates
 class GenericResourceBundle(models.Model):
     id = models.AutoField(primary_key=True)
@@ -145,6 +125,32 @@ class GenericResourceBundle(models.Model):
         return self.name
 
 
+class Network(models.Model):
+    id = models.AutoField(primary_key=True)
+    name = models.CharField(max_length=100)
+    bundle = models.ForeignKey(GenericResourceBundle, on_delete=models.CASCADE, related_name="networks")
+    is_public = models.BooleanField()
+
+    def __str__(self):
+        return self.name
+
+
+class NetworkConnection(models.Model):
+    network = models.ForeignKey(Network, on_delete=models.CASCADE)
+    vlan_is_tagged = models.BooleanField()
+
+
+class Vlan(models.Model):
+    id = models.AutoField(primary_key=True)
+    vlan_id = models.IntegerField()
+    tagged = models.BooleanField()
+    public = models.BooleanField(default=False)
+    network = models.ForeignKey(Network, on_delete=models.DO_NOTHING, null=True)
+
+    def __str__(self):
+        return str(self.vlan_id) + ("_T" if self.tagged else "")
+
+
 class GenericResource(models.Model):
     bundle = models.ForeignKey(GenericResourceBundle, related_name='generic_resources', on_delete=models.CASCADE)
     hostname_validchars = RegexValidator(regex=r'(?=^.{1,253}$)(?=(^([A-Za-z0-9\-\_]{1,62}\.)*[A-Za-z0-9\-\_]{1,63}$))', message="Enter a valid hostname. Full domain name may be 1-253 characters, each hostname 1-63 characters (including suffixed dot), and valid characters for hostnames are A-Z, a-z, 0-9, hyphen (-), and underscore (_)")
@@ -188,14 +194,11 @@ class ResourceBundle(models.Model):
         return Host.objects.filter(bundle=self, config__opnfvRole__name=role).first()
 
 
-# Networking
-
-
 class GenericInterface(models.Model):
     id = models.AutoField(primary_key=True)
-    vlans = models.ManyToManyField(Vlan)
     profile = models.ForeignKey(InterfaceProfile, on_delete=models.CASCADE)
     host = models.ForeignKey(GenericHost, on_delete=models.CASCADE, related_name='generic_interfaces')
+    connections = models.ManyToManyField(NetworkConnection)
 
     def __str__(self):
         return "type " + str(self.profile) + " on host " + str(self.host)
@@ -227,6 +230,11 @@ class Opsys(models.Model):
         return self.name
 
 
+class NetworkRole(models.Model):
+    name = models.CharField(max_length=100)
+    network = models.ForeignKey(Network, on_delete=models.CASCADE)
+
+
 class ConfigBundle(models.Model):
     id = models.AutoField(primary_key=True)
     owner = models.ForeignKey(User, on_delete=models.CASCADE)
@@ -243,6 +251,7 @@ class OPNFVConfig(models.Model):
     installer = models.ForeignKey(Installer, on_delete=models.CASCADE)
     scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE)
     bundle = models.ForeignKey(ConfigBundle, related_name="opnfv_config", on_delete=models.CASCADE)
+    networks = models.ManyToManyField(NetworkRole)
 
     def __str__(self):
         return "OPNFV job with " + str(self.installer) + " and " + str(self.scenario)
index 52b0055..d3d3ed4 100644 (file)
@@ -14,7 +14,14 @@ from dashboard.exceptions import (
     ResourceProvisioningException,
     ModelValidationException,
 )
-from resource_inventory.models import Host, HostConfiguration, ResourceBundle, HostProfile
+from resource_inventory.models import (
+    Host,
+    HostConfiguration,
+    ResourceBundle,
+    HostProfile,
+    Network,
+    Vlan
+)
 
 
 class ResourceManager:
@@ -66,39 +73,48 @@ class ResourceManager:
             self.releaseHost(host)
         resourceBundle.delete()
 
-    def convertResourceBundle(self, genericResourceBundle, lab=None, config=None):
+    def get_vlans(self, genericResourceBundle):
+        networks = {}
+        vlan_manager = genericResourceBundle.lab.vlan_manager
+        for network in genericResourceBundle.networks.all():
+            if network.is_public:
+                public_net = vlan_manager.get_public_vlan()
+                vlan_manager.reserve_public_vlan(public_net.vlan)
+                networks[network.name] = public_net.vlan
+            else:
+                vlan = vlan_manager.get_vlan()
+                vlan_manager.reserve_vlans(vlan)
+                networks[network.name] = vlan
+        return networks
+
+    def convertResourceBundle(self, genericResourceBundle, config=None):
         """
         Takes in a GenericResourceBundle and 'converts' it into a ResourceBundle
         """
-        resource_bundle = ResourceBundle()
-        resource_bundle.template = genericResourceBundle
-        resource_bundle.save()
-
-        hosts = genericResourceBundle.getHosts()
-
-        # current supported case: user creating new booking
-        # currently unsupported: editing existing booking
-
+        resource_bundle = ResourceBundle.objects.create(template=genericResourceBundle)
+        generic_hosts = genericResourceBundle.getHosts()
         physical_hosts = []
 
-        for host in hosts:
+        vlan_map = self.get_vlans(genericResourceBundle)
+
+        for generic_host in generic_hosts:
             host_config = None
             if config:
-                host_config = HostConfiguration.objects.get(bundle=config, host=host)
+                host_config = HostConfiguration.objects.get(bundle=config, host=generic_host)
             try:
-                physical_host = self.acquireHost(host, genericResourceBundle.lab.name)
+                physical_host = self.acquireHost(generic_host, genericResourceBundle.lab.name)
             except ResourceAvailabilityException:
-                self.fail_acquire(physical_hosts)
+                self.fail_acquire(physical_hosts, vlan_map)
                 raise ResourceAvailabilityException("Could not provision hosts, not enough available")
             try:
                 physical_host.bundle = resource_bundle
-                physical_host.template = host
+                physical_host.template = generic_host
                 physical_host.config = host_config
                 physical_hosts.append(physical_host)
 
-                self.configureNetworking(physical_host)
+                self.configureNetworking(physical_host, vlan_map)
             except Exception:
-                self.fail_acquire(physical_hosts)
+                self.fail_acquire(physical_hosts, vlan_map)
                 raise ResourceProvisioningException("Network configuration failed.")
             try:
                 physical_host.save()
@@ -108,13 +124,20 @@ class ResourceManager:
 
         return resource_bundle
 
-    def configureNetworking(self, host):
+    def configureNetworking(self, host, vlan_map):
         generic_interfaces = list(host.template.generic_interfaces.all())
         for int_num, physical_interface in enumerate(host.interfaces.all()):
             generic_interface = generic_interfaces[int_num]
             physical_interface.config.clear()
-            for vlan in generic_interface.vlans.all():
-                physical_interface.config.add(vlan)
+            for connection in generic_interface.connections.all():
+                physical_interface.config.add(
+                    Vlan.objects.create(
+                        vlan_id=vlan_map[connection.network.name],
+                        tagged=connection.vlan_is_tagged,
+                        public=connection.network.is_public,
+                        network=connection.network
+                    )
+                )
 
     # private interface
     def acquireHost(self, genericHost, labName):
@@ -136,6 +159,17 @@ class ResourceManager:
         host.booked = False
         host.save()
 
-    def fail_acquire(self, hosts):
+    def releaseNetworks(self, grb, vlan_manager, vlans):
+        for net_name, vlan_id in vlans.items():
+            net = Network.objects.get(name=net_name, bundle=grb)
+            if(net.is_public):
+                vlan_manager.release_public_vlan(vlan_id)
+            else:
+                vlan_manager.release_vlans(vlan_id)
+
+    def fail_acquire(self, hosts, vlans):
+        grb = hosts[0].template.resource.bundle
+        vlan_manager = hosts[0].lab.vlan_manager
+        self.releaseNetworks(grb, vlan_manager, vlans)
         for host in hosts:
             self.releaseHost(host)
index 2c1a3d2..38294b2 100644 (file)
 
         $('#id_image').children().hide();
 
+        for( var i = 0; i < drop.childNodes.length; i++ )
+        {
+            drop.childNodes[i].disabled = true; // closest we can get on safari to hiding it outright
+        }
+
+
         var empty_map = {}
 
         for ( var i=0; i < drop.childNodes.length; i++ )
                 if( image_object.host_profile == host_pk && image_object.lab == lab_pk )
                 {
                     drop.childNodes[i].style.display = "inherit";
+                    drop.childNodes[i].disabled = false;
                 }
             }
         }
     }
 
-    $('#id_image').children().hide();
+    imageHider();
     $('#id_installer').children().hide();
     $('#id_scenario').children().hide();
 
         }
 
         targ_id = "#" + target;
+
         $(targ_id).children().hide();
+
+        for (var i = 0; i < document.getElementById(target).childNodes.length; i++)
+        {
+            document.getElementById(target).childNodes[i].disabled = true;
+        }
         var drop = document.getElementById(master);
         var opts = target_filter[drop.options[drop.selectedIndex].value];
         if (!opts) {
         for (var i = 0; i < document.getElementById(target).childNodes.length; i++) {
             if (document.getElementById(target).childNodes[i].value in opts && !(document.getElementById(target).childNodes[i].value in emptyMap) ) {
                 document.getElementById(target).childNodes[i].style.display = "inherit";
+                document.getElementById(target).childNodes[i].disabled = false;
             }
         }
     }
index 8599bb0..2cb6257 100644 (file)
@@ -22,23 +22,9 @@ var netColors = ['red', 'blue', 'purple', 'green', 'orange', '#8CCDF5', '#1E9BAC
 var hostCount = 0;
 var lastHostBottom = 100;
 var networks = new Set([]);
-var network_names = new Set([]);
 var has_public_net = false;
-var vlans = {{vlans|default:'null'}};
-var vlan_string = "";
 
 function main(graphContainer, overviewContainer, toolbarContainer) {
-    if(vlans){
-        for(var i=0; i<vlans.length-1; i++){
-            vlan_string += vlans[i] + ", ";
-        }
-        if(vlans.length > 0){
-            vlan_string += vlans[vlans.length-1];
-        }
-
-        var str = "Available vlans for your POD: " + vlan_string;
-        document.getElementById("vlan_notice").innerHTML = str;
-    }
     //check if the browser is supported
     if (!mxClient.isBrowserSupported()) {
         mxUtils.error('Browser is not supported', 200, false);
@@ -55,14 +41,6 @@ function main(graphContainer, overviewContainer, toolbarContainer) {
     var model = graph.getModel();
     editor.setGraphContainer(graphContainer);
 
-    {% if debug %}
-    editor.addAction('printXML', function(editor, cell) {
-        mxLog.write(encodeGraph(graph));
-        mxLog.show();
-    });
-    {% endif %}
-
-
     doGlobalConfig(graph);
     currentGraph = graph;
 
@@ -70,7 +48,6 @@ function main(graphContainer, overviewContainer, toolbarContainer) {
         restoreFromXml('{{xml|safe}}', editor);
     {% elif hosts %}
         {% for host in hosts %}
-
         var host = {{host|safe}};
         makeHost(host);
         {% endfor %}
@@ -87,12 +64,15 @@ function main(graphContainer, overviewContainer, toolbarContainer) {
     addToolbarButton(editor, toolbarContainer, 'zoomOut', '', "/static/img/mxgraph/zoom_out.png", true);
 
     {% if debug %}
+    editor.addAction('printXML', function(editor, cell) {
+        mxLog.write(encodeGraph(graph));
+        mxLog.show();
+    });
     addToolbarButton(editor, toolbarContainer, 'printXML', '', '/static/img/mxgraph/fit_to_size.png', true);
     {% endif %}
 
     var outline = new mxOutline(graph, overviewContainer);
 
-
     var checkAllowed = function(edge, terminal, source) {
         //check if other terminal is null, and that they are different
         otherTerminal = edge.getTerminal(!source);
@@ -145,14 +125,14 @@ function main(graphContainer, overviewContainer, toolbarContainer) {
         }
     });
 
-    createDeleteDialog = function(id)
-    {
-                var content = document.createElement('div');
-                var innerHTML = "<button style='width: 46%;' onclick=deleteCell('" + id + "');>Remove</button>"
-                innerHTML += "<button style='width: 46%;' onclick='currentWindow.destroy();'>Cancel</button>"
-                content.innerHTML = innerHTML;
-                showWindow(currentGraph, 'Do you want to delete this network?', content, 200, 62);
+    createDeleteDialog = function(id) {
+        var content = document.createElement('div');
+        var innerHTML = "<button style='width: 46%;' onclick=deleteCell('" + id + "');>Remove</button>"
+        innerHTML += "<button style='width: 46%;' onclick='currentWindow.destroy();'>Cancel</button>"
+        content.innerHTML = innerHTML;
+        showWindow(currentGraph, 'Do you want to delete this network?', content, 200, 62);
     }
+
     graph.dblClick = function(evt, cell) {
 
         if( cell != null ){
@@ -167,8 +147,6 @@ function main(graphContainer, overviewContainer, toolbarContainer) {
            }
         }
     };
-    graph.setCellsSelectable(false);
-    graph.setCellsMovable(false);
 
     updateHosts({{ removed_hosts|default:"[]"|safe }});
     if(!has_public_net){
@@ -197,10 +175,8 @@ function restoreFromXml(xml, editor) {
         var cell = root.getChildAt(i);
         if(cell.getId().indexOf("network") > -1) {
             var info = JSON.parse(cell.getValue());
-            var vlan_id = info['vlan_id'];
-            networks.add(vlan_id);
             var name = info['name'];
-            network_names.add(name);
+            networks.add(name);
             var styles = cell.getStyle().split(";");
             var color = null;
             for(var j=0; j< styles.length; j++){
@@ -211,11 +187,10 @@ function restoreFromXml(xml, editor) {
                 }
             }
             if(info.public){
-                vlan_id = "";
                 has_public_net = true;
             }
             netCount++;
-            makeSidebarNetwork(name, vlan_id, color, cell.getId());
+            makeSidebarNetwork(name, color, cell.getId());
         }
     }
 }
@@ -228,50 +203,27 @@ function deleteCell(cellId) {
     }
     currentGraph.removeCells([cell]);
     currentWindow.destroy();
-
 }
 
 function newNetworkWindow() {
     var innerHtml = 'Name: <input type="text" name="net_name" id="net_name_input" style="margin:5px;"><br>';
-    innerHtml += 'Vlan: <input type="number" step="1" name="vlan_id" id="vlan_id_input" style="margin:5px;"><br>';
     innerHtml += '<button style="width: 46%;" onclick="parseNetworkWindow()">Okay</button>';
     innerHtml += '<button style="width: 46%;" onclick="currentWindow.destroy();">Cancel</button><br>';
-    innerHtml += '<div id="current_window_vlans"/>';
     innerHtml += '<div id="current_window_errors"/>';
     var content = document.createElement("div");
     content.innerHTML = innerHtml;
 
     showWindow(currentGraph, "Network Creation", content, 300, 300);
-
-    if(vlans){
-        vlan_notice = document.getElementById("current_window_vlans");
-        vlan_notice.appendChild(document.createTextNode("Available Vlans: " + vlan_string));
-        }
 }
 
 function parseNetworkWindow() {
     var net_name = document.getElementById("net_name_input").value
-    var vlan_id = document.getElementById("vlan_id_input").value
     var error_div = document.getElementById("current_window_errors");
-    var vlan_valid = Number.isInteger(Number(vlan_id)) && (vlan_id < 4095) && (vlan_id > 1)
-    if(vlans){
-        vlan_valid = vlan_valid & vlans.indexOf(Number(vlan_id)) >= 0;
-    }
-    if( !vlan_valid)
-    {
-        error_div.innerHTML = "Please only enter an integer in the valid range (default 1-4095) for the VLAN ID";
-        return;
-    }
-    if( networks.has(vlan_id))
-    {
-        error_div.innerHTML = "All VLAN IDs must be unique";
-        return;
-    }
-    if( network_names.has(net_name) ){
+    if( networks.has(net_name) ){
         error_div.innerHTML = "All network names must be unique";
         return;
     }
-    addNetwork(net_name, vlan_id);
+    addNetwork(net_name);
     currentWindow.destroy();
 }
 
@@ -312,6 +264,12 @@ function encodeGraph(graph) {
 function doGlobalConfig(graph) {
     //general graph stuff
     graph.setMultigraph(false);
+    graph.setCellsSelectable(false);
+    graph.setCellsMovable(false);
+
+    //testing
+    graph.vertexLabelIsMovable = true;
+
 
     //edge behavior
     graph.setConnectable(true);
@@ -332,6 +290,9 @@ function doGlobalConfig(graph) {
     style[mxConstants.STYLE_ROUNDED] = true;
     style[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation;
 
+    hostStyle = graph.getStylesheet().getDefaultVertexStyle();
+    hostStyle[mxConstants.STYLE_ROUNDED] = 1;
+
     // TODO: Proper override
     graph.convertValueToString = function(cell) {
         try{
@@ -395,29 +356,21 @@ function othersUntagged(edgeID) {
     var end1 = edge.getTerminal(true);
     var end2 = edge.getTerminal(false);
 
-    if( end1.getParent().getId().split('_')[0] == 'host' )
-    {
+    if( end1.getParent().getId().split('_')[0] == 'host' ){
         var netint = end1;
-    }
-    else
-    {
+    } else {
         var netint = end2;
     }
 
     var edges = netint.edges;
 
-    for( var i=0; i < edges.length; i++ )
-    {
-        if( edges[i].getValue() )
-        {
+    for( var i=0; i < edges.length; i++ ) {
+        if( edges[i].getValue() ) {
             var tagged = JSON.parse(edges[i].getValue()).tagged;
-        }
-        else
-        {
+        } else {
             var tagged = true;
         }
-        if( !tagged )
-        {
+        if( !tagged ) {
             return true;
         }
     }
@@ -454,12 +407,11 @@ function parseVlanWindow(edgeID) {
             break;
         }
     }
-    //edge.setValue(cellValue);
     currentGraph.refresh(edge);
     closeWindow();
 }
 
-function makeMxNetwork(vlan_id, net_name) {
+function makeMxNetwork(net_name, public = false) {
     model = currentGraph.getModel();
     width = 10;
     height = 1700;
@@ -472,9 +424,8 @@ function makeMxNetwork(vlan_id, net_name) {
         //alert(color);
     }
     var net_val = Object();
-    net_val['vlan_id'] = vlan_id;
     net_val['name'] = net_name;
-    net_val['public'] = vlan_id < 0;
+    net_val['public'] = public;
     net = currentGraph.insertVertex(
         currentGraph.getDefaultParent(),
         'network_' + netCount,
@@ -505,23 +456,23 @@ function makeMxNetwork(vlan_id, net_name) {
     retVal['color'] = color;
     retVal['element_id'] = "network_" + netCount;
 
+    networks.add(net_name);
+
     netCount++;
     return retVal;
 }
 
 function addPublicNetwork() {
-    var net = makeMxNetwork(-1, "public");
-    network_names.add("public");
-    makeSidebarNetwork("public", "", net['color'], net['element_id']);
+    var net = makeMxNetwork("public", true);
+    makeSidebarNetwork("public", net['color'], net['element_id']);
+    has_public_net = true;
 }
 
-function addNetwork(net_name, vlan_id) {
-    var ret = makeMxNetwork(vlan_id, net_name);
+function addNetwork(net_name) {
+    var ret = makeMxNetwork(net_name);
     var color = ret['color'];
     var net_id = ret['element_id'];
-    networks.add(vlan_id);
-    network_names.add(net_name);
-    makeSidebarNetwork(net_name, vlan_id, color, net_id);
+    makeSidebarNetwork(net_name, color, net_id);
 }
 
 function updateHosts(removed) {
@@ -535,8 +486,7 @@ function updateHosts(removed) {
 
     var hosts = currentGraph.getChildVertices(currentGraph.getDefaultParent());
     var topdist = 100;
-    for(var i=0; i<hosts.length; i++)
-    {
+    for(var i=0; i<hosts.length; i++) {
         var host = hosts[i];
         if(!host.id.startsWith("host_"))
         {
@@ -549,7 +499,7 @@ function updateHosts(removed) {
     }
 }
 
-function makeSidebarNetwork(net_name, vlan_id, color, net_id){
+function makeSidebarNetwork(net_name, color, net_id){
     var newNet = document.createElement("li");
     var colorBlob = document.createElement("div");
     colorBlob.className = "colorblob";
@@ -565,9 +515,6 @@ function makeSidebarNetwork(net_name, vlan_id, color, net_id){
             createDeleteDialog(net_id);
     }, false);
     var text = net_name;
-    if(vlan_id){
-        text += " : " + vlan_id;
-    }
     var newNetValue = document.createTextNode(text);
     textContainer.appendChild(newNetValue);
     colorBlob.style['background'] = color;
@@ -585,7 +532,7 @@ function makeHost(hostInfo) {
     interfaces = hostInfo['interfaces'];
     graph = currentGraph;
     width = 100;
-    height = (25 * interfaces.length) + 10;
+    height = (25 * interfaces.length) + 25;
     xoff = 75;
     yoff = lastHostBottom + 50;
     lastHostBottom = yoff + height;
@@ -600,6 +547,7 @@ function makeHost(hostInfo) {
         'editable=0',
         false
     );
+    host.getGeometry().offset = new mxPoint(-50,0);
     host.setConnectable(false);
     hostCount++;
 
@@ -609,13 +557,16 @@ function makeHost(hostInfo) {
             null,
             JSON.stringify(interfaces[i]),
             90,
-            (i * 25) + 5,
+            (i * 25) + 12,
             20,
             20,
             'fillColor=blue;editable=0',
             false
         );
+        port.getGeometry().offset = new mxPoint(-4*interfaces[i].name.length -2,0);
+        currentGraph.refresh(port);
     }
+    currentGraph.refresh(host);
 }
 
 function submitForm() {
@@ -709,7 +660,6 @@ function submitForm() {
         </div>
         <ul id="network_list">
         </ul>
-        <p id="vlan_notice"></p>
         <button type="button" style="display: none" onclick="submitForm();">Submit</button>
     </div>
     <form id="xml_form" method="post" action="/wf/workflow/">
index 7d24668..4ebb042 100644 (file)
@@ -205,27 +205,6 @@ class Confirmation_Step(WorkflowStep):
     title = "Confirm Changes"
     description = "Does this all look right?"
 
-    def get_vlan_warning(self):
-        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
-        hosts = grb.getHosts()
-        for host in hosts:
-            for interface in host.generic_interfaces.all():
-                for vlan in interface.vlans.all():
-                    if vlan.public:
-                        if not vlan_manager.public_vlan_is_available(vlan.vlan_id):
-                            return 1
-                    else:
-                        if not vlan_manager.is_available(vlan.vlan_id):
-                            return 1  # There is a problem with these vlans
-        return 0
-
     def get_context(self):
         context = super(Confirmation_Step, self).get_context()
         context['form'] = ConfirmationForm()
@@ -233,7 +212,6 @@ class Confirmation_Step(WorkflowStep):
             self.repo_get(self.repo.CONFIRMATION),
             default_flow_style=False
         ).strip()
-        context['vlan_warning'] = self.get_vlan_warning()
 
         return context
 
@@ -264,33 +242,8 @@ class Confirmation_Step(WorkflowStep):
                 pass
 
         else:
-            if "vlan_input" in request.POST:
-                if request.POST.get("vlan_input") == "True":
-                    self.translate_vlans()
-                    return self.render(request)
             pass
 
-    def translate_vlans(self):
-        grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False)
-        if not grb:
-            return 0
-        vlan_manager = grb.lab.vlan_manager
-        if vlan_manager is None:
-            return 0
-        hosts = grb.getHosts()
-        for host in hosts:
-            for interface in host.generic_interfaces.all():
-                for vlan in interface.vlans.all():
-                    if not vlan.public:
-                        if not vlan_manager.is_available(vlan.vlan_id):
-                            vlan.vlan_id = vlan_manager.get_vlan()
-                            vlan.save()
-                    else:
-                        if not vlan_manager.public_vlan_is_available(vlan.vlan_id):
-                            pub_vlan = vlan_manager.get_public_vlan()
-                            vlan.vlan_id = pub_vlan.vlan
-                            vlan.save()
-
 
 class Workflow():
 
@@ -455,6 +408,11 @@ class Repository():
             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:
@@ -466,20 +424,21 @@ class Repository():
             else:
                 return "GRB, no interface set provided. CODE:0x001a"
 
-            if 'vlans' in models:
-                for resource_name, mapping in models['vlans'].items():
-                    for profile_name, vlan_set in mapping.items():
+            if 'connections' in models:
+                for resource_name, mapping in models['connections'].items():
+                    for profile_name, connection_set in mapping.items():
                         interface = GenericInterface.objects.get(
                             profile__name=profile_name,
                             host__resource__name=resource_name,
                             host__resource__bundle=models['bundle']
                         )
-                        for vlan in vlan_set:
+                        for connection in connection_set:
                             try:
-                                vlan.save()
-                                interface.vlans.add(vlan)
+                                connection.network = connection.network
+                                connection.save()
+                                interface.connections.add(connection)
                             except Exception as e:
-                                return "GRB, saving vlan " + str(vlan) + " failed. Exception: " + str(e) + ". CODE:0x0017"
+                                return "GRB, saving vlan " + str(connection) + " failed. Exception: " + str(e) + ". CODE:0x0017"
             else:
                 return "GRB, no vlan set provided. CODE:0x0018"
 
@@ -536,9 +495,6 @@ class Repository():
         else:
             return "BOOK, no selected resource. CODE:0x000e"
 
-        if not self.reserve_vlans(selected_grb):
-            return "BOOK, vlans not available"
-
         if 'booking' in models:
             booking = models['booking']
         else:
@@ -595,30 +551,6 @@ class Repository():
         except Exception as e:
             return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0016"
 
-    def reserve_vlans(self, grb):
-        """
-        True is success
-        """
-        vlans = []
-        public_vlan = None
-        vlan_manager = grb.lab.vlan_manager
-        if vlan_manager is None:
-            return True
-        for host in grb.getHosts():
-            for interface in host.generic_interfaces.all():
-                for vlan in interface.vlans.all():
-                    if vlan.public:
-                        public_vlan = vlan
-                    else:
-                        vlans.append(vlan.vlan_id)
-
-        try:
-            vlan_manager.reserve_vlans(vlans)
-            vlan_manager.reserve_public_vlan(public_vlan.vlan_id)
-            return True
-        except Exception:
-            return False
-
     def __init__(self):
         self.el = {}
         self.el[self.CONFIRMATION] = {}
index 4858ebe..536187f 100644 (file)
@@ -10,6 +10,7 @@
 
 from django.shortcuts import render
 from django.forms import formset_factory
+from django.conf import settings
 
 import json
 import re
@@ -25,11 +26,12 @@ from workflow.forms import (
 )
 from resource_inventory.models import (
     GenericResourceBundle,
-    Vlan,
     GenericInterface,
     GenericHost,
     GenericResource,
-    HostProfile
+    HostProfile,
+    Network,
+    NetworkConnection
 )
 from dashboard.exceptions import (
     InvalidVlanConfigurationException,
@@ -185,6 +187,7 @@ class Define_Nets(WorkflowStep):
             hostlist = self.repo_get(self.repo.GRB_LAST_HOSTLIST, None)
             added_list = []
             added_dict = {}
+            context['debug'] = settings.DEBUG
             context['added_hosts'] = []
             if hostlist is not None:
                 new_hostlist = []
@@ -239,15 +242,15 @@ class Define_Nets(WorkflowStep):
             self.metastep.set_valid("Networks applied successfully")
         except ResourceAvailabilityException:
             self.metastep.set_invalid("Public network not availble")
-        except Exception:
-            self.metastep.set_invalid("An error occurred when applying networks")
+        except Exception as e:
+            self.metastep.set_invalid("An error occurred when applying networks: " + str(e))
         return self.render(request)
 
     def updateModels(self, xmlData):
         models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
-        models["vlans"] = {}
-        given_hosts, interfaces = self.parseXml(xmlData)
-        vlan_manager = models['bundle'].lab.vlan_manager
+        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:
@@ -255,104 +258,133 @@ class Define_Nets(WorkflowStep):
 
         bundle = models.get("bundle", GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER)))
 
+        for net_id, net in networks.items():
+            network = Network()
+            network.name = net['name']
+            network.bundle = bundle
+            network.is_public = net['public']
+            models['networks'][net_id] = network
+
         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['vlans']:
-                    models['vlans'][existing_host.resource.name] = {}
-                models['vlans'][existing_host.resource.name][iface['profile_name']] = []
-                for network in iface['networks']:
-                    vlan_id = network['network']['vlan']
-                    is_public = network['network']['public']
-                    if is_public:
-                        public_net = vlan_manager.get_public_vlan()
-                        if public_net is None:
-                            raise ResourceAvailabilityException("No public networks available")
-                        vlan_id = vlan_manager.get_public_vlan().vlan
-                    vlan = Vlan(vlan_id=vlan_id, tagged=network['tagged'], public=is_public)
-                    models['vlans'][existing_host.resource.name][iface['profile_name']].append(vlan)
+                if existing_host.resource.name not in models['connections']:
+                    models['connections'][existing_host.resource.name] = {}
+                models['connections'][existing_host.resource.name][iface['profile_name']] = []
+                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
         self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
 
-    # serialize and deserialize xml from mxGraph
-    def parseXml(self, xmlString):
-        parent_nets = {}  # map network ports to networks
-        networks = {}  # maps net id to network object
-        hosts = {}  # cotains id -> hosts, each containing interfaces, referencing networks
-        interfaces = {}  # maps id -> interface
+    def decomposeXml(self, xmlString):
+        """
+        This function takes in an xml doc from our front end
+        and returns dictionaries that map cellIds to the xml
+        nodes themselves. There is no unpacking of the
+        xml objects, just grouping and organizing
+        """
+
+        connections = {}
+        networks = {}
+        hosts = {}
+        interfaces = {}
+        network_ports = {}
+
         xmlDom = minidom.parseString(xmlString)
         root = xmlDom.documentElement.firstChild
-        netids = {}
-        untagged_ints = {}
         for cell in root.childNodes:
             cellId = cell.getAttribute('id')
+            group = cellId.split("_")[0]
+            parentGroup = cell.getAttribute("parent").split("_")[0]
+            # place cell into correct group
 
             if cell.getAttribute("edge"):
-                # cell is a network connection
-                escaped_json_str = cell.getAttribute("value")
-                json_str = escaped_json_str.replace('&quot;', '"')
-                attributes = json.loads(json_str)
-                tagged = attributes['tagged']
-                interface = None
-                network = None
-                src = cell.getAttribute("source")
-                tgt = cell.getAttribute("target")
-                if src in parent_nets:
-                    # src is a network port
-                    network = networks[parent_nets[src]]
-                    if tgt in untagged_ints and not tagged:
-                        raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
-                    interface = interfaces[tgt]
-                    untagged_ints[tgt] = True
-                else:
-                    network = networks[parent_nets[tgt]]
-                    if src in untagged_ints and not tagged:
-                        raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
-                    interface = interfaces[src]
-                    untagged_ints[src] = True
-                interface['networks'].append({"network": network, "tagged": tagged})
-
-            elif "network" in cellId:  # cell is a network
-                escaped_json_str = cell.getAttribute("value")
-                json_str = escaped_json_str.replace('&quot;', '"')
-                net_info = json.loads(json_str)
-                nid = net_info['vlan_id']
-                public = net_info['public']
-                try:
-                    int_netid = int(nid)
-                    assert public or int_netid > 1, "Net id is 1 or lower"
-                    assert int_netid < 4095, "Net id is 4095 or greater"
-                except Exception:
-                    raise InvalidVlanConfigurationException("VLAN ID is not an integer more than 1 and less than 4095")
-                if nid in netids:
-                    raise NetworkExistsException("Non unique network id found")
-                else:
-                    pass
-                network = {"name": net_info['name'], "vlan": net_info['vlan_id'], "public": public}
-                netids[net_info['vlan_id']] = True
-                networks[cellId] = network
-
-            elif "host" in cellId:  # cell is a host/machine
-                # TODO gather host info
-                cell_json_str = cell.getAttribute("value")
-                cell_json = json.loads(cell_json_str)
-                host = {"interfaces": [], "name": cellId, "profile_name": cell_json['name']}
-                hosts[cellId] = host
-
-            elif cell.hasAttribute("parent"):
-                parentId = cell.getAttribute('parent')
-                if "network" in parentId:
-                    parent_nets[cellId] = parentId
-                elif "host" in parentId:
-                    # TODO gather iface info
-                    cell_json_str = cell.getAttribute("value")
-                    cell_json = json.loads(cell_json_str)
-                    iface = {"name": cellId, "networks": [], "profile_name": cell_json['name']}
-                    hosts[parentId]['interfaces'].append(cellId)
-                    interfaces[cellId] = iface
-        return hosts, interfaces
+                connections[cellId] = cell
+
+            elif "network" in group:
+                networks[cellId] = cell
+
+            elif "host" in group:
+                hosts[cellId] = cell
+
+            elif "host" in parentGroup:
+                interfaces[cellId] = cell
+
+            # make network ports also map to thier network
+            elif "network" in parentGroup:
+                network_ports[cellId] = cell.getAttribute("parent")  # maps port ID to net ID
+
+        return connections, networks, hosts, interfaces, network_ports
+
+    # serialize and deserialize xml from mxGraph
+    def parseXml(self, xmlString):
+        networks = {}  # maps net name to network object
+        hosts = {}  # cotains id -> hosts, each containing interfaces, referencing networks
+        interfaces = {}  # maps id -> interface
+        untagged_ifaces = set()  # used to check vlan config
+        network_names = set()  # used to check network names
+        xml_connections, xml_nets, xml_hosts, xml_ifaces, xml_ports = self.decomposeXml(xmlString)
+
+        # parse Hosts
+        for cellId, cell in xml_hosts.items():
+            cell_json_str = cell.getAttribute("value")
+            cell_json = json.loads(cell_json_str)
+            host = {"interfaces": [], "name": cellId, "profile_name": cell_json['name']}
+            hosts[cellId] = host
+
+        # parse networks
+        for cellId, cell in xml_nets.items():
+            escaped_json_str = cell.getAttribute("value")
+            json_str = escaped_json_str.replace('&quot;', '"')
+            net_info = json.loads(json_str)
+            net_name = net_info['name']
+            public = net_info['public']
+            if net_name in network_names:
+                raise NetworkExistsException("Non unique network name found")
+            network = {"name": net_name, "public": public, "id": cellId}
+            networks[cellId] = network
+            network_names.add(net_name)
+
+        # parse interfaces
+        for cellId, cell in xml_ifaces.items():
+            parentId = cell.getAttribute('parent')
+            cell_json_str = cell.getAttribute("value")
+            cell_json = json.loads(cell_json_str)
+            iface = {"name": cellId, "connections": [], "profile_name": cell_json['name']}
+            hosts[parentId]['interfaces'].append(cellId)
+            interfaces[cellId] = iface
+
+        # parse connections
+        for cellId, cell in xml_connections.items():
+            escaped_json_str = cell.getAttribute("value")
+            json_str = escaped_json_str.replace('&quot;', '"')
+            attributes = json.loads(json_str)
+            tagged = attributes['tagged']
+            interface = None
+            network = None
+            src = cell.getAttribute("source")
+            tgt = cell.getAttribute("target")
+            if src in interfaces:
+                interface = interfaces[src]
+                network = networks[xml_ports[tgt]]
+            else:
+                interface = interfaces[tgt]
+                network = networks[xml_ports[src]]
+
+            if not tagged:
+                if interface['name'] in untagged_ifaces:
+                    raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
+                untagged_ifaces.add(interface['name'])
+
+            # add connection to interface
+            interface['connections'].append({"tagged": tagged, "network": network['id']})
+
+        return hosts, interfaces, networks
 
 
 class Resource_Meta_Info(WorkflowStep):