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