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