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
16 from xml.dom import minidom
18 from workflow.models import WorkflowStep
19 from workflow.forms import *
20 from resource_inventory.models import *
21 from dashboard.exceptions import *
24 logger = logging.getLogger(__name__)
27 class Define_Hardware(WorkflowStep):
29 template = 'resource/steps/define_hardware.html'
30 title = "Define Hardware"
31 description = "Choose the type and amount of machines you want"
33 def get_context(self):
34 context = super(Define_Hardware, self).get_context()
35 selection_data = {"hosts": {}, "labs": {}}
36 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
37 hosts = models.get("hosts", [])
39 profile_id = "host_" + str(host.profile.id)
40 if profile_id not in selection_data['hosts']:
41 selection_data['hosts'][profile_id] = []
42 selection_data['hosts'][profile_id].append({"host_name": host.resource.name, "class": profile_id})
44 if models.get("bundle", GenericResourceBundle()).lab:
45 selection_data['labs'] = {"lab_" + str(models.get("bundle").lab.lab_user.id): "true"}
47 form = HardwareDefinitionForm(
48 selection_data=selection_data
50 context['form'] = form
53 def render(self, request):
54 self.context = self.get_context()
55 return render(request, self.template, self.context)
58 def update_models(self, data):
59 data = json.loads(data['filter_field'])
60 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
61 models['hosts'] = [] # This will always clear existing data when this step changes
62 models['interfaces'] = {}
63 if "bundle" not in models:
64 models['bundle'] = GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER))
65 host_data = data['hosts']
67 for host_dict in host_data:
68 id = host_dict['class']
70 id = int(id.split("_")[-1])
71 profile = HostProfile.objects.get(id=id)
72 # instantiate genericHost and store in repo
73 name = host_dict['host_name']
74 if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})", name):
75 raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
77 raise NonUniqueHostnameException("All hosts must have unique names")
79 genericResource = GenericResource(bundle=models['bundle'], name=name)
80 genericHost = GenericHost(profile=profile, resource=genericResource)
81 models['hosts'].append(genericHost)
82 for interface_profile in profile.interfaceprofile.all():
83 genericInterface = GenericInterface(profile=interface_profile, host=genericHost)
84 if genericHost.resource.name not in models['interfaces']:
85 models['interfaces'][genericHost.resource.name] = []
86 models['interfaces'][genericHost.resource.name].append(genericInterface)
88 # add selected lab to models
89 for lab_dict in data['labs']:
90 if list(lab_dict.values())[0]: # True for lab the user selected
91 lab_user_id = int(list(lab_dict.keys())[0].split("_")[-1])
92 models['bundle'].lab = Lab.objects.get(lab_user__id=lab_user_id)
93 break # if somehow we get two 'true' labs, we only use one
96 self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
99 def update_confirmation(self):
100 confirm = self.repo_get(self.repo.CONFIRMATION, {})
101 if "resource" not in confirm:
102 confirm['resource'] = {}
103 confirm['resource']['hosts'] = []
104 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {"hosts": []})
105 for host in models['hosts']:
106 host_dict = {"name": host.resource.name, "profile": host.profile.name}
107 confirm['resource']['hosts'].append(host_dict)
109 confirm['resource']['lab'] = models['lab'].lab_user.username
110 self.repo_put(self.repo.CONFIRMATION, confirm)
113 def post_render(self, request):
115 self.form = HardwareDefinitionForm(request.POST)
116 if self.form.is_valid():
117 self.update_models(self.form.cleaned_data)
118 self.update_confirmation()
119 self.metastep.set_valid("Step Completed")
121 self.metastep.set_invalid("Please complete the fields highlighted in red to continue")
123 except Exception as e:
124 self.metastep.set_invalid(str(e))
125 self.context = self.get_context()
126 return render(request, self.template, self.context)
128 class Define_Nets(WorkflowStep):
129 template = 'resource/steps/pod_definition.html'
130 title = "Define Networks"
131 description = "Use the tool below to draw the network topology of your POD"
132 short_title = "networking"
133 form = NetworkDefinitionForm
136 vlans = self.repo_get(self.repo.VLANS)
139 # try to grab some vlans from lab
140 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
141 if "bundle" not in models:
143 lab = models['bundle'].lab
144 if lab is None or lab.vlan_manager is None:
147 vlans = lab.vlan_manager.get_vlan(count=lab.vlan_manager.block_size)
148 self.repo_put(self.repo.VLANS, vlans)
150 except Exception as e:
153 def get_context(self):
154 # TODO: render *primarily* on hosts in repo models
155 context = super(Define_Nets, self).get_context()
156 context['form'] = NetworkDefinitionForm()
158 context['hosts'] = []
159 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
160 vlans = self.get_vlans()
162 context['vlans'] = vlans
163 hosts = models.get("hosts", [])
164 hostlist = self.repo_get(self.repo.GRB_LAST_HOSTLIST, None)
167 context['added_hosts'] = []
168 if not hostlist is None:
170 for host in models['hosts']:
171 intcount = host.profile.interfaceprofile.count()
172 new_hostlist.append(str(host.resource.name) + "*" + str(host.profile) + "&" + str(intcount))
173 context['removed_hosts'] = list(set(hostlist) - set(new_hostlist))
174 added_list = list(set(new_hostlist) - set(hostlist))
175 for hoststr in added_list:
176 key = hoststr.split("*")[0]
177 added_dict[key] = hoststr
178 for generic_host in hosts:
179 host_profile = generic_host.profile
181 host['id'] = generic_host.resource.name
182 host['interfaces'] = []
183 for iface in host_profile.interfaceprofile.all():
184 host['interfaces'].append({
186 "description": "speed: " + str(iface.speed) + "M\ntype: " + iface.nic_type})
187 host['value'] = {"name": generic_host.resource.name}
188 host['value']['description'] = generic_host.profile.description
189 context['hosts'].append(json.dumps(host))
190 if host['id'] in added_dict:
191 context['added_hosts'].append(json.dumps(host))
192 bundle = models.get("bundle", False)
193 if bundle and bundle.xml:
194 context['xml'] = bundle.xml
196 context['xml'] = False
198 except Exception as e:
202 def post_render(self, request):
203 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
204 if 'hosts' in models:
206 for host in models['hosts']:
207 intcount = host.profile.interfaceprofile.count()
208 hostlist.append(str(host.resource.name) + "*" + str(host.profile) + "&" + str(intcount))
209 self.repo_put(self.repo.GRB_LAST_HOSTLIST, hostlist)
211 xmlData = request.POST.get("xml")
212 self.updateModels(xmlData)
213 # update model with xml
214 self.metastep.set_valid("Networks applied successfully")
215 except Exception as e:
216 self.metastep.set_invalid("An error occurred when applying networks")
217 return self.render(request)
219 def updateModels(self, xmlData):
220 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
222 given_hosts, interfaces = self.parseXml(xmlData)
223 vlan_manager = models['bundle'].lab.vlan_manager
224 existing_host_list = models.get("hosts", [])
225 existing_hosts = {} # maps id to host
226 for host in existing_host_list:
227 existing_hosts[host.resource.name] = host
229 bundle = models.get("bundle", GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER)))
231 for hostid, given_host in given_hosts.items():
232 existing_host = existing_hosts[hostid[5:]]
234 for ifaceId in given_host['interfaces']:
235 iface = interfaces[ifaceId]
236 iface_profile = existing_host.profile.interfaceprofile.get(name=iface['profile_name'])
237 if existing_host.resource.name not in models['vlans']:
238 models['vlans'][existing_host.resource.name] = {}
239 models['vlans'][existing_host.resource.name][iface['profile_name']] = []
240 for network in iface['networks']:
241 vlan_id = network['network']['vlan']
242 is_public = network['network']['public']
244 vlan_id = vlan_manager.get_public_vlan().vlan
245 vlan = Vlan(vlan_id=vlan_id, tagged=network['tagged'], public=is_public)
246 models['vlans'][existing_host.resource.name][iface['profile_name']].append(vlan)
248 self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
250 # serialize and deserialize xml from mxGraph
251 def parseXml(self, xmlString):
252 parent_nets = {} # map network ports to networks
253 networks = {} # maps net id to network object
254 hosts = {} # cotains id -> hosts, each containing interfaces, referencing networks
255 interfaces = {} # maps id -> interface
256 xmlDom = minidom.parseString(xmlString)
257 root = xmlDom.documentElement.firstChild
261 for cell in root.childNodes:
262 cellId = cell.getAttribute('id')
264 if cell.getAttribute("edge"):
265 #cell is a network connection
266 escaped_json_str = cell.getAttribute("value")
267 json_str = escaped_json_str.replace('"', '"')
268 attributes = json.loads(json_str)
269 tagged = attributes['tagged']
272 src = cell.getAttribute("source")
273 tgt = cell.getAttribute("target")
274 if src in parent_nets:
275 #src is a network port
276 network = networks[parent_nets[src]]
277 if tgt in untagged_ints and tagged==False:
278 raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
279 interface = interfaces[tgt]
280 untagged_ints[tgt] = True
282 network = networks[parent_nets[tgt]]
283 if src in untagged_ints and tagged==False:
284 raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
285 interface = interfaces[src]
286 untagged_ints[src] = True
287 interface['networks'].append({"network": network, "tagged": tagged})
289 elif "network" in cellId: # cell is a network
290 escaped_json_str = cell.getAttribute("value")
291 json_str = escaped_json_str.replace('"', '"')
292 net_info = json.loads(json_str)
293 nid = net_info['vlan_id']
294 public = net_info['public']
297 assert public or int_netid > 1, "Net id is 1 or lower"
298 assert int_netid < 4095, "Net id is 4095 or greater"
299 except Exception as e:
300 raise InvalidVlanConfigurationException("VLAN ID is not an integer more than 1 and less than 4095")
302 raise NetworkExistsException("Non unique network id found")
305 network = {"name": net_info['name'], "vlan": net_info['vlan_id'], "public": public}
306 netids[net_info['vlan_id']] = True
307 networks[cellId] = network
309 elif "host" in cellId: # cell is a host/machine
310 #TODO gather host info
311 cell_json_str = cell.getAttribute("value")
312 cell_json = json.loads(cell_json_str)
313 host = {"interfaces": [], "name": cellId, "profile_name": cell_json['name']}
316 elif cell.hasAttribute("parent"):
317 parentId = cell.getAttribute('parent')
318 if "network" in parentId:
319 parent_nets[cellId] = parentId
320 elif "host" in parentId:
321 #TODO gather iface info
322 cell_json_str = cell.getAttribute("value")
323 cell_json = json.loads(cell_json_str)
324 iface = {"name": cellId, "networks": [], "profile_name": cell_json['name']}
325 hosts[parentId]['interfaces'].append(cellId)
326 interfaces[cellId] = iface
327 return hosts, interfaces
330 class Resource_Meta_Info(WorkflowStep):
331 template = 'resource/steps/meta_info.html'
333 description = "Please fill out the rest of the information about your resource"
334 short_title = "pod info"
336 def get_context(self):
337 context = super(Resource_Meta_Info, self).get_context()
340 bundle = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}).get("bundle", False)
341 if bundle and bundle.name:
343 desc = bundle.description
344 context['form'] = ResourceMetaForm(initial={"bundle_name": name, "bundle_description": desc})
347 def post_render(self, request):
348 form = ResourceMetaForm(request.POST)
350 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
351 name = form.cleaned_data['bundle_name']
352 desc = form.cleaned_data['bundle_description']
353 bundle = models.get("bundle", GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER)))
355 bundle.description = desc
356 models['bundle'] = bundle
357 self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
358 confirm = self.repo_get(self.repo.CONFIRMATION)
359 if "resource" not in confirm:
360 confirm['resource'] = {}
361 confirm_info = confirm['resource']
362 confirm_info["name"] = name
365 tmp = tmp[:60] + "..."
366 confirm_info["description"] = tmp
367 self.repo_put(self.repo.CONFIRMATION, confirm)
368 self.metastep.set_valid("Step Completed")
371 self.metastep.set_invalid("Please correct the fields highlighted in red to continue")
373 return self.render(request)
376 class Host_Meta_Info(WorkflowStep):
377 template = "resource/steps/host_info.html"
379 description = "We need a little bit of information about your chosen machines"
380 short_title = "host info"
382 def __init__(self, *args, **kwargs):
383 super(Host_Meta_Info, self).__init__(*args, **kwargs)
384 self.formset = formset_factory(GenericHostMetaForm, extra=0)
386 def get_context(self):
387 context = super(Host_Meta_Info, self).get_context()
388 GenericHostFormset = self.formset
389 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
391 if "hosts" not in models:
392 context['error'] = "Please go back and select your hosts"
394 for host in models['hosts']:
395 profile = host.profile.name
396 name = host.resource.name
399 initial_data.append({"host_profile": profile, "host_name": name})
400 context['formset'] = GenericHostFormset(initial=initial_data)
403 def post_render(self, request):
404 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
405 if 'hosts' not in models:
407 hosts = models['hosts']
410 GenericHostFormset = self.formset
411 formset = GenericHostFormset(request.POST)
412 if formset.is_valid():
415 host.resource.name = form.cleaned_data['host_name']
417 confirm_hosts.append({"name": host.resource.name, "profile": host.profile.name})
418 models['hosts'] = hosts
419 self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
420 confirm = self.repo_get(self.repo.CONFIRMATION, {})
421 if "resource" not in confirm:
422 confirm['resource'] = {}
423 confirm['resource']['hosts'] = confirm_hosts
424 self.repo_put(self.repo.CONFIRMATION, confirm)
427 return self.render(request)