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