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