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
14 from django.shortcuts import get_object_or_404
19 from booking.models import Booking
20 from resource_inventory.models import (
29 from resource_inventory.idf_templater import IDFTemplater
30 from resource_inventory.pdf_templater import PDFTemplater
33 class JobStatus(object):
40 class LabManagerTracker(object):
43 def get(cls, lab_name, token):
45 Takes in a lab name (from a url path)
46 returns a lab manager instance for that lab, if it exists
49 lab = Lab.objects.get(name=lab_name)
51 raise PermissionDenied("Lab not found")
52 if lab.api_token == token:
53 return LabManager(lab)
54 raise PermissionDenied("Lab not authorized")
57 class LabManager(object):
59 This is the class that will ultimately handle all REST calls to
61 handles jobs, inventory, status, etc
62 may need to create helper classes
65 def __init__(self, lab):
68 def update_host_remote_info(self, data, host_id):
69 host = get_object_or_404(Host, labid=host_id, lab=self.lab)
72 info['address'] = data['address']
73 info['mac_address'] = data['mac_address']
74 info['password'] = data['password']
75 info['user'] = data['user']
76 info['type'] = data['type']
77 info['versions'] = json.dumps(data['versions'])
78 except Exception as e:
79 return {"error": "invalid arguement: " + str(e)}
80 remote_info = host.remote_management
81 if "default" in remote_info.mac_address:
82 remote_info = RemoteInfo()
83 remote_info.address = info['address']
84 remote_info.mac_address = info['mac_address']
85 remote_info.password = info['password']
86 remote_info.user = info['user']
87 remote_info.type = info['type']
88 remote_info.versions = info['versions']
90 host.remote_management = remote_info
92 booking = Booking.objects.get(resource=host.bundle)
93 self.update_xdf(booking)
94 return {"status": "success"}
96 def update_xdf(self, booking):
97 booking.pdf = PDFTemplater.makePDF(booking)
98 booking.idf = IDFTemplater().makeIDF(booking)
101 def get_profile(self):
103 prof['name'] = self.lab.name
105 "phone": self.lab.contact_phone,
106 "email": self.lab.contact_email
108 prof['host_count'] = []
109 for host in HostProfile.objects.filter(labs=self.lab):
110 count = Host.objects.filter(profile=host, lab=self.lab).count()
111 prof['host_count'].append(
119 def get_inventory(self):
121 hosts = Host.objects.filter(lab=self.lab)
122 images = Image.objects.filter(from_lab=self.lab)
123 profiles = HostProfile.objects.filter(labs=self.lab)
124 inventory['hosts'] = self.serialize_hosts(hosts)
125 inventory['images'] = self.serialize_images(images)
126 inventory['host_types'] = self.serialize_host_profiles(profiles)
129 def get_host(self, hostname):
130 host = get_object_or_404(Host, labid=hostname, lab=self.lab)
132 "booked": host.booked,
133 "working": host.working,
134 "type": host.profile.name
137 def update_host(self, hostname, data):
138 host = get_object_or_404(Host, labid=hostname, lab=self.lab)
139 if "working" in data:
140 working = data['working'] == "true"
141 host.working = working
143 return self.get_host(hostname)
145 def get_status(self):
146 return {"status": self.lab.status}
148 def set_status(self, payload):
151 def get_current_jobs(self):
152 jobs = Job.objects.filter(booking__lab=self.lab)
154 return self.serialize_jobs(jobs, status=JobStatus.CURRENT)
156 def get_new_jobs(self):
157 jobs = Job.objects.filter(booking__lab=self.lab)
159 return self.serialize_jobs(jobs, status=JobStatus.NEW)
161 def get_done_jobs(self):
162 jobs = Job.objects.filter(booking__lab=self.lab)
164 return self.serialize_jobs(jobs, status=JobStatus.DONE)
166 def get_job(self, jobid):
167 return Job.objects.get(pk=jobid).to_dict()
169 def update_job(self, jobid, data):
172 def serialize_jobs(self, jobs, status=JobStatus.NEW):
175 jsonized_job = job.get_delta(status)
176 if len(jsonized_job['payload']) < 1:
178 job_ser.append(jsonized_job)
182 def serialize_hosts(self, hosts):
187 h['hostname'] = host.name
188 h['host_type'] = host.profile.name
189 for iface in host.interfaces.all():
191 eth['mac'] = iface.mac_address
192 eth['busaddr'] = iface.bus_address
193 eth['name'] = iface.name
194 eth['switchport'] = {"switch_name": iface.switch_name, "port_name": iface.port_name}
195 h['interfaces'].append(eth)
198 def serialize_images(self, images):
204 "lab_id": image.lab_id,
205 "dashboard_id": image.id
210 def serialize_host_profiles(self, profiles):
212 for profile in profiles:
215 "cores": profile.cpuprofile.first().cores,
216 "arch": profile.cpuprofile.first().architecture,
217 "cpus": profile.cpuprofile.first().cpus,
220 for disk in profile.storageprofile.all():
223 "type": disk.media_type,
227 p['description'] = profile.description
229 for iface in profile.interfaceprofile.all():
230 p['interfaces'].append(
232 "speed": iface.speed,
237 p['ram'] = {"amount": profile.ramprofile.first().amount}
238 p['name'] = profile.name
239 profile_ser.append(p)
243 class Job(models.Model):
245 This is the class that is serialized and put into the api
247 booking = models.OneToOneField(Booking, on_delete=models.CASCADE, null=True)
248 status = models.IntegerField(default=JobStatus.NEW)
249 complete = models.BooleanField(default=False)
255 for relation in AccessRelation.objects.filter(job=self):
256 if 'access' not in d:
258 d['access'][relation.task_id] = relation.config.to_dict()
259 for relation in SoftwareRelation.objects.filter(job=self):
260 if 'software' not in d:
262 d['software'][relation.task_id] = relation.config.to_dict()
263 for relation in HostHardwareRelation.objects.filter(job=self):
264 if 'hardware' not in d:
266 d['hardware'][relation.task_id] = relation.config.to_dict()
267 for relation in HostNetworkRelation.objects.filter(job=self):
268 if 'network' not in d:
270 d['network'][relation.task_id] = relation.config.to_dict()
271 for relation in SnapshotRelation.objects.filter(job=self):
272 if 'snapshot' not in d:
274 d['snapshot'][relation.task_id] = relation.config.to_dict()
280 def get_tasklist(self, status="all"):
283 HostHardwareRelation,
291 tasklist += list(cls.objects.filter(job=self))
294 tasklist += list(cls.objects.filter(job=self).filter(status=status))
297 def is_fulfilled(self):
299 This method should return true if all of the job's tasks are done,
302 my_tasks = self.get_tasklist()
303 for task in my_tasks:
304 if task.status != JobStatus.DONE:
308 def get_delta(self, status):
312 for relation in AccessRelation.objects.filter(job=self).filter(status=status):
313 if 'access' not in d:
315 d['access'][relation.task_id] = relation.config.get_delta()
316 for relation in SoftwareRelation.objects.filter(job=self).filter(status=status):
317 if 'software' not in d:
319 d['software'][relation.task_id] = relation.config.get_delta()
320 for relation in HostHardwareRelation.objects.filter(job=self).filter(status=status):
321 if 'hardware' not in d:
323 d['hardware'][relation.task_id] = relation.config.get_delta()
324 for relation in HostNetworkRelation.objects.filter(job=self).filter(status=status):
325 if 'network' not in d:
327 d['network'][relation.task_id] = relation.config.get_delta()
328 for relation in SnapshotRelation.objects.filter(job=self).filter(status=status):
329 if 'snapshot' not in d:
331 d['snapshot'][relation.task_id] = relation.config.get_delta()
337 return json.dumps(self.to_dict())
340 class TaskConfig(models.Model):
348 return json.dumps(self.to_dict())
350 def clear_delta(self):
354 class OpnfvApiConfig(models.Model):
356 installer = models.CharField(max_length=200)
357 scenario = models.CharField(max_length=300)
358 roles = models.ManyToManyField(Host)
359 delta = models.TextField()
360 opnfv_config = models.ForeignKey(OPNFVConfig, null=True, on_delete=models.SET_NULL)
364 if not self.opnfv_config:
367 d['installer'] = self.installer
369 d['scenario'] = self.scenario
371 hosts = self.roles.all()
376 host.labid: self.opnfv_config.host_opnfv_config.get(
377 host_config__pk=host.config.pk
384 return json.dumps(self.to_dict())
386 def set_installer(self, installer):
387 self.installer = installer
388 d = json.loads(self.delta)
389 d['installer'] = installer
390 self.delta = json.dumps(d)
392 def set_scenario(self, scenario):
393 self.scenario = scenario
394 d = json.loads(self.delta)
395 d['scenario'] = scenario
396 self.delta = json.dumps(d)
398 def add_role(self, host):
400 d = json.loads(self.delta)
403 d['roles'].append({host.labid: host.config.opnfvRole.name})
404 self.delta = json.dumps(d)
406 def clear_delta(self):
411 self.delta = self.to_json()
413 return json.loads(self.delta)
416 class AccessConfig(TaskConfig):
417 access_type = models.CharField(max_length=50)
418 user = models.ForeignKey(User, on_delete=models.CASCADE)
419 revoke = models.BooleanField(default=False)
420 context = models.TextField(default="")
421 delta = models.TextField(default="{}")
425 d['access_type'] = self.access_type
426 d['user'] = self.user.id
427 d['revoke'] = self.revoke
429 d['context'] = json.loads(self.context)
436 self.delta = self.to_json()
438 d = json.loads(self.delta)
439 d["lab_token"] = self.accessrelation.lab_token
444 return json.dumps(self.to_dict())
446 def clear_delta(self):
448 d["lab_token"] = self.accessrelation.lab_token
449 self.delta = json.dumps(d)
451 def set_access_type(self, access_type):
452 self.access_type = access_type
453 d = json.loads(self.delta)
454 d['access_type'] = access_type
455 self.delta = json.dumps(d)
457 def set_user(self, user):
459 d = json.loads(self.delta)
460 d['user'] = self.user.id
461 self.delta = json.dumps(d)
463 def set_revoke(self, revoke):
465 d = json.loads(self.delta)
467 self.delta = json.dumps(d)
469 def set_context(self, context):
470 self.context = json.dumps(context)
471 d = json.loads(self.delta)
472 d['context'] = context
473 self.delta = json.dumps(d)
476 class SoftwareConfig(TaskConfig):
478 handled opnfv installations, etc
480 opnfv = models.ForeignKey(OpnfvApiConfig, on_delete=models.CASCADE)
485 d['opnfv'] = self.opnfv.to_dict()
487 d["lab_token"] = self.softwarerelation.lab_token
488 self.delta = json.dumps(d)
494 d['opnfv'] = self.opnfv.get_delta()
495 d['lab_token'] = self.softwarerelation.lab_token
499 def clear_delta(self):
500 self.opnfv.clear_delta()
503 return json.dumps(self.to_dict())
506 class HardwareConfig(TaskConfig):
508 handles imaging, user accounts, etc
510 image = models.CharField(max_length=100, default="defimage")
511 power = models.CharField(max_length=100, default="off")
512 hostname = models.CharField(max_length=100, default="hostname")
513 ipmi_create = models.BooleanField(default=False)
514 delta = models.TextField()
518 d['image'] = self.image
519 d['power'] = self.power
520 d['hostname'] = self.hostname
521 d['ipmi_create'] = str(self.ipmi_create)
522 d['id'] = self.hosthardwarerelation.host.labid
526 return json.dumps(self.to_dict())
530 self.delta = self.to_json()
532 d = json.loads(self.delta)
533 d['lab_token'] = self.hosthardwarerelation.lab_token
536 def clear_delta(self):
538 d["id"] = self.hosthardwarerelation.host.labid
539 d["lab_token"] = self.hosthardwarerelation.lab_token
540 self.delta = json.dumps(d)
542 def set_image(self, image):
544 d = json.loads(self.delta)
545 d['image'] = self.image
546 self.delta = json.dumps(d)
548 def set_power(self, power):
550 d = json.loads(self.delta)
552 self.delta = json.dumps(d)
554 def set_hostname(self, hostname):
555 self.hostname = hostname
556 d = json.loads(self.delta)
557 d['hostname'] = hostname
558 self.delta = json.dumps(d)
560 def set_ipmi_create(self, ipmi_create):
561 self.ipmi_create = ipmi_create
562 d = json.loads(self.delta)
563 d['ipmi_create'] = ipmi_create
564 self.delta = json.dumps(d)
567 class NetworkConfig(TaskConfig):
569 handles network configuration
571 interfaces = models.ManyToManyField(Interface)
572 delta = models.TextField()
576 hid = self.hostnetworkrelation.host.labid
578 for interface in self.interfaces.all():
579 d[hid][interface.mac_address] = []
580 for vlan in interface.config.all():
581 d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
586 return json.dumps(self.to_dict())
590 self.delta = self.to_json()
592 d = json.loads(self.delta)
593 d['lab_token'] = self.hostnetworkrelation.lab_token
596 def clear_delta(self):
597 self.delta = json.dumps(self.to_dict())
600 def add_interface(self, interface):
601 self.interfaces.add(interface)
602 d = json.loads(self.delta)
603 hid = self.hostnetworkrelation.host.labid
606 d[hid][interface.mac_address] = []
607 for vlan in interface.config.all():
608 d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
609 self.delta = json.dumps(d)
612 class SnapshotConfig(TaskConfig):
614 host = models.ForeignKey(Host, null=True, on_delete=models.DO_NOTHING)
615 image = models.IntegerField(null=True)
616 dashboard_id = models.IntegerField()
617 delta = models.TextField(default="{}")
622 d['host'] = self.host.labid
624 d['image'] = self.image
625 d['dashboard_id'] = self.dashboard_id
629 return json.dumps(self.to_dict())
633 self.delta = self.to_json()
635 d = json.loads(self.delta)
638 def clear_delta(self):
639 self.delta = json.dumps(self.to_dict())
642 def set_host(self, host):
644 d = json.loads(self.delta)
645 d['host'] = host.labid
646 self.delta = json.dumps(d)
648 def set_image(self, image):
650 d = json.loads(self.delta)
651 d['image'] = self.image
652 self.delta = json.dumps(d)
654 def clear_image(self):
656 d = json.loads(self.delta)
658 self.delta = json.dumps(d)
660 def set_dashboard_id(self, dash):
661 self.dashboard_id = dash
662 d = json.loads(self.delta)
663 d['dashboard_id'] = self.dashboard_id
664 self.delta = json.dumps(d)
667 def get_task(task_id):
668 for taskclass in [AccessRelation, SoftwareRelation, HostHardwareRelation, HostNetworkRelation, SnapshotRelation]:
670 ret = taskclass.objects.get(task_id=task_id)
672 except taskclass.DoesNotExist:
674 from django.core.exceptions import ObjectDoesNotExist
675 raise ObjectDoesNotExist("Could not find matching TaskRelation instance")
679 return str(uuid.uuid4())
682 class TaskRelation(models.Model):
683 status = models.IntegerField(default=JobStatus.NEW)
684 job = models.ForeignKey(Job, on_delete=models.CASCADE)
685 config = models.OneToOneField(TaskConfig, on_delete=models.CASCADE)
686 task_id = models.CharField(default=get_task_uuid, max_length=37)
687 lab_token = models.CharField(default="null", max_length=50)
688 message = models.TextField(default="")
690 def delete(self, *args, **kwargs):
692 return super(self.__class__, self).delete(*args, **kwargs)
695 return "Generic Task"
701 class AccessRelation(TaskRelation):
702 config = models.OneToOneField(AccessConfig, on_delete=models.CASCADE)
707 def delete(self, *args, **kwargs):
709 return super(self.__class__, self).delete(*args, **kwargs)
712 class SoftwareRelation(TaskRelation):
713 config = models.OneToOneField(SoftwareConfig, on_delete=models.CASCADE)
716 return "Software Configuration Task"
718 def delete(self, *args, **kwargs):
720 return super(self.__class__, self).delete(*args, **kwargs)
723 class HostHardwareRelation(TaskRelation):
724 host = models.ForeignKey(Host, on_delete=models.CASCADE)
725 config = models.OneToOneField(HardwareConfig, on_delete=models.CASCADE)
728 return "Hardware Configuration Task"
731 return self.config.to_dict()
733 def delete(self, *args, **kwargs):
735 return super(self.__class__, self).delete(*args, **kwargs)
738 class HostNetworkRelation(TaskRelation):
739 host = models.ForeignKey(Host, on_delete=models.CASCADE)
740 config = models.OneToOneField(NetworkConfig, on_delete=models.CASCADE)
743 return "Network Configuration Task"
745 def delete(self, *args, **kwargs):
747 return super(self.__class__, self).delete(*args, **kwargs)
750 class SnapshotRelation(TaskRelation):
751 snapshot = models.ForeignKey(Image, on_delete=models.CASCADE)
752 config = models.OneToOneField(SnapshotConfig, on_delete=models.CASCADE)
755 return "Snapshot Task"
758 return self.config.to_dict()
760 def delete(self, *args, **kwargs):
762 return super(self.__class__, self).delete(*args, **kwargs)
765 class JobFactory(object):
768 def reimageHost(cls, new_image, booking, host):
770 This method will make all necessary changes to make a lab
773 job = Job.objects.get(booking=booking)
774 # make hardware task new
775 hardware_relation = HostHardwareRelation.objects.get(host=host, job=job)
776 hardware_relation.config.set_image(new_image.lab_id)
777 hardware_relation.config.save()
778 hardware_relation.status = JobStatus.NEW
780 # re-apply networking after host is reset
781 net_relation = HostNetworkRelation.objects.get(host=host, job=job)
782 net_relation.status = JobStatus.NEW
784 # re-apply ssh access after host is reset
785 for relation in AccessRelation.objects.filter(job=job, config__access_type="ssh"):
786 relation.status = JobStatus.NEW
789 hardware_relation.save()
793 def makeSnapshotTask(cls, image, booking, host):
794 relation = SnapshotRelation()
795 job = Job.objects.get(booking=booking)
796 config = SnapshotConfig.objects.create(dashboard_id=image.id)
799 relation.config = config
800 relation.config.save()
801 relation.config = relation.config
802 relation.snapshot = image
806 config.set_host(host)
810 def makeCompleteJob(cls, booking):
811 hosts = Host.objects.filter(bundle=booking.resource)
814 job = Job.objects.get(booking=booking)
816 job = Job.objects.create(status=JobStatus.NEW, booking=booking)
817 cls.makeHardwareConfigs(
821 cls.makeNetworkConfigs(
830 all_users = list(booking.collaborators.all())
831 all_users.append(booking.owner)
832 cls.makeAccessConfig(
838 for user in all_users:
840 cls.makeAccessConfig(
846 "key": user.userprofile.ssh_public_key.open().read().decode(encoding="UTF-8"),
847 "hosts": [host.labid for host in hosts]
854 def makeHardwareConfigs(cls, hosts=[], job=Job()):
856 hardware_config = None
858 hardware_config = HardwareConfig.objects.get(relation__host=host)
860 hardware_config = HardwareConfig()
862 relation = HostHardwareRelation()
865 relation.config = hardware_config
866 relation.config.save()
867 relation.config = relation.config
870 hardware_config.clear_delta()
871 hardware_config.set_image(host.config.image.lab_id)
872 hardware_config.set_hostname(host.template.resource.name)
873 hardware_config.set_power("on")
874 hardware_config.set_ipmi_create(True)
875 hardware_config.save()
878 def makeAccessConfig(cls, users, access_type, revoke=False, job=Job(), context=False):
880 relation = AccessRelation()
882 config = AccessConfig()
883 config.access_type = access_type
886 relation.config = config
890 config.set_context(context)
891 config.set_access_type(access_type)
892 config.set_revoke(revoke)
893 config.set_user(user)
897 def makeNetworkConfigs(cls, hosts=[], job=Job()):
899 network_config = None
901 network_config = NetworkConfig.objects.get(relation__host=host)
903 network_config = NetworkConfig.objects.create()
905 relation = HostNetworkRelation()
908 network_config.save()
909 relation.config = network_config
911 network_config.clear_delta()
913 for interface in host.interfaces.all():
914 network_config.add_interface(interface)
915 network_config.save()
918 def makeSoftware(cls, hosts=[], booking=None, job=Job()):
920 if not booking.opnfv_config:
923 opnfv_api_config = OpnfvApiConfig.objects.create(
924 opnfv_config=booking.opnfv_config,
925 installer=booking.opnfv_config.installer,
926 scenario=booking.opnfv_config.scenario,
930 opnfv_api_config.roles.add(host)
931 software_config = SoftwareConfig.objects.create(opnfv=opnfv_api_config)
932 software_relation = SoftwareRelation.objects.create(job=job, config=software_config)
933 return software_relation