1 ##############################################################################
2 # Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, 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.db import models
12 from django.core.exceptions import PermissionDenied
17 from resource_inventory.models import *
18 from booking.models import Booking
21 class JobStatus(object):
28 class LabManagerTracker(object):
31 def get(cls, lab_name, token):
33 Takes in a lab name (from a url path)
34 returns a lab manager instance for that lab, if it exists
37 lab = Lab.objects.get(name=lab_name)
39 raise PermissionDenied("Lab not found")
40 if lab.api_token == token:
41 return LabManager(lab)
42 raise PermissionDenied("Lab not authorized")
45 class LabManager(object):
47 This is the class that will ultimately handle all REST calls to
49 handles jobs, inventory, status, etc
50 may need to create helper classes
53 def __init__(self, lab):
56 def get_profile(self):
58 prof['name'] = self.lab.name
60 "phone": self.lab.contact_phone,
61 "email": self.lab.contact_email
63 prof['host_count'] = []
64 for host in HostProfile.objects.filter(labs=self.lab):
65 count = Host.objects.filter(profile=host, lab=self.lab).count()
66 prof['host_count'].append({
72 def get_inventory(self):
74 hosts = Host.objects.filter(lab=self.lab)
75 images = Image.objects.filter(from_lab=self.lab)
76 profiles = HostProfile.objects.filter(labs=self.lab)
77 inventory['hosts'] = self.serialize_hosts(hosts)
78 inventory['images'] = self.serialize_images(images)
79 inventory['host_types'] = self.serialize_host_profiles(profiles)
83 return {"status": self.lab.status}
85 def set_status(self, payload):
88 def get_current_jobs(self):
89 jobs = Job.objects.filter(booking__lab=self.lab)
91 return self.serialize_jobs(jobs, status=JobStatus.CURRENT)
93 def get_new_jobs(self):
94 jobs = Job.objects.filter(booking__lab=self.lab)
96 return self.serialize_jobs(jobs, status=JobStatus.NEW)
98 def get_done_jobs(self):
99 jobs = Job.objects.filter(booking__lab=self.lab)
101 return self.serialize_jobs(jobs, status=JobStatus.DONE)
103 def get_job(self, jobid):
104 return Job.objects.get(pk=jobid).to_dict()
106 def update_job(self, jobid, data):
109 def serialize_jobs(self, jobs, status=JobStatus.NEW):
112 jsonized_job = job.get_delta(status)
113 if len(jsonized_job['payload']) < 1:
115 job_ser.append(jsonized_job)
119 def serialize_hosts(self, hosts):
124 h['hostname'] = host.name
125 h['host_type'] = host.profile.name
126 for iface in host.interfaces.all():
128 eth['mac'] = iface.mac_address
129 eth['busaddr'] = iface.bus_address
130 eth['name'] = iface.name
131 eth['switchport'] = {"switch_name": iface.switch_name, "port_name": iface.port_name}
132 h['interfaces'].append(eth)
135 def serialize_images(self, images):
140 "lab_id": image.lab_id,
141 "dashboard_id": image.id
145 def serialize_host_profiles(self, profiles):
147 for profile in profiles:
150 "cores": profile.cpuprofile.first().cores,
151 "arch": profile.cpuprofile.first().architecture,
152 "cpus": profile.cpuprofile.first().cpus,
155 for disk in profile.storageprofile.all():
158 "type": disk.media_type,
162 p['description'] = profile.description
164 for iface in profile.interfaceprofile.all():
165 p['interfaces'].append({
166 "speed": iface.speed,
170 p['ram'] = {"amount": profile.ramprofile.first().amount}
171 p['name'] = profile.name
172 profile_ser.append(p)
176 class Job(models.Model):
178 This is the class that is serialized and put into the api
180 booking = models.OneToOneField(Booking, on_delete=models.CASCADE, null=True)
181 status = models.IntegerField(default=JobStatus.NEW)
182 complete = models.BooleanField(default=False)
188 for relation in AccessRelation.objects.filter(job=self):
189 if 'access' not in d:
191 d['access'][relation.task_id] = relation.config.to_dict()
192 for relation in SoftwareRelation.objects.filter(job=self):
193 if 'software' not in d:
195 d['software'][relation.task_id] = relation.config.to_dict()
196 for relation in HostHardwareRelation.objects.filter(job=self):
197 if 'hardware' not in d:
199 d['hardware'][relation.task_id] = relation.config.to_dict()
200 for relation in HostNetworkRelation.objects.filter(job=self):
201 if 'network' not in d:
203 d['network'][relation.task_id] = relation.config.to_dict()
209 def get_tasklist(self, status="all"):
211 clist = [HostHardwareRelation, AccessRelation, HostNetworkRelation, SoftwareRelation]
214 tasklist += list(cls.objects.filter(job=self))
217 tasklist += list(cls.objects.filter(job=self).filter(status=status))
220 def is_fulfilled(self):
222 This method should return true if all of the job's tasks are done,
225 my_tasks = self.get_tasklist()
226 for task in my_tasks:
227 if task.status != JobStatus.DONE:
232 def get_delta(self, status):
236 for relation in AccessRelation.objects.filter(job=self).filter(status=status):
237 if 'access' not in d:
239 d['access'][relation.task_id] = relation.config.get_delta()
240 for relation in SoftwareRelation.objects.filter(job=self).filter(status=status):
241 if 'software' not in d:
243 d['software'][relation.task_id] = relation.config.get_delta()
244 for relation in HostHardwareRelation.objects.filter(job=self).filter(status=status):
245 if 'hardware' not in d:
247 d['hardware'][relation.task_id] = relation.config.get_delta()
248 for relation in HostNetworkRelation.objects.filter(job=self).filter(status=status):
249 if 'network' not in d:
251 d['network'][relation.task_id] = relation.config.get_delta()
257 return json.dumps(self.to_dict())
260 class TaskConfig(models.Model):
268 return json.dumps(self.to_dict())
270 def clear_delta(self):
273 class OpnfvApiConfig(models.Model):
275 installer = models.CharField(max_length=100)
276 scenario = models.CharField(max_length=100)
277 roles = models.ManyToManyField(Host)
278 delta = models.TextField()
283 d['installer'] = self.installer
285 d['scenario'] = self.scenario
287 hosts = self.roles.all()
290 for host in self.roles.all():
291 d['roles'].append({host.labid: host.config.opnfvRole.name})
296 return json.dumps(self.to_dict())
298 def set_installer(self, installer):
299 self.installer = installer
300 d = json.loads(self.delta)
301 d['installer'] = installer
302 self.delta = json.dumps(d)
304 def set_scenario(self, scenario):
305 self.scenario = scenario
306 d = json.loads(self.delta)
307 d['scenario'] = scenario
308 self.delta = json.dumps(d)
310 def add_role(self, host):
312 d = json.loads(self.delta)
315 d['roles'].append({host.labid: host.config.opnfvRole.name})
316 self.delta = json.dumps(d)
318 def clear_delta(self):
323 self.delta = self.to_json()
325 return json.loads(self.delta)
327 class AccessConfig(TaskConfig):
328 access_type = models.CharField(max_length=50)
329 user = models.ForeignKey(User, on_delete=models.CASCADE)
330 revoke = models.BooleanField(default=False)
331 context = models.TextField(default="")
332 delta = models.TextField()
336 d['access_type'] = self.access_type
337 d['user'] = self.user.id
338 d['revoke'] = self.revoke
339 d['context'] = json.loads(self.context)
344 self.delta = self.to_json()
346 d = json.loads(self.delta)
347 d["lab_token"] = self.accessrelation.lab_token
352 return json.dumps(self.to_dict())
354 def clear_delta(self):
356 d["lab_token"] = self.accessrelation.lab_token
357 self.delta = json.dumps(d)
359 def set_access_type(self, access_type):
360 self.access_type = access_type
361 d = json.loads(self.delta)
362 d['access_type'] = access_type
363 self.delta = json.dumps(d)
365 def set_user(self, user):
367 d = json.loads(self.delta)
368 d['user'] = self.user.id
369 self.delta = json.dumps(d)
371 def set_revoke(self, revoke):
373 d = json.loads(self.delta)
375 self.delta = json.dumps(d)
377 def set_context(self, context):
378 self.context = json.dumps(context)
379 d = json.loads(self.delta)
380 d['context'] = context
381 self.delta = json.dumps(d)
383 class SoftwareConfig(TaskConfig):
385 handled opnfv installations, etc
387 opnfv = models.ForeignKey(OpnfvApiConfig, on_delete=models.CASCADE)
392 d['opnfv'] = self.opnfv.to_dict()
394 d["lab_token"] = self.softwarerelation.lab_token
395 self.delta = json.dumps(d)
401 d['opnfv'] = self.opnfv.get_delta()
402 d['lab_token'] = self.softwarerelation.lab_token
406 def clear_delta(self):
407 self.opnfv.clear_delta()
410 return json.dumps(self.to_dict())
412 class HardwareConfig(TaskConfig):
414 handles imaging, user accounts, etc
416 image = models.CharField(max_length=100, default="defimage")
417 power = models.CharField(max_length=100, default="off")
418 hostname = models.CharField(max_length=100, default="hostname")
419 ipmi_create = models.BooleanField(default=False)
420 delta = models.TextField()
424 d['image'] = self.image
425 d['power'] = self.power
426 d['hostname'] = self.hostname
427 d['ipmi_create'] = str(self.ipmi_create)
428 d['id'] = self.hosthardwarerelation.host.labid
432 return json.dumps(self.to_dict())
436 self.delta = self.to_json()
438 d = json.loads(self.delta)
439 d['lab_token'] = self.hosthardwarerelation.lab_token
442 def clear_delta(self):
444 d["id"] = self.hosthardwarerelation.host.labid
445 d["lab_token"] = self.hosthardwarerelation.lab_token
446 self.delta = json.dumps(d)
448 def set_image(self, image):
450 d = json.loads(self.delta)
451 d['image'] = self.image
452 self.delta = json.dumps(d)
454 def set_power(self, power):
456 d = json.loads(self.delta)
458 self.delta = json.dumps(d)
460 def set_hostname(self, hostname):
461 self.hostname = hostname
462 d = json.loads(self.delta)
463 d['hostname'] = hostname
464 self.delta = json.dumps(d)
466 def set_ipmi_create(self, ipmi_create):
467 self.ipmi_create = ipmi_create
468 d = json.loads(self.delta)
469 d['ipmi_create'] = ipmi_create
470 self.delta = json.dumps(d)
473 class NetworkConfig(TaskConfig):
475 handles network configuration
477 interfaces = models.ManyToManyField(Interface)
478 delta = models.TextField()
482 hid = self.hostnetworkrelation.host.labid
484 for interface in self.interfaces.all():
485 d[hid][interface.mac_address] = []
486 for vlan in interface.config.all():
487 d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
492 return json.dumps(self.to_dict())
496 self.delta = self.to_json()
498 d = json.loads(self.delta)
499 d['lab_token'] = self.hostnetworkrelation.lab_token
502 def clear_delta(self):
503 self.delta = json.dumps(self.to_dict())
506 def add_interface(self, interface):
507 self.interfaces.add(interface)
508 d = json.loads(self.delta)
509 hid = self.hostnetworkrelation.host.labid
512 d[hid][interface.mac_address] = []
513 for vlan in interface.config.all():
514 d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
515 self.delta = json.dumps(d)
518 def get_task(task_id):
519 for taskclass in [AccessRelation, SoftwareRelation, HostHardwareRelation, HostNetworkRelation]:
521 ret = taskclass.objects.get(task_id=task_id)
523 except taskclass.DoesNotExist:
525 from django.core.exceptions import ObjectDoesNotExist
526 raise ObjectDoesNotExist("Could not find matching TaskRelation instance")
530 return str(uuid.uuid4())
533 class TaskRelation(models.Model):
534 status = models.IntegerField(default=JobStatus.NEW)
535 job = models.ForeignKey(Job, on_delete=models.CASCADE)
536 config = models.OneToOneField(TaskConfig, on_delete=models.CASCADE)
537 task_id = models.CharField(default=get_task_uuid, max_length=37)
538 lab_token = models.CharField(default="null", max_length=50)
539 message = models.TextField(default="")
541 def delete(self, *args, **kwargs):
543 return super(self.__class__, self).delete(*args, **kwargs)
546 return "Generic Task"
552 class AccessRelation(TaskRelation):
553 config = models.OneToOneField(AccessConfig, on_delete=models.CASCADE)
558 def delete(self, *args, **kwargs):
560 return super(self.__class__, self).delete(*args, **kwargs)
563 class SoftwareRelation(TaskRelation):
564 config = models.OneToOneField(SoftwareConfig, on_delete=models.CASCADE)
567 return "Software Configuration Task"
569 def delete(self, *args, **kwargs):
571 return super(self.__class__, self).delete(*args, **kwargs)
574 class HostHardwareRelation(TaskRelation):
575 host = models.ForeignKey(Host, on_delete=models.CASCADE)
576 config = models.OneToOneField(HardwareConfig, on_delete=models.CASCADE)
579 return "Hardware Configuration Task"
582 return self.config.to_dict()
584 def delete(self, *args, **kwargs):
586 return super(self.__class__, self).delete(*args, **kwargs)
589 class HostNetworkRelation(TaskRelation):
590 host = models.ForeignKey(Host, on_delete=models.CASCADE)
591 config = models.OneToOneField(NetworkConfig, on_delete=models.CASCADE)
594 return "Network Configuration Task"
596 def delete(self, *args, **kwargs):
598 return super(self.__class__, self).delete(*args, **kwargs)
601 class JobFactory(object):
604 def makeCompleteJob(cls, booking):
605 hosts = Host.objects.filter(bundle=booking.resource)
608 job = Job.objects.get(booking=booking)
610 job = Job.objects.create(status=JobStatus.NEW, booking=booking)
611 cls.makeHardwareConfigs(
615 cls.makeNetworkConfigs(
623 all_users = list(booking.collaborators.all())
624 all_users.append(booking.owner)
625 cls.makeAccessConfig(
631 for user in all_users:
633 cls.makeAccessConfig(
639 "key": user.userprofile.ssh_public_key.read(),
640 "hosts": [host.labid for host in hosts]
647 def makeHardwareConfigs(cls, hosts=[], job=Job()):
649 hardware_config = None
651 hardware_config = HardwareConfig.objects.get(relation__host=host)
653 hardware_config = HardwareConfig()
655 relation = HostHardwareRelation()
658 relation.config = hardware_config
659 relation.config.save()
660 relation.config = relation.config
663 hardware_config.clear_delta()
664 hardware_config.set_image(host.config.image.lab_id)
665 hardware_config.set_hostname(host.template.resource.name)
666 hardware_config.set_power("on")
667 hardware_config.set_ipmi_create(True)
668 hardware_config.save()
671 def makeAccessConfig(cls, users, access_type, revoke=False, job=Job(), context=False):
673 relation = AccessRelation()
675 config = AccessConfig()
676 config.access_type = access_type
679 config.set_context(context)
681 relation.config = config
684 config.set_access_type(access_type)
685 config.set_revoke(revoke)
686 config.set_user(user)
690 def makeNetworkConfigs(cls, hosts=[], job=Job()):
692 network_config = None
694 network_config = NetworkConfig.objects.get(relation__host=host)
696 network_config = NetworkConfig.objects.create()
698 relation = HostNetworkRelation()
701 network_config.save()
702 relation.config = network_config
704 network_config.clear_delta()
706 for interface in host.interfaces.all():
707 network_config.add_interface(interface)
708 network_config.save()
711 def makeSoftware(cls, hosts=[], job=Job()):
712 def init_config(host):
713 opnfv_config = OpnfvApiConfig()
715 opnfv = host.config.bundle.opnfv_config.first()
716 opnfv_config.installer = opnfv.installer.name
717 opnfv_config.scenario = opnfv.scenario.name
725 opnfv_config = init_config(host)
728 opnfv_config.roles.add(host)
729 software_config = SoftwareConfig.objects.create(opnfv=opnfv_config)
730 software_config.save()
731 software_relation = SoftwareRelation.objects.create(job=job, config=software_config)
732 software_relation.save()
733 return software_relation