From 2810ec64be1950e9ef45a29634ee72db00701021 Mon Sep 17 00:00:00 2001 From: Tzu-Mainn Chen Date: Tue, 19 Mar 2024 10:44:41 -0400 Subject: [PATCH] Push up node resource_class/properties/traits to node/lease/offer APIs These attributes are of interest to users, and should be viewable by users. This PR also renames resource_object 'config' to 'properties', as that is what that attribute actually represents. --- README.md | 2 +- esi_leap/api/controllers/v1/lease.py | 4 +++- esi_leap/api/controllers/v1/node.py | 8 +++++++- esi_leap/api/controllers/v1/offer.py | 4 +++- esi_leap/api/controllers/v1/utils.py | 2 ++ esi_leap/common/ironic.py | 8 ++++++++ esi_leap/objects/lease.py | 6 +++--- esi_leap/resource_objects/base.py | 4 ++-- esi_leap/resource_objects/dummy_node.py | 8 ++++---- esi_leap/resource_objects/error.py | 2 +- esi_leap/resource_objects/ironic_node.py | 13 ++++++++----- esi_leap/resource_objects/test_node.py | 2 +- esi_leap/tests/api/controllers/v1/test_node.py | 6 +++++- esi_leap/tests/api/controllers/v1/test_utils.py | 2 ++ esi_leap/tests/common/test_ironic.py | 17 +++++++++++++++++ .../tests/resource_objects/test_dummy_node.py | 10 +++++----- .../tests/resource_objects/test_ironic_node.py | 16 ++++++++++------ .../tests/resource_objects/test_test_node.py | 4 ++-- 18 files changed, 84 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index afb19049..befe9ada 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ as specified above. Once you do so, add dummy nodes as follows: cat < /tmp/nodes/1718 { "project_owner_id": "project id of dummy node owner", - "server_config": { + "properties": { "new attribute XYZ": "This is just a sample list of free-form attributes used for describing a server.", "cpu_type": "Intel Xeon", "cores": 16, diff --git a/esi_leap/api/controllers/v1/lease.py b/esi_leap/api/controllers/v1/lease.py index c636a061..0d710772 100644 --- a/esi_leap/api/controllers/v1/lease.py +++ b/esi_leap/api/controllers/v1/lease.py @@ -46,6 +46,7 @@ class Lease(base.ESILEAPBase): resource_type = wsme.wsattr(wtypes.text) resource_uuid = wsme.wsattr(wtypes.text) resource_class = wsme.wsattr(wtypes.text) + resource_properties = {wtypes.text: types.jsontype} resource = wsme.wsattr(wtypes.text, readonly=True) start_time = wsme.wsattr(datetime.datetime) fulfill_time = wsme.wsattr(datetime.datetime, readonly=True) @@ -62,7 +63,8 @@ def __init__(self, **kwargs): for field in self.fields: setattr(self, field, kwargs.get(field, wtypes.Unset)) - for attr in ('project', 'owner', 'resource', 'resource_class'): + for attr in ('project', 'owner', 'resource', 'resource_class', + 'resource_properties'): setattr(self, attr, kwargs.get(attr, wtypes.Unset)) diff --git a/esi_leap/api/controllers/v1/node.py b/esi_leap/api/controllers/v1/node.py index 0af1d71d..dc296ee8 100644 --- a/esi_leap/api/controllers/v1/node.py +++ b/esi_leap/api/controllers/v1/node.py @@ -36,6 +36,8 @@ class Node(base.ESILEAPBase): owner = wsme.wsattr(wtypes.text) maintenance = wsme.wsattr(wtypes.text) provision_state = wsme.wsattr(wtypes.text) + properties = {wtypes.text: types.jsontype} + resource_class = wsme.wsattr(wtypes.text) uuid = wsme.wsattr(wtypes.text) offer_uuid = wsme.wsattr(wtypes.text) lease_uuid = wsme.wsattr(wtypes.text) @@ -46,7 +48,8 @@ class Node(base.ESILEAPBase): def __init__(self, **kwargs): self.fields = ('name', 'owner', 'uuid', 'offer_uuid', 'lease_uuid', 'lessee', 'future_offers', 'future_leases', - 'provision_state', 'maintenance') + 'resource_class', 'provision_state', 'maintenance', + 'properties') for field in self.fields: setattr(self, field, kwargs.get(field, wtypes.Unset)) @@ -103,6 +106,9 @@ def get_all(self): n = Node(name=node.name, uuid=node.uuid, provision_state=node.provision_state, + resource_class=node.resource_class, + properties=ironic.get_condensed_properties( + node.properties, node.traits), maintenance=str(node.maintenance), owner=keystone.get_project_name(node.owner, project_list), lessee=keystone.get_project_name(node.lessee, diff --git a/esi_leap/api/controllers/v1/offer.py b/esi_leap/api/controllers/v1/offer.py index fccd8a02..d9f387b8 100644 --- a/esi_leap/api/controllers/v1/offer.py +++ b/esi_leap/api/controllers/v1/offer.py @@ -48,6 +48,7 @@ class Offer(base.ESILEAPBase): resource_uuid = wsme.wsattr(wtypes.text, mandatory=True) resource = wsme.wsattr(wtypes.text, readonly=True) resource_class = wsme.wsattr(wtypes.text) + resource_properties = {wtypes.text: types.jsontype} start_time = wsme.wsattr(datetime.datetime) end_time = wsme.wsattr(datetime.datetime) status = wsme.wsattr(wtypes.text, readonly=True) @@ -62,7 +63,8 @@ def __init__(self, **kwargs): setattr(self, field, kwargs.get(field, wtypes.Unset)) for attr in ('availabilities', 'project', 'lessee', - 'resource', 'resource_class'): + 'resource', 'resource_class', + 'resource_properties'): setattr(self, attr, kwargs.get(attr, wtypes.Unset)) diff --git a/esi_leap/api/controllers/v1/utils.py b/esi_leap/api/controllers/v1/utils.py index 7206dd31..f4335d65 100644 --- a/esi_leap/api/controllers/v1/utils.py +++ b/esi_leap/api/controllers/v1/utils.py @@ -155,6 +155,7 @@ def offer_get_dict_with_added_info(offer, project_list=None, node_list=None): o['lessee'] = keystone.get_project_name(offer.lessee_id, project_list) o['resource'] = resource.get_name(node_list) o['resource_class'] = resource.get_resource_class(node_list) + o['resource_properties'] = resource.get_properties(node_list) return o @@ -168,6 +169,7 @@ def lease_get_dict_with_added_info(lease, project_list=None, node_list=None): project_list) lease_dict['resource'] = resource.get_name(node_list) lease_dict['resource_class'] = resource.get_resource_class(node_list) + lease_dict['resource_properties'] = resource.get_properties(node_list) return lease_dict diff --git a/esi_leap/common/ironic.py b/esi_leap/common/ironic.py index 86c1d9f6..ccdbfcb0 100644 --- a/esi_leap/common/ironic.py +++ b/esi_leap/common/ironic.py @@ -53,3 +53,11 @@ def get_node(node_uuid, node_list=None): else: node = next((n for n in node_list if n.uuid == node_uuid), None) return node + + +def get_condensed_properties(properties, traits): + cp = properties.copy() + cp.pop('lease_uuid', None) + cp.pop('capabilities', None) + cp['traits'] = traits + return cp diff --git a/esi_leap/objects/lease.py b/esi_leap/objects/lease.py index e62176cd..7b2e3d64 100644 --- a/esi_leap/objects/lease.py +++ b/esi_leap/objects/lease.py @@ -100,9 +100,9 @@ def __init__(self, lease, node): setattr(node, 'node_name', node.get_name()) setattr(node, 'node_provision_state', node.get_node_provision_state()) setattr(node, 'node_power_state', node.get_node_power_state()) - node_config = node.get_config().copy() - node_config.pop('lease_uuid', None) - setattr(node, 'node_properties', node_config) + node_properties = node.get_properties().copy() + node_properties.pop('lease_uuid', None) + setattr(node, 'node_properties', node_properties) self.populate_schema(lease=lease, node=node) diff --git a/esi_leap/resource_objects/base.py b/esi_leap/resource_objects/base.py index d2431bab..a015eace 100644 --- a/esi_leap/resource_objects/base.py +++ b/esi_leap/resource_objects/base.py @@ -33,8 +33,8 @@ def get_resource_class(self, resource_list): """Return resource's associated class, if any""" @abc.abstractmethod - def get_config(self): - """Return resource's associated config, if any""" + def get_properties(self, resource_list): + """Return resource's associated properties, if any""" @abc.abstractmethod def get_owner_project_id(self): diff --git a/esi_leap/resource_objects/dummy_node.py b/esi_leap/resource_objects/dummy_node.py index 49b14397..55046dd2 100644 --- a/esi_leap/resource_objects/dummy_node.py +++ b/esi_leap/resource_objects/dummy_node.py @@ -47,10 +47,10 @@ def get_resource_class(self, resource_list=None): err_msg='Error getting resource class', err_val=error.UNKNOWN['resource_class']) - def get_config(self): - return self._get_node_attr('server_config', {}, - err_msg='Error getting resource config', - err_val=error.UNKNOWN['config']) + def get_properties(self, resource_list=None): + return self._get_node_attr('properties', {}, + err_msg='Error getting resource properties', + err_val=error.UNKNOWN['properties']) def get_owner_project_id(self): return self._get_node_attr('project_owner_id', None, diff --git a/esi_leap/resource_objects/error.py b/esi_leap/resource_objects/error.py index 6aa12bfc..454d6441 100644 --- a/esi_leap/resource_objects/error.py +++ b/esi_leap/resource_objects/error.py @@ -14,7 +14,7 @@ 'uuid': 'unknown-uuid', 'name': 'unknown-name', 'resource_class': 'unknown-class', - 'config': {}, + 'properties': {}, 'owner_project_id': 'unknown-owner', 'lease_uuid': 'unknown-lease', 'lessee_project_id': 'unknown-lessee', diff --git a/esi_leap/resource_objects/ironic_node.py b/esi_leap/resource_objects/ironic_node.py index b7a35ebf..e0e1a2a1 100644 --- a/esi_leap/resource_objects/ironic_node.py +++ b/esi_leap/resource_objects/ironic_node.py @@ -61,11 +61,14 @@ def get_resource_class(self, resource_list=None): err_msg='Error getting resource class', err_val=error.UNKNOWN['resource_class']) - def get_config(self): - config = self._get_node_attr('properties', {}, - err_msg='Error getting resource config', - err_val=error.UNKNOWN['config']) - return config + def get_properties(self, resource_list=None): + properties = self._get_node_attr( + 'properties', {}, resource_list=resource_list, + err_msg='Error getting resource properties', + err_val=error.UNKNOWN['properties']) + traits = self._get_node_attr( + 'traits', [], resource_list=resource_list) + return ironic.get_condensed_properties(properties, traits) def get_owner_project_id(self): return self._get_node_attr('owner', '', diff --git a/esi_leap/resource_objects/test_node.py b/esi_leap/resource_objects/test_node.py index 584f18d5..b01ed3ff 100644 --- a/esi_leap/resource_objects/test_node.py +++ b/esi_leap/resource_objects/test_node.py @@ -30,7 +30,7 @@ def get_name(self, resource_list=None): def get_resource_class(self, resource_list=None): return 'fake' - def get_config(self): + def get_properties(self, resource_list=None): return {} def get_owner_project_id(self): diff --git a/esi_leap/tests/api/controllers/v1/test_node.py b/esi_leap/tests/api/controllers/v1/test_node.py index 4693fcbe..f8781e3b 100644 --- a/esi_leap/tests/api/controllers/v1/test_node.py +++ b/esi_leap/tests/api/controllers/v1/test_node.py @@ -20,10 +20,12 @@ def __init__(self): self.name = 'fake-node' self.owner = 'fake-project-uuid' self.uuid = 'fake-uuid' - self.properties = {'lease_uuid': 'fake-lease-uuid'} + self.properties = {'lease_uuid': 'fake-lease-uuid', 'cpu': '40'} + self.traits = ['trait1', 'trait2'] self.lessee = 'fake-project-uuid' self.maintenance = False self.provision_state = 'active' + self.resource_class = 'baremetal' class FakeProject(object): @@ -61,3 +63,5 @@ def test_get_all(self, mock_gpl, mock_lga, mock_oga, mock_gnl): self.assertEqual(data['nodes'][0]['owner'], 'fake-project') self.assertEqual(data['nodes'][0]['lease_uuid'], 'fake-lease-uuid') self.assertEqual(data['nodes'][0]['lessee'], 'fake-project') + self.assertEqual(data['nodes'][0]['properties'], { + 'cpu': '40', 'traits': ['trait1', 'trait2']}) diff --git a/esi_leap/tests/api/controllers/v1/test_utils.py b/esi_leap/tests/api/controllers/v1/test_utils.py index 96ed96f3..edfbd41e 100644 --- a/esi_leap/tests/api/controllers/v1/test_utils.py +++ b/esi_leap/tests/api/controllers/v1/test_utils.py @@ -642,6 +642,7 @@ def test_offer_get_dict_with_added_info(self, 'resource_type': o.resource_type, 'resource_uuid': o.resource_uuid, 'resource_class': 'fake', + 'resource_properties': {}, 'resource': 'test-node-1234567890', 'name': o.name, 'project_id': o.project_id, @@ -689,6 +690,7 @@ def test_lease_get_dict_with_added_info(self, mock_gro, mock_gpn, mock_gn): expected_output_dict['project'] = 'project-name' expected_output_dict['owner'] = 'project-name' expected_output_dict['resource_class'] = 'fake' + expected_output_dict['resource_properties'] = {} mock_gro.assert_called_once() self.assertEqual(2, mock_gpn.call_count) diff --git a/esi_leap/tests/common/test_ironic.py b/esi_leap/tests/common/test_ironic.py index 7f210ddf..bc66c536 100644 --- a/esi_leap/tests/common/test_ironic.py +++ b/esi_leap/tests/common/test_ironic.py @@ -48,3 +48,20 @@ def test_get_node_list_no_match(self, mock_ironic): node = ironic.get_node('uuid2', node_list) self.assertEqual(None, node) + + def test_get_condensed_properties(self): + properties = { + 'lease_uuid': '12345', + 'capabilities': 'magic', + 'cpu': '40', + 'local_gb': '1000' + } + traits = ['trait1', 'trait2'] + cp = ironic.get_condensed_properties( + properties, traits) + + self.assertEqual(cp, { + 'cpu': '40', + 'local_gb': '1000', + 'traits': ['trait1', 'trait2'] + }) diff --git a/esi_leap/tests/resource_objects/test_dummy_node.py b/esi_leap/tests/resource_objects/test_dummy_node.py index 172b2125..df3b04eb 100644 --- a/esi_leap/tests/resource_objects/test_dummy_node.py +++ b/esi_leap/tests/resource_objects/test_dummy_node.py @@ -42,7 +42,7 @@ class TestDummyNode(base.TestCase): 'resource_class': 'fake', 'power_state': 'off', 'provision_state': 'enroll', - 'server_config': { + 'properties': { 'new attribute XYZ': 'new attribute XYZ', 'cpu_type': 'Intel Xeon', 'cores': 16, @@ -55,7 +55,7 @@ class TestDummyNode(base.TestCase): test_node_2 = { 'project_owner_id': '123456', 'resource_class': 'fake', - 'server_config': { + 'properties': { 'new attribute XYZ': 'new attribute XYZ', 'cpu_type': 'Intel Xeon', 'cores': 16, @@ -89,11 +89,11 @@ def test_get_resource_class(self): self.test_node_1['resource_class']) mock_file_open.assert_called_once() - def test_get_config(self): + def test_get_properties(self): mock_open = mock.mock_open(read_data=self.fake_read_data_1) with mock.patch('builtins.open', mock_open) as mock_file_open: - config = self.fake_dummy_node.get_config() - self.assertEqual(config, self.test_node_1['server_config']) + properties = self.fake_dummy_node.get_properties() + self.assertEqual(properties, self.test_node_1['properties']) mock_file_open.assert_called_once() def test_get_owner_project_id(self): diff --git a/esi_leap/tests/resource_objects/test_ironic_node.py b/esi_leap/tests/resource_objects/test_ironic_node.py index 6d8e2054..fcd6b285 100644 --- a/esi_leap/tests/resource_objects/test_ironic_node.py +++ b/esi_leap/tests/resource_objects/test_ironic_node.py @@ -30,8 +30,9 @@ def __init__(self): self.lessee = 'abcdef' self.owner = '123456' self.name = 'fake-node' - self.properties = {'lease_uuid': '001'} + self.properties = {'lease_uuid': '001', 'cpu': '40'} self.provision_state = 'available' + self.traits = ['trait1', 'trait2'] self.uuid = fake_uuid self.resource_class = 'baremetal' self.power_state = 'off' @@ -87,15 +88,18 @@ def test_get_resource_class(self, mock_gn): mock_gn.assert_called_once() @mock.patch('esi_leap.resource_objects.ironic_node.IronicNode._get_node') - def test_get_config(self, mock_gn): + def test_get_properties(self, mock_gn): fake_get_node = FakeIronicNode() mock_gn.return_value = fake_get_node test_ironic_node = ironic_node.IronicNode(fake_uuid) - config = test_ironic_node.get_config() - expected_config = fake_get_node.properties - self.assertEqual(config, expected_config) - mock_gn.assert_called_once() + properties = test_ironic_node.get_properties() + expected_properties = { + 'cpu': '40', + 'traits': ['trait1', 'trait2'] + } + self.assertEqual(properties, expected_properties) + assert mock_gn.call_count == 2 @mock.patch('esi_leap.resource_objects.ironic_node.IronicNode._get_node') def test_get_owner_project_id(self, mock_gn): diff --git a/esi_leap/tests/resource_objects/test_test_node.py b/esi_leap/tests/resource_objects/test_test_node.py index 426c9b55..754e35bf 100644 --- a/esi_leap/tests/resource_objects/test_test_node.py +++ b/esi_leap/tests/resource_objects/test_test_node.py @@ -55,8 +55,8 @@ def test_get_name(self): def test_get_resource_class(self): self.assertEqual(self.fake_test_node.get_resource_class(), 'fake') - def test_get_config(self): - self.assertEqual(self.fake_test_node.get_config(), {}) + def test_get_properties(self): + self.assertEqual(self.fake_test_node.get_properties(), {}) def test_get_owner_project_id(self): self.assertEqual(self.fake_test_node.get_owner_project_id(), '123456')