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