1 ##############################################################################
2 # Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
11 from django.shortcuts import render
12 from django.forms import formset_factory
13 from django.conf import settings
17 from xml.dom import minidom
19 from workflow.models import WorkflowStep
20 from account.models import Lab
21 from workflow.forms import (
22 HardwareDefinitionForm,
23 NetworkDefinitionForm,
27 from resource_inventory.models import (
28 GenericResourceBundle,
36 from dashboard.exceptions import (
37 InvalidVlanConfigurationException,
38 NetworkExistsException,
39 InvalidHostnameException,
40 NonUniqueHostnameException,
41 ResourceAvailabilityException
45 logger = logging.getLogger(__name__)
48 class Define_Hardware(WorkflowStep):
50 template = 'resource/steps/define_hardware.html'
51 title = "Define Hardware"
52 description = "Choose the type and amount of machines you want"
55 def __init__(self, *args, **kwargs):
57 super().__init__(*args, **kwargs)
59 def get_context(self):
60 context = super(Define_Hardware, self).get_context()
61 context['form'] = self.form or HardwareDefinitionForm()
64 def update_models(self, data):
65 data = data['filter_field']
66 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
67 models['hosts'] = [] # This will always clear existing data when this step changes
68 models['interfaces'] = {}
69 if "bundle" not in models:
70 models['bundle'] = GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER))
71 host_data = data['host']
73 for host_profile_dict in host_data.values():
74 id = host_profile_dict['id']
75 profile = HostProfile.objects.get(id=id)
76 # instantiate genericHost and store in repo
77 for name in host_profile_dict['values'].values():
78 if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})", name):
79 raise InvalidHostnameException("Invalid hostname: '" + name + "'")
81 raise NonUniqueHostnameException("All hosts must have unique names")
83 genericResource = GenericResource(bundle=models['bundle'], name=name)
84 genericHost = GenericHost(profile=profile, resource=genericResource)
85 models['hosts'].append(genericHost)
86 for interface_profile in profile.interfaceprofile.all():
87 genericInterface = GenericInterface(profile=interface_profile, host=genericHost)
88 if genericHost.resource.name not in models['interfaces']:
89 models['interfaces'][genericHost.resource.name] = []
90 models['interfaces'][genericHost.resource.name].append(genericInterface)
92 # add selected lab to models
93 for lab_dict in data['lab'].values():
94 if lab_dict['selected']:
95 models['bundle'].lab = Lab.objects.get(lab_user__id=lab_dict['id'])
96 break # if somehow we get two 'true' labs, we only use one
99 self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
101 def update_confirmation(self):
102 confirm = self.repo_get(self.repo.CONFIRMATION, {})
103 if "resource" not in confirm:
104 confirm['resource'] = {}
105 confirm['resource']['hosts'] = []
106 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {"hosts": []})
107 for host in models['hosts']:
108 host_dict = {"name": host.resource.name, "profile": host.profile.name}
109 confirm['resource']['hosts'].append(host_dict)
111 confirm['resource']['lab'] = models['lab'].lab_user.username
112 self.repo_put(self.repo.CONFIRMATION, confirm)
114 def post_render(self, request):
116 self.form = HardwareDefinitionForm(request.POST)
117 if self.form.is_valid():
118 self.update_models(self.form.cleaned_data)
119 self.update_confirmation()
120 self.set_valid("Step Completed")
122 self.set_invalid("Please complete the fields highlighted in red to continue")
123 except Exception as e:
124 self.set_invalid(str(e))
125 self.context = self.get_context()
126 return render(request, self.template, self.context)
129 class Define_Nets(WorkflowStep):
130 template = 'resource/steps/pod_definition.html'
131 title = "Define Networks"
132 description = "Use the tool below to draw the network topology of your POD"
133 short_title = "networking"
134 form = NetworkDefinitionForm
137 vlans = self.repo_get(self.repo.VLANS)
140 # try to grab some vlans from lab
141 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
142 if "bundle" not in models:
144 lab = models['bundle'].lab
145 if lab is None or lab.vlan_manager is None:
148 vlans = lab.vlan_manager.get_vlan(count=lab.vlan_manager.block_size)
149 self.repo_put(self.repo.VLANS, vlans)
154 def get_context(self):
155 # TODO: render *primarily* on hosts in repo models
156 context = super(Define_Nets, self).get_context()
157 context['form'] = NetworkDefinitionForm()
159 context['hosts'] = []
160 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
161 vlans = self.get_vlans()
163 context['vlans'] = vlans
164 hosts = models.get("hosts", [])
165 hostlist = self.repo_get(self.repo.GRB_LAST_HOSTLIST, None)
168 context['debug'] = settings.DEBUG
169 context['added_hosts'] = []
170 if hostlist is not None:
172 for host in models['hosts']:
173 intcount = host.profile.interfaceprofile.count()
174 new_hostlist.append(str(host.resource.name) + "*" + str(host.profile) + "&" + str(intcount))
175 context['removed_hosts'] = list(set(hostlist) - set(new_hostlist))
176 added_list = list(set(new_hostlist) - set(hostlist))
177 for hoststr in added_list:
178 key = hoststr.split("*")[0]
179 added_dict[key] = hoststr
180 for generic_host in hosts:
181 host_profile = generic_host.profile
183 host['id'] = generic_host.resource.name
184 host['interfaces'] = []
185 for iface in host_profile.interfaceprofile.all():
186 host['interfaces'].append(
189 "description": "speed: " + str(iface.speed) + "M\ntype: " + iface.nic_type
192 host['value'] = {"name": generic_host.resource.name}
193 host['value']['description'] = generic_host.profile.description
194 context['hosts'].append(json.dumps(host))
195 if host['id'] in added_dict:
196 context['added_hosts'].append(json.dumps(host))
197 bundle = models.get("bundle", False)
198 if bundle and bundle.xml:
199 context['xml'] = bundle.xml
201 context['xml'] = False
208 def post_render(self, request):
209 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
210 if 'hosts' in models:
212 for host in models['hosts']:
213 intcount = host.profile.interfaceprofile.count()
214 hostlist.append(str(host.resource.name) + "*" + str(host.profile) + "&" + str(intcount))
215 self.repo_put(self.repo.GRB_LAST_HOSTLIST, hostlist)
217 xmlData = request.POST.get("xml")
218 self.updateModels(xmlData)
219 # update model with xml
220 self.set_valid("Networks applied successfully")
221 except ResourceAvailabilityException:
222 self.set_invalid("Public network not availble")
223 except Exception as e:
224 self.set_invalid("An error occurred when applying networks: " + str(e))
225 return self.render(request)
227 def updateModels(self, xmlData):
228 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
229 models["connections"] = {}
230 models['networks'] = {}
231 given_hosts, interfaces, networks = self.parseXml(xmlData)
232 existing_host_list = models.get("hosts", [])
233 existing_hosts = {} # maps id to host
234 for host in existing_host_list:
235 existing_hosts[host.resource.name] = host
237 bundle = models.get("bundle", GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER)))
239 for net_id, net in networks.items():
241 network.name = net['name']
242 network.bundle = bundle
243 network.is_public = net['public']
244 models['networks'][net_id] = network
246 for hostid, given_host in given_hosts.items():
247 existing_host = existing_hosts[hostid[5:]]
249 for ifaceId in given_host['interfaces']:
250 iface = interfaces[ifaceId]
251 if existing_host.resource.name not in models['connections']:
252 models['connections'][existing_host.resource.name] = {}
253 models['connections'][existing_host.resource.name][iface['profile_name']] = []
254 for connection in iface['connections']:
255 network_id = connection['network']
256 net = models['networks'][network_id]
257 connection = NetworkConnection(vlan_is_tagged=connection['tagged'], network=net)
258 models['connections'][existing_host.resource.name][iface['profile_name']].append(connection)
260 self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
262 def decomposeXml(self, xmlString):
264 This function takes in an xml doc from our front end
265 and returns dictionaries that map cellIds to the xml
266 nodes themselves. There is no unpacking of the
267 xml objects, just grouping and organizing
276 xmlDom = minidom.parseString(xmlString)
277 root = xmlDom.documentElement.firstChild
278 for cell in root.childNodes:
279 cellId = cell.getAttribute('id')
280 group = cellId.split("_")[0]
281 parentGroup = cell.getAttribute("parent").split("_")[0]
282 # place cell into correct group
284 if cell.getAttribute("edge"):
285 connections[cellId] = cell
287 elif "network" in group:
288 networks[cellId] = cell
290 elif "host" in group:
293 elif "host" in parentGroup:
294 interfaces[cellId] = cell
296 # make network ports also map to thier network
297 elif "network" in parentGroup:
298 network_ports[cellId] = cell.getAttribute("parent") # maps port ID to net ID
300 return connections, networks, hosts, interfaces, network_ports
302 # serialize and deserialize xml from mxGraph
303 def parseXml(self, xmlString):
304 networks = {} # maps net name to network object
305 hosts = {} # cotains id -> hosts, each containing interfaces, referencing networks
306 interfaces = {} # maps id -> interface
307 untagged_ifaces = set() # used to check vlan config
308 network_names = set() # used to check network names
309 xml_connections, xml_nets, xml_hosts, xml_ifaces, xml_ports = self.decomposeXml(xmlString)
312 for cellId, cell in xml_hosts.items():
313 cell_json_str = cell.getAttribute("value")
314 cell_json = json.loads(cell_json_str)
315 host = {"interfaces": [], "name": cellId, "profile_name": cell_json['name']}
319 for cellId, cell in xml_nets.items():
320 escaped_json_str = cell.getAttribute("value")
321 json_str = escaped_json_str.replace('"', '"')
322 net_info = json.loads(json_str)
323 net_name = net_info['name']
324 public = net_info['public']
325 if net_name in network_names:
326 raise NetworkExistsException("Non unique network name found")
327 network = {"name": net_name, "public": public, "id": cellId}
328 networks[cellId] = network
329 network_names.add(net_name)
332 for cellId, cell in xml_ifaces.items():
333 parentId = cell.getAttribute('parent')
334 cell_json_str = cell.getAttribute("value")
335 cell_json = json.loads(cell_json_str)
336 iface = {"name": cellId, "connections": [], "profile_name": cell_json['name']}
337 hosts[parentId]['interfaces'].append(cellId)
338 interfaces[cellId] = iface
341 for cellId, cell in xml_connections.items():
342 escaped_json_str = cell.getAttribute("value")
343 json_str = escaped_json_str.replace('"', '"')
344 attributes = json.loads(json_str)
345 tagged = attributes['tagged']
348 src = cell.getAttribute("source")
349 tgt = cell.getAttribute("target")
350 if src in interfaces:
351 interface = interfaces[src]
352 network = networks[xml_ports[tgt]]
354 interface = interfaces[tgt]
355 network = networks[xml_ports[src]]
358 if interface['name'] in untagged_ifaces:
359 raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
360 untagged_ifaces.add(interface['name'])
362 # add connection to interface
363 interface['connections'].append({"tagged": tagged, "network": network['id']})
365 return hosts, interfaces, networks
368 class Resource_Meta_Info(WorkflowStep):
369 template = 'resource/steps/meta_info.html'
371 description = "Please fill out the rest of the information about your resource"
372 short_title = "pod info"
374 def get_context(self):
375 context = super(Resource_Meta_Info, self).get_context()
378 bundle = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}).get("bundle", False)
379 if bundle and bundle.name:
381 desc = bundle.description
382 context['form'] = ResourceMetaForm(initial={"bundle_name": name, "bundle_description": desc})
385 def post_render(self, request):
386 form = ResourceMetaForm(request.POST)
388 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
389 name = form.cleaned_data['bundle_name']
390 desc = form.cleaned_data['bundle_description']
391 bundle = models.get("bundle", GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER)))
393 bundle.description = desc
394 models['bundle'] = bundle
395 self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
396 confirm = self.repo_get(self.repo.CONFIRMATION)
397 if "resource" not in confirm:
398 confirm['resource'] = {}
399 confirm_info = confirm['resource']
400 confirm_info["name"] = name
403 tmp = tmp[:60] + "..."
404 confirm_info["description"] = tmp
405 self.repo_put(self.repo.CONFIRMATION, confirm)
406 self.set_valid("Step Completed")
409 self.set_invalid("Please correct the fields highlighted in red to continue")
411 return self.render(request)
414 class Host_Meta_Info(WorkflowStep):
415 template = "resource/steps/host_info.html"
417 description = "We need a little bit of information about your chosen machines"
418 short_title = "host info"
420 def __init__(self, *args, **kwargs):
421 super(Host_Meta_Info, self).__init__(*args, **kwargs)
422 self.formset = formset_factory(GenericHostMetaForm, extra=0)
424 def get_context(self):
425 context = super(Host_Meta_Info, self).get_context()
426 GenericHostFormset = self.formset
427 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
429 if "hosts" not in models:
430 context['error'] = "Please go back and select your hosts"
432 for host in models['hosts']:
433 profile = host.profile.name
434 name = host.resource.name
437 initial_data.append({"host_profile": profile, "host_name": name})
438 context['formset'] = GenericHostFormset(initial=initial_data)
441 def post_render(self, request):
442 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
443 if 'hosts' not in models:
445 hosts = models['hosts']
448 GenericHostFormset = self.formset
449 formset = GenericHostFormset(request.POST)
450 if formset.is_valid():
453 host.resource.name = form.cleaned_data['host_name']
455 confirm_hosts.append({"name": host.resource.name, "profile": host.profile.name})
456 models['hosts'] = hosts
457 self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
458 confirm = self.repo_get(self.repo.CONFIRMATION, {})
459 if "resource" not in confirm:
460 confirm['resource'] = {}
461 confirm['resource']['hosts'] = confirm_hosts
462 self.repo_put(self.repo.CONFIRMATION, confirm)
465 return self.render(request)