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 account.models import Lab
20 from workflow.forms import (
21 HardwareDefinitionForm,
22 NetworkDefinitionForm,
26 from resource_inventory.models import (
27 GenericResourceBundle,
34 from dashboard.exceptions import (
35 InvalidVlanConfigurationException,
36 NetworkExistsException,
37 InvalidHostnameException,
38 NonUniqueHostnameException,
39 ResourceAvailabilityException
43 logger = logging.getLogger(__name__)
46 class Define_Hardware(WorkflowStep):
48 template = 'resource/steps/define_hardware.html'
49 title = "Define Hardware"
50 description = "Choose the type and amount of machines you want"
53 def get_context(self):
54 context = super(Define_Hardware, self).get_context()
55 selection_data = {"hosts": {}, "labs": {}}
56 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
57 hosts = models.get("hosts", [])
59 profile_id = "host_" + str(host.profile.id)
60 if profile_id not in selection_data['hosts']:
61 selection_data['hosts'][profile_id] = []
62 selection_data['hosts'][profile_id].append({"host_name": host.resource.name, "class": profile_id})
64 if models.get("bundle", GenericResourceBundle()).lab:
65 selection_data['labs'] = {"lab_" + str(models.get("bundle").lab.lab_user.id): "true"}
67 form = HardwareDefinitionForm(
68 selection_data=selection_data
70 context['form'] = form
73 def render(self, request):
74 self.context = self.get_context()
75 return render(request, self.template, self.context)
77 def update_models(self, data):
78 data = json.loads(data['filter_field'])
79 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
80 models['hosts'] = [] # This will always clear existing data when this step changes
81 models['interfaces'] = {}
82 if "bundle" not in models:
83 models['bundle'] = GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER))
84 host_data = data['hosts']
86 for host_dict in host_data:
87 id = host_dict['class']
89 id = int(id.split("_")[-1])
90 profile = HostProfile.objects.get(id=id)
91 # instantiate genericHost and store in repo
92 name = host_dict['host_name']
93 if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})", name):
94 raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
96 raise NonUniqueHostnameException("All hosts must have unique names")
98 genericResource = GenericResource(bundle=models['bundle'], name=name)
99 genericHost = GenericHost(profile=profile, resource=genericResource)
100 models['hosts'].append(genericHost)
101 for interface_profile in profile.interfaceprofile.all():
102 genericInterface = GenericInterface(profile=interface_profile, host=genericHost)
103 if genericHost.resource.name not in models['interfaces']:
104 models['interfaces'][genericHost.resource.name] = []
105 models['interfaces'][genericHost.resource.name].append(genericInterface)
107 # add selected lab to models
108 for lab_dict in data['labs']:
109 if list(lab_dict.values())[0]: # True for lab the user selected
110 lab_user_id = int(list(lab_dict.keys())[0].split("_")[-1])
111 models['bundle'].lab = Lab.objects.get(lab_user__id=lab_user_id)
112 break # if somehow we get two 'true' labs, we only use one
115 self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
117 def update_confirmation(self):
118 confirm = self.repo_get(self.repo.CONFIRMATION, {})
119 if "resource" not in confirm:
120 confirm['resource'] = {}
121 confirm['resource']['hosts'] = []
122 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {"hosts": []})
123 for host in models['hosts']:
124 host_dict = {"name": host.resource.name, "profile": host.profile.name}
125 confirm['resource']['hosts'].append(host_dict)
127 confirm['resource']['lab'] = models['lab'].lab_user.username
128 self.repo_put(self.repo.CONFIRMATION, confirm)
130 def post_render(self, request):
132 self.form = HardwareDefinitionForm(request.POST)
133 if self.form.is_valid():
134 if len(json.loads(self.form.cleaned_data['filter_field'])['labs']) != 1:
135 self.metastep.set_invalid("Please select one lab")
137 self.update_models(self.form.cleaned_data)
138 self.update_confirmation()
139 self.metastep.set_valid("Step Completed")
141 self.metastep.set_invalid("Please complete the fields highlighted in red to continue")
143 except Exception as e:
144 self.metastep.set_invalid(str(e))
145 self.context = self.get_context()
146 return render(request, self.template, self.context)
149 class Define_Nets(WorkflowStep):
150 template = 'resource/steps/pod_definition.html'
151 title = "Define Networks"
152 description = "Use the tool below to draw the network topology of your POD"
153 short_title = "networking"
154 form = NetworkDefinitionForm
157 vlans = self.repo_get(self.repo.VLANS)
160 # try to grab some vlans from lab
161 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
162 if "bundle" not in models:
164 lab = models['bundle'].lab
165 if lab is None or lab.vlan_manager is None:
168 vlans = lab.vlan_manager.get_vlan(count=lab.vlan_manager.block_size)
169 self.repo_put(self.repo.VLANS, vlans)
174 def get_context(self):
175 # TODO: render *primarily* on hosts in repo models
176 context = super(Define_Nets, self).get_context()
177 context['form'] = NetworkDefinitionForm()
179 context['hosts'] = []
180 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
181 vlans = self.get_vlans()
183 context['vlans'] = vlans
184 hosts = models.get("hosts", [])
185 hostlist = self.repo_get(self.repo.GRB_LAST_HOSTLIST, None)
188 context['added_hosts'] = []
189 if hostlist is not None:
191 for host in models['hosts']:
192 intcount = host.profile.interfaceprofile.count()
193 new_hostlist.append(str(host.resource.name) + "*" + str(host.profile) + "&" + str(intcount))
194 context['removed_hosts'] = list(set(hostlist) - set(new_hostlist))
195 added_list = list(set(new_hostlist) - set(hostlist))
196 for hoststr in added_list:
197 key = hoststr.split("*")[0]
198 added_dict[key] = hoststr
199 for generic_host in hosts:
200 host_profile = generic_host.profile
202 host['id'] = generic_host.resource.name
203 host['interfaces'] = []
204 for iface in host_profile.interfaceprofile.all():
205 host['interfaces'].append(
208 "description": "speed: " + str(iface.speed) + "M\ntype: " + iface.nic_type
211 host['value'] = {"name": generic_host.resource.name}
212 host['value']['description'] = generic_host.profile.description
213 context['hosts'].append(json.dumps(host))
214 if host['id'] in added_dict:
215 context['added_hosts'].append(json.dumps(host))
216 bundle = models.get("bundle", False)
217 if bundle and bundle.xml:
218 context['xml'] = bundle.xml
220 context['xml'] = False
227 def post_render(self, request):
228 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
229 if 'hosts' in models:
231 for host in models['hosts']:
232 intcount = host.profile.interfaceprofile.count()
233 hostlist.append(str(host.resource.name) + "*" + str(host.profile) + "&" + str(intcount))
234 self.repo_put(self.repo.GRB_LAST_HOSTLIST, hostlist)
236 xmlData = request.POST.get("xml")
237 self.updateModels(xmlData)
238 # update model with xml
239 self.metastep.set_valid("Networks applied successfully")
240 except ResourceAvailabilityException:
241 self.metastep.set_invalid("Public network not availble")
243 self.metastep.set_invalid("An error occurred when applying networks")
244 return self.render(request)
246 def updateModels(self, xmlData):
247 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
249 given_hosts, interfaces = self.parseXml(xmlData)
250 vlan_manager = models['bundle'].lab.vlan_manager
251 existing_host_list = models.get("hosts", [])
252 existing_hosts = {} # maps id to host
253 for host in existing_host_list:
254 existing_hosts[host.resource.name] = host
256 bundle = models.get("bundle", GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER)))
258 for hostid, given_host in given_hosts.items():
259 existing_host = existing_hosts[hostid[5:]]
261 for ifaceId in given_host['interfaces']:
262 iface = interfaces[ifaceId]
263 if existing_host.resource.name not in models['vlans']:
264 models['vlans'][existing_host.resource.name] = {}
265 models['vlans'][existing_host.resource.name][iface['profile_name']] = []
266 for network in iface['networks']:
267 vlan_id = network['network']['vlan']
268 is_public = network['network']['public']
270 public_net = vlan_manager.get_public_vlan()
271 if public_net is None:
272 raise ResourceAvailabilityException("No public networks available")
273 vlan_id = vlan_manager.get_public_vlan().vlan
274 vlan = Vlan(vlan_id=vlan_id, tagged=network['tagged'], public=is_public)
275 models['vlans'][existing_host.resource.name][iface['profile_name']].append(vlan)
277 self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
279 # serialize and deserialize xml from mxGraph
280 def parseXml(self, xmlString):
281 parent_nets = {} # map network ports to networks
282 networks = {} # maps net id to network object
283 hosts = {} # cotains id -> hosts, each containing interfaces, referencing networks
284 interfaces = {} # maps id -> interface
285 xmlDom = minidom.parseString(xmlString)
286 root = xmlDom.documentElement.firstChild
289 for cell in root.childNodes:
290 cellId = cell.getAttribute('id')
292 if cell.getAttribute("edge"):
293 # cell is a network connection
294 escaped_json_str = cell.getAttribute("value")
295 json_str = escaped_json_str.replace('"', '"')
296 attributes = json.loads(json_str)
297 tagged = attributes['tagged']
300 src = cell.getAttribute("source")
301 tgt = cell.getAttribute("target")
302 if src in parent_nets:
303 # src is a network port
304 network = networks[parent_nets[src]]
305 if tgt in untagged_ints and not tagged:
306 raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
307 interface = interfaces[tgt]
308 untagged_ints[tgt] = True
310 network = networks[parent_nets[tgt]]
311 if src in untagged_ints and not tagged:
312 raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
313 interface = interfaces[src]
314 untagged_ints[src] = True
315 interface['networks'].append({"network": network, "tagged": tagged})
317 elif "network" in cellId: # cell is a network
318 escaped_json_str = cell.getAttribute("value")
319 json_str = escaped_json_str.replace('"', '"')
320 net_info = json.loads(json_str)
321 nid = net_info['vlan_id']
322 public = net_info['public']
325 assert public or int_netid > 1, "Net id is 1 or lower"
326 assert int_netid < 4095, "Net id is 4095 or greater"
328 raise InvalidVlanConfigurationException("VLAN ID is not an integer more than 1 and less than 4095")
330 raise NetworkExistsException("Non unique network id found")
333 network = {"name": net_info['name'], "vlan": net_info['vlan_id'], "public": public}
334 netids[net_info['vlan_id']] = True
335 networks[cellId] = network
337 elif "host" in cellId: # cell is a host/machine
338 # TODO gather host info
339 cell_json_str = cell.getAttribute("value")
340 cell_json = json.loads(cell_json_str)
341 host = {"interfaces": [], "name": cellId, "profile_name": cell_json['name']}
344 elif cell.hasAttribute("parent"):
345 parentId = cell.getAttribute('parent')
346 if "network" in parentId:
347 parent_nets[cellId] = parentId
348 elif "host" in parentId:
349 # TODO gather iface info
350 cell_json_str = cell.getAttribute("value")
351 cell_json = json.loads(cell_json_str)
352 iface = {"name": cellId, "networks": [], "profile_name": cell_json['name']}
353 hosts[parentId]['interfaces'].append(cellId)
354 interfaces[cellId] = iface
355 return hosts, interfaces
358 class Resource_Meta_Info(WorkflowStep):
359 template = 'resource/steps/meta_info.html'
361 description = "Please fill out the rest of the information about your resource"
362 short_title = "pod info"
364 def get_context(self):
365 context = super(Resource_Meta_Info, self).get_context()
368 bundle = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}).get("bundle", False)
369 if bundle and bundle.name:
371 desc = bundle.description
372 context['form'] = ResourceMetaForm(initial={"bundle_name": name, "bundle_description": desc})
375 def post_render(self, request):
376 form = ResourceMetaForm(request.POST)
378 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
379 name = form.cleaned_data['bundle_name']
380 desc = form.cleaned_data['bundle_description']
381 bundle = models.get("bundle", GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER)))
383 bundle.description = desc
384 models['bundle'] = bundle
385 self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
386 confirm = self.repo_get(self.repo.CONFIRMATION)
387 if "resource" not in confirm:
388 confirm['resource'] = {}
389 confirm_info = confirm['resource']
390 confirm_info["name"] = name
393 tmp = tmp[:60] + "..."
394 confirm_info["description"] = tmp
395 self.repo_put(self.repo.CONFIRMATION, confirm)
396 self.metastep.set_valid("Step Completed")
399 self.metastep.set_invalid("Please correct the fields highlighted in red to continue")
401 return self.render(request)
404 class Host_Meta_Info(WorkflowStep):
405 template = "resource/steps/host_info.html"
407 description = "We need a little bit of information about your chosen machines"
408 short_title = "host info"
410 def __init__(self, *args, **kwargs):
411 super(Host_Meta_Info, self).__init__(*args, **kwargs)
412 self.formset = formset_factory(GenericHostMetaForm, extra=0)
414 def get_context(self):
415 context = super(Host_Meta_Info, self).get_context()
416 GenericHostFormset = self.formset
417 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
419 if "hosts" not in models:
420 context['error'] = "Please go back and select your hosts"
422 for host in models['hosts']:
423 profile = host.profile.name
424 name = host.resource.name
427 initial_data.append({"host_profile": profile, "host_name": name})
428 context['formset'] = GenericHostFormset(initial=initial_data)
431 def post_render(self, request):
432 models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
433 if 'hosts' not in models:
435 hosts = models['hosts']
438 GenericHostFormset = self.formset
439 formset = GenericHostFormset(request.POST)
440 if formset.is_valid():
443 host.resource.name = form.cleaned_data['host_name']
445 confirm_hosts.append({"name": host.resource.name, "profile": host.profile.name})
446 models['hosts'] = hosts
447 self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
448 confirm = self.repo_get(self.repo.CONFIRMATION, {})
449 if "resource" not in confirm:
450 confirm['resource'] = {}
451 confirm['resource']['hosts'] = confirm_hosts
452 self.repo_put(self.repo.CONFIRMATION, confirm)
455 return self.render(request)