Adds Downtime Awareness
[laas.git] / 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.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
15 from django.urls import reverse
16 from django.utils import timezone
17
18 import json
19 import uuid
20
21 from booking.models import Booking
22 from resource_inventory.models import (
23     Lab,
24     HostProfile,
25     Host,
26     Image,
27     Interface,
28     HostOPNFVConfig,
29     RemoteInfo,
30     OPNFVConfig
31 )
32 from resource_inventory.idf_templater import IDFTemplater
33 from resource_inventory.pdf_templater import PDFTemplater
34 from account.models import Downtime
35
36
37 class JobStatus(object):
38     NEW = 0
39     CURRENT = 100
40     DONE = 200
41     ERROR = 300
42
43
44 class LabManagerTracker(object):
45
46     @classmethod
47     def get(cls, lab_name, token):
48         """
49         Takes in a lab name (from a url path)
50         returns a lab manager instance for that lab, if it exists
51         """
52         try:
53             lab = Lab.objects.get(name=lab_name)
54         except Exception:
55             raise PermissionDenied("Lab not found")
56         if lab.api_token == token:
57             return LabManager(lab)
58         raise PermissionDenied("Lab not authorized")
59
60
61 class LabManager(object):
62     """
63     This is the class that will ultimately handle all REST calls to
64     lab endpoints.
65     handles jobs, inventory, status, etc
66     may need to create helper classes
67     """
68
69     def __init__(self, lab):
70         self.lab = lab
71
72     def get_downtime(self):
73         return Downtime.objects.filter(start__lt=timezone.now(), end__gt=timezone.now(), lab=self.lab)
74
75     def get_downtime_json(self):
76         downtime = self.get_downtime().first()  # should only be one item in queryset
77         if downtime:
78             return {
79                 "is_down": True,
80                 "start": downtime.start,
81                 "end": downtime.end,
82                 "description": downtime.description
83             }
84         return {"is_down": False}
85
86     def create_downtime(self, form):
87         """
88         takes in a dictionary that describes the model.
89         {
90           "start": utc timestamp
91           "end": utc timestamp
92           "description": human text (optional)
93         }
94         For timestamp structure, https://docs.djangoproject.com/en/2.2/ref/forms/fields/#datetimefield
95         """
96         Downtime.objects.create(
97             start=form.cleaned_data['start'],
98             end=form.cleaned_data['end'],
99             description=form.cleaned_data['description'],
100             lab=self.lab
101         )
102         return self.get_downtime_json()
103
104     def update_host_remote_info(self, data, host_id):
105         host = get_object_or_404(Host, labid=host_id, lab=self.lab)
106         info = {}
107         try:
108             info['address'] = data['address']
109             info['mac_address'] = data['mac_address']
110             info['password'] = data['password']
111             info['user'] = data['user']
112             info['type'] = data['type']
113             info['versions'] = json.dumps(data['versions'])
114         except Exception as e:
115             return {"error": "invalid arguement: " + str(e)}
116         remote_info = host.remote_management
117         if "default" in remote_info.mac_address:
118             remote_info = RemoteInfo()
119         remote_info.address = info['address']
120         remote_info.mac_address = info['mac_address']
121         remote_info.password = info['password']
122         remote_info.user = info['user']
123         remote_info.type = info['type']
124         remote_info.versions = info['versions']
125         remote_info.save()
126         host.remote_management = remote_info
127         host.save()
128         booking = Booking.objects.get(resource=host.bundle)
129         self.update_xdf(booking)
130         return {"status": "success"}
131
132     def update_xdf(self, booking):
133         booking.pdf = PDFTemplater.makePDF(booking)
134         booking.idf = IDFTemplater().makeIDF(booking)
135         booking.save()
136
137     def get_pdf(self, booking_id):
138         booking = get_object_or_404(Booking, pk=booking_id, lab=self.lab)
139         return booking.pdf
140
141     def get_idf(self, booking_id):
142         booking = get_object_or_404(Booking, pk=booking_id, lab=self.lab)
143         return booking.idf
144
145     def get_profile(self):
146         prof = {}
147         prof['name'] = self.lab.name
148         prof['contact'] = {
149             "phone": self.lab.contact_phone,
150             "email": self.lab.contact_email
151         }
152         prof['host_count'] = []
153         for host in HostProfile.objects.filter(labs=self.lab):
154             count = Host.objects.filter(profile=host, lab=self.lab).count()
155             prof['host_count'].append(
156                 {
157                     "type": host.name,
158                     "count": count
159                 }
160             )
161         return prof
162
163     def get_inventory(self):
164         inventory = {}
165         hosts = Host.objects.filter(lab=self.lab)
166         images = Image.objects.filter(from_lab=self.lab)
167         profiles = HostProfile.objects.filter(labs=self.lab)
168         inventory['hosts'] = self.serialize_hosts(hosts)
169         inventory['images'] = self.serialize_images(images)
170         inventory['host_types'] = self.serialize_host_profiles(profiles)
171         return inventory
172
173     def get_host(self, hostname):
174         host = get_object_or_404(Host, labid=hostname, lab=self.lab)
175         return {
176             "booked": host.booked,
177             "working": host.working,
178             "type": host.profile.name
179         }
180
181     def update_host(self, hostname, data):
182         host = get_object_or_404(Host, labid=hostname, lab=self.lab)
183         if "working" in data:
184             working = data['working'] == "true"
185             host.working = working
186         host.save()
187         return self.get_host(hostname)
188
189     def get_status(self):
190         return {"status": self.lab.status}
191
192     def set_status(self, payload):
193         {}
194
195     def get_current_jobs(self):
196         jobs = Job.objects.filter(booking__lab=self.lab)
197
198         return self.serialize_jobs(jobs, status=JobStatus.CURRENT)
199
200     def get_new_jobs(self):
201         jobs = Job.objects.filter(booking__lab=self.lab)
202
203         return self.serialize_jobs(jobs, status=JobStatus.NEW)
204
205     def get_done_jobs(self):
206         jobs = Job.objects.filter(booking__lab=self.lab)
207
208         return self.serialize_jobs(jobs, status=JobStatus.DONE)
209
210     def get_job(self, jobid):
211         return Job.objects.get(pk=jobid).to_dict()
212
213     def update_job(self, jobid, data):
214         {}
215
216     def serialize_jobs(self, jobs, status=JobStatus.NEW):
217         job_ser = []
218         for job in jobs:
219             jsonized_job = job.get_delta(status)
220             if len(jsonized_job['payload']) < 1:
221                 continue
222             job_ser.append(jsonized_job)
223
224         return job_ser
225
226     def serialize_hosts(self, hosts):
227         host_ser = []
228         for host in hosts:
229             h = {}
230             h['interfaces'] = []
231             h['hostname'] = host.name
232             h['host_type'] = host.profile.name
233             for iface in host.interfaces.all():
234                 eth = {}
235                 eth['mac'] = iface.mac_address
236                 eth['busaddr'] = iface.bus_address
237                 eth['name'] = iface.name
238                 eth['switchport'] = {"switch_name": iface.switch_name, "port_name": iface.port_name}
239                 h['interfaces'].append(eth)
240         return host_ser
241
242     def serialize_images(self, images):
243         images_ser = []
244         for image in images:
245             images_ser.append(
246                 {
247                     "name": image.name,
248                     "lab_id": image.lab_id,
249                     "dashboard_id": image.id
250                 }
251             )
252         return images_ser
253
254     def serialize_host_profiles(self, profiles):
255         profile_ser = []
256         for profile in profiles:
257             p = {}
258             p['cpu'] = {
259                 "cores": profile.cpuprofile.first().cores,
260                 "arch": profile.cpuprofile.first().architecture,
261                 "cpus": profile.cpuprofile.first().cpus,
262             }
263             p['disks'] = []
264             for disk in profile.storageprofile.all():
265                 d = {
266                     "size": disk.size,
267                     "type": disk.media_type,
268                     "name": disk.name
269                 }
270                 p['disks'].append(d)
271             p['description'] = profile.description
272             p['interfaces'] = []
273             for iface in profile.interfaceprofile.all():
274                 p['interfaces'].append(
275                     {
276                         "speed": iface.speed,
277                         "name": iface.name
278                     }
279                 )
280
281             p['ram'] = {"amount": profile.ramprofile.first().amount}
282             p['name'] = profile.name
283             profile_ser.append(p)
284         return profile_ser
285
286
287 class Job(models.Model):
288     """
289     This is the class that is serialized and put into the api
290     """
291     booking = models.OneToOneField(Booking, on_delete=models.CASCADE, null=True)
292     status = models.IntegerField(default=JobStatus.NEW)
293     complete = models.BooleanField(default=False)
294
295     def to_dict(self):
296         d = {}
297         j = {}
298         j['id'] = self.id
299         for relation in AccessRelation.objects.filter(job=self):
300             if 'access' not in d:
301                 d['access'] = {}
302             d['access'][relation.task_id] = relation.config.to_dict()
303         for relation in SoftwareRelation.objects.filter(job=self):
304             if 'software' not in d:
305                 d['software'] = {}
306             d['software'][relation.task_id] = relation.config.to_dict()
307         for relation in HostHardwareRelation.objects.filter(job=self):
308             if 'hardware' not in d:
309                 d['hardware'] = {}
310             d['hardware'][relation.task_id] = relation.config.to_dict()
311         for relation in HostNetworkRelation.objects.filter(job=self):
312             if 'network' not in d:
313                 d['network'] = {}
314             d['network'][relation.task_id] = relation.config.to_dict()
315         for relation in SnapshotRelation.objects.filter(job=self):
316             if 'snapshot' not in d:
317                 d['snapshot'] = {}
318             d['snapshot'][relation.task_id] = relation.config.to_dict()
319
320         j['payload'] = d
321
322         return j
323
324     def get_tasklist(self, status="all"):
325         tasklist = []
326         clist = [
327             HostHardwareRelation,
328             AccessRelation,
329             HostNetworkRelation,
330             SoftwareRelation,
331             SnapshotRelation
332         ]
333         if status == "all":
334             for cls in clist:
335                 tasklist += list(cls.objects.filter(job=self))
336         else:
337             for cls in clist:
338                 tasklist += list(cls.objects.filter(job=self).filter(status=status))
339         return tasklist
340
341     def is_fulfilled(self):
342         """
343         This method should return true if all of the job's tasks are done,
344         and false otherwise
345         """
346         my_tasks = self.get_tasklist()
347         for task in my_tasks:
348             if task.status != JobStatus.DONE:
349                 return False
350         return True
351
352     def get_delta(self, status):
353         d = {}
354         j = {}
355         j['id'] = self.id
356         for relation in AccessRelation.objects.filter(job=self).filter(status=status):
357             if 'access' not in d:
358                 d['access'] = {}
359             d['access'][relation.task_id] = relation.config.get_delta()
360         for relation in SoftwareRelation.objects.filter(job=self).filter(status=status):
361             if 'software' not in d:
362                 d['software'] = {}
363             d['software'][relation.task_id] = relation.config.get_delta()
364         for relation in HostHardwareRelation.objects.filter(job=self).filter(status=status):
365             if 'hardware' not in d:
366                 d['hardware'] = {}
367             d['hardware'][relation.task_id] = relation.config.get_delta()
368         for relation in HostNetworkRelation.objects.filter(job=self).filter(status=status):
369             if 'network' not in d:
370                 d['network'] = {}
371             d['network'][relation.task_id] = relation.config.get_delta()
372         for relation in SnapshotRelation.objects.filter(job=self).filter(status=status):
373             if 'snapshot' not in d:
374                 d['snapshot'] = {}
375             d['snapshot'][relation.task_id] = relation.config.get_delta()
376
377         j['payload'] = d
378         return j
379
380     def to_json(self):
381         return json.dumps(self.to_dict())
382
383
384 class TaskConfig(models.Model):
385     def to_dict(self):
386         pass
387
388     def get_delta(self):
389         pass
390
391     def to_json(self):
392         return json.dumps(self.to_dict())
393
394     def clear_delta(self):
395         self.delta = '{}'
396
397
398 class BridgeConfig(models.Model):
399     """
400     Displays mapping between jumphost interfaces and
401     bridges
402     """
403     interfaces = models.ManyToManyField(Interface)
404     opnfv_config = models.ForeignKey(OPNFVConfig, on_delete=models.CASCADE)
405
406     def to_dict(self):
407         d = {}
408         hid = self.interfaces.first().host.labid
409         d[hid] = {}
410         for interface in self.interfaces.all():
411             d[hid][interface.mac_address] = []
412             for vlan in interface.config.all():
413                 network_role = self.opnfv_model.networks().filter(network=vlan.network)
414                 bridge = IDFTemplater.bridge_names[network_role.name]
415                 br_config = {
416                     "vlan_id": vlan.vlan_id,
417                     "tagged": vlan.tagged,
418                     "bridge": bridge
419                 }
420                 d[hid][interface.mac_address].append(br_config)
421         return d
422
423     def to_json(self):
424         return json.dumps(self.to_dict())
425
426
427 class OpnfvApiConfig(models.Model):
428
429     installer = models.CharField(max_length=200)
430     scenario = models.CharField(max_length=300)
431     roles = models.ManyToManyField(Host)
432     # pdf and idf are url endpoints, not the actual file
433     pdf = models.CharField(max_length=100)
434     idf = models.CharField(max_length=100)
435     bridge_config = models.OneToOneField(BridgeConfig, on_delete=models.CASCADE, null=True)
436     delta = models.TextField()
437     opnfv_config = models.ForeignKey(OPNFVConfig, null=True, on_delete=models.SET_NULL)
438
439     def to_dict(self):
440         d = {}
441         if not self.opnfv_config:
442             return d
443         if self.installer:
444             d['installer'] = self.installer
445         if self.scenario:
446             d['scenario'] = self.scenario
447         if self.pdf:
448             d['pdf'] = self.pdf
449         if self.idf:
450             d['idf'] = self.idf
451         if self.bridge_config:
452             d['bridged_interfaces'] = self.bridge_config.to_dict()
453
454         hosts = self.roles.all()
455         if hosts.exists():
456             d['roles'] = []
457             for host in hosts:
458                 d['roles'].append({
459                     host.labid: self.opnfv_config.host_opnfv_config.get(
460                         host_config__pk=host.config.pk
461                     ).role.name
462                 })
463
464         return d
465
466     def to_json(self):
467         return json.dumps(self.to_dict())
468
469     def set_installer(self, installer):
470         self.installer = installer
471         d = json.loads(self.delta)
472         d['installer'] = installer
473         self.delta = json.dumps(d)
474
475     def set_scenario(self, scenario):
476         self.scenario = scenario
477         d = json.loads(self.delta)
478         d['scenario'] = scenario
479         self.delta = json.dumps(d)
480
481     def set_xdf(self, booking, update_delta=True):
482         kwargs = {'lab_name': booking.lab.name, 'booking_id': booking.id}
483         self.pdf = reverse('get-pdf', kwargs=kwargs)
484         self.idf = reverse('get-idf', kwargs=kwargs)
485         if update_delta:
486             d = json.loads(self.delta)
487             d['pdf'] = self.pdf
488             d['idf'] = self.idf
489             self.delta = json.dumps(d)
490
491     def add_role(self, host):
492         self.roles.add(host)
493         d = json.loads(self.delta)
494         if 'role' not in d:
495             d['role'] = []
496         d['roles'].append({host.labid: host.config.opnfvRole.name})
497         self.delta = json.dumps(d)
498
499     def clear_delta(self):
500         self.delta = '{}'
501
502     def get_delta(self):
503         if not self.delta:
504             self.delta = self.to_json()
505             self.save()
506         return json.loads(self.delta)
507
508
509 class AccessConfig(TaskConfig):
510     access_type = models.CharField(max_length=50)
511     user = models.ForeignKey(User, on_delete=models.CASCADE)
512     revoke = models.BooleanField(default=False)
513     context = models.TextField(default="")
514     delta = models.TextField(default="{}")
515
516     def to_dict(self):
517         d = {}
518         d['access_type'] = self.access_type
519         d['user'] = self.user.id
520         d['revoke'] = self.revoke
521         try:
522             d['context'] = json.loads(self.context)
523         except Exception:
524             pass
525         return d
526
527     def get_delta(self):
528         if not self.delta:
529             self.delta = self.to_json()
530             self.save()
531         d = json.loads(self.delta)
532         d["lab_token"] = self.accessrelation.lab_token
533
534         return d
535
536     def to_json(self):
537         return json.dumps(self.to_dict())
538
539     def clear_delta(self):
540         d = {}
541         d["lab_token"] = self.accessrelation.lab_token
542         self.delta = json.dumps(d)
543
544     def set_access_type(self, access_type):
545         self.access_type = access_type
546         d = json.loads(self.delta)
547         d['access_type'] = access_type
548         self.delta = json.dumps(d)
549
550     def set_user(self, user):
551         self.user = user
552         d = json.loads(self.delta)
553         d['user'] = self.user.id
554         self.delta = json.dumps(d)
555
556     def set_revoke(self, revoke):
557         self.revoke = revoke
558         d = json.loads(self.delta)
559         d['revoke'] = revoke
560         self.delta = json.dumps(d)
561
562     def set_context(self, context):
563         self.context = json.dumps(context)
564         d = json.loads(self.delta)
565         d['context'] = context
566         self.delta = json.dumps(d)
567
568
569 class SoftwareConfig(TaskConfig):
570     """
571     handled opnfv installations, etc
572     """
573     opnfv = models.ForeignKey(OpnfvApiConfig, on_delete=models.CASCADE)
574
575     def to_dict(self):
576         d = {}
577         if self.opnfv:
578             d['opnfv'] = self.opnfv.to_dict()
579
580         d["lab_token"] = self.softwarerelation.lab_token
581         self.delta = json.dumps(d)
582
583         return d
584
585     def get_delta(self):
586         d = {}
587         d['opnfv'] = self.opnfv.get_delta()
588         d['lab_token'] = self.softwarerelation.lab_token
589
590         return d
591
592     def clear_delta(self):
593         self.opnfv.clear_delta()
594
595     def to_json(self):
596         return json.dumps(self.to_dict())
597
598
599 class HardwareConfig(TaskConfig):
600     """
601     handles imaging, user accounts, etc
602     """
603     image = models.CharField(max_length=100, default="defimage")
604     power = models.CharField(max_length=100, default="off")
605     hostname = models.CharField(max_length=100, default="hostname")
606     ipmi_create = models.BooleanField(default=False)
607     delta = models.TextField()
608
609     def to_dict(self):
610         d = {}
611         d['image'] = self.image
612         d['power'] = self.power
613         d['hostname'] = self.hostname
614         d['ipmi_create'] = str(self.ipmi_create)
615         d['id'] = self.hosthardwarerelation.host.labid
616         return d
617
618     def to_json(self):
619         return json.dumps(self.to_dict())
620
621     def get_delta(self):
622         if not self.delta:
623             self.delta = self.to_json()
624             self.save()
625         d = json.loads(self.delta)
626         d['lab_token'] = self.hosthardwarerelation.lab_token
627         return d
628
629     def clear_delta(self):
630         d = {}
631         d["id"] = self.hosthardwarerelation.host.labid
632         d["lab_token"] = self.hosthardwarerelation.lab_token
633         self.delta = json.dumps(d)
634
635     def set_image(self, image):
636         self.image = image
637         d = json.loads(self.delta)
638         d['image'] = self.image
639         self.delta = json.dumps(d)
640
641     def set_power(self, power):
642         self.power = power
643         d = json.loads(self.delta)
644         d['power'] = power
645         self.delta = json.dumps(d)
646
647     def set_hostname(self, hostname):
648         self.hostname = hostname
649         d = json.loads(self.delta)
650         d['hostname'] = hostname
651         self.delta = json.dumps(d)
652
653     def set_ipmi_create(self, ipmi_create):
654         self.ipmi_create = ipmi_create
655         d = json.loads(self.delta)
656         d['ipmi_create'] = ipmi_create
657         self.delta = json.dumps(d)
658
659
660 class NetworkConfig(TaskConfig):
661     """
662     handles network configuration
663     """
664     interfaces = models.ManyToManyField(Interface)
665     delta = models.TextField()
666
667     def to_dict(self):
668         d = {}
669         hid = self.hostnetworkrelation.host.labid
670         d[hid] = {}
671         for interface in self.interfaces.all():
672             d[hid][interface.mac_address] = []
673             for vlan in interface.config.all():
674                 d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
675
676         return d
677
678     def to_json(self):
679         return json.dumps(self.to_dict())
680
681     def get_delta(self):
682         if not self.delta:
683             self.delta = self.to_json()
684             self.save()
685         d = json.loads(self.delta)
686         d['lab_token'] = self.hostnetworkrelation.lab_token
687         return d
688
689     def clear_delta(self):
690         self.delta = json.dumps(self.to_dict())
691         self.save()
692
693     def add_interface(self, interface):
694         self.interfaces.add(interface)
695         d = json.loads(self.delta)
696         hid = self.hostnetworkrelation.host.labid
697         if hid not in d:
698             d[hid] = {}
699         d[hid][interface.mac_address] = []
700         for vlan in interface.config.all():
701             d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
702         self.delta = json.dumps(d)
703
704
705 class SnapshotConfig(TaskConfig):
706
707     host = models.ForeignKey(Host, null=True, on_delete=models.DO_NOTHING)
708     image = models.IntegerField(null=True)
709     dashboard_id = models.IntegerField()
710     delta = models.TextField(default="{}")
711
712     def to_dict(self):
713         d = {}
714         if self.host:
715             d['host'] = self.host.labid
716         if self.image:
717             d['image'] = self.image
718         d['dashboard_id'] = self.dashboard_id
719         return d
720
721     def to_json(self):
722         return json.dumps(self.to_dict())
723
724     def get_delta(self):
725         if not self.delta:
726             self.delta = self.to_json()
727             self.save()
728
729         d = json.loads(self.delta)
730         return d
731
732     def clear_delta(self):
733         self.delta = json.dumps(self.to_dict())
734         self.save()
735
736     def set_host(self, host):
737         self.host = host
738         d = json.loads(self.delta)
739         d['host'] = host.labid
740         self.delta = json.dumps(d)
741
742     def set_image(self, image):
743         self.image = image
744         d = json.loads(self.delta)
745         d['image'] = self.image
746         self.delta = json.dumps(d)
747
748     def clear_image(self):
749         self.image = None
750         d = json.loads(self.delta)
751         d.pop("image", None)
752         self.delta = json.dumps(d)
753
754     def set_dashboard_id(self, dash):
755         self.dashboard_id = dash
756         d = json.loads(self.delta)
757         d['dashboard_id'] = self.dashboard_id
758         self.delta = json.dumps(d)
759
760
761 def get_task(task_id):
762     for taskclass in [AccessRelation, SoftwareRelation, HostHardwareRelation, HostNetworkRelation, SnapshotRelation]:
763         try:
764             ret = taskclass.objects.get(task_id=task_id)
765             return ret
766         except taskclass.DoesNotExist:
767             pass
768     from django.core.exceptions import ObjectDoesNotExist
769     raise ObjectDoesNotExist("Could not find matching TaskRelation instance")
770
771
772 def get_task_uuid():
773     return str(uuid.uuid4())
774
775
776 class TaskRelation(models.Model):
777     status = models.IntegerField(default=JobStatus.NEW)
778     job = models.ForeignKey(Job, on_delete=models.CASCADE)
779     config = models.OneToOneField(TaskConfig, on_delete=models.CASCADE)
780     task_id = models.CharField(default=get_task_uuid, max_length=37)
781     lab_token = models.CharField(default="null", max_length=50)
782     message = models.TextField(default="")
783
784     def delete(self, *args, **kwargs):
785         self.config.delete()
786         return super(self.__class__, self).delete(*args, **kwargs)
787
788     def type_str(self):
789         return "Generic Task"
790
791     class Meta:
792         abstract = True
793
794
795 class AccessRelation(TaskRelation):
796     config = models.OneToOneField(AccessConfig, on_delete=models.CASCADE)
797
798     def type_str(self):
799         return "Access Task"
800
801     def delete(self, *args, **kwargs):
802         self.config.delete()
803         return super(self.__class__, self).delete(*args, **kwargs)
804
805
806 class SoftwareRelation(TaskRelation):
807     config = models.OneToOneField(SoftwareConfig, on_delete=models.CASCADE)
808
809     def type_str(self):
810         return "Software Configuration Task"
811
812     def delete(self, *args, **kwargs):
813         self.config.delete()
814         return super(self.__class__, self).delete(*args, **kwargs)
815
816
817 class HostHardwareRelation(TaskRelation):
818     host = models.ForeignKey(Host, on_delete=models.CASCADE)
819     config = models.OneToOneField(HardwareConfig, on_delete=models.CASCADE)
820
821     def type_str(self):
822         return "Hardware Configuration Task"
823
824     def get_delta(self):
825         return self.config.to_dict()
826
827     def delete(self, *args, **kwargs):
828         self.config.delete()
829         return super(self.__class__, self).delete(*args, **kwargs)
830
831
832 class HostNetworkRelation(TaskRelation):
833     host = models.ForeignKey(Host, on_delete=models.CASCADE)
834     config = models.OneToOneField(NetworkConfig, on_delete=models.CASCADE)
835
836     def type_str(self):
837         return "Network Configuration Task"
838
839     def delete(self, *args, **kwargs):
840         self.config.delete()
841         return super(self.__class__, self).delete(*args, **kwargs)
842
843
844 class SnapshotRelation(TaskRelation):
845     snapshot = models.ForeignKey(Image, on_delete=models.CASCADE)
846     config = models.OneToOneField(SnapshotConfig, on_delete=models.CASCADE)
847
848     def type_str(self):
849         return "Snapshot Task"
850
851     def get_delta(self):
852         return self.config.to_dict()
853
854     def delete(self, *args, **kwargs):
855         self.config.delete()
856         return super(self.__class__, self).delete(*args, **kwargs)
857
858
859 class JobFactory(object):
860
861     @classmethod
862     def reimageHost(cls, new_image, booking, host):
863         """
864         This method will make all necessary changes to make a lab
865         reimage a host.
866         """
867         job = Job.objects.get(booking=booking)
868         # make hardware task new
869         hardware_relation = HostHardwareRelation.objects.get(host=host, job=job)
870         hardware_relation.config.set_image(new_image.lab_id)
871         hardware_relation.config.save()
872         hardware_relation.status = JobStatus.NEW
873
874         # re-apply networking after host is reset
875         net_relation = HostNetworkRelation.objects.get(host=host, job=job)
876         net_relation.status = JobStatus.NEW
877
878         # re-apply ssh access after host is reset
879         for relation in AccessRelation.objects.filter(job=job, config__access_type="ssh"):
880             relation.status = JobStatus.NEW
881             relation.save()
882
883         hardware_relation.save()
884         net_relation.save()
885
886     @classmethod
887     def makeSnapshotTask(cls, image, booking, host):
888         relation = SnapshotRelation()
889         job = Job.objects.get(booking=booking)
890         config = SnapshotConfig.objects.create(dashboard_id=image.id)
891
892         relation.job = job
893         relation.config = config
894         relation.config.save()
895         relation.config = relation.config
896         relation.snapshot = image
897         relation.save()
898
899         config.clear_delta()
900         config.set_host(host)
901         config.save()
902
903     @classmethod
904     def makeCompleteJob(cls, booking):
905         hosts = Host.objects.filter(bundle=booking.resource)
906         job = None
907         try:
908             job = Job.objects.get(booking=booking)
909         except Exception:
910             job = Job.objects.create(status=JobStatus.NEW, booking=booking)
911         cls.makeHardwareConfigs(
912             hosts=hosts,
913             job=job
914         )
915         cls.makeNetworkConfigs(
916             hosts=hosts,
917             job=job
918         )
919         cls.makeSoftware(
920             booking=booking,
921             job=job
922         )
923         all_users = list(booking.collaborators.all())
924         all_users.append(booking.owner)
925         cls.makeAccessConfig(
926             users=all_users,
927             access_type="vpn",
928             revoke=False,
929             job=job
930         )
931         for user in all_users:
932             try:
933                 cls.makeAccessConfig(
934                     users=[user],
935                     access_type="ssh",
936                     revoke=False,
937                     job=job,
938                     context={
939                         "key": user.userprofile.ssh_public_key.open().read().decode(encoding="UTF-8"),
940                         "hosts": [host.labid for host in hosts]
941                     }
942                 )
943             except Exception:
944                 continue
945
946     @classmethod
947     def makeHardwareConfigs(cls, hosts=[], job=Job()):
948         for host in hosts:
949             hardware_config = None
950             try:
951                 hardware_config = HardwareConfig.objects.get(relation__host=host)
952             except Exception:
953                 hardware_config = HardwareConfig()
954
955             relation = HostHardwareRelation()
956             relation.host = host
957             relation.job = job
958             relation.config = hardware_config
959             relation.config.save()
960             relation.config = relation.config
961             relation.save()
962
963             hardware_config.clear_delta()
964             hardware_config.set_image(host.config.image.lab_id)
965             hardware_config.set_hostname(host.template.resource.name)
966             hardware_config.set_power("on")
967             hardware_config.set_ipmi_create(True)
968             hardware_config.save()
969
970     @classmethod
971     def makeAccessConfig(cls, users, access_type, revoke=False, job=Job(), context=False):
972         for user in users:
973             relation = AccessRelation()
974             relation.job = job
975             config = AccessConfig()
976             config.access_type = access_type
977             config.user = user
978             config.save()
979             relation.config = config
980             relation.save()
981             config.clear_delta()
982             if context:
983                 config.set_context(context)
984             config.set_access_type(access_type)
985             config.set_revoke(revoke)
986             config.set_user(user)
987             config.save()
988
989     @classmethod
990     def makeNetworkConfigs(cls, hosts=[], job=Job()):
991         for host in hosts:
992             network_config = None
993             try:
994                 network_config = NetworkConfig.objects.get(relation__host=host)
995             except Exception:
996                 network_config = NetworkConfig.objects.create()
997
998             relation = HostNetworkRelation()
999             relation.host = host
1000             relation.job = job
1001             network_config.save()
1002             relation.config = network_config
1003             relation.save()
1004             network_config.clear_delta()
1005
1006             for interface in host.interfaces.all():
1007                 network_config.add_interface(interface)
1008             network_config.save()
1009
1010     @classmethod
1011     def make_bridge_config(cls, booking):
1012         if booking.resource.hosts.count() < 2:
1013             return None
1014         try:
1015             jumphost_config = HostOPNFVConfig.objects.filter(
1016                 role__name__iexact="jumphost"
1017             )
1018             jumphost = Host.objects.get(
1019                 bundle=booking.resource,
1020                 config=jumphost_config.host_config
1021             )
1022         except Exception:
1023             return None
1024         br_config = BridgeConfig.objects.create(opnfv_config=booking.opnfv_config)
1025         for iface in jumphost.interfaces.all():
1026             br_config.interfaces.add(iface)
1027         return br_config
1028
1029     @classmethod
1030     def makeSoftware(cls, booking=None, job=Job()):
1031
1032         if not booking.opnfv_config:
1033             return None
1034
1035         opnfv_api_config = OpnfvApiConfig.objects.create(
1036             opnfv_config=booking.opnfv_config,
1037             installer=booking.opnfv_config.installer.name,
1038             scenario=booking.opnfv_config.scenario.name,
1039             bridge_config=cls.make_bridge_config(booking)
1040         )
1041
1042         opnfv_api_config.set_xdf(booking, False)
1043         opnfv_api_config.save()
1044
1045         for host in booking.resource.hosts.all():
1046             opnfv_api_config.roles.add(host)
1047         software_config = SoftwareConfig.objects.create(opnfv=opnfv_api_config)
1048         software_relation = SoftwareRelation.objects.create(job=job, config=software_config)
1049         return software_relation