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.contrib.auth.models import User
12 from django.db import models
13 from django.core.exceptions import PermissionDenied
18 from booking.models import Booking
19 from resource_inventory.models import (
28 class JobStatus(object):
35 class LabManagerTracker(object):
38 def get(cls, lab_name, token):
40 Takes in a lab name (from a url path)
41 returns a lab manager instance for that lab, if it exists
44 lab = Lab.objects.get(name=lab_name)
46 raise PermissionDenied("Lab not found")
47 if lab.api_token == token:
48 return LabManager(lab)
49 raise PermissionDenied("Lab not authorized")
52 class LabManager(object):
54 This is the class that will ultimately handle all REST calls to
56 handles jobs, inventory, status, etc
57 may need to create helper classes
60 def __init__(self, lab):
63 def get_profile(self):
65 prof['name'] = self.lab.name
67 "phone": self.lab.contact_phone,
68 "email": self.lab.contact_email
70 prof['host_count'] = []
71 for host in HostProfile.objects.filter(labs=self.lab):
72 count = Host.objects.filter(profile=host, lab=self.lab).count()
73 prof['host_count'].append(
81 def get_inventory(self):
83 hosts = Host.objects.filter(lab=self.lab)
84 images = Image.objects.filter(from_lab=self.lab)
85 profiles = HostProfile.objects.filter(labs=self.lab)
86 inventory['hosts'] = self.serialize_hosts(hosts)
87 inventory['images'] = self.serialize_images(images)
88 inventory['host_types'] = self.serialize_host_profiles(profiles)
92 return {"status": self.lab.status}
94 def set_status(self, payload):
97 def get_current_jobs(self):
98 jobs = Job.objects.filter(booking__lab=self.lab)
100 return self.serialize_jobs(jobs, status=JobStatus.CURRENT)
102 def get_new_jobs(self):
103 jobs = Job.objects.filter(booking__lab=self.lab)
105 return self.serialize_jobs(jobs, status=JobStatus.NEW)
107 def get_done_jobs(self):
108 jobs = Job.objects.filter(booking__lab=self.lab)
110 return self.serialize_jobs(jobs, status=JobStatus.DONE)
112 def get_job(self, jobid):
113 return Job.objects.get(pk=jobid).to_dict()
115 def update_job(self, jobid, data):
118 def serialize_jobs(self, jobs, status=JobStatus.NEW):
121 jsonized_job = job.get_delta(status)
122 if len(jsonized_job['payload']) < 1:
124 job_ser.append(jsonized_job)
128 def serialize_hosts(self, hosts):
133 h['hostname'] = host.name
134 h['host_type'] = host.profile.name
135 for iface in host.interfaces.all():
137 eth['mac'] = iface.mac_address
138 eth['busaddr'] = iface.bus_address
139 eth['name'] = iface.name
140 eth['switchport'] = {"switch_name": iface.switch_name, "port_name": iface.port_name}
141 h['interfaces'].append(eth)
144 def serialize_images(self, images):
150 "lab_id": image.lab_id,
151 "dashboard_id": image.id
156 def serialize_host_profiles(self, profiles):
158 for profile in profiles:
161 "cores": profile.cpuprofile.first().cores,
162 "arch": profile.cpuprofile.first().architecture,
163 "cpus": profile.cpuprofile.first().cpus,
166 for disk in profile.storageprofile.all():
169 "type": disk.media_type,
173 p['description'] = profile.description
175 for iface in profile.interfaceprofile.all():
176 p['interfaces'].append(
178 "speed": iface.speed,
183 p['ram'] = {"amount": profile.ramprofile.first().amount}
184 p['name'] = profile.name
185 profile_ser.append(p)
189 class Job(models.Model):
191 This is the class that is serialized and put into the api
193 booking = models.OneToOneField(Booking, on_delete=models.CASCADE, null=True)
194 status = models.IntegerField(default=JobStatus.NEW)
195 complete = models.BooleanField(default=False)
201 for relation in AccessRelation.objects.filter(job=self):
202 if 'access' not in d:
204 d['access'][relation.task_id] = relation.config.to_dict()
205 for relation in SoftwareRelation.objects.filter(job=self):
206 if 'software' not in d:
208 d['software'][relation.task_id] = relation.config.to_dict()
209 for relation in HostHardwareRelation.objects.filter(job=self):
210 if 'hardware' not in d:
212 d['hardware'][relation.task_id] = relation.config.to_dict()
213 for relation in HostNetworkRelation.objects.filter(job=self):
214 if 'network' not in d:
216 d['network'][relation.task_id] = relation.config.to_dict()
217 for relation in SnapshotRelation.objects.filter(job=self):
218 if 'snapshot' not in d:
220 d['snapshot'][relation.task_id] = relation.config.to_dict()
226 def get_tasklist(self, status="all"):
229 HostHardwareRelation,
237 tasklist += list(cls.objects.filter(job=self))
240 tasklist += list(cls.objects.filter(job=self).filter(status=status))
243 def is_fulfilled(self):
245 This method should return true if all of the job's tasks are done,
248 my_tasks = self.get_tasklist()
249 for task in my_tasks:
250 if task.status != JobStatus.DONE:
254 def get_delta(self, status):
258 for relation in AccessRelation.objects.filter(job=self).filter(status=status):
259 if 'access' not in d:
261 d['access'][relation.task_id] = relation.config.get_delta()
262 for relation in SoftwareRelation.objects.filter(job=self).filter(status=status):
263 if 'software' not in d:
265 d['software'][relation.task_id] = relation.config.get_delta()
266 for relation in HostHardwareRelation.objects.filter(job=self).filter(status=status):
267 if 'hardware' not in d:
269 d['hardware'][relation.task_id] = relation.config.get_delta()
270 for relation in HostNetworkRelation.objects.filter(job=self).filter(status=status):
271 if 'network' not in d:
273 d['network'][relation.task_id] = relation.config.get_delta()
274 for relation in SnapshotRelation.objects.filter(job=self).filter(status=status):
275 if 'snapshot' not in d:
277 d['snapshot'][relation.task_id] = relation.config.get_delta()
283 return json.dumps(self.to_dict())
286 class TaskConfig(models.Model):
294 return json.dumps(self.to_dict())
296 def clear_delta(self):
300 class OpnfvApiConfig(models.Model):
302 installer = models.CharField(max_length=100)
303 scenario = models.CharField(max_length=100)
304 roles = models.ManyToManyField(Host)
305 delta = models.TextField()
310 d['installer'] = self.installer
312 d['scenario'] = self.scenario
314 hosts = self.roles.all()
317 for host in self.roles.all():
318 d['roles'].append({host.labid: host.config.opnfvRole.name})
323 return json.dumps(self.to_dict())
325 def set_installer(self, installer):
326 self.installer = installer
327 d = json.loads(self.delta)
328 d['installer'] = installer
329 self.delta = json.dumps(d)
331 def set_scenario(self, scenario):
332 self.scenario = scenario
333 d = json.loads(self.delta)
334 d['scenario'] = scenario
335 self.delta = json.dumps(d)
337 def add_role(self, host):
339 d = json.loads(self.delta)
342 d['roles'].append({host.labid: host.config.opnfvRole.name})
343 self.delta = json.dumps(d)
345 def clear_delta(self):
350 self.delta = self.to_json()
352 return json.loads(self.delta)
355 class AccessConfig(TaskConfig):
356 access_type = models.CharField(max_length=50)
357 user = models.ForeignKey(User, on_delete=models.CASCADE)
358 revoke = models.BooleanField(default=False)
359 context = models.TextField(default="")
360 delta = models.TextField(default="{}")
364 d['access_type'] = self.access_type
365 d['user'] = self.user.id
366 d['revoke'] = self.revoke
368 d['context'] = json.loads(self.context)
375 self.delta = self.to_json()
377 d = json.loads(self.delta)
378 d["lab_token"] = self.accessrelation.lab_token
383 return json.dumps(self.to_dict())
385 def clear_delta(self):
387 d["lab_token"] = self.accessrelation.lab_token
388 self.delta = json.dumps(d)
390 def set_access_type(self, access_type):
391 self.access_type = access_type
392 d = json.loads(self.delta)
393 d['access_type'] = access_type
394 self.delta = json.dumps(d)
396 def set_user(self, user):
398 d = json.loads(self.delta)
399 d['user'] = self.user.id
400 self.delta = json.dumps(d)
402 def set_revoke(self, revoke):
404 d = json.loads(self.delta)
406 self.delta = json.dumps(d)
408 def set_context(self, context):
409 self.context = json.dumps(context)
410 d = json.loads(self.delta)
411 d['context'] = context
412 self.delta = json.dumps(d)
415 class SoftwareConfig(TaskConfig):
417 handled opnfv installations, etc
419 opnfv = models.ForeignKey(OpnfvApiConfig, on_delete=models.CASCADE)
424 d['opnfv'] = self.opnfv.to_dict()
426 d["lab_token"] = self.softwarerelation.lab_token
427 self.delta = json.dumps(d)
433 d['opnfv'] = self.opnfv.get_delta()
434 d['lab_token'] = self.softwarerelation.lab_token
438 def clear_delta(self):
439 self.opnfv.clear_delta()
442 return json.dumps(self.to_dict())
445 class HardwareConfig(TaskConfig):
447 handles imaging, user accounts, etc
449 image = models.CharField(max_length=100, default="defimage")
450 power = models.CharField(max_length=100, default="off")
451 hostname = models.CharField(max_length=100, default="hostname")
452 ipmi_create = models.BooleanField(default=False)
453 delta = models.TextField()
457 d['image'] = self.image
458 d['power'] = self.power
459 d['hostname'] = self.hostname
460 d['ipmi_create'] = str(self.ipmi_create)
461 d['id'] = self.hosthardwarerelation.host.labid
465 return json.dumps(self.to_dict())
469 self.delta = self.to_json()
471 d = json.loads(self.delta)
472 d['lab_token'] = self.hosthardwarerelation.lab_token
475 def clear_delta(self):
477 d["id"] = self.hosthardwarerelation.host.labid
478 d["lab_token"] = self.hosthardwarerelation.lab_token
479 self.delta = json.dumps(d)
481 def set_image(self, image):
483 d = json.loads(self.delta)
484 d['image'] = self.image
485 self.delta = json.dumps(d)
487 def set_power(self, power):
489 d = json.loads(self.delta)
491 self.delta = json.dumps(d)
493 def set_hostname(self, hostname):
494 self.hostname = hostname
495 d = json.loads(self.delta)
496 d['hostname'] = hostname
497 self.delta = json.dumps(d)
499 def set_ipmi_create(self, ipmi_create):
500 self.ipmi_create = ipmi_create
501 d = json.loads(self.delta)
502 d['ipmi_create'] = ipmi_create
503 self.delta = json.dumps(d)
506 class NetworkConfig(TaskConfig):
508 handles network configuration
510 interfaces = models.ManyToManyField(Interface)
511 delta = models.TextField()
515 hid = self.hostnetworkrelation.host.labid
517 for interface in self.interfaces.all():
518 d[hid][interface.mac_address] = []
519 for vlan in interface.config.all():
520 d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
525 return json.dumps(self.to_dict())
529 self.delta = self.to_json()
531 d = json.loads(self.delta)
532 d['lab_token'] = self.hostnetworkrelation.lab_token
535 def clear_delta(self):
536 self.delta = json.dumps(self.to_dict())
539 def add_interface(self, interface):
540 self.interfaces.add(interface)
541 d = json.loads(self.delta)
542 hid = self.hostnetworkrelation.host.labid
545 d[hid][interface.mac_address] = []
546 for vlan in interface.config.all():
547 d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
548 self.delta = json.dumps(d)
551 class SnapshotConfig(TaskConfig):
553 host = models.ForeignKey(Host, null=True, on_delete=models.DO_NOTHING)
554 image = models.IntegerField(null=True)
555 dashboard_id = models.IntegerField()
556 delta = models.TextField(default="{}")
561 d['host'] = self.host.labid
563 d['image'] = self.image
564 d['dashboard_id'] = self.dashboard_id
568 return json.dumps(self.to_dict())
572 self.delta = self.to_json()
574 d = json.loads(self.delta)
577 def clear_delta(self):
578 self.delta = json.dumps(self.to_dict())
581 def set_host(self, host):
583 d = json.loads(self.delta)
584 d['host'] = host.labid
585 self.delta = json.dumps(d)
587 def set_image(self, image):
589 d = json.loads(self.delta)
590 d['image'] = self.image
591 self.delta = json.dumps(d)
593 def clear_image(self):
595 d = json.loads(self.delta)
597 self.delta = json.dumps(d)
599 def set_dashboard_id(self, dash):
600 self.dashboard_id = dash
601 d = json.loads(self.delta)
602 d['dashboard_id'] = self.dashboard_id
603 self.delta = json.dumps(d)
606 def get_task(task_id):
607 for taskclass in [AccessRelation, SoftwareRelation, HostHardwareRelation, HostNetworkRelation]:
609 ret = taskclass.objects.get(task_id=task_id)
611 except taskclass.DoesNotExist:
613 from django.core.exceptions import ObjectDoesNotExist
614 raise ObjectDoesNotExist("Could not find matching TaskRelation instance")
618 return str(uuid.uuid4())
621 class TaskRelation(models.Model):
622 status = models.IntegerField(default=JobStatus.NEW)
623 job = models.ForeignKey(Job, on_delete=models.CASCADE)
624 config = models.OneToOneField(TaskConfig, on_delete=models.CASCADE)
625 task_id = models.CharField(default=get_task_uuid, max_length=37)
626 lab_token = models.CharField(default="null", max_length=50)
627 message = models.TextField(default="")
629 def delete(self, *args, **kwargs):
631 return super(self.__class__, self).delete(*args, **kwargs)
634 return "Generic Task"
640 class AccessRelation(TaskRelation):
641 config = models.OneToOneField(AccessConfig, on_delete=models.CASCADE)
646 def delete(self, *args, **kwargs):
648 return super(self.__class__, self).delete(*args, **kwargs)
651 class SoftwareRelation(TaskRelation):
652 config = models.OneToOneField(SoftwareConfig, on_delete=models.CASCADE)
655 return "Software Configuration Task"
657 def delete(self, *args, **kwargs):
659 return super(self.__class__, self).delete(*args, **kwargs)
662 class HostHardwareRelation(TaskRelation):
663 host = models.ForeignKey(Host, on_delete=models.CASCADE)
664 config = models.OneToOneField(HardwareConfig, on_delete=models.CASCADE)
667 return "Hardware Configuration Task"
670 return self.config.to_dict()
672 def delete(self, *args, **kwargs):
674 return super(self.__class__, self).delete(*args, **kwargs)
677 class HostNetworkRelation(TaskRelation):
678 host = models.ForeignKey(Host, on_delete=models.CASCADE)
679 config = models.OneToOneField(NetworkConfig, on_delete=models.CASCADE)
682 return "Network Configuration Task"
684 def delete(self, *args, **kwargs):
686 return super(self.__class__, self).delete(*args, **kwargs)
689 class SnapshotRelation(TaskRelation):
690 snapshot = models.ForeignKey(Image, on_delete=models.CASCADE)
691 config = models.OneToOneField(SnapshotConfig, on_delete=models.CASCADE)
694 return "Snapshot Task"
697 return self.config.to_dict()
699 def delete(self, *args, **kwargs):
701 return super(self.__class__, self).delete(*args, **kwargs)
704 class JobFactory(object):
707 def makeSnapshotTask(cls, image, booking, host):
708 relation = SnapshotRelation()
709 job = Job.objects.get(booking=booking)
710 config = SnapshotConfig.objects.create(dashboard_id=image.id)
713 relation.config = config
714 relation.config.save()
715 relation.config = relation.config
716 relation.snapshot = image
720 config.set_host(host)
724 def makeCompleteJob(cls, booking):
725 hosts = Host.objects.filter(bundle=booking.resource)
728 job = Job.objects.get(booking=booking)
730 job = Job.objects.create(status=JobStatus.NEW, booking=booking)
731 cls.makeHardwareConfigs(
735 cls.makeNetworkConfigs(
743 all_users = list(booking.collaborators.all())
744 all_users.append(booking.owner)
745 cls.makeAccessConfig(
751 for user in all_users:
753 cls.makeAccessConfig(
759 "key": user.userprofile.ssh_public_key.open().read().decode(encoding="UTF-8"),
760 "hosts": [host.labid for host in hosts]
767 def makeHardwareConfigs(cls, hosts=[], job=Job()):
769 hardware_config = None
771 hardware_config = HardwareConfig.objects.get(relation__host=host)
773 hardware_config = HardwareConfig()
775 relation = HostHardwareRelation()
778 relation.config = hardware_config
779 relation.config.save()
780 relation.config = relation.config
783 hardware_config.clear_delta()
784 hardware_config.set_image(host.config.image.lab_id)
785 hardware_config.set_hostname(host.template.resource.name)
786 hardware_config.set_power("on")
787 hardware_config.set_ipmi_create(True)
788 hardware_config.save()
791 def makeAccessConfig(cls, users, access_type, revoke=False, job=Job(), context=False):
793 relation = AccessRelation()
795 config = AccessConfig()
796 config.access_type = access_type
799 relation.config = config
803 config.set_context(context)
804 config.set_access_type(access_type)
805 config.set_revoke(revoke)
806 config.set_user(user)
810 def makeNetworkConfigs(cls, hosts=[], job=Job()):
812 network_config = None
814 network_config = NetworkConfig.objects.get(relation__host=host)
816 network_config = NetworkConfig.objects.create()
818 relation = HostNetworkRelation()
821 network_config.save()
822 relation.config = network_config
824 network_config.clear_delta()
826 for interface in host.interfaces.all():
827 network_config.add_interface(interface)
828 network_config.save()
831 def makeSoftware(cls, hosts=[], job=Job()):
832 def init_config(host):
833 opnfv_config = OpnfvApiConfig()
835 opnfv = host.config.bundle.opnfv_config.first()
836 opnfv_config.installer = opnfv.installer.name
837 opnfv_config.scenario = opnfv.scenario.name
845 opnfv_config = init_config(host)
848 opnfv_config.roles.add(host)
849 software_config = SoftwareConfig.objects.create(opnfv=opnfv_config)
850 software_config.save()
851 software_relation = SoftwareRelation.objects.create(job=job, config=software_config)
852 software_relation.save()
853 return software_relation