aboutsummaryrefslogtreecommitdiffstats
path: root/src/workflow/resource_bundle_workflow.py
diff options
context:
space:
mode:
authorParker Berberian <pberberian@iol.unh.edu>2018-10-10 16:06:47 -0400
committerParker Berberian <pberberian@iol.unh.edu>2018-10-15 13:16:11 -0400
commit1f3a770d2547848590f39e9d9b9bdffeb94eec14 (patch)
tree97222e5facd1a242d951c38482315057b5790d51 /src/workflow/resource_bundle_workflow.py
parent6d4019e59eda897384e9c00d1daf8b2ce87d128f (diff)
Lab as a Service 2.0
See changes here: https://wiki.opnfv.org/display/INF/Pharos+Laas Change-Id: I59ada5f98e70a28d7f8c14eab3239597e236ca26 Signed-off-by: Sawyer Bergeron <sbergeron@iol.unh.edu> Signed-off-by: Parker Berberian <pberberian@iol.unh.edu>
Diffstat (limited to 'src/workflow/resource_bundle_workflow.py')
-rw-r--r--src/workflow/resource_bundle_workflow.py427
1 files changed, 427 insertions, 0 deletions
diff --git a/src/workflow/resource_bundle_workflow.py b/src/workflow/resource_bundle_workflow.py
new file mode 100644
index 0000000..63ce3bd
--- /dev/null
+++ b/src/workflow/resource_bundle_workflow.py
@@ -0,0 +1,427 @@
+##############################################################################
+# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from django.shortcuts import render
+from django.forms import formset_factory
+
+import json
+import re
+from xml.dom import minidom
+
+from workflow.models import WorkflowStep
+from workflow.forms import *
+from resource_inventory.models import *
+from dashboard.exceptions import *
+
+import logging
+logger = logging.getLogger(__name__)
+
+
+class Define_Hardware(WorkflowStep):
+
+ template = 'resource/steps/define_hardware.html'
+ title = "Define Hardware"
+ description = "Choose the type and amount of machines you want"
+ short_title = "hosts"
+ 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
+ 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'])
+ 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']
+ names = {}
+ for host_dict in host_data:
+ id = host_dict['class']
+ # bit of formatting
+ id = int(id.split("_")[-1])
+ 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)
+
+ # 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)
+ break # if somehow we get two 'true' labs, we only use one
+
+ # return to repo
+ self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
+
+
+ def update_confirmation(self):
+ confirm = self.repo_get(self.repo.CONFIRMATION, {})
+ if "resource" not in confirm:
+ confirm['resource'] = {}
+ confirm['resource']['hosts'] = []
+ models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {"hosts": []})
+ for host in models['hosts']:
+ host_dict = {"name": host.resource.name, "profile": host.profile.name}
+ confirm['resource']['hosts'].append(host_dict)
+ if "lab" in models:
+ confirm['resource']['lab'] = models['lab'].lab_user.username
+ self.repo_put(self.repo.CONFIRMATION, confirm)
+
+
+ def post_render(self, request):
+ try:
+ self.form = HardwareDefinitionForm(request.POST)
+ if self.form.is_valid():
+ self.update_models(self.form.cleaned_data)
+ self.update_confirmation()
+ self.metastep.set_valid("Step Completed")
+ else:
+ self.metastep.set_invalid("Please complete the fields highlighted in red to continue")
+ pass
+ except Exception as e:
+ self.metastep.set_invalid(str(e))
+ self.context = self.get_context()
+ return render(request, self.template, self.context)
+
+class Define_Nets(WorkflowStep):
+ template = 'resource/steps/pod_definition.html'
+ title = "Define Networks"
+ description = "Use the tool below to draw the network topology of your POD"
+ short_title = "networking"
+ form = NetworkDefinitionForm
+
+ def get_vlans(self):
+ vlans = self.repo_get(self.repo.VLANS)
+ if vlans:
+ return vlans
+ # try to grab some vlans from lab
+ models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
+ if "bundle" not in models:
+ return None
+ lab = models['bundle'].lab
+ if lab is None or lab.vlan_manager is None:
+ return None
+ try:
+ vlans = lab.vlan_manager.get_vlan(count=lab.vlan_manager.block_size)
+ self.repo_put(self.repo.VLANS, vlans)
+ return vlans
+ except Exception as e:
+ return None
+
+ def get_context(self):
+ # TODO: render *primarily* on hosts in repo models
+ context = super(Define_Nets, self).get_context()
+ context['form'] = NetworkDefinitionForm()
+ 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 not hostlist is 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
+ 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))
+ bundle = models.get("bundle", False)
+ if bundle and bundle.xml:
+ context['xml'] = bundle.xml
+ else:
+ context['xml'] = False
+
+ except Exception as e:
+ pass
+ return context
+
+ 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)
+ try:
+ xmlData = request.POST.get("xml")
+ self.updateModels(xmlData)
+ # update model with xml
+ self.metastep.set_valid("Networks applied successfully")
+ except Exception as e:
+ self.metastep.set_invalid("An error occurred when applying networks")
+ 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
+ existing_host_list = models.get("hosts", [])
+ existing_hosts = {} # maps id to host
+ for host in existing_host_list:
+ existing_hosts[host.resource.name] = host
+
+ bundle = models.get("bundle", GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER)))
+
+ for hostid, given_host in given_hosts.items():
+ existing_host = existing_hosts[hostid[5:]]
+
+ for ifaceId in given_host['interfaces']:
+ iface = interfaces[ifaceId]
+ iface_profile = existing_host.profile.interfaceprofile.get(name=iface['profile_name'])
+ 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:
+ 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)
+ 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
+ xmlDom = minidom.parseString(xmlString)
+ root = xmlDom.documentElement.firstChild
+ connections = []
+ netids = {}
+ untagged_ints = {}
+ for cell in root.childNodes:
+ cellId = cell.getAttribute('id')
+
+ 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 tagged==False:
+ 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 tagged==False:
+ 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 as e:
+ 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
+
+
+class Resource_Meta_Info(WorkflowStep):
+ template = 'resource/steps/meta_info.html'
+ title = "Extra Info"
+ description = "Please fill out the rest of the information about your resource"
+ short_title = "pod info"
+
+ def get_context(self):
+ context = super(Resource_Meta_Info, self).get_context()
+ name = ""
+ desc = ""
+ bundle = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}).get("bundle", False)
+ if bundle and bundle.name:
+ name = bundle.name
+ desc = bundle.description
+ context['form'] = ResourceMetaForm(initial={"bundle_name": name, "bundle_description": desc})
+ return context
+
+ def post_render(self, request):
+ form = ResourceMetaForm(request.POST)
+ if form.is_valid():
+ models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
+ name = form.cleaned_data['bundle_name']
+ desc = form.cleaned_data['bundle_description']
+ bundle = models.get("bundle", GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER)))
+ bundle.name = name
+ bundle.description = desc
+ models['bundle'] = bundle
+ self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
+ confirm = self.repo_get(self.repo.CONFIRMATION)
+ if "resource" not in confirm:
+ confirm['resource'] = {}
+ confirm_info = confirm['resource']
+ confirm_info["name"] = name
+ tmp = desc
+ if len(tmp) > 60:
+ tmp = tmp[:60] + "..."
+ confirm_info["description"] = tmp
+ self.repo_put(self.repo.CONFIRMATION, confirm)
+ self.metastep.set_valid("Step Completed")
+
+ else:
+ self.metastep.set_invalid("Please correct the fields highlighted in red to continue")
+ pass
+ return self.render(request)
+
+
+class Host_Meta_Info(WorkflowStep):
+ template = "resource/steps/host_info.html"
+ title = "Host Info"
+ description = "We need a little bit of information about your chosen machines"
+ short_title = "host info"
+
+ def __init__(self, *args, **kwargs):
+ super(Host_Meta_Info, self).__init__(*args, **kwargs)
+ self.formset = formset_factory(GenericHostMetaForm, extra=0)
+
+ def get_context(self):
+ context = super(Host_Meta_Info, self).get_context()
+ GenericHostFormset = self.formset
+ models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
+ initial_data = []
+ if "hosts" not in models:
+ context['error'] = "Please go back and select your hosts"
+ else:
+ for host in models['hosts']:
+ profile = host.profile.name
+ name = host.resource.name
+ if not name:
+ name = ""
+ initial_data.append({"host_profile": profile, "host_name": name})
+ context['formset'] = GenericHostFormset(initial=initial_data)
+ return context
+
+ def post_render(self, request):
+ models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
+ if 'hosts' not in models:
+ models['hosts'] = []
+ hosts = models['hosts']
+ i = 0
+ confirm_hosts = []
+ GenericHostFormset = self.formset
+ formset = GenericHostFormset(request.POST)
+ if formset.is_valid():
+ for form in formset:
+ host = hosts[i]
+ host.resource.name = form.cleaned_data['host_name']
+ i += 1
+ confirm_hosts.append({"name": host.resource.name, "profile": host.profile.name})
+ models['hosts'] = hosts
+ self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
+ confirm = self.repo_get(self.repo.CONFIRMATION, {})
+ if "resource" not in confirm:
+ confirm['resource'] = {}
+ confirm['resource']['hosts'] = confirm_hosts
+ self.repo_put(self.repo.CONFIRMATION, confirm)
+ else:
+ pass
+ return self.render(request)