From: Parker Berberian Date: Tue, 12 Nov 2019 17:54:20 +0000 (-0500) Subject: Begin Resource Refactor X-Git-Tag: 2.0.99~47 X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F56%2F68856%2F12;p=laas.git Begin Resource Refactor Begins the Resource Refactor by creating new interfaces to the resources through a Resource super class and using that new interface in the api Change-Id: I15a8179bfe915d2cde6d658d056e11cbd2c70e43 Signed-off-by: Parker Berberian --- diff --git a/src/account/views.py b/src/account/views.py index 2b4eccb..5b91550 100644 --- a/src/account/views.py +++ b/src/account/views.py @@ -33,7 +33,7 @@ from account.forms import AccountSettingsForm from account.jira_util import SignatureMethod_RSA_SHA1 from account.models import UserProfile from booking.models import Booking -from resource_inventory.models import GenericResourceBundle, ConfigBundle, Image, Host +from resource_inventory.models import GenericResourceBundle, ConfigBundle, Image @method_decorator(login_required, name='dispatch') @@ -229,7 +229,7 @@ def account_images_view(request): public_images = Image.objects.filter(public=True) used_images = {} for image in my_images: - if Host.objects.filter(booked=True, config__image=image).exists(): + if image.in_use(): used_images[image.id] = "true" context = { "title": "Images", @@ -286,7 +286,7 @@ def image_delete_view(request, image_id=None): if image.public or image.owner.id != request.user.id: return HttpResponse('no') # 403? # check if used in booking - if Host.objects.filter(booked=True, config__image=image).exists(): + if image.in_use(): return HttpResponse('no') # 403? image.delete() return HttpResponse('') diff --git a/src/api/migrations/0010_auto_20191219_2004.py b/src/api/migrations/0010_auto_20191219_2004.py new file mode 100644 index 0000000..ec48584 --- /dev/null +++ b/src/api/migrations/0010_auto_20191219_2004.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2 on 2019-12-19 20:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0009_merge_20190508_1317'), + ] + + operations = [ + migrations.AddField( + model_name='taskconfig', + name='delta_keys_list', + field=models.CharField(default='[]', max_length=200), + ), + migrations.AddField( + model_name='taskconfig', + name='state', + field=models.IntegerField(default=200), + ), + ] diff --git a/src/api/models.py b/src/api/models.py index 682785b..520e747 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -27,7 +27,8 @@ from resource_inventory.models import ( Interface, HostOPNFVConfig, RemoteInfo, - OPNFVConfig + OPNFVConfig, + ConfigState ) from resource_inventory.idf_templater import IDFTemplater from resource_inventory.pdf_templater import PDFTemplater @@ -294,32 +295,12 @@ class Job(models.Model): def to_dict(self): d = {} - j = {} - j['id'] = self.id - for relation in AccessRelation.objects.filter(job=self): - if 'access' not in d: - d['access'] = {} - d['access'][relation.task_id] = relation.config.to_dict() - for relation in SoftwareRelation.objects.filter(job=self): - if 'software' not in d: - d['software'] = {} - d['software'][relation.task_id] = relation.config.to_dict() - for relation in HostHardwareRelation.objects.filter(job=self): - if 'hardware' not in d: - d['hardware'] = {} - d['hardware'][relation.task_id] = relation.config.to_dict() - for relation in HostNetworkRelation.objects.filter(job=self): - if 'network' not in d: - d['network'] = {} - d['network'][relation.task_id] = relation.config.to_dict() - for relation in SnapshotRelation.objects.filter(job=self): - if 'snapshot' not in d: - d['snapshot'] = {} - d['snapshot'][relation.task_id] = relation.config.to_dict() - - j['payload'] = d - - return j + for relation in self.get_tasklist(): + if relation.job_key not in d: + d[relation.job_key] = {} + d[relation.job_key][relation.task_id] = relation.config.to_dict() + + return {"id": self.id, "payload": d} def get_tasklist(self, status="all"): tasklist = [] @@ -351,48 +332,54 @@ class Job(models.Model): def get_delta(self, status): d = {} - j = {} - j['id'] = self.id - for relation in AccessRelation.objects.filter(job=self).filter(status=status): - if 'access' not in d: - d['access'] = {} - d['access'][relation.task_id] = relation.config.get_delta() - for relation in SoftwareRelation.objects.filter(job=self).filter(status=status): - if 'software' not in d: - d['software'] = {} - d['software'][relation.task_id] = relation.config.get_delta() - for relation in HostHardwareRelation.objects.filter(job=self).filter(status=status): - if 'hardware' not in d: - d['hardware'] = {} - d['hardware'][relation.task_id] = relation.config.get_delta() - for relation in HostNetworkRelation.objects.filter(job=self).filter(status=status): - if 'network' not in d: - d['network'] = {} - d['network'][relation.task_id] = relation.config.get_delta() - for relation in SnapshotRelation.objects.filter(job=self).filter(status=status): - if 'snapshot' not in d: - d['snapshot'] = {} - d['snapshot'][relation.task_id] = relation.config.get_delta() - - j['payload'] = d - return j + for relation in self.get_tasklist(status=status): + if relation.job_key not in d: + d[relation.job_key] = {} + d[relation.job_key][relation.task_id] = relation.config.get_delta() + + return {"id": self.id, "payload": d} def to_json(self): return json.dumps(self.to_dict()) class TaskConfig(models.Model): + state = models.IntegerField(default=ConfigState.CLEAN) + + keys = set() # TODO: This needs to be an instance variable, not a class variable + delta_keys_list = models.CharField(max_length=200, default="[]") + + @property + def delta_keys(self): + return list(set(json.loads(self.delta_keys_list))) + + @delta_keys.setter + def delta_keys(self, keylist): + self.delta_keys_list = json.dumps(keylist) + def to_dict(self): - pass + raise NotImplementedError def get_delta(self): - pass + raise NotImplementedError + + def format_delta(self, config, token): + delta = {k: config[k] for k in self.delta_keys} + delta['lab_token'] = token + return delta def to_json(self): return json.dumps(self.to_dict()) def clear_delta(self): - self.delta = '{}' + self.delta_keys = [] + + def set(self, *args): + dkeys = self.delta_keys + for arg in args: + if arg in self.keys: + dkeys.append(arg) + self.delta_keys = dkeys class BridgeConfig(models.Model): @@ -606,55 +593,12 @@ class HardwareConfig(TaskConfig): ipmi_create = models.BooleanField(default=False) delta = models.TextField() - def to_dict(self): - d = {} - d['image'] = self.image - d['power'] = self.power - d['hostname'] = self.hostname - d['ipmi_create'] = str(self.ipmi_create) - d['id'] = self.hosthardwarerelation.host.labid - return d - - def to_json(self): - return json.dumps(self.to_dict()) + keys = set(["id", "image", "power", "hostname", "ipmi_create"]) def get_delta(self): - if not self.delta: - self.delta = self.to_json() - self.save() - d = json.loads(self.delta) - d['lab_token'] = self.hosthardwarerelation.lab_token - return d - - def clear_delta(self): - d = {} - d["id"] = self.hosthardwarerelation.host.labid - d["lab_token"] = self.hosthardwarerelation.lab_token - self.delta = json.dumps(d) - - def set_image(self, image): - self.image = image - d = json.loads(self.delta) - d['image'] = self.image - self.delta = json.dumps(d) - - def set_power(self, power): - self.power = power - d = json.loads(self.delta) - d['power'] = power - self.delta = json.dumps(d) - - def set_hostname(self, hostname): - self.hostname = hostname - d = json.loads(self.delta) - d['hostname'] = hostname - self.delta = json.dumps(d) - - def set_ipmi_create(self, ipmi_create): - self.ipmi_create = ipmi_create - d = json.loads(self.delta) - d['ipmi_create'] = ipmi_create - self.delta = json.dumps(d) + return self.format_delta( + self.hosthardwarerelation.host.get_configuration(self.state), + self.hosthardwarerelation.lab_token) class NetworkConfig(TaskConfig): @@ -781,6 +725,8 @@ class TaskRelation(models.Model): lab_token = models.CharField(default="null", max_length=50) message = models.TextField(default="") + job_key = None + def delete(self, *args, **kwargs): self.config.delete() return super(self.__class__, self).delete(*args, **kwargs) @@ -794,6 +740,7 @@ class TaskRelation(models.Model): class AccessRelation(TaskRelation): config = models.OneToOneField(AccessConfig, on_delete=models.CASCADE) + job_key = "access" def type_str(self): return "Access Task" @@ -805,6 +752,7 @@ class AccessRelation(TaskRelation): class SoftwareRelation(TaskRelation): config = models.OneToOneField(SoftwareConfig, on_delete=models.CASCADE) + job_key = "software" def type_str(self): return "Software Configuration Task" @@ -817,6 +765,7 @@ class SoftwareRelation(TaskRelation): class HostHardwareRelation(TaskRelation): host = models.ForeignKey(Host, on_delete=models.CASCADE) config = models.OneToOneField(HardwareConfig, on_delete=models.CASCADE) + job_key = "hardware" def type_str(self): return "Hardware Configuration Task" @@ -832,6 +781,7 @@ class HostHardwareRelation(TaskRelation): class HostNetworkRelation(TaskRelation): host = models.ForeignKey(Host, on_delete=models.CASCADE) config = models.OneToOneField(NetworkConfig, on_delete=models.CASCADE) + job_key = "network" def type_str(self): return "Network Configuration Task" @@ -844,6 +794,7 @@ class HostNetworkRelation(TaskRelation): class SnapshotRelation(TaskRelation): snapshot = models.ForeignKey(Image, on_delete=models.CASCADE) config = models.OneToOneField(SnapshotConfig, on_delete=models.CASCADE) + job_key = "snapshot" def type_str(self): return "Snapshot Task" @@ -960,11 +911,7 @@ class JobFactory(object): relation.config = relation.config relation.save() - hardware_config.clear_delta() - hardware_config.set_image(host.config.image.lab_id) - hardware_config.set_hostname(host.template.resource.name) - hardware_config.set_power("on") - hardware_config.set_ipmi_create(True) + hardware_config.set("image", "hostname", "power", "ipmi_create") hardware_config.save() @classmethod diff --git a/src/api/tests/test_models_unittest.py b/src/api/tests/test_models_unittest.py index e6f97a6..2ecbe42 100644 --- a/src/api/tests/test_models_unittest.py +++ b/src/api/tests/test_models_unittest.py @@ -1,4 +1,3 @@ -############################################################################## # Copyright (c) 2019 Sawyer Bergeron, Parker Berberian, and others. # # All rights reserved. This program and the accompanying materials @@ -21,6 +20,7 @@ from api.models import ( from resource_inventory.models import ( OPNFVRole, HostProfile, + ConfigState, ) from django.test import TestCase, Client @@ -201,7 +201,7 @@ class ValidBookingCreatesValidJob(TestCase): self.assertEqual(relation.status, JobStatus.NEW) config = relation.config host = relation.host - self.assertEqual(config.hostname, host.template.resource.name) + self.assertEqual(config.get_delta()["hostname"], host.template.resource.name) def test_complete_job_makes_software_configs(self): JobFactory.makeCompleteJob(self.booking) @@ -261,9 +261,10 @@ class ValidBookingCreatesValidJob(TestCase): host_set.remove(relation.host.id) except KeyError: self.fail("Hardware Relation/Config not created for host " + str(relation.host)) - - self.assertEqual(relation.config.power, "on") - self.assertTrue(relation.config.ipmi_create) + # TODO: ConfigState needs to be fixed in factory methods + relation.config.state = ConfigState.NEW + self.assertEqual(relation.config.get_delta()["power"], "on") + self.assertTrue(relation.config.get_delta()["ipmi_create"]) # TODO: the rest of hwconf attrs self.assertEqual(len(host_set), 0) diff --git a/src/booking/quick_deployer.py b/src/booking/quick_deployer.py index 0e0cc5a..4ec488e 100644 --- a/src/booking/quick_deployer.py +++ b/src/booking/quick_deployer.py @@ -245,7 +245,7 @@ def configure_networking(grb, config): # create network net = Network.objects.create(name="public", bundle=grb, is_public=True) # connect network to generic host - grb.getHosts()[0].generic_interfaces.first().connections.add( + grb.getResources()[0].generic_interfaces.first().connections.add( NetworkConnection.objects.create(network=net, vlan_is_tagged=False) ) # asign network role diff --git a/src/dashboard/tasks.py b/src/dashboard/tasks.py index 597629f..71afed2 100644 --- a/src/dashboard/tasks.py +++ b/src/dashboard/tasks.py @@ -23,7 +23,7 @@ def booking_poll(): for hostrelation in qs: config = hostrelation.config config.clear_delta() - config.set_power("off") + config.power = "off" config.save() hostrelation.status = JobStatus.NEW hostrelation.save() diff --git a/src/resource_inventory/migrations/0012_auto_20200103_1850.py b/src/resource_inventory/migrations/0012_auto_20200103_1850.py new file mode 100644 index 0000000..2bb203e --- /dev/null +++ b/src/resource_inventory/migrations/0012_auto_20200103_1850.py @@ -0,0 +1,76 @@ +# Generated by Django 2.2 on 2020-01-03 18:50 + +from django.db import migrations, models +import django.db.models.deletion + + +def genTempVlanNetwork(apps, editor): + Vlan = apps.get_model("resource_inventory", "Vlan") + Network = apps.get_model("resource_inventory", "Network") + tempVlanNetwork = apps.get_model("resource_inventory", "tempVlanNetwork") + for vlan in Vlan.objects.filter(network__isnull=False): + tempVlanNetwork.objects.create(network=vlan.network, vlan=vlan) + +def deleteTempVlanNetworks(apps, editor): + tempVlanNetwork = apps.get_model("resource_inventory", "tempVlanNetwork") + tempVlanNetwork.objects.all().delete() + + +def pairVlanPhysicalNetworks(apps, editor): + PhysicalNetwork = apps.get_model("resource_inventory", "PhysicalNetwork") + tempVlanPair = apps.get_model("resource_inventory", "tempVlanNetwork") + for pair in tempVlanPair.objects.all(): + physicalNetwork = PhysicalNetwork.objects.create(vlan_id=vlan.vlan_id, + generic_network=pair.network) + pair.vlan.network = physicalNetwork + +def deletePhysicalNetworks(apps, editor): + PhysicalNetwork = apps.get_model("resource_inventory", "PhysicalNetwork") + PhysicalNetwork.objects.all().delete() + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0011_auto_20191106_2024'), + ] + + operations = [ + migrations.CreateModel( + name='PhysicalNetwork', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('vlan_id', models.IntegerField()), + ('generic_network', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Network')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AlterField( + model_name='host', + name='id', + field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='resourcebundle', + name='id', + field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.CreateModel( + name='tempVlanNetwork', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('vlan', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.vlan')), + ('network', models.ForeignKey(null=True, to='resource_inventory.network', on_delete=django.db.models.deletion.CASCADE)), + ] + ), + migrations.RunPython(genTempVlanNetwork, deleteTempVlanNetworks), + migrations.AlterField( + model_name='vlan', + name='network', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, + to='resource_inventory.PhysicalNetwork', null=True), + ), + migrations.RunPython(pairVlanPhysicalNetworks, deletePhysicalNetworks), + migrations.DeleteModel("tempVlanNetwork") + ] diff --git a/src/resource_inventory/models.py b/src/resource_inventory/models.py index 031ccce..d152698 100644 --- a/src/resource_inventory/models.py +++ b/src/resource_inventory/models.py @@ -105,6 +105,33 @@ class RamProfile(models.Model): return str(self.amount) + "G for " + str(self.host) +class Resource(models.Model): + class Meta: + abstract = True + + def get_configuration(self, state): + """ + Returns the desired configuration for this host as a + JSON object as defined in the rest api spec. + state is a ConfigState + TODO: single method, or different methods for hw, network, snapshot, etc? + """ + raise NotImplementedError("Must implement in concrete Resource classes") + + def reserve(self): + """ + Reserves this resource for its currently + assigned booking. + """ + raise NotImplementedError("Must implement in concrete Resource classes") + + def release(self): + """ + Makes this resource available again for new boookings + """ + raise NotImplementedError("Must implement in concrete Resource classes") + + # Generic resource templates class GenericResourceBundle(models.Model): id = models.AutoField(primary_key=True) @@ -116,12 +143,12 @@ class GenericResourceBundle(models.Model): public = models.BooleanField(default=False) hidden = models.BooleanField(default=False) - def getHosts(self): - return_hosts = [] + def getResources(self): + my_resources = [] for genericResource in self.generic_resources.all(): - return_hosts.append(genericResource.getHost()) + my_resources.append(genericResource.getResource()) - return return_hosts + return my_resources def __str__(self): return self.name @@ -137,6 +164,29 @@ class Network(models.Model): return self.name +class PhysicalNetwork(Resource): + vlan_id = models.IntegerField() + generic_network = models.ForeignKey(Network, on_delete=models.CASCADE) + + def get_configuration(self, state): + """ + Returns the network configuration + Collects info about each attached network interface and vlan, etc + """ + return {} + + def reserve(self): + """ + Reserves vlan(s) associated with this network + """ + # vlan_manager = self.bundle.lab.vlan_manager + return False + + def release(self): + # vlan_manager = self.bundle.lab.vlan_manager + return False + + class NetworkConnection(models.Model): network = models.ForeignKey(Network, on_delete=models.CASCADE) vlan_is_tagged = models.BooleanField() @@ -147,18 +197,25 @@ class Vlan(models.Model): vlan_id = models.IntegerField() tagged = models.BooleanField() public = models.BooleanField(default=False) - network = models.ForeignKey(Network, on_delete=models.DO_NOTHING, null=True) + network = models.ForeignKey(PhysicalNetwork, on_delete=models.DO_NOTHING, null=True) def __str__(self): return str(self.vlan_id) + ("_T" if self.tagged else "") +class ConfigState: + NEW = 0 + RESET = 100 + CLEAN = 200 + + class GenericResource(models.Model): bundle = models.ForeignKey(GenericResourceBundle, related_name='generic_resources', on_delete=models.CASCADE) hostname_validchars = RegexValidator(regex=r'(?=^.{1,253}$)(?=(^([A-Za-z0-9\-\_]{1,62}\.)*[A-Za-z0-9\-\_]{1,63}$))', message="Enter a valid hostname. Full domain name may be 1-253 characters, each hostname 1-63 characters (including suffixed dot), and valid characters for hostnames are A-Z, a-z, 0-9, hyphen (-), and underscore (_)") name = models.CharField(max_length=200, validators=[hostname_validchars]) - def getHost(self): + def getResource(self): + # TODO: This will have to be dealt with return self.generic_host def __str__(self): @@ -183,8 +240,7 @@ class GenericHost(models.Model): # Physical, actual resources -class ResourceBundle(models.Model): - id = models.AutoField(primary_key=True) +class ResourceBundle(Resource): template = models.ForeignKey(GenericResourceBundle, on_delete=models.SET_NULL, null=True) def __str__(self): @@ -289,6 +345,9 @@ class Image(models.Model): def __str__(self): return self.name + def in_use(self): + return Host.objects.filter(booked=True, config__image=self).exists() + def get_sentinal_opnfv_role(): return OPNFVRole.objects.get_or_create(name="deleted", description="Role was deleted.") @@ -336,8 +395,7 @@ def get_default_remote_info(): # Concrete host, actual machine in a lab -class Host(models.Model): - id = models.AutoField(primary_key=True) +class Host(Resource): template = models.ForeignKey(GenericHost, on_delete=models.SET_NULL, null=True) booked = models.BooleanField(default=False) name = models.CharField(max_length=200, unique=True) @@ -354,6 +412,18 @@ class Host(models.Model): def __str__(self): return self.name + def get_configuration(self, state): + ipmi = state == ConfigState.NEW + power = "off" if state == ConfigState.CLEAN else "on" + + return { + "id": self.labid, + "image": self.config.image.lab_id, + "hostname": self.template.resource.name, + "power": power, + "ipmi_create": str(ipmi) + } + class Interface(models.Model): id = models.AutoField(primary_key=True) diff --git a/src/resource_inventory/resource_manager.py b/src/resource_inventory/resource_manager.py index e94b4ec..7df4263 100644 --- a/src/resource_inventory/resource_manager.py +++ b/src/resource_inventory/resource_manager.py @@ -50,7 +50,7 @@ class ResourceManager: # count up hosts profile_count = {} - for host in grb.getHosts(): + for host in grb.getResources(): if host.profile not in profile_count: profile_count[host.profile] = 0 profile_count[host.profile] += 1 @@ -71,7 +71,7 @@ class ResourceManager: # public interface def deleteResourceBundle(self, resourceBundle): for host in Host.objects.filter(bundle=resourceBundle): - self.releaseHost(host) + host.release() resourceBundle.delete() def get_vlans(self, genericResourceBundle): @@ -93,7 +93,7 @@ class ResourceManager: Takes in a GenericResourceBundle and 'converts' it into a ResourceBundle """ resource_bundle = ResourceBundle.objects.create(template=genericResourceBundle) - generic_hosts = genericResourceBundle.getHosts() + generic_hosts = genericResourceBundle.getResources() physical_hosts = [] vlan_map = self.get_vlans(genericResourceBundle) @@ -154,12 +154,6 @@ class ResourceManager: host.save() return host - def releaseHost(self, host): - host.template = None - host.bundle = None - host.booked = False - host.save() - def releaseNetworks(self, grb, vlan_manager, vlans): for net_name, vlan_id in vlans.items(): net = Network.objects.get(name=net_name, bundle=grb) @@ -172,7 +166,7 @@ class ResourceManager: vlan_manager = grb.lab.vlan_manager self.releaseNetworks(grb, vlan_manager, vlans) for host in hosts: - self.releaseHost(host) + host.release() class HostNameValidator(object): diff --git a/src/workflow/models.py b/src/workflow/models.py index 9d1fac2..99608f6 100644 --- a/src/workflow/models.py +++ b/src/workflow/models.py @@ -146,7 +146,7 @@ class BookingAuthManager(): return True # admin override for this user if Booking.objects.filter(owner=booking.owner, end__gt=timezone.now()).count() >= 3: return False - if len(booking.resource.template.getHosts()) < 2: + if len(booking.resource.template.getResources()) < 2: return True # if they only have one server, we dont care if repo.BOOKING_INFO_FILE not in repo.el: return False # INFO file not provided diff --git a/src/workflow/opnfv_workflow.py b/src/workflow/opnfv_workflow.py index a192d6e..0cac48e 100644 --- a/src/workflow/opnfv_workflow.py +++ b/src/workflow/opnfv_workflow.py @@ -202,7 +202,7 @@ class Assign_Host_Roles(WorkflowStep): # taken verbatim from Define_Software in if config is None: context['error'] = "Please select a Configuration on the first step" - formset = self.create_host_role_formset(hostlist=config.bundle.getHosts()) + formset = self.create_host_role_formset(hostlist=config.bundle.getResources()) context['formset'] = formset return context diff --git a/src/workflow/snapshot_workflow.py b/src/workflow/snapshot_workflow.py index 4266587..c2f4cd6 100644 --- a/src/workflow/snapshot_workflow.py +++ b/src/workflow/snapshot_workflow.py @@ -36,7 +36,7 @@ class Select_Host_Step(WorkflowStep): booking_hosts[booking.id]['start'] = booking.start.strftime("%Y-%m-%d") booking_hosts[booking.id]['end'] = booking.end.strftime("%Y-%m-%d") booking_hosts[booking.id]['hosts'] = [] - for genericHost in booking.resource.template.getHosts(): + for genericHost in booking.resource.template.getResources(): booking_hosts[booking.id]['hosts'].append({"name": genericHost.resource.name}) context['booking_hosts'] = booking_hosts diff --git a/src/workflow/workflow_manager.py b/src/workflow/workflow_manager.py index 4677829..fda105e 100644 --- a/src/workflow/workflow_manager.py +++ b/src/workflow/workflow_manager.py @@ -189,7 +189,7 @@ class SessionManager(): models['bundle'] = resource models['interfaces'] = {} models['vlans'] = {} - for host in resource.getHosts(): + for host in resource.getResources(): models['hosts'].append(host) models['interfaces'][host.resource.name] = [] models['vlans'][host.resource.name] = {} @@ -205,7 +205,7 @@ class SessionManager(): confirm['resource'] = {} confirm['resource']['hosts'] = [] confirm['resource']['lab'] = resource.lab.lab_user.username - for host in resource.getHosts(): + for host in resource.getResources(): confirm['resource']['hosts'].append({"name": host.resource.name, "profile": host.profile.name}) return confirm