1 ##############################################################################
2 # Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others.
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 ##############################################################################
11 from django.contrib.auth.models import User
12 from django.db import models
13 from django.core.exceptions import PermissionDenied, ValidationError
14 from django.shortcuts import get_object_or_404
15 from django.http import HttpResponseNotFound
16 from django.urls import reverse
17 from django.utils import timezone
22 from booking.models import Booking
23 from resource_inventory.models import (
34 from resource_inventory.idf_templater import IDFTemplater
35 from resource_inventory.pdf_templater import PDFTemplater
36 from account.models import Downtime
37 from dashboard.utils import AbstractModelQuery
40 class JobStatus(object):
42 A poor man's enum for a job's status.
44 A job is NEW if it has not been started or recognized by the Lab
45 A job is CURRENT if it has been started by the lab but it is not yet completed
46 a job is DONE if all the tasks are complete and the booking is ready to use
55 class LabManagerTracker(object):
58 def get(cls, lab_name, token):
62 Takes in a lab name (from a url path)
63 returns a lab manager instance for that lab, if it exists
64 Also checks that the given API token is correct
67 lab = Lab.objects.get(name=lab_name)
69 raise PermissionDenied("Lab not found")
70 if lab.api_token == token:
71 return LabManager(lab)
72 raise PermissionDenied("Lab not authorized")
75 class LabManager(object):
77 Handles all lab REST calls.
79 handles jobs, inventory, status, etc
80 may need to create helper classes
83 def __init__(self, lab):
86 def get_downtime(self):
87 return Downtime.objects.filter(start__lt=timezone.now(), end__gt=timezone.now(), lab=self.lab)
89 def get_downtime_json(self):
90 downtime = self.get_downtime().first() # should only be one item in queryset
94 "start": downtime.start,
96 "description": downtime.description
98 return {"is_down": False}
100 def create_downtime(self, form):
102 Create a downtime event.
104 Takes in a dictionary that describes the model.
106 "start": utc timestamp
108 "description": human text (optional)
110 For timestamp structure, https://docs.djangoproject.com/en/2.2/ref/forms/fields/#datetimefield
112 Downtime.objects.create(
113 start=form.cleaned_data['start'],
114 end=form.cleaned_data['end'],
115 description=form.cleaned_data['description'],
118 return self.get_downtime_json()
120 def update_host_remote_info(self, data, res_id):
121 resource = ResourceQuery.filter(labid=res_id, lab=self.lab)
122 if len(resource) != 1:
123 return HttpResponseNotFound("Could not find single host with id " + str(res_id))
124 resource = resource[0]
127 info['address'] = data['address']
128 info['mac_address'] = data['mac_address']
129 info['password'] = data['password']
130 info['user'] = data['user']
131 info['type'] = data['type']
132 info['versions'] = json.dumps(data['versions'])
133 except Exception as e:
134 return {"error": "invalid arguement: " + str(e)}
135 remote_info = resource.remote_management
136 if "default" in remote_info.mac_address:
137 remote_info = RemoteInfo()
138 remote_info.address = info['address']
139 remote_info.mac_address = info['mac_address']
140 remote_info.password = info['password']
141 remote_info.user = info['user']
142 remote_info.type = info['type']
143 remote_info.versions = info['versions']
145 resource.remote_management = remote_info
147 booking = Booking.objects.get(resource=resource.bundle)
148 self.update_xdf(booking)
149 return {"status": "success"}
151 def update_xdf(self, booking):
152 booking.pdf = PDFTemplater.makePDF(booking)
153 booking.idf = IDFTemplater().makeIDF(booking)
156 def get_pdf(self, booking_id):
157 booking = get_object_or_404(Booking, pk=booking_id, lab=self.lab)
160 def get_idf(self, booking_id):
161 booking = get_object_or_404(Booking, pk=booking_id, lab=self.lab)
164 def get_profile(self):
166 prof['name'] = self.lab.name
168 "phone": self.lab.contact_phone,
169 "email": self.lab.contact_email
171 prof['host_count'] = [{
172 "type": profile.name,
173 "count": len(profile.get_resources(lab=self.lab))}
174 for profile in ResourceProfile.objects.filter(labs=self.lab)]
177 def get_inventory(self):
179 resources = ResourceQuery.filter(lab=self.lab)
180 images = Image.objects.filter(from_lab=self.lab)
181 profiles = ResourceProfile.objects.filter(labs=self.lab)
182 inventory['resources'] = self.serialize_resources(resources)
183 inventory['images'] = self.serialize_images(images)
184 inventory['host_types'] = self.serialize_host_profiles(profiles)
187 def get_host(self, hostname):
188 resource = ResourceQuery.filter(labid=hostname, lab=self.lab)
189 if len(resource) != 1:
190 return HttpResponseNotFound("Could not find single host with id " + str(hostname))
191 resource = resource[0]
193 "booked": resource.booked,
194 "working": resource.working,
195 "type": resource.profile.name
198 def update_host(self, hostname, data):
199 resource = ResourceQuery.filter(labid=hostname, lab=self.lab)
200 if len(resource) != 1:
201 return HttpResponseNotFound("Could not find single host with id " + str(hostname))
202 resource = resource[0]
203 if "working" in data:
204 working = data['working'] == "true"
205 resource.working = working
207 return self.get_host(hostname)
209 def get_status(self):
210 return {"status": self.lab.status}
212 def set_status(self, payload):
215 def get_current_jobs(self):
216 jobs = Job.objects.filter(booking__lab=self.lab)
218 return self.serialize_jobs(jobs, status=JobStatus.CURRENT)
220 def get_new_jobs(self):
221 jobs = Job.objects.filter(booking__lab=self.lab)
223 return self.serialize_jobs(jobs, status=JobStatus.NEW)
225 def get_done_jobs(self):
226 jobs = Job.objects.filter(booking__lab=self.lab)
228 return self.serialize_jobs(jobs, status=JobStatus.DONE)
230 def get_job(self, jobid):
231 return Job.objects.get(pk=jobid).to_dict()
233 def update_job(self, jobid, data):
236 def serialize_jobs(self, jobs, status=JobStatus.NEW):
239 jsonized_job = job.get_delta(status)
240 if len(jsonized_job['payload']) < 1:
242 job_ser.append(jsonized_job)
246 def serialize_resources(self, resources):
247 # TODO: rewrite for Resource model
249 for res in resources:
252 'hostname': res.name,
253 'host_type': res.profile.name
255 for iface in res.get_interfaces():
256 r['interfaces'].append({
257 'mac': iface.mac_address,
258 'busaddr': iface.bus_address,
260 'switchport': {"switch_name": iface.switch_name, "port_name": iface.port_name}
264 def serialize_images(self, images):
270 "lab_id": image.lab_id,
271 "dashboard_id": image.id
276 def serialize_resource_profiles(self, profiles):
278 for profile in profiles:
281 "cores": profile.cpuprofile.first().cores,
282 "arch": profile.cpuprofile.first().architecture,
283 "cpus": profile.cpuprofile.first().cpus,
286 for disk in profile.storageprofile.all():
289 "type": disk.media_type,
293 p['description'] = profile.description
295 for iface in profile.interfaceprofile.all():
296 p['interfaces'].append(
298 "speed": iface.speed,
303 p['ram'] = {"amount": profile.ramprofile.first().amount}
304 p['name'] = profile.name
305 profile_ser.append(p)
309 class Job(models.Model):
311 A Job to be performed by the Lab.
313 The API uses Jobs and Tasks to communicate actions that need to be taken to the Lab
314 that is hosting a booking. A booking from a user has an associated Job which tells
315 the lab how to configure the hardware, networking, etc to fulfill the booking
317 This is the class that is serialized and put into the api
320 booking = models.OneToOneField(Booking, on_delete=models.CASCADE, null=True)
321 status = models.IntegerField(default=JobStatus.NEW)
322 complete = models.BooleanField(default=False)
326 for relation in self.get_tasklist():
327 if relation.job_key not in d:
328 d[relation.job_key] = {}
329 d[relation.job_key][relation.task_id] = relation.config.to_dict()
331 return {"id": self.id, "payload": d}
333 def get_tasklist(self, status="all"):
335 return JobTaskQuery.filter(job=self, status=status)
336 return JobTaskQuery.filter(job=self)
338 def is_fulfilled(self):
340 If a job has been completed by the lab.
342 This method should return true if all of the job's tasks are done,
345 my_tasks = self.get_tasklist()
346 for task in my_tasks:
347 if task.status != JobStatus.DONE:
351 def get_delta(self, status):
353 for relation in self.get_tasklist(status=status):
354 if relation.job_key not in d:
355 d[relation.job_key] = {}
356 d[relation.job_key][relation.task_id] = relation.config.get_delta()
358 return {"id": self.id, "payload": d}
361 return json.dumps(self.to_dict())
364 class TaskConfig(models.Model):
365 state = models.IntegerField(default=ConfigState.CLEAN)
367 keys = set() # TODO: This needs to be an instance variable, not a class variable
368 delta_keys_list = models.CharField(max_length=200, default="[]")
371 def delta_keys(self):
372 return list(set(json.loads(self.delta_keys_list)))
375 def delta_keys(self, keylist):
376 self.delta_keys_list = json.dumps(keylist)
379 raise NotImplementedError
382 raise NotImplementedError
384 def format_delta(self, config, token):
385 delta = {k: config[k] for k in self.delta_keys}
386 delta['lab_token'] = token
390 return json.dumps(self.to_dict())
392 def clear_delta(self):
395 def set(self, *args):
396 dkeys = self.delta_keys
400 self.delta_keys = dkeys
403 class BridgeConfig(models.Model):
404 """Displays mapping between jumphost interfaces and bridges."""
406 interfaces = models.ManyToManyField(Interface)
407 opnfv_config = models.ForeignKey(OPNFVConfig, on_delete=models.CASCADE)
411 hid = self.interfaces.first().host.labid
413 for interface in self.interfaces.all():
414 d[hid][interface.mac_address] = []
415 for vlan in interface.config.all():
416 network_role = self.opnfv_model.networks().filter(network=vlan.network)
417 bridge = IDFTemplater.bridge_names[network_role.name]
419 "vlan_id": vlan.vlan_id,
420 "tagged": vlan.tagged,
423 d[hid][interface.mac_address].append(br_config)
427 return json.dumps(self.to_dict())
430 class OpnfvApiConfig(models.Model):
432 installer = models.CharField(max_length=200)
433 scenario = models.CharField(max_length=300)
434 roles = models.ManyToManyField(ResourceOPNFVConfig)
435 # pdf and idf are url endpoints, not the actual file
436 pdf = models.CharField(max_length=100)
437 idf = models.CharField(max_length=100)
438 bridge_config = models.OneToOneField(BridgeConfig, on_delete=models.CASCADE, null=True)
439 delta = models.TextField()
440 opnfv_config = models.ForeignKey(OPNFVConfig, null=True, on_delete=models.SET_NULL)
444 if not self.opnfv_config:
447 d['installer'] = self.installer
449 d['scenario'] = self.scenario
454 if self.bridge_config:
455 d['bridged_interfaces'] = self.bridge_config.to_dict()
457 hosts = self.roles.all()
462 host.labid: self.opnfv_config.host_opnfv_config.get(
463 host_config__pk=host.config.pk
470 return json.dumps(self.to_dict())
472 def set_installer(self, installer):
473 self.installer = installer
474 d = json.loads(self.delta)
475 d['installer'] = installer
476 self.delta = json.dumps(d)
478 def set_scenario(self, scenario):
479 self.scenario = scenario
480 d = json.loads(self.delta)
481 d['scenario'] = scenario
482 self.delta = json.dumps(d)
484 def set_xdf(self, booking, update_delta=True):
485 kwargs = {'lab_name': booking.lab.name, 'booking_id': booking.id}
486 self.pdf = reverse('get-pdf', kwargs=kwargs)
487 self.idf = reverse('get-idf', kwargs=kwargs)
489 d = json.loads(self.delta)
492 self.delta = json.dumps(d)
494 def add_role(self, host):
496 d = json.loads(self.delta)
499 d['roles'].append({host.labid: host.config.opnfvRole.name})
500 self.delta = json.dumps(d)
502 def clear_delta(self):
507 self.delta = self.to_json()
509 return json.loads(self.delta)
512 class AccessConfig(TaskConfig):
513 access_type = models.CharField(max_length=50)
514 user = models.ForeignKey(User, on_delete=models.CASCADE)
515 revoke = models.BooleanField(default=False)
516 context = models.TextField(default="")
517 delta = models.TextField(default="{}")
521 d['access_type'] = self.access_type
522 d['user'] = self.user.id
523 d['revoke'] = self.revoke
525 d['context'] = json.loads(self.context)
532 self.delta = self.to_json()
534 d = json.loads(self.delta)
535 d["lab_token"] = self.accessrelation.lab_token
540 return json.dumps(self.to_dict())
542 def clear_delta(self):
544 d["lab_token"] = self.accessrelation.lab_token
545 self.delta = json.dumps(d)
547 def set_access_type(self, access_type):
548 self.access_type = access_type
549 d = json.loads(self.delta)
550 d['access_type'] = access_type
551 self.delta = json.dumps(d)
553 def set_user(self, user):
555 d = json.loads(self.delta)
556 d['user'] = self.user.id
557 self.delta = json.dumps(d)
559 def set_revoke(self, revoke):
561 d = json.loads(self.delta)
563 self.delta = json.dumps(d)
565 def set_context(self, context):
566 self.context = json.dumps(context)
567 d = json.loads(self.delta)
568 d['context'] = context
569 self.delta = json.dumps(d)
572 class SoftwareConfig(TaskConfig):
573 """Handles software installations, such as OPNFV or ONAP."""
575 opnfv = models.ForeignKey(OpnfvApiConfig, on_delete=models.CASCADE)
580 d['opnfv'] = self.opnfv.to_dict()
582 d["lab_token"] = self.softwarerelation.lab_token
583 self.delta = json.dumps(d)
589 d['opnfv'] = self.opnfv.get_delta()
590 d['lab_token'] = self.softwarerelation.lab_token
594 def clear_delta(self):
595 self.opnfv.clear_delta()
598 return json.dumps(self.to_dict())
601 class HardwareConfig(TaskConfig):
602 """Describes the desired configuration of the hardware."""
604 image = models.CharField(max_length=100, default="defimage")
605 power = models.CharField(max_length=100, default="off")
606 hostname = models.CharField(max_length=100, default="hostname")
607 ipmi_create = models.BooleanField(default=False)
608 delta = models.TextField()
610 keys = set(["id", "image", "power", "hostname", "ipmi_create"])
613 return self.format_delta(
614 self.hosthardwarerelation.host.get_configuration(self.state),
615 self.hosthardwarerelation.lab_token)
618 class NetworkConfig(TaskConfig):
619 """Handles network configuration."""
621 interfaces = models.ManyToManyField(Interface)
622 delta = models.TextField()
626 hid = self.hostnetworkrelation.host.labid
628 for interface in self.interfaces.all():
629 d[hid][interface.mac_address] = []
630 for vlan in interface.config.all():
631 # TODO: should this come from the interface?
632 # e.g. will different interfaces for different resources need different configs?
633 d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
638 return json.dumps(self.to_dict())
642 self.delta = self.to_json()
644 d = json.loads(self.delta)
645 d['lab_token'] = self.hostnetworkrelation.lab_token
648 def clear_delta(self):
649 self.delta = json.dumps(self.to_dict())
652 def add_interface(self, interface):
653 self.interfaces.add(interface)
654 d = json.loads(self.delta)
655 hid = self.hostnetworkrelation.host.labid
658 d[hid][interface.mac_address] = []
659 for vlan in interface.config.all():
660 d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
661 self.delta = json.dumps(d)
664 class SnapshotConfig(TaskConfig):
666 resource_id = models.CharField(max_length=200, default="default_id")
667 image = models.IntegerField(null=True)
668 dashboard_id = models.IntegerField()
669 delta = models.TextField(default="{}")
674 d['host'] = self.host.labid
676 d['image'] = self.image
677 d['dashboard_id'] = self.dashboard_id
681 return json.dumps(self.to_dict())
685 self.delta = self.to_json()
688 d = json.loads(self.delta)
691 def clear_delta(self):
692 self.delta = json.dumps(self.to_dict())
695 def set_host(self, host):
697 d = json.loads(self.delta)
698 d['host'] = host.labid
699 self.delta = json.dumps(d)
701 def set_image(self, image):
703 d = json.loads(self.delta)
704 d['image'] = self.image
705 self.delta = json.dumps(d)
707 def clear_image(self):
709 d = json.loads(self.delta)
711 self.delta = json.dumps(d)
713 def set_dashboard_id(self, dash):
714 self.dashboard_id = dash
715 d = json.loads(self.delta)
716 d['dashboard_id'] = self.dashboard_id
717 self.delta = json.dumps(d)
719 def save(self, *args, **kwargs):
720 if len(ResourceQuery.filter(labid=self.resource_id)) != 1:
721 raise ValidationError("resource_id " + str(self.resource_id) + " does not refer to a single resource")
722 super().save(*args, **kwargs)
725 def get_task(task_id):
726 for taskclass in [AccessRelation, SoftwareRelation, HostHardwareRelation, HostNetworkRelation, SnapshotRelation]:
728 ret = taskclass.objects.get(task_id=task_id)
730 except taskclass.DoesNotExist:
732 from django.core.exceptions import ObjectDoesNotExist
733 raise ObjectDoesNotExist("Could not find matching TaskRelation instance")
737 return str(uuid.uuid4())
740 class TaskRelation(models.Model):
742 Relates a Job to a TaskConfig.
744 superclass that relates a Job to tasks anc maintains information
745 like status and messages from the lab
748 status = models.IntegerField(default=JobStatus.NEW)
749 job = models.ForeignKey(Job, on_delete=models.CASCADE)
750 config = models.OneToOneField(TaskConfig, on_delete=models.CASCADE)
751 task_id = models.CharField(default=get_task_uuid, max_length=37)
752 lab_token = models.CharField(default="null", max_length=50)
753 message = models.TextField(default="")
757 def delete(self, *args, **kwargs):
759 return super(self.__class__, self).delete(*args, **kwargs)
762 return "Generic Task"
768 class AccessRelation(TaskRelation):
769 config = models.OneToOneField(AccessConfig, on_delete=models.CASCADE)
775 def delete(self, *args, **kwargs):
777 return super(self.__class__, self).delete(*args, **kwargs)
780 class SoftwareRelation(TaskRelation):
781 config = models.OneToOneField(SoftwareConfig, on_delete=models.CASCADE)
785 return "Software Configuration Task"
787 def delete(self, *args, **kwargs):
789 return super(self.__class__, self).delete(*args, **kwargs)
792 class HostHardwareRelation(TaskRelation):
793 resource_id = models.CharField(max_length=200, default="default_id")
794 config = models.OneToOneField(HardwareConfig, on_delete=models.CASCADE)
798 return "Hardware Configuration Task"
801 return self.config.to_dict()
803 def delete(self, *args, **kwargs):
805 return super(self.__class__, self).delete(*args, **kwargs)
807 def save(self, *args, **kwargs):
808 if len(ResourceQuery.filter(labid=self.resource_id)) != 1:
809 raise ValidationError("resource_id " + str(self.resource_id) + " does not refer to a single resource")
810 super().save(*args, **kwargs)
813 class HostNetworkRelation(TaskRelation):
814 resource_id = models.CharField(max_length=200, default="default_id")
815 config = models.OneToOneField(NetworkConfig, on_delete=models.CASCADE)
819 return "Network Configuration Task"
821 def delete(self, *args, **kwargs):
823 return super(self.__class__, self).delete(*args, **kwargs)
825 def save(self, *args, **kwargs):
826 if len(ResourceQuery.filter(labid=self.resource_id)) != 1:
827 raise ValidationError("resource_id " + str(self.resource_id) + " does not refer to a single resource")
828 super().save(*args, **kwargs)
831 class SnapshotRelation(TaskRelation):
832 snapshot = models.ForeignKey(Image, on_delete=models.CASCADE)
833 config = models.OneToOneField(SnapshotConfig, on_delete=models.CASCADE)
837 return "Snapshot Task"
840 return self.config.to_dict()
842 def delete(self, *args, **kwargs):
844 return super(self.__class__, self).delete(*args, **kwargs)
847 class JobFactory(object):
848 """This class creates all the API models (jobs, tasks, etc) needed to fulfill a booking."""
851 def reimageHost(cls, new_image, booking, host):
852 """Modify an existing job to reimage the given host."""
853 job = Job.objects.get(booking=booking)
854 # make hardware task new
855 hardware_relation = HostHardwareRelation.objects.get(host=host, job=job)
856 hardware_relation.config.set_image(new_image.lab_id)
857 hardware_relation.config.save()
858 hardware_relation.status = JobStatus.NEW
860 # re-apply networking after host is reset
861 net_relation = HostNetworkRelation.objects.get(host=host, job=job)
862 net_relation.status = JobStatus.NEW
864 # re-apply ssh access after host is reset
865 for relation in AccessRelation.objects.filter(job=job, config__access_type="ssh"):
866 relation.status = JobStatus.NEW
869 hardware_relation.save()
873 def makeSnapshotTask(cls, image, booking, host):
874 relation = SnapshotRelation()
875 job = Job.objects.get(booking=booking)
876 config = SnapshotConfig.objects.create(dashboard_id=image.id)
879 relation.config = config
880 relation.config.save()
881 relation.config = relation.config
882 relation.snapshot = image
886 config.set_host(host)
890 def makeCompleteJob(cls, booking):
891 """Create everything that is needed to fulfill the given booking."""
892 resources = booking.resource.get_resources()
895 job = Job.objects.get(booking=booking)
897 job = Job.objects.create(status=JobStatus.NEW, booking=booking)
898 cls.makeHardwareConfigs(
902 cls.makeNetworkConfigs(
910 all_users = list(booking.collaborators.all())
911 all_users.append(booking.owner)
912 cls.makeAccessConfig(
918 for user in all_users:
920 cls.makeAccessConfig(
926 "key": user.userprofile.ssh_public_key.open().read().decode(encoding="UTF-8"),
927 "hosts": [r.labid for r in resources]
934 def makeHardwareConfigs(cls, resources=[], job=Job()):
936 Create and save HardwareConfig.
938 Helper function to create the tasks related to
939 configuring the hardware
941 for res in resources:
942 hardware_config = None
944 hardware_config = HardwareConfig.objects.get(relation__resource_id=res.labid)
946 hardware_config = HardwareConfig()
948 relation = HostHardwareRelation()
949 relation.resource_id = res.labid
951 relation.config = hardware_config
952 relation.config.save()
953 relation.config = relation.config
956 hardware_config.set("image", "hostname", "power", "ipmi_create")
957 hardware_config.save()
960 def makeAccessConfig(cls, users, access_type, revoke=False, job=Job(), context=False):
962 Create and save AccessConfig.
964 Helper function to create the tasks related to
965 configuring the VPN, SSH, etc access for users
968 relation = AccessRelation()
970 config = AccessConfig()
971 config.access_type = access_type
974 relation.config = config
978 config.set_context(context)
979 config.set_access_type(access_type)
980 config.set_revoke(revoke)
981 config.set_user(user)
985 def makeNetworkConfigs(cls, resources=[], job=Job()):
987 Create and save NetworkConfig.
989 Helper function to create the tasks related to
990 configuring the networking
992 for res in resources:
993 network_config = None
995 network_config = NetworkConfig.objects.get(relation__host=res)
997 network_config = NetworkConfig.objects.create()
999 relation = HostNetworkRelation()
1000 relation.resource_id = res.labid
1002 network_config.save()
1003 relation.config = network_config
1005 network_config.clear_delta()
1007 # TODO: use get_interfaces() on resource
1008 for interface in res.interfaces.all():
1009 network_config.add_interface(interface)
1010 network_config.save()
1013 def make_bridge_config(cls, booking):
1014 if booking.resource.hosts.count() < 2:
1017 jumphost_config = ResourceOPNFVConfig.objects.filter(
1018 role__name__iexact="jumphost"
1020 jumphost = ResourceQuery.filter(
1021 bundle=booking.resource,
1022 config=jumphost_config.resource_config
1026 br_config = BridgeConfig.objects.create(opnfv_config=booking.opnfv_config)
1027 for iface in jumphost.interfaces.all():
1028 br_config.interfaces.add(iface)
1032 def makeSoftware(cls, booking=None, job=Job()):
1034 Create and save SoftwareConfig.
1036 Helper function to create the tasks related to
1037 configuring the desired software, e.g. an OPNFV deployment
1039 if not booking.opnfv_config:
1042 opnfv_api_config = OpnfvApiConfig.objects.create(
1043 opnfv_config=booking.opnfv_config,
1044 installer=booking.opnfv_config.installer.name,
1045 scenario=booking.opnfv_config.scenario.name,
1046 bridge_config=cls.make_bridge_config(booking)
1049 opnfv_api_config.set_xdf(booking, False)
1050 opnfv_api_config.save()
1052 for host in booking.resource.hosts.all():
1053 opnfv_api_config.roles.add(host)
1054 software_config = SoftwareConfig.objects.create(opnfv=opnfv_api_config)
1055 software_relation = SoftwareRelation.objects.create(job=job, config=software_config)
1056 return software_relation
1059 JOB_TASK_CLASSLIST = [
1060 HostHardwareRelation,
1062 HostNetworkRelation,
1068 class JobTaskQuery(AbstractModelQuery):
1069 model_list = JOB_TASK_CLASSLIST