9a7f77555302e8fdb6e1afc112203256aa1d673e
[pharos-tools.git] / dashboard / src / api / models.py
1 ##############################################################################
2 # Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others.
3 #
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 ##############################################################################
9
10
11 from django.db import models
12 from django.core.exceptions import PermissionDenied
13
14 import json
15 import uuid
16
17 from resource_inventory.models import *
18 from booking.models import Booking
19
20
21 class JobStatus(object):
22     NEW = 0
23     CURRENT = 100
24     DONE = 200
25     ERROR = 300
26
27
28 class LabManagerTracker(object):
29
30     @classmethod
31     def get(cls, lab_name, token):
32         """
33         Takes in a lab name (from a url path)
34         returns a lab manager instance for that lab, if it exists
35         """
36         try:
37             lab = Lab.objects.get(name=lab_name)
38         except:
39             raise PermissionDenied("Lab not found")
40         if lab.api_token == token:
41             return LabManager(lab)
42         raise PermissionDenied("Lab not authorized")
43
44
45 class LabManager(object):
46     """
47     This is the class that will ultimately handle all REST calls to
48     lab endpoints.
49     handles jobs, inventory, status, etc
50     may need to create helper classes
51     """
52
53     def __init__(self, lab):
54         self.lab = lab
55
56     def get_profile(self):
57         prof = {}
58         prof['name'] = self.lab.name
59         prof['contact'] = {
60                 "phone": self.lab.contact_phone,
61                 "email": self.lab.contact_email
62                 }
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({
67                 "type": host.name,
68                 "count": count
69                 })
70         return prof
71
72     def get_inventory(self):
73         inventory = {}
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)
80         return inventory
81
82     def get_status(self):
83         return {"status": self.lab.status}
84
85     def set_status(self, payload):
86         {}
87
88     def get_current_jobs(self):
89         jobs = Job.objects.filter(booking__lab=self.lab)
90
91         return self.serialize_jobs(jobs, status=JobStatus.CURRENT)
92
93     def get_new_jobs(self):
94         jobs = Job.objects.filter(booking__lab=self.lab)
95
96         return self.serialize_jobs(jobs, status=JobStatus.NEW)
97
98     def get_done_jobs(self):
99         jobs = Job.objects.filter(booking__lab=self.lab)
100
101         return self.serialize_jobs(jobs, status=JobStatus.DONE)
102
103     def get_job(self, jobid):
104         return Job.objects.get(pk=jobid).to_dict()
105
106     def update_job(self, jobid, data):
107         {}
108
109     def serialize_jobs(self, jobs, status=JobStatus.NEW):
110         job_ser = []
111         for job in jobs:
112             jsonized_job = job.get_delta(status)
113             if len(jsonized_job['payload']) < 1:
114                 continue
115             job_ser.append(jsonized_job)
116
117         return job_ser
118
119     def serialize_hosts(self, hosts):
120         host_ser = []
121         for host in hosts:
122             h = {}
123             h['interfaces'] = []
124             h['hostname'] = host.name
125             h['host_type'] = host.profile.name
126             for iface in host.interfaces.all():
127                 eth = {}
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)
133         return host_ser
134
135     def serialize_images(self, images):
136         images_ser = []
137         for image in images:
138             images_ser.append({
139                 "name": image.name,
140                 "lab_id": image.lab_id,
141                 "dashboard_id": image.id
142                 })
143         return images_ser
144
145     def serialize_host_profiles(self, profiles):
146         profile_ser = []
147         for profile in profiles:
148             p = {}
149             p['cpu'] = {
150                     "cores": profile.cpuprofile.first().cores,
151                     "arch": profile.cpuprofile.first().architecture,
152                     "cpus": profile.cpuprofile.first().cpus,
153                     }
154             p['disks'] = []
155             for disk in profile.storageprofile.all():
156                 d = {
157                         "size": disk.size,
158                         "type": disk.media_type,
159                         "name": disk.name
160                     }
161                 p['disks'].append(d)
162             p['description'] = profile.description
163             p['interfaces'] = []
164             for iface in profile.interfaceprofile.all():
165                 p['interfaces'].append({
166                     "speed": iface.speed,
167                     "name": iface.name
168                     })
169
170             p['ram'] = {"amount": profile.ramprofile.first().amount}
171             p['name'] = profile.name
172             profile_ser.append(p)
173         return profile_ser
174
175
176 class Job(models.Model):
177     """
178     This is the class that is serialized and put into the api
179     """
180     booking = models.OneToOneField(Booking, on_delete=models.CASCADE, null=True)
181     status = models.IntegerField(default=JobStatus.NEW)
182     complete = models.BooleanField(default=False)
183
184     def to_dict(self):
185         d = {}
186         j = {}
187         j['id'] = self.id
188         for relation in AccessRelation.objects.filter(job=self):
189             if 'access' not in d:
190                 d['access'] = {}
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:
194                 d['software'] = {}
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:
198                 d['hardware'] = {}
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:
202                 d['network'] = {}
203             d['network'][relation.task_id] = relation.config.to_dict()
204
205         j['payload'] = d
206
207         return j
208
209     def get_tasklist(self, status="all"):
210         tasklist = []
211         clist = [HostHardwareRelation, AccessRelation, HostNetworkRelation, SoftwareRelation]
212         if status == "all":
213             for cls in clist:
214                 tasklist += list(cls.objects.filter(job=self))
215         else:
216             for cls in clist:
217                 tasklist += list(cls.objects.filter(job=self).filter(status=status))
218         return tasklist
219
220     def is_fulfilled(self):
221         """
222         This method should return true if all of the job's tasks are done,
223         and false otherwise
224         """
225         my_tasks = self.get_tasklist()
226         for task in my_tasks:
227             if task.status != JobStatus.DONE:
228                 return False
229         return True
230
231
232     def get_delta(self, status):
233         d = {}
234         j = {}
235         j['id'] = self.id
236         for relation in AccessRelation.objects.filter(job=self).filter(status=status):
237             if 'access' not in d:
238                 d['access'] = {}
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:
242                 d['software'] = {}
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:
246                 d['hardware'] = {}
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:
250                 d['network'] = {}
251             d['network'][relation.task_id] = relation.config.get_delta()
252
253         j['payload'] = d
254         return j
255
256     def to_json(self):
257         return json.dumps(self.to_dict())
258
259
260 class TaskConfig(models.Model):
261     def to_dict(self):
262         pass
263
264     def get_delta(self):
265         pass
266
267     def to_json(self):
268         return json.dumps(self.to_dict())
269
270     def clear_delta(self):
271         self.delta = '{}'
272
273 class OpnfvApiConfig(models.Model):
274
275     installer = models.CharField(max_length=100)
276     scenario = models.CharField(max_length=100)
277     roles = models.ManyToManyField(Host)
278     delta = models.TextField()
279
280     def to_dict(self):
281         d = {}
282         if self.installer:
283             d['installer'] = self.installer
284         if self.scenario:
285             d['scenario'] = self.scenario
286
287         hosts = self.roles.all()
288         if hosts.exists():
289             d['roles'] = []
290         for host in self.roles.all():
291             d['roles'].append({host.labid: host.config.opnfvRole.name})
292
293         return d
294
295     def to_json(self):
296         return json.dumps(self.to_dict())
297
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)
303
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)
309
310     def add_role(self, host):
311         self.roles.add(host)
312         d = json.loads(self.delta)
313         if 'role' not in d:
314             d['role'] = []
315         d['roles'].append({host.labid: host.config.opnfvRole.name})
316         self.delta = json.dumps(d)
317
318     def clear_delta(self):
319         self.delta = '{}'
320
321     def get_delta(self):
322         if not self.delta:
323             self.delta = self.to_json()
324             self.save()
325         return json.loads(self.delta)
326
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()
333
334     def to_dict(self):
335         d = {}
336         d['access_type'] =  self.access_type
337         d['user'] = self.user.id
338         d['revoke'] = self.revoke
339         d['context'] = self.context
340         return d
341
342     def get_delta(self):
343         if not self.delta:
344             self.delta = self.to_json()
345             self.save()
346         d = json.loads(self.delta)
347         d["lab_token"] = self.accessrelation.lab_token
348
349         return d
350
351     def to_json(self):
352         return json.dumps(self.to_dict())
353
354     def clear_delta(self):
355         d = {}
356         d["lab_token"] = self.accessrelation.lab_token
357         self.delta = json.dumps(d)
358
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)
364
365     def set_user(self, user):
366         self.user = user
367         d = json.loads(self.delta)
368         d['user'] = self.user.id
369         self.delta = json.dumps(d)
370
371     def set_revoke(self, revoke):
372         self.revoke = revoke
373         d = json.loads(self.delta)
374         d['revoke'] = revoke
375         self.delta = json.dumps(d)
376
377     def set_context(self, context):
378         self.context = context
379         d = json.loads(self.delta)
380         d['context'] = context
381         self.delta = json.dumps(d)
382
383 class SoftwareConfig(TaskConfig):
384     """
385     handled opnfv installations, etc
386     """
387     opnfv = models.ForeignKey(OpnfvApiConfig, on_delete=models.CASCADE)
388
389     def to_dict(self):
390         d = {}
391         if self.opnfv:
392             d['opnfv'] = self.opnfv.to_dict()
393
394         d["lab_token"] = self.softwarerelation.lab_token
395         self.delta = json.dumps(d)
396
397         return d
398
399     def get_delta(self):
400         d = {}
401         d['opnfv'] = self.opnfv.get_delta()
402         d['lab_token'] = self.softwarerelation.lab_token
403
404         return d
405
406     def clear_delta(self):
407         self.opnfv.clear_delta()
408
409     def to_json(self):
410         return json.dumps(self.to_dict())
411
412 class HardwareConfig(TaskConfig):
413     """
414     handles imaging, user accounts, etc
415     """
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()
421
422     def to_dict(self):
423         d = {}
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
429         return d
430
431     def to_json(self):
432         return json.dumps(self.to_dict())
433
434     def get_delta(self):
435         if not self.delta:
436             self.delta = self.to_json()
437             self.save()
438         d = json.loads(self.delta)
439         d['lab_token'] = self.hosthardwarerelation.lab_token
440         return d
441
442     def clear_delta(self):
443         d = {}
444         d["id"] = self.hosthardwarerelation.host.labid
445         d["lab_token"] = self.hosthardwarerelation.lab_token
446         self.delta = json.dumps(d)
447
448     def set_image(self, image):
449         self.image = image
450         d = json.loads(self.delta)
451         d['image'] = self.image
452         self.delta = json.dumps(d)
453
454     def set_power(self, power):
455         self.power = power
456         d = json.loads(self.delta)
457         d['power'] = power
458         self.delta = json.dumps(d)
459
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)
465
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)
471
472
473 class NetworkConfig(TaskConfig):
474     """
475     handles network configuration
476     """
477     interfaces = models.ManyToManyField(Interface)
478     delta = models.TextField()
479
480     def to_dict(self):
481         d = {}
482         hid = self.hostnetworkrelation.host.labid
483         d[hid] = {}
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})
488
489         return d
490
491     def to_json(self):
492         return json.dumps(self.to_dict())
493
494     def get_delta(self):
495         if not self.delta:
496             self.delta = self.to_json()
497             self.save()
498         d = json.loads(self.delta)
499         d['lab_token'] = self.hostnetworkrelation.lab_token
500         return d
501
502     def clear_delta(self):
503         self.delta = json.dumps(self.to_dict())
504         self.save()
505
506     def add_interface(self, interface):
507         self.interfaces.add(interface)
508         d = json.loads(self.delta)
509         hid = self.hostnetworkrelation.host.labid
510         if hid not in d:
511             d[hid] = {}
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)
516
517
518 def get_task(task_id):
519     for taskclass in [AccessRelation, SoftwareRelation, HostHardwareRelation, HostNetworkRelation]:
520         try:
521             ret = taskclass.objects.get(task_id=task_id)
522             return ret
523         except taskclass.DoesNotExist:
524             pass
525     from django.core.exceptions import ObjectDoesNotExist
526     raise ObjectDoesNotExist("Could not find matching TaskRelation instance")
527
528
529 def get_task_uuid():
530     return str(uuid.uuid4())
531
532
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="")
540
541     def delete(self, *args, **kwargs):
542         self.config.delete()
543         return super(self.__class__, self).delete(*args, **kwargs)
544
545     def type_str(self):
546         return "Generic Task"
547
548     class Meta:
549         abstract = True
550
551
552 class AccessRelation(TaskRelation):
553     config = models.OneToOneField(AccessConfig, on_delete=models.CASCADE)
554
555     def type_str(self):
556         return "Access Task"
557
558     def delete(self, *args, **kwargs):
559         self.config.delete()
560         return super(self.__class__, self).delete(*args, **kwargs)
561
562
563 class SoftwareRelation(TaskRelation):
564     config = models.OneToOneField(SoftwareConfig, on_delete=models.CASCADE)
565
566     def type_str(self):
567         return "Software Configuration Task"
568
569     def delete(self, *args, **kwargs):
570         self.config.delete()
571         return super(self.__class__, self).delete(*args, **kwargs)
572
573
574 class HostHardwareRelation(TaskRelation):
575     host = models.ForeignKey(Host, on_delete=models.CASCADE)
576     config = models.OneToOneField(HardwareConfig, on_delete=models.CASCADE)
577
578     def type_str(self):
579         return "Hardware Configuration Task"
580
581     def get_delta(self):
582         return self.config.to_dict()
583
584     def delete(self, *args, **kwargs):
585         self.config.delete()
586         return super(self.__class__, self).delete(*args, **kwargs)
587
588
589 class HostNetworkRelation(TaskRelation):
590     host = models.ForeignKey(Host, on_delete=models.CASCADE)
591     config = models.OneToOneField(NetworkConfig, on_delete=models.CASCADE)
592
593     def type_str(self):
594         return "Network Configuration Task"
595
596     def delete(self, *args, **kwargs):
597         self.config.delete()
598         return super(self.__class__, self).delete(*args, **kwargs)
599
600
601 class JobFactory(object):
602
603     @classmethod
604     def makeCompleteJob(cls, booking):
605         hosts = Host.objects.filter(bundle=booking.resource)
606         job = None
607         try:
608             job = Job.objects.get(booking=booking)
609         except:
610             job = Job.objects.create(status=JobStatus.NEW, booking=booking)
611         cls.makeHardwareConfigs(
612                 hosts=hosts,
613                 job=job
614                 )
615         cls.makeNetworkConfigs(
616                 hosts=hosts,
617                 job=job
618                 )
619         cls.makeSoftware(
620                 hosts=hosts,
621                 job=job
622                 )
623         cls.makeAccessConfig(
624                 users=booking.collaborators.all(),
625                 access_type="vpn",
626                 revoke=False,
627                 job=job
628                 )
629         cls.makeAccessConfig(
630                 users=[booking.owner],
631                 access_type="vpn",
632                 revoke=False,
633                 job=job
634                 )
635
636     @classmethod
637     def makeHardwareConfigs(cls, hosts=[], job=Job()):
638         for host in hosts:
639             hardware_config = None
640             try:
641                 hardware_config = HardwareConfig.objects.get(relation__host=host)
642             except:
643                 hardware_config = HardwareConfig()
644
645             relation = HostHardwareRelation()
646             relation.host = host
647             relation.job = job
648             relation.config = hardware_config
649             relation.config.save()
650             relation.config = relation.config
651             relation.save()
652
653             hardware_config.clear_delta()
654             hardware_config.set_image(host.config.image.lab_id)
655             hardware_config.set_hostname(host.template.resource.name)
656             hardware_config.set_power("on")
657             hardware_config.set_ipmi_create(True)
658             hardware_config.save()
659
660     @classmethod
661     def makeAccessConfig(cls, users, access_type, revoke=False, job=Job()):
662         for user in users:
663             relation = AccessRelation()
664             relation.job = job
665             config = AccessConfig()
666             config.access_type = access_type
667             config.user = user
668             config.save()
669             relation.config = config
670             relation.save()
671             config.clear_delta()
672             config.set_access_type(access_type)
673             config.set_revoke(revoke)
674             config.set_user(user)
675             config.save()
676
677     @classmethod
678     def makeNetworkConfigs(cls, hosts=[], job=Job()):
679         for host in hosts:
680             network_config = None
681             try:
682                 network_config = NetworkConfig.objects.get(relation__host=host)
683             except:
684                 network_config = NetworkConfig.objects.create()
685
686             relation = HostNetworkRelation()
687             relation.host = host
688             relation.job = job
689             network_config.save()
690             relation.config = network_config
691             relation.save()
692             network_config.clear_delta()
693
694             for interface in host.interfaces.all():
695                 network_config.add_interface(interface)
696             network_config.save()
697
698     @classmethod
699     def makeSoftware(cls, hosts=[], job=Job()):
700         def init_config(host):
701             opnfv_config = OpnfvApiConfig()
702             if host is not None:
703                 opnfv = host.config.bundle.opnfv_config.first()
704                 opnfv_config.installer = opnfv.installer.name
705                 opnfv_config.scenario = opnfv.scenario.name
706             opnfv_config.save()
707             return opnfv_config
708
709         try:
710             host = None
711             if len(hosts) > 0:
712                 host = hosts[0]
713             opnfv_config = init_config(host)
714
715             for host in hosts:
716                 opnfv_config.roles.add(host)
717             software_config = SoftwareConfig.objects.create(opnfv=opnfv_config)
718             software_config.save()
719             software_relation = SoftwareRelation.objects.create(job=job, config=software_config)
720             software_relation.save()
721             return software_relation
722         except:
723             return None
724
725     def makeAccess(cls, user, access_type, revoke):
726         pass