From d182d6fac1098c046cfa57b07eea8a3c0c5a5038 Mon Sep 17 00:00:00 2001 From: Varsha Date: Wed, 17 Jul 2019 14:57:00 +0530 Subject: [PATCH] Add MappedListField The MappedList field supports a list of mapped instances. This type of field is being used by the `SupportedControllerProtocols` and the `SupportedDeviceProtocols` fields of a Storage Controller in the Storage resource. Change-Id: Ifd83b72a283e44d890c98ae646ef831467ed4af8 --- ...dd-mapped-list-field-04c671f7a73d83f6.yaml | 5 ++ sushy/resources/base.py | 46 +++++++++++++++++++ sushy/resources/system/storage/storage.py | 9 ++-- .../resources/system/storage/test_storage.py | 6 ++- sushy/tests/unit/resources/test_base.py | 9 +++- 5 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/add-mapped-list-field-04c671f7a73d83f6.yaml diff --git a/releasenotes/notes/add-mapped-list-field-04c671f7a73d83f6.yaml b/releasenotes/notes/add-mapped-list-field-04c671f7a73d83f6.yaml new file mode 100644 index 00000000..2006fad1 --- /dev/null +++ b/releasenotes/notes/add-mapped-list-field-04c671f7a73d83f6.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds a new field called ``MappedListField`` which supports a list of + mapped instances. diff --git a/sushy/resources/base.py b/sushy/resources/base.py index 0c53a0e7..b9c08969 100644 --- a/sushy/resources/base.py +++ b/sushy/resources/base.py @@ -258,6 +258,52 @@ class MappedField(Field): adapter=mapping.get) +class MappedListField(Field): + """Field taking a list of values with a mapping for the values + + Given JSON {'field':['xxx', 'yyy']}, a sushy resource definition and + mapping {'xxx':'a', 'yyy':'b'}, the sushy object to come out will be like + resource.field = ['a', 'b'] + """ + + def __init__(self, field, mapping, required=False, default=None): + """Create a mapped list field definition. + + :param field: JSON field to fetch the list of values from. + :param mapping: a mapping for the list elements. + :param required: whether this field is required. Missing required + fields result in MissingAttributeError. + :param default: the default value to use when the field is missing. + Only has effect when the field is not required. + """ + if not isinstance(mapping, collectionsAbc.Mapping): + raise TypeError("The mapping argument must be a mapping") + + self._mapping_adapter = mapping.get + super(MappedListField, self).__init__( + field, required=required, default=default, + adapter=lambda x: x) + + def _load(self, body, resource, nested_in=None): + """Load the mapped list. + + :param body: parent JSON body. + :param resource: parent resource. + :param nested_in: parent resource name (for error reporting only). + :returns: a new list object containing the mapped values. + """ + nested_in = (nested_in or []) + self._path + values = super(MappedListField, self)._load(body, resource) + + if values is None: + return + + instances = [self._mapping_adapter(value) for value in values + if self._mapping_adapter(value) is not None] + + return instances + + @six.add_metaclass(abc.ABCMeta) class AbstractJsonReader(object): diff --git a/sushy/resources/system/storage/storage.py b/sushy/resources/system/storage/storage.py index 3e76a459..c22a4e0f 100644 --- a/sushy/resources/system/storage/storage.py +++ b/sushy/resources/system/storage/storage.py @@ -17,6 +17,7 @@ import logging from sushy.resources import base from sushy.resources import common +from sushy.resources import mappings as res_maps from sushy.resources.system.storage import drive from sushy.resources.system.storage import volume from sushy import utils @@ -43,12 +44,12 @@ class StorageControllersListField(base.ListField): speed_gbps = base.Field('SpeedGbps') """The maximum speed of the storage controller's device interface.""" - controller_protocols = base.Field('SupportedControllerProtocols', - adapter=list) + controller_protocols = base.MappedListField( + 'SupportedControllerProtocols', res_maps.PROTOCOL_TYPE_VALUE_MAP) """The protocols by which this storage controller can be communicated to""" - device_protocols = base.Field('SupportedDeviceProtocols', - adapter=list) + device_protocols = base.MappedListField('SupportedDeviceProtocols', + res_maps.PROTOCOL_TYPE_VALUE_MAP) """The protocols which the controller can use tocommunicate with devices""" diff --git a/sushy/tests/unit/resources/system/storage/test_storage.py b/sushy/tests/unit/resources/system/storage/test_storage.py index 0561f064..0e5807da 100644 --- a/sushy/tests/unit/resources/system/storage/test_storage.py +++ b/sushy/tests/unit/resources/system/storage/test_storage.py @@ -122,8 +122,10 @@ class StorageTestCase(base.TestCase): identifier.durable_name_format) self.assertEqual('345C59DBD970859C', identifier.durable_name) self.assertEqual(12, controller.speed_gbps) - self.assertEqual(["PCIe"], controller.controller_protocols) - self.assertEqual(["SAS", "SATA"], controller.device_protocols) + self.assertEqual([sushy.PROTOCOL_TYPE_PCIe], + controller.controller_protocols) + self.assertEqual([sushy.PROTOCOL_TYPE_SAS, sushy.PROTOCOL_TYPE_SATA], + controller.device_protocols) def test_drives_after_refresh(self): self.storage.refresh() diff --git a/sushy/tests/unit/resources/test_base.py b/sushy/tests/unit/resources/test_base.py index 976493c7..6d33dd52 100644 --- a/sushy/tests/unit/resources/test_base.py +++ b/sushy/tests/unit/resources/test_base.py @@ -289,7 +289,7 @@ class ResourceCollectionBaseTestCase(base.TestCase): TEST_JSON = { 'String': 'a string', 'Integer': '42', - 'List': ['a string', 42], + 'MappedList': ['raw1', 'raw2', 'raw'], 'Nested': { 'String': 'another string', 'Integer': 0, @@ -316,7 +316,9 @@ TEST_JSON = { MAPPING = { - 'raw': 'real' + 'raw': 'real', + 'raw1': 'real1', + 'raw2': 'real2' } @@ -342,6 +344,7 @@ class ComplexResource(resource_base.ResourceBase): string = resource_base.Field('String', required=True) integer = resource_base.Field('Integer', adapter=int) nested = NestedTestField('Nested') + mapped_list = resource_base.MappedListField('MappedList', MAPPING) field_list = TestListField('ListField') dictionary = TestDictionaryField('Dictionary') non_existing_nested = NestedTestField('NonExistingNested') @@ -366,6 +369,8 @@ class FieldTestCase(base.TestCase): self.assertEqual('field value', self.test_resource.nested.nested_field) self.assertEqual('real', self.test_resource.nested.mapped) self.assertEqual(3.14, self.test_resource.nested.non_existing) + self.assertEqual(['real1', 'real2', 'real'], + self.test_resource.mapped_list) self.assertEqual('a third string', self.test_resource.field_list[0].string) self.assertEqual(2, self.test_resource.field_list[1].integer)