summaryrefslogtreecommitdiffstats
path: root/dashboard/src/workflow/resource_bundle_workflow.py
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard/src/workflow/resource_bundle_workflow.py')
-rw-r--r--dashboard/src/workflow/resource_bundle_workflow.py387
1 files changed, 199 insertions, 188 deletions
diff --git a/dashboard/src/workflow/resource_bundle_workflow.py b/dashboard/src/workflow/resource_bundle_workflow.py
index 712c92b..06737d2 100644
--- a/dashboard/src/workflow/resource_bundle_workflow.py
+++ b/dashboard/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,
@@ -50,65 +52,47 @@ class Define_Hardware(WorkflowStep):
description = "Choose the type and amount of machines you want"
short_title = "hosts"
+ def __init__(self, *args, **kwargs):
+ self.form = None
+ super().__init__(*args, **kwargs)
+
def get_context(self):
context = super(Define_Hardware, self).get_context()
- selection_data = {"hosts": {}, "labs": {}}
- models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
- hosts = models.get("hosts", [])
- for host in hosts:
- profile_id = "host_" + str(host.profile.id)
- if profile_id not in selection_data['hosts']:
- selection_data['hosts'][profile_id] = []
- selection_data['hosts'][profile_id].append({"host_name": host.resource.name, "class": profile_id})
-
- if models.get("bundle", GenericResourceBundle()).lab:
- selection_data['labs'] = {"lab_" + str(models.get("bundle").lab.lab_user.id): "true"}
-
- form = HardwareDefinitionForm(
- selection_data=selection_data
- )
- context['form'] = form
+ context['form'] = self.form or HardwareDefinitionForm()
return context
- def render(self, request):
- self.context = self.get_context()
- return render(request, self.template, self.context)
-
def update_models(self, data):
- data = json.loads(data['filter_field'])
+ data = data['filter_field']
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
models['hosts'] = [] # This will always clear existing data when this step changes
models['interfaces'] = {}
if "bundle" not in models:
models['bundle'] = GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER))
- host_data = data['hosts']
+ host_data = data['host']
names = {}
- for host_dict in host_data:
- id = host_dict['class']
- # bit of formatting
- id = int(id.split("_")[-1])
+ for host_profile_dict in host_data.values():
+ id = host_profile_dict['id']
profile = HostProfile.objects.get(id=id)
# instantiate genericHost and store in repo
- name = host_dict['host_name']
- if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})", name):
- raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
- if name in names:
- raise NonUniqueHostnameException("All hosts must have unique names")
- names[name] = True
- genericResource = GenericResource(bundle=models['bundle'], name=name)
- genericHost = GenericHost(profile=profile, resource=genericResource)
- models['hosts'].append(genericHost)
- for interface_profile in profile.interfaceprofile.all():
- genericInterface = GenericInterface(profile=interface_profile, host=genericHost)
- if genericHost.resource.name not in models['interfaces']:
- models['interfaces'][genericHost.resource.name] = []
- models['interfaces'][genericHost.resource.name].append(genericInterface)
+ 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
+ genericResource = GenericResource(bundle=models['bundle'], name=name)
+ genericHost = GenericHost(profile=profile, resource=genericResource)
+ models['hosts'].append(genericHost)
+ for interface_profile in profile.interfaceprofile.all():
+ genericInterface = GenericInterface(profile=interface_profile, host=genericHost)
+ if genericHost.resource.name not in models['interfaces']:
+ models['interfaces'][genericHost.resource.name] = []
+ models['interfaces'][genericHost.resource.name].append(genericInterface)
# add selected lab to models
- for lab_dict in data['labs']:
- if list(lab_dict.values())[0]: # True for lab the user selected
- lab_user_id = int(list(lab_dict.keys())[0].split("_")[-1])
- models['bundle'].lab = Lab.objects.get(lab_user__id=lab_user_id)
+ for lab_dict in data['lab'].values():
+ if lab_dict['selected']:
+ models['bundle'].lab = Lab.objects.get(lab_user__id=lab_dict['id'])
break # if somehow we get two 'true' labs, we only use one
# return to repo
@@ -133,12 +117,11 @@ class Define_Hardware(WorkflowStep):
if self.form.is_valid():
self.update_models(self.form.cleaned_data)
self.update_confirmation()
- self.metastep.set_valid("Step Completed")
+ self.set_valid("Step Completed")
else:
- self.metastep.set_invalid("Please complete the fields highlighted in red to continue")
- pass
+ self.set_invalid("Please complete the fields highlighted in red to continue")
except Exception as e:
- self.metastep.set_invalid(str(e))
+ self.set_invalid(str(e))
self.context = self.get_context()
return render(request, self.template, self.context)
@@ -168,53 +151,55 @@ class Define_Nets(WorkflowStep):
except Exception:
return None
+ def make_mx_host_dict(self, generic_host):
+ host = {
+ 'id': generic_host.resource.name,
+ 'interfaces': [],
+ 'value': {
+ "name": generic_host.resource.name,
+ "description": generic_host.profile.description
+ }
+ }
+ for iface in generic_host.profile.interfaceprofile.all():
+ host['interfaces'].append({
+ "name": iface.name,
+ "description": "speed: " + str(iface.speed) + "M\ntype: " + iface.nic_type
+ })
+ return host
+
def get_context(self):
- # TODO: render *primarily* on hosts in repo models
context = super(Define_Nets, self).get_context()
- context['form'] = NetworkDefinitionForm()
+ context.update({
+ 'form': NetworkDefinitionForm(),
+ 'debug': settings.DEBUG,
+ 'hosts': [],
+ 'added_hosts': [],
+ 'removed_hosts': []
+ })
+ vlans = self.get_vlans()
+ if vlans:
+ context['vlans'] = vlans
try:
- context['hosts'] = []
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
- vlans = self.get_vlans()
- if vlans:
- context['vlans'] = vlans
hosts = models.get("hosts", [])
- hostlist = self.repo_get(self.repo.GRB_LAST_HOSTLIST, None)
- added_list = []
- added_dict = {}
- context['added_hosts'] = []
- if hostlist is not None:
- new_hostlist = []
- for host in models['hosts']:
- intcount = host.profile.interfaceprofile.count()
- new_hostlist.append(str(host.resource.name) + "*" + str(host.profile) + "&" + str(intcount))
- context['removed_hosts'] = list(set(hostlist) - set(new_hostlist))
- added_list = list(set(new_hostlist) - set(hostlist))
- for hoststr in added_list:
- key = hoststr.split("*")[0]
- added_dict[key] = hoststr
+ # calculate if the selected hosts have changed
+ added_hosts = set()
+ host_set = set(self.repo_get(self.repo.GRB_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_profile = generic_host.profile
- host = {}
- host['id'] = generic_host.resource.name
- host['interfaces'] = []
- for iface in host_profile.interfaceprofile.all():
- host['interfaces'].append(
- {
- "name": iface.name,
- "description": "speed: " + str(iface.speed) + "M\ntype: " + iface.nic_type
- }
- )
- host['value'] = {"name": generic_host.resource.name}
- host['value']['description'] = generic_host.profile.description
- context['hosts'].append(json.dumps(host))
- if host['id'] in added_dict:
- context['added_hosts'].append(json.dumps(host))
+ 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 and bundle.xml:
- context['xml'] = bundle.xml
- else:
- context['xml'] = False
+ if bundle:
+ context['xml'] = bundle.xml or False
except Exception:
pass
@@ -224,27 +209,24 @@ class Define_Nets(WorkflowStep):
def post_render(self, request):
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
if 'hosts' in models:
- hostlist = []
- for host in models['hosts']:
- intcount = host.profile.interfaceprofile.count()
- hostlist.append(str(host.resource.name) + "*" + str(host.profile) + "&" + str(intcount))
- self.repo_put(self.repo.GRB_LAST_HOSTLIST, hostlist)
+ host_set = set([h.resource.name + "*" + h.profile.name for h in models['hosts']])
+ self.repo_put(self.repo.GRB_LAST_HOSTLIST, host_set)
try:
xmlData = request.POST.get("xml")
self.updateModels(xmlData)
# update model with xml
- self.metastep.set_valid("Networks applied successfully")
+ self.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")
+ self.set_invalid("Public network not availble")
+ except Exception as e:
+ self.set_invalid("An error occurred when applying networks: " + str(e))
return self.render(request)
def updateModels(self, xmlData):
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
- 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:
@@ -252,104 +234,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('&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):
@@ -390,10 +401,10 @@ class Resource_Meta_Info(WorkflowStep):
tmp = tmp[:60] + "..."
confirm_info["description"] = tmp
self.repo_put(self.repo.CONFIRMATION, confirm)
- self.metastep.set_valid("Step Completed")
+ self.set_valid("Step Completed")
else:
- self.metastep.set_invalid("Please correct the fields highlighted in red to continue")
+ self.set_invalid("Please correct the fields highlighted in red to continue")
pass
return self.render(request)