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 (
30 class JobStatus(object):
37 class LabManagerTracker(object):
40 def get(cls, lab_name, token):
42 Takes in a lab name (from a url path)
43 returns a lab manager instance for that lab, if it exists
46 lab = Lab.objects.get(name=lab_name)
48 raise PermissionDenied("Lab not found")
49 if lab.api_token == token:
50 return LabManager(lab)
51 raise PermissionDenied("Lab not authorized")
54 class LabManager(object):
56 This is the class that will ultimately handle all REST calls to
58 handles jobs, inventory, status, etc
59 may need to create helper classes
62 def __init__(self, lab):
65 def update_host_remote_info(self, data, host_id):
66 host = get_object_or_404(Host, labid=host_id, lab=self.lab)
69 info['address'] = data['address']
70 info['mac_address'] = data['mac_address']
71 info['password'] = data['password']
72 info['user'] = data['user']
73 info['type'] = data['type']
74 info['versions'] = json.dumps(data['versions'])
75 except Exception as e:
76 return {"error": "invalid arguement: " + str(e)}
77 remote_info = host.remote_management
78 if "default" in remote_info.mac_address:
79 remote_info = RemoteInfo()
80 remote_info.address = info['address']
81 remote_info.mac_address = info['mac_address']
82 remote_info.password = info['password']
83 remote_info.user = info['user']
84 remote_info.type = info['type']
85 remote_info.versions = info['versions']
87 host.remote_management = remote_info
89 return {"status": "success"}
91 def get_profile(self):
93 prof['name'] = self.lab.name
95 "phone": self.lab.contact_phone,
96 "email": self.lab.contact_email
98 prof['host_count'] = []
99 for host in HostProfile.objects.filter(labs=self.lab):
100 count = Host.objects.filter(profile=host, lab=self.lab).count()
101 prof['host_count'].append(
109 def get_inventory(self):
111 hosts = Host.objects.filter(lab=self.lab)
112 images = Image.objects.filter(from_lab=self.lab)
113 profiles = HostProfile.objects.filter(labs=self.lab)
114 inventory['hosts'] = self.serialize_hosts(hosts)
115 inventory['images'] = self.serialize_images(images)
116 inventory['host_types'] = self.serialize_host_profiles(profiles)
119 def get_host(self, hostname):
120 host = get_object_or_404(Host, labid=hostname, lab=self.lab)
122 "booked": host.booked,
123 "working": host.working,
124 "type": host.profile.name
127 def update_host(self, hostname, data):
128 host = get_object_or_404(Host, labid=hostname, lab=self.lab)
129 if "working" in data:
130 working = data['working'] == "true"
131 host.working = working
133 return self.get_host(hostname)
135 def get_status(self):
136 return {"status": self.lab.status}
138 def set_status(self, payload):
141 def get_current_jobs(self):
142 jobs = Job.objects.filter(booking__lab=self.lab)
144 return self.serialize_jobs(jobs, status=JobStatus.CURRENT)
146 def get_new_jobs(self):
147 jobs = Job.objects.filter(booking__lab=self.lab)
149 return self.serialize_jobs(jobs, status=JobStatus.NEW)
151 def get_done_jobs(self):
152 jobs = Job.objects.filter(booking__lab=self.lab)
154 return self.serialize_jobs(jobs, status=JobStatus.DONE)
156 def get_job(self, jobid):
157 return Job.objects.get(pk=jobid).to_dict()
159 def update_job(self, jobid, data):
162 def serialize_jobs(self, jobs, status=JobStatus.NEW):
165 jsonized_job = job.get_delta(status)
166 if len(jsonized_job['payload']) < 1:
168 job_ser.append(jsonized_job)
172 def serialize_hosts(self, hosts):
177 h['hostname'] = host.name
178 h['host_type'] = host.profile.name
179 for iface in host.interfaces.all():
181 eth['mac'] = iface.mac_address
182 eth['busaddr'] = iface.bus_address
183 eth['name'] = iface.name
184 eth['switchport'] = {"switch_name": iface.switch_name, "port_name": iface.port_name}
185 h['interfaces'].append(eth)
188 def serialize_images(self, images):
194 "lab_id": image.lab_id,
195 "dashboard_id": image.id
200 def serialize_host_profiles(self, profiles):
202 for profile in profiles:
205 "cores": profile.cpuprofile.first().cores,
206 "arch": profile.cpuprofile.first().architecture,
207 "cpus": profile.cpuprofile.first().cpus,
210 for disk in profile.storageprofile.all():
213 "type": disk.media_type,
217 p['description'] = profile.description
219 for iface in profile.interfaceprofile.all():
220 p['interfaces'].append(
222 "speed": iface.speed,
227 p['ram'] = {"amount": profile.ramprofile.first().amount}
228 p['name'] = profile.name
229 profile_ser.append(p)
233 class Job(models.Model):
235 This is the class that is serialized and put into the api
237 booking = models.OneToOneField(Booking, on_delete=models.CASCADE, null=True)
238 status = models.IntegerField(default=JobStatus.NEW)
239 complete = models.BooleanField(default=False)
245 for relation in AccessRelation.objects.filter(job=self):
246 if 'access' not in d:
248 d['access'][relation.task_id] = relation.config.to_dict()
249 for relation in SoftwareRelation.objects.filter(job=self):
250 if 'software' not in d:
252 d['software'][relation.task_id] = relation.config.to_dict()
253 for relation in HostHardwareRelation.objects.filter(job=self):
254 if 'hardware' not in d:
256 d['hardware'][relation.task_id] = relation.config.to_dict()
257 for relation in HostNetworkRelation.objects.filter(job=self):
258 if 'network' not in d:
260 d['network'][relation.task_id] = relation.config.to_dict()
261 for relation in SnapshotRelation.objects.filter(job=self):
262 if 'snapshot' not in d:
264 d['snapshot'][relation.task_id] = relation.config.to_dict()
270 def get_tasklist(self, status="all"):
273 HostHardwareRelation,
281 tasklist += list(cls.objects.filter(job=self))
284 tasklist += list(cls.objects.filter(job=self).filter(status=status))
287 def is_fulfilled(self):
289 This method should return true if all of the job's tasks are done,
292 my_tasks = self.get_tasklist()
293 for task in my_tasks:
294 if task.status != JobStatus.DONE:
298 def get_delta(self, status):
302 for relation in AccessRelation.objects.filter(job=self).filter(status=status):
303 if 'access' not in d:
305 d['access'][relation.task_id] = relation.config.get_delta()
306 for relation in SoftwareRelation.objects.filter(job=self).filter(status=status):
307 if 'software' not in d:
309 d['software'][relation.task_id] = relation.config.get_delta()
310 for relation in HostHardwareRelation.objects.filter(job=self).filter(status=status):
311 if 'hardware' not in d:
313 d['hardware'][relation.task_id] = relation.config.get_delta()
314 for relation in HostNetworkRelation.objects.filter(job=self).filter(status=status):
315 if 'network' not in d:
317 d['network'][relation.task_id] = relation.config.get_delta()
318 for relation in SnapshotRelation.objects.filter(job=self).filter(status=status):
319 if 'snapshot' not in d:
321 d['snapshot'][relation.task_id] = relation.config.get_delta()
327 return json.dumps(self.to_dict())
330 class TaskConfig(models.Model):
338 return json.dumps(self.to_dict())
340 def clear_delta(self):
344 class OpnfvApiConfig(models.Model):
346 installer = models.CharField(max_length=200)
347 scenario = models.CharField(max_length=300)
348 roles = models.ManyToManyField(Host)
349 delta = models.TextField()
354 d['installer'] = self.installer
356 d['scenario'] = self.scenario
358 hosts = self.roles.all()
361 for host in self.roles.all():
362 d['roles'].append({host.labid: host.config.opnfvRole.name})
367 return json.dumps(self.to_dict())
369 def set_installer(self, installer):
370 self.installer = installer
371 d = json.loads(self.delta)
372 d['installer'] = installer
373 self.delta = json.dumps(d)
375 def set_scenario(self, scenario):
376 self.scenario = scenario
377 d = json.loads(self.delta)
378 d['scenario'] = scenario
379 self.delta = json.dumps(d)
381 def add_role(self, host):
383 d = json.loads(self.delta)
386 d['roles'].append({host.labid: host.config.opnfvRole.name})
387 self.delta = json.dumps(d)
389 def clear_delta(self):
394 self.delta = self.to_json()
396 return json.loads(self.delta)
399 class AccessConfig(TaskConfig):
400 access_type = models.CharField(max_length=50)
401 user = models.ForeignKey(User, on_delete=models.CASCADE)
402 revoke = models.BooleanField(default=False)
403 context = models.TextField(default="")
404 delta = models.TextField(default="{}")
408 d['access_type'] = self.access_type
409 d['user'] = self.user.id
410 d['revoke'] = self.revoke
412 d['context'] = json.loads(self.context)
419 self.delta = self.to_json()
421 d = json.loads(self.delta)
422 d["lab_token"] = self.accessrelation.lab_token
427 return json.dumps(self.to_dict())
429 def clear_delta(self):
431 d["lab_token"] = self.accessrelation.lab_token
432 self.delta = json.dumps(d)
434 def set_access_type(self, access_type):
435 self.access_type = access_type
436 d = json.loads(self.delta)
437 d['access_type'] = access_type
438 self.delta = json.dumps(d)
440 def set_user(self, user):
442 d = json.loads(self.delta)
443 d['user'] = self.user.id
444 self.delta = json.dumps(d)
446 def set_revoke(self, revoke):
448 d = json.loads(self.delta)
450 self.delta = json.dumps(d)
452 def set_context(self, context):
453 self.context = json.dumps(context)
454 d = json.loads(self.delta)
455 d['context'] = context
456 self.delta = json.dumps(d)
459 class SoftwareConfig(TaskConfig):
461 handled opnfv installations, etc
463 opnfv = models.ForeignKey(OpnfvApiConfig, on_delete=models.CASCADE)
468 d['opnfv'] = self.opnfv.to_dict()
470 d["lab_token"] = self.softwarerelation.lab_token
471 self.delta = json.dumps(d)
477 d['opnfv'] = self.opnfv.get_delta()
478 d['lab_token'] = self.softwarerelation.lab_token
482 def clear_delta(self):
483 self.opnfv.clear_delta()
486 return json.dumps(self.to_dict())
489 class HardwareConfig(TaskConfig):
491 handles imaging, user accounts, etc
493 image = models.CharField(max_length=100, default="defimage")
494 power = models.CharField(max_length=100, default="off")
495 hostname = models.CharField(max_length=100, default="hostname")
496 ipmi_create = models.BooleanField(default=False)
497 delta = models.TextField()
501 d['image'] = self.image
502 d['power'] = self.power
503 d['hostname'] = self.hostname
504 d['ipmi_create'] = str(self.ipmi_create)
505 d['id'] = self.hosthardwarerelation.host.labid
509 return json.dumps(self.to_dict())
513 self.delta = self.to_json()
515 d = json.loads(self.delta)
516 d['lab_token'] = self.hosthardwarerelation.lab_token
519 def clear_delta(self):
521 d["id"] = self.hosthardwarerelation.host.labid
522 d["lab_token"] = self.hosthardwarerelation.lab_token
523 self.delta = json.dumps(d)
525 def set_image(self, image):
527 d = json.loads(self.delta)
528 d['image'] = self.image
529 self.delta = json.dumps(d)
531 def set_power(self, power):
533 d = json.loads(self.delta)
535 self.delta = json.dumps(d)
537 def set_hostname(self, hostname):
538 self.hostname = hostname
539 d = json.loads(self.delta)
540 d['hostname'] = hostname
541 self.delta = json.dumps(d)
543 def set_ipmi_create(self, ipmi_create):
544 self.ipmi_create = ipmi_create
545 d = json.loads(self.delta)
546 d['ipmi_create'] = ipmi_create
547 self.delta = json.dumps(d)
550 class NetworkConfig(TaskConfig):
552 handles network configuration
554 interfaces = models.ManyToManyField(Interface)
555 delta = models.TextField()
559 hid = self.hostnetworkrelation.host.labid
561 for interface in self.interfaces.all():
562 d[hid][interface.mac_address] = []
563 for vlan in interface.config.all():
564 d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
569 return json.dumps(self.to_dict())
573 self.delta = self.to_json()
575 d = json.loads(self.delta)
576 d['lab_token'] = self.hostnetworkrelation.lab_token
579 def clear_delta(self):
580 self.delta = json.dumps(self.to_dict())
583 def add_interface(self, interface):
584 self.interfaces.add(interface)
585 d = json.loads(self.delta)
586 hid = self.hostnetworkrelation.host.labid
589 d[hid][interface.mac_address] = []
590 for vlan in interface.config.all():
591 d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
592 self.delta = json.dumps(d)
595 class SnapshotConfig(TaskConfig):
597 host = models.ForeignKey(Host, null=True, on_delete=models.DO_NOTHING)
598 image = models.IntegerField(null=True)
599 dashboard_id = models.IntegerField()
600 delta = models.TextField(default="{}")
605 d['host'] = self.host.labid
607 d['image'] = self.image
608 d['dashboard_id'] = self.dashboard_id
612 return json.dumps(self.to_dict())
616 self.delta = self.to_json()
618 d = json.loads(self.delta)
621 def clear_delta(self):
622 self.delta = json.dumps(self.to_dict())
625 def set_host(self, host):
627 d = json.loads(self.delta)
628 d['host'] = host.labid
629 self.delta = json.dumps(d)
631 def set_image(self, image):
633 d = json.loads(self.delta)
634 d['image'] = self.image
635 self.delta = json.dumps(d)
637 def clear_image(self):
639 d = json.loads(self.delta)
641 self.delta = json.dumps(d)
643 def set_dashboard_id(self, dash):
644 self.dashboard_id = dash
645 d = json.loads(self.delta)
646 d['dashboard_id'] = self.dashboard_id
647 self.delta = json.dumps(d)
650 def get_task(task_id):
651 for taskclass in [AccessRelation, SoftwareRelation, HostHardwareRelation, HostNetworkRelation, SnapshotRelation]:
653 ret = taskclass.objects.get(task_id=task_id)
655 except taskclass.DoesNotExist:
657 from django.core.exceptions import ObjectDoesNotExist
658 raise ObjectDoesNotExist("Could not find matching TaskRelation instance")
662 return str(uuid.uuid4())
665 class TaskRelation(models.Model):
666 status = models.IntegerField(default=JobStatus.NEW)
667 job = models.ForeignKey(Job, on_delete=models.CASCADE)
668 config = models.OneToOneField(TaskConfig, on_delete=models.CASCADE)
669 task_id = models.CharField(default=get_task_uuid, max_length=37)
670 lab_token = models.CharField(default="null", max_length=50)
671 message = models.TextField(default="")
673 def delete(self, *args, **kwargs):
675 return super(self.__class__, self).delete(*args, **kwargs)
678 return "Generic Task"
684 class AccessRelation(TaskRelation):
685 config = models.OneToOneField(AccessConfig, on_delete=models.CASCADE)
690 def delete(self, *args, **kwargs):
692 return super(self.__class__, self).delete(*args, **kwargs)
695 class SoftwareRelation(TaskRelation):
696 config = models.OneToOneField(SoftwareConfig, on_delete=models.CASCADE)
699 return "Software Configuration Task"
701 def delete(self, *args, **kwargs):
703 return super(self.__class__, self).delete(*args, **kwargs)
706 class HostHardwareRelation(TaskRelation):
707 host = models.ForeignKey(Host, on_delete=models.CASCADE)
708 config = models.OneToOneField(HardwareConfig, on_delete=models.CASCADE)
711 return "Hardware Configuration Task"
714 return self.config.to_dict()
716 def delete(self, *args, **kwargs):
718 return super(self.__class__, self).delete(*args, **kwargs)
721 class HostNetworkRelation(TaskRelation):
722 host = models.ForeignKey(Host, on_delete=models.CASCADE)
723 config = models.OneToOneField(NetworkConfig, on_delete=models.CASCADE)
726 return "Network Configuration Task"
728 def delete(self, *args, **kwargs):
730 return super(self.__class__, self).delete(*args, **kwargs)
733 class SnapshotRelation(TaskRelation):
734 snapshot = models.ForeignKey(Image, on_delete=models.CASCADE)
735 config = models.OneToOneField(SnapshotConfig, on_delete=models.CASCADE)
738 return "Snapshot Task"
741 return self.config.to_dict()
743 def delete(self, *args, **kwargs):
745 return super(self.__class__, self).delete(*args, **kwargs)
748 class JobFactory(object):
751 def reimageHost(cls, new_image, booking, host):
753 This method will make all necessary changes to make a lab
756 job = Job.objects.get(booking=booking)
757 # make hardware task new
758 hardware_relation = HostHardwareRelation.objects.get(host=host, job=job)
759 hardware_relation.config.set_image(new_image.lab_id)
760 hardware_relation.config.save()
761 hardware_relation.status = JobStatus.NEW
763 # re-apply networking after host is reset
764 net_relation = HostNetworkRelation.objects.get(host=host, job=job)
765 net_relation.status = JobStatus.NEW
767 # re-apply ssh access after host is reset
768 ssh_relation = AccessRelation.objects.get(job=job, config__access_type="ssh")
769 ssh_relation.status = JobStatus.NEW
771 # save them all at once to reduce the chance
772 # of a lab polling and only seeing partial change
773 hardware_relation.save()
778 def makeSnapshotTask(cls, image, booking, host):
779 relation = SnapshotRelation()
780 job = Job.objects.get(booking=booking)
781 config = SnapshotConfig.objects.create(dashboard_id=image.id)
784 relation.config = config
785 relation.config.save()
786 relation.config = relation.config
787 relation.snapshot = image
791 config.set_host(host)
795 def makeCompleteJob(cls, booking):
796 hosts = Host.objects.filter(bundle=booking.resource)
799 job = Job.objects.get(booking=booking)
801 job = Job.objects.create(status=JobStatus.NEW, booking=booking)
802 cls.makeHardwareConfigs(
806 cls.makeNetworkConfigs(
814 all_users = list(booking.collaborators.all())
815 all_users.append(booking.owner)
816 cls.makeAccessConfig(
822 for user in all_users:
824 cls.makeAccessConfig(
830 "key": user.userprofile.ssh_public_key.open().read().decode(encoding="UTF-8"),
831 "hosts": [host.labid for host in hosts]
838 def makeHardwareConfigs(cls, hosts=[], job=Job()):
840 hardware_config = None
842 hardware_config = HardwareConfig.objects.get(relation__host=host)
844 hardware_config = HardwareConfig()
846 relation = HostHardwareRelation()
849 relation.config = hardware_config
850 relation.config.save()
851 relation.config = relation.config
854 hardware_config.clear_delta()
855 hardware_config.set_image(host.config.image.lab_id)
856 hardware_config.set_hostname(host.template.resource.name)
857 hardware_config.set_power("on")
858 hardware_config.set_ipmi_create(True)
859 hardware_config.save()
862 def makeAccessConfig(cls, users, access_type, revoke=False, job=Job(), context=False):
864 relation = AccessRelation()
866 config = AccessConfig()
867 config.access_type = access_type
870 relation.config = config
874 config.set_context(context)
875 config.set_access_type(access_type)
876 config.set_revoke(revoke)
877 config.set_user(user)
881 def makeNetworkConfigs(cls, hosts=[], job=Job()):
883 network_config = None
885 network_config = NetworkConfig.objects.get(relation__host=host)
887 network_config = NetworkConfig.objects.create()
889 relation = HostNetworkRelation()
892 network_config.save()
893 relation.config = network_config
895 network_config.clear_delta()
897 for interface in host.interfaces.all():
898 network_config.add_interface(interface)
899 network_config.save()
902 def makeSoftware(cls, hosts=[], job=Job()):
903 def init_config(host):
904 opnfv_config = OpnfvApiConfig()
906 opnfv = host.config.bundle.opnfv_config.first()
907 opnfv_config.installer = opnfv.installer.name
908 opnfv_config.scenario = opnfv.scenario.name
916 opnfv_config = init_config(host)
919 opnfv_config.roles.add(host)
920 software_config = SoftwareConfig.objects.create(opnfv=opnfv_config)
921 software_config.save()
922 software_relation = SoftwareRelation.objects.create(job=job, config=software_config)
923 software_relation.save()
924 return software_relation