diff options
-rw-r--r-- | src/booking/quick_deployer.py | 33 | ||||
-rw-r--r-- | src/resource_inventory/migrations/0009_auto_20190315_1757.py | 73 | ||||
-rw-r--r-- | src/resource_inventory/models.py | 57 | ||||
-rw-r--r-- | src/resource_inventory/resource_manager.py | 78 | ||||
-rw-r--r-- | src/templates/resource/steps/pod_definition.html | 146 | ||||
-rw-r--r-- | src/workflow/models.py | 94 | ||||
-rw-r--r-- | src/workflow/resource_bundle_workflow.py | 214 |
7 files changed, 365 insertions, 330 deletions
diff --git a/src/booking/quick_deployer.py b/src/booking/quick_deployer.py index 8a81d18..f8dc9ff 100644 --- a/src/booking/quick_deployer.py +++ b/src/booking/quick_deployer.py @@ -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 @@ -226,6 +228,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()) @@ -267,18 +283,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 index 0000000..92ed0e9 --- /dev/null +++ b/src/resource_inventory/migrations/0009_auto_20190315_1757.py @@ -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'), + ), + ] diff --git a/src/resource_inventory/models.py b/src/resource_inventory/models.py index 4e3974e..0b7b24c 100644 --- a/src/resource_inventory/models.py +++ b/src/resource_inventory/models.py @@ -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 (_)") @@ -185,14 +191,11 @@ class ResourceBundle(models.Model): return "instance of " + str(self.template) -# 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) @@ -224,6 +227,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) @@ -240,6 +248,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) diff --git a/src/resource_inventory/resource_manager.py b/src/resource_inventory/resource_manager.py index 52b0055..d3d3ed4 100644 --- a/src/resource_inventory/resource_manager.py +++ b/src/resource_inventory/resource_manager.py @@ -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) diff --git a/src/templates/resource/steps/pod_definition.html b/src/templates/resource/steps/pod_definition.html index 8599bb0..2cb6257 100644 --- a/src/templates/resource/steps/pod_definition.html +++ b/src/templates/resource/steps/pod_definition.html @@ -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/"> diff --git a/src/workflow/models.py b/src/workflow/models.py index cdfddef..3784fe1 100644 --- a/src/workflow/models.py +++ b/src/workflow/models.py @@ -203,27 +203,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() @@ -231,7 +210,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 @@ -262,33 +240,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(): @@ -453,6 +406,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: @@ -464,20 +422,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" @@ -534,9 +493,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: @@ -593,30 +549,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] = {} diff --git a/src/workflow/resource_bundle_workflow.py b/src/workflow/resource_bundle_workflow.py index 4858ebe..536187f 100644 --- a/src/workflow/resource_bundle_workflow.py +++ b/src/workflow/resource_bundle_workflow.py @@ -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('"', '"') - 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('"', '"') - 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('"', '"') + 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('"', '"') + 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): |