diff --git a/releasenotes/notes/add-endpoint-subresource-to-fabric-b03e5fd99ece1bf4.yaml b/releasenotes/notes/add-endpoint-subresource-to-fabric-b03e5fd99ece1bf4.yaml new file mode 100644 index 00000000..fa752fcb --- /dev/null +++ b/releasenotes/notes/add-endpoint-subresource-to-fabric-b03e5fd99ece1bf4.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Exposes the ``endpoint`` sub-resource from the ``fabric`` resource. + ``endpoint`` represents the properties of an entity that sends or receives + protocol defined messages over a transport. diff --git a/sushy/resources/fabric/constants.py b/sushy/resources/fabric/constants.py index 27f094fa..7d4fd8c2 100644 --- a/sushy/resources/fabric/constants.py +++ b/sushy/resources/fabric/constants.py @@ -13,30 +13,87 @@ # Values come from the Redfish Fabric json-schema 1.0.4: # http://redfish.dmtf.org/schemas/v1/Fabric.v1_0_4.json#/definitions/Fabric -# Fabric Types constants +# Protocol type constants -FABRIC_TYPE_AHCI = 'Advanced Host Controller Interface' -FABRIC_TYPE_FC = 'Fibre Channel' -FABRIC_TYPE_FCP = 'Fibre Channel Protocol for SCSI' -FABRIC_TYPE_FCoE = 'Fibre Channel over Ethernet' -FABRIC_TYPE_FICON = 'FIbre CONnection (FICON)' -FABRIC_TYPE_FTP = 'File Transfer Protocol' -FABRIC_TYPE_HTTP = 'Hypertext Transport Protocol' -FABRIC_TYPE_HTTPS = 'Secure Hypertext Transport Protocol' -FABRIC_TYPE_I2C = 'Inter-Integrated Circuit Bus' -FABRIC_TYPE_NFSv3 = 'Network File System version 3' -FABRIC_TYPE_NFSv4 = 'Network File System version 4' -FABRIC_TYPE_NVMe = 'Non-Volatile Memory Express' -FABRIC_TYPE_NVMeOverFabrics = 'NVMe over Fabrics' -FABRIC_TYPE_OEM = 'OEM specific' -FABRIC_TYPE_PCIe = 'PCI Express' -FABRIC_TYPE_RoCE = 'RDMA over Converged Ethernet Protocol' -FABRIC_TYPE_RoCEv2 = 'RDMA over Converged Ethernet Protocol Version 2' -FABRIC_TYPE_SAS = 'Serial Attached SCSI' -FABRIC_TYPE_SATA = 'Serial AT Attachment' -FABRIC_TYPE_SFTP = 'Secure File Transfer Protocol' -FABRIC_TYPE_SMB = 'Server Message Block (aka CIFS Common Internet File System)' -FABRIC_TYPE_UHCI = 'Universal Host Controller Interface' -FABRIC_TYPE_USB = 'Universal Serial Bus' -FABRIC_TYPE_iSCSI = 'Internet SCSI' -FABRIC_TYPE_iWARP = 'Internet Wide Area Remote Direct Memory Access Protocol' +PROTOCOL_TYPE_AHCI = 'Advanced Host Controller Interface' +PROTOCOL_TYPE_FC = 'Fibre Channel' +PROTOCOL_TYPE_FCP = 'Fibre Channel Protocol for SCSI' +PROTOCOL_TYPE_FCoE = 'Fibre Channel over Ethernet' +PROTOCOL_TYPE_FICON = 'FIbre CONnection (FICON)' +PROTOCOL_TYPE_FTP = 'File Transfer Protocol' +PROTOCOL_TYPE_HTTP = 'Hypertext Transport Protocol' +PROTOCOL_TYPE_HTTPS = 'Secure Hypertext Transport Protocol' +PROTOCOL_TYPE_I2C = 'Inter-Integrated Circuit Bus' +PROTOCOL_TYPE_NFSv3 = 'Network File System version 3' +PROTOCOL_TYPE_NFSv4 = 'Network File System version 4' +PROTOCOL_TYPE_NVMe = 'Non-Volatile Memory Express' +PROTOCOL_TYPE_NVMeOverFabrics = 'NVMe over Fabrics' +PROTOCOL_TYPE_OEM = 'OEM specific' +PROTOCOL_TYPE_PCIe = 'PCI Express' +PROTOCOL_TYPE_RoCE = 'RDMA over Converged Ethernet Protocol' +PROTOCOL_TYPE_RoCEv2 = 'RDMA over Converged Ethernet Protocol Version 2' +PROTOCOL_TYPE_SAS = 'Serial Attached SCSI' +PROTOCOL_TYPE_SATA = 'Serial AT Attachment' +PROTOCOL_TYPE_SFTP = 'Secure File Transfer Protocol' +PROTOCOL_TYPE_SMB = 'Server Message Block (CIFS Common Internet File System)' +PROTOCOL_TYPE_UHCI = 'Universal Host Controller Interface' +PROTOCOL_TYPE_USB = 'Universal Serial Bus' +PROTOCOL_TYPE_iSCSI = 'Internet SCSI' +PROTOCOL_TYPE_iWARP = 'Internet Wide Area Remote Direct Memory Access Protocol' + +# Address origin IPv4 constants + +ADDRESS_ORIGIN_IPv4_BOOTP = 'Address is provided by a BOOTP service' +ADDRESS_ORIGIN_IPv4_DHCP = 'Address is provided by a DHCPv4 service' +ADDRESS_ORIGIN_IPv4_IPv4LINKLOCAL = 'Address valid only for this segment' +ADDRESS_ORIGIN_IPv4_STATIC = 'A static address as configured by the user' + +# Address origin IPv6 constants + +ADDRESS_ORIGIN_IPv6_DHCPv6 = 'Address is provided by a DHCPv6 service' +ADDRESS_ORIGIN_IPv6_LINKLOCAL = 'Address valid only for this network segment' +ADDRESS_ORIGIN_IPv6_SLAAC = 'Stateless Address Auto Configuration service' +ADDRESS_ORIGIN_IPv6_STATIC = 'A static address as configured by the user' + +# Address state constants + +ADDRESS_STATE_DEPRECATED = 'Deprecated' +"""This address is currently within it's valid lifetime, but is now outside of +it's preferred lifetime as defined in RFC 4862.""" +ADDRESS_STATE_FAILED = 'Failed' +"""This address has failed Duplicate Address Detection testing as defined in +RFC 4862 section 5.4 and is not currently in use.""" +ADDRESS_STATE_PREFERRED = 'Preferred' +"""This address is currently within both it's valid and preferred lifetimes as +defined in RFC 4862.""" +ADDRESS_STATE_TENTATIVE = 'Tentative' +"""This address is currently undergoing Duplicate Address Detection testing as +defined in RFC 4862 section 5.4.""" + +# Durable name format constants + +DURABLE_NAME_FORMAT_EUI = 'IEEE-defined 64-bit Extended Unique Identifier' +DURABLE_NAME_FORMAT_FC_WWN = 'Fibre Channel World Wide Name' +DURABLE_NAME_FORMAT_NAA = 'Name Address Authority Format' +DURABLE_NAME_FORMAT_NQN = 'NVMe Qualified Name' +DURABLE_NAME_FORMAT_NSID = 'NVM Namespace Identifier' +DURABLE_NAME_FORMAT_UUID = 'Universally Unique Identifier' +DURABLE_NAME_FORMAT_iQN = 'iSCSI Qualified Name' + +# Entity role constants + +ENTITY_ROLE_BOTH = 'The entity is acting as both an initiator and a target' +ENTITY_ROLE_INITIATOR = 'The entity is acting as an initiator' +ENTITY_ROLE_TARGET = 'The entity is acting as a target' + +# Entity type constants + +ENTITY_TYPE_PCI_BRIDGE = 'PCI(e) Bridge' +ENTITY_TYPE_DISPLAY_CONTROLLER = 'Display Controller' +ENTITY_TYPE_DRIVE = 'Disk Drive' +ENTITY_TYPE_NETWORK_CONTROLLER = 'Network Controller' +ENTITY_TYPE_PROCESSOR = 'Processor Device' +ENTITY_TYPE_ROOT_COMPLEX = 'Root Complex' +ENTITY_TYPE_STORAGE_EXPANDER = 'Storage Expander' +ENTITY_TYPE_STORAGE_INITIATOR = 'Storage Initiator' +ENTITY_TYPE_VOLUME = 'Volume' diff --git a/sushy/resources/fabric/endpoint.py b/sushy/resources/fabric/endpoint.py new file mode 100644 index 00000000..ef22b43b --- /dev/null +++ b/sushy/resources/fabric/endpoint.py @@ -0,0 +1,178 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# This is referred from Redfish standard schema. +# https://redfish.dmtf.org/schemas/Endpoint.v1_3_0.json + +import logging + +from sushy.resources import base +from sushy.resources import common +from sushy.resources.fabric import mappings as fab_maps +from sushy import utils + +LOG = logging.getLogger(__name__) + + +class IPv4AddressField(base.CompositeField): + + address = base.Field('Address') + """This is the IPv4 Address.""" + + gateway = base.Field('Gateway') + """This is the IPv4 gateway for this address.""" + + subnet_mask = base.Field('SubnetMask') + """This is the IPv4 Subnet mask.""" + + address_origin = base.MappedField('AddressOrigin', + fab_maps.ADDRESS_ORIGIN_IPv4_VALUE_MAP) + """This indicates how the address was determined.""" + + +class IPv6AddressField(base.CompositeField): + + address = base.Field('Address') + """This is the IPv6 Address.""" + + prefix_length = base.Field('PrefixLength', adapter=utils.int_or_none) + """This is the IPv6 Address Prefix Length.""" + + address_origin = base.MappedField('AddressOrigin', + fab_maps.ADDRESS_ORIGIN_IPv6_VALUE_MAP) + """This indicates how the address was determined.""" + + address_state = base.MappedField('AddressState', + fab_maps.ADDRESS_STATE_VALUE_MAP) + """The current state of this address as defined in RFC 4862.""" + + +class IPTransportDetailsListField(base.ListField): + """IP transport details + + This array contains details for each IP transport supported by this + endpoint. The array structure can be used to model multiple IP addresses + for this endpoint. + """ + + port = base.Field('Port', adapter=utils.int_or_none) + """The UDP or TCP port number used by the Endpoint.""" + + transport_protocol = base.MappedField('TransportProtocol', + fab_maps.PROTOCOL_TYPE_VALUE_MAP) + """The protocol used by the connection entity.""" + + ipv4_address = IPv4AddressField('IPv4Address') + """The IPv4 address object.""" + + ipv6_address = IPv6AddressField('IPv6Address') + """The IPv6 address object.""" + + +class PciIdField(base.CompositeField): + + device_id = base.Field('DeviceId') + """The Device ID of this PCIe function.""" + + subsystem_id = base.Field('SubsystemId') + """The Subsystem ID of this PCIefunction.""" + + subsystem_vendor_id = base.Field('SubsystemVendorId') + """The Subsystem Vendor ID of thisPCIe function.""" + + vendor_id = base.Field('VendorId') + """The Vendor ID of this PCIe function.""" + + +class IdentifierListField(base.ListField): + + durable_name = base.Field('DurableName') + """This indicates the world wide, persistent name of the entity.""" + + durable_name_format = base.MappedField('DurableNameFormat', + fab_maps.DUR_NAME_FORMAT_VALUE_MAP) + """This represents the format of the DurableName property.""" + + +class ConnectedEntitiesListField(base.ListField): + """All the entities connected to this endpoint.""" + + pci_class_code = base.Field('PciClassCode') + """The Class Code, Subclass code, and Programming Interface code of + this PCIe function.""" + + pci_function_number = base.Field('PciFunctionNumber', + adapter=utils.int_or_none) + """The PCI ID of the connected entity.""" + + entity_pci_id = PciIdField('EntityPciId') + """The PCI ID of the connected entity.""" + + identifiers = IdentifierListField('Identifiers') + """Identifiers for the remote entity.""" + + entity_role = base.MappedField('EntityRole', + fab_maps.ENTITY_ROLE_VALUE_MAP) + """The role of the connected entity.""" + + entity_type = base.MappedField('EntityType', + fab_maps.ENTITY_TYPE_VALUE_MAP) + """The type of the connected entity.""" + + +class Endpoint(base.ResourceBase): + """This class represents a fabric endpoint. + + It represents the properties of an entity that sends or receives protocol + defined messages over a transport. + """ + + identity = base.Field('Id', required=True) + """Identifier for the endpoint""" + + name = base.Field('Name', required=True) + """The endpoint name""" + + description = base.Field('Description') + """The endpoint description""" + + status = common.StatusField('Status') + """The endpoint status""" + + host_reservation_memory_bytes = base.Field('HostReservationMemoryBytes', + adapter=utils.int_or_none) + """The amount of memory in Bytes that the Host should allocate to connect + to this endpoint. + """ + + endpoint_protocol = base.MappedField('EndpointProtocol', + fab_maps.PROTOCOL_TYPE_VALUE_MAP) + """The protocol supported by this endpoint.""" + + pci_id = PciIdField('PciId') + """The PCI ID of the endpoint.""" + + IP_transport_details = IPTransportDetailsListField('IPTransportDetails') + """This array contains details for each IP transport supported by this + endpoint. The array structure can be used to model multiple IP addresses + for this endpoint.""" + + connected_entities = ConnectedEntitiesListField('ConnectedEntities') + """All entities connected to this endpoint.""" + + +class EndpointCollection(base.ResourceCollectionBase): + """Represents a collection of endpoints associated with the fabric.""" + + @property + def _resource_type(self): + return Endpoint diff --git a/sushy/resources/fabric/fabric.py b/sushy/resources/fabric/fabric.py index e9c6fc2e..57537185 100644 --- a/sushy/resources/fabric/fabric.py +++ b/sushy/resources/fabric/fabric.py @@ -15,7 +15,9 @@ from sushy.resources import base from sushy.resources import common +from sushy.resources.fabric import endpoint as fab_endpoint from sushy.resources.fabric import mappings as fab_maps +from sushy import utils import logging @@ -38,14 +40,14 @@ class Fabric(base.ResourceBase): description = base.Field('Description') """The fabric description""" - max_zones = base.Field('MaxZones') + max_zones = base.Field('MaxZones', adapter=utils.int_or_none) """The maximum number of zones the switch can currently configure""" status = common.StatusField('Status') """The fabric status""" fabric_type = base.MappedField('FabricType', - fab_maps.FABRIC_TYPE_VALUE_MAP) + fab_maps.PROTOCOL_TYPE_VALUE_MAP) """The protocol being sent over this fabric""" def __init__(self, connector, identity, redfish_version=None): @@ -58,6 +60,13 @@ class Fabric(base.ResourceBase): """ super(Fabric, self).__init__(connector, identity, redfish_version) + @property + @utils.cache_it + def endpoints(self): + return fab_endpoint.EndpointCollection( + self._conn, utils.get_sub_resource_path_by(self, 'Endpoints'), + redfish_version=self.redfish_version) + class FabricCollection(base.ResourceCollectionBase): diff --git a/sushy/resources/fabric/mappings.py b/sushy/resources/fabric/mappings.py index 6ddd8909..0ced928a 100644 --- a/sushy/resources/fabric/mappings.py +++ b/sushy/resources/fabric/mappings.py @@ -16,32 +16,89 @@ from sushy.resources.fabric import constants as fab_cons from sushy import utils -FABRIC_TYPE_VALUE_MAP = { - 'AHCI': fab_cons.FABRIC_TYPE_AHCI, - 'FC': fab_cons.FABRIC_TYPE_FC, - 'FCP': fab_cons.FABRIC_TYPE_FCP, - 'FCoE': fab_cons.FABRIC_TYPE_FCoE, - 'FICON': fab_cons.FABRIC_TYPE_FICON, - 'FTP': fab_cons.FABRIC_TYPE_FTP, - 'HTTP': fab_cons.FABRIC_TYPE_HTTP, - 'HTTPS': fab_cons.FABRIC_TYPE_HTTPS, - 'I2C': fab_cons.FABRIC_TYPE_I2C, - 'NFSv3': fab_cons.FABRIC_TYPE_NFSv3, - 'NFSv4': fab_cons.FABRIC_TYPE_NFSv4, - 'NVMe': fab_cons.FABRIC_TYPE_NVMe, - 'NVMeOverFabrics': fab_cons.FABRIC_TYPE_NVMeOverFabrics, - 'OEM': fab_cons.FABRIC_TYPE_OEM, - 'PCIe': fab_cons.FABRIC_TYPE_PCIe, - 'RoCE': fab_cons.FABRIC_TYPE_RoCE, - 'RoCEv2': fab_cons.FABRIC_TYPE_RoCEv2, - 'SAS': fab_cons.FABRIC_TYPE_SAS, - 'SATA': fab_cons.FABRIC_TYPE_SATA, - 'SFTP': fab_cons.FABRIC_TYPE_SFTP, - 'SMB': fab_cons.FABRIC_TYPE_SMB, - 'UHCI': fab_cons.FABRIC_TYPE_UHCI, - 'USB': fab_cons.FABRIC_TYPE_USB, - 'iSCSI': fab_cons.FABRIC_TYPE_iSCSI, - 'iWARP': fab_cons.FABRIC_TYPE_iWARP, +PROTOCOL_TYPE_VALUE_MAP = { + 'AHCI': fab_cons.PROTOCOL_TYPE_AHCI, + 'FC': fab_cons.PROTOCOL_TYPE_FC, + 'FCP': fab_cons.PROTOCOL_TYPE_FCP, + 'FCoE': fab_cons.PROTOCOL_TYPE_FCoE, + 'FICON': fab_cons.PROTOCOL_TYPE_FICON, + 'FTP': fab_cons.PROTOCOL_TYPE_FTP, + 'HTTP': fab_cons.PROTOCOL_TYPE_HTTP, + 'HTTPS': fab_cons.PROTOCOL_TYPE_HTTPS, + 'I2C': fab_cons.PROTOCOL_TYPE_I2C, + 'NFSv3': fab_cons.PROTOCOL_TYPE_NFSv3, + 'NFSv4': fab_cons.PROTOCOL_TYPE_NFSv4, + 'NVMe': fab_cons.PROTOCOL_TYPE_NVMe, + 'NVMeOverFabrics': fab_cons.PROTOCOL_TYPE_NVMeOverFabrics, + 'OEM': fab_cons.PROTOCOL_TYPE_OEM, + 'PCIe': fab_cons.PROTOCOL_TYPE_PCIe, + 'RoCE': fab_cons.PROTOCOL_TYPE_RoCE, + 'RoCEv2': fab_cons.PROTOCOL_TYPE_RoCEv2, + 'SAS': fab_cons.PROTOCOL_TYPE_SAS, + 'SATA': fab_cons.PROTOCOL_TYPE_SATA, + 'SFTP': fab_cons.PROTOCOL_TYPE_SFTP, + 'SMB': fab_cons.PROTOCOL_TYPE_SMB, + 'UHCI': fab_cons.PROTOCOL_TYPE_UHCI, + 'USB': fab_cons.PROTOCOL_TYPE_USB, + 'iSCSI': fab_cons.PROTOCOL_TYPE_iSCSI, + 'iWARP': fab_cons.PROTOCOL_TYPE_iWARP, } -FABRIC_TYPE_VALUE_MAP_REV = utils.revert_dictionary(FABRIC_TYPE_VALUE_MAP) + +ADDRESS_ORIGIN_IPv4_VALUE_MAP = { + 'BOOTP': fab_cons.ADDRESS_ORIGIN_IPv4_BOOTP, + 'DHCP': fab_cons.ADDRESS_ORIGIN_IPv4_DHCP, + 'IPv4LinkLocal': fab_cons.ADDRESS_ORIGIN_IPv4_IPv4LINKLOCAL, + 'Static': fab_cons.ADDRESS_ORIGIN_IPv4_STATIC, +} + + +ADDRESS_ORIGIN_IPv6_VALUE_MAP = { + 'DHCPv6': fab_cons.ADDRESS_ORIGIN_IPv6_DHCPv6, + 'LinkLocal': fab_cons.ADDRESS_ORIGIN_IPv6_LINKLOCAL, + 'SLAAC': fab_cons.ADDRESS_ORIGIN_IPv6_SLAAC, + 'Static': fab_cons.ADDRESS_ORIGIN_IPv6_STATIC, +} + + +ADDRESS_STATE_VALUE_MAP = { + 'Deprecated': fab_cons.ADDRESS_STATE_DEPRECATED, + 'Failed': fab_cons.ADDRESS_STATE_FAILED, + 'Preferred': fab_cons.ADDRESS_STATE_PREFERRED, + 'Tentative': fab_cons.ADDRESS_STATE_TENTATIVE, +} + + +DUR_NAME_FORMAT_VALUE_MAP = { + 'EUI': fab_cons.DURABLE_NAME_FORMAT_EUI, + 'FC_WWN': fab_cons.DURABLE_NAME_FORMAT_FC_WWN, + 'NAA': fab_cons.DURABLE_NAME_FORMAT_NAA, + 'NQN': fab_cons.DURABLE_NAME_FORMAT_NQN, + 'NSID': fab_cons.DURABLE_NAME_FORMAT_NSID, + 'UUID': fab_cons.DURABLE_NAME_FORMAT_UUID, + 'iQN': fab_cons.DURABLE_NAME_FORMAT_iQN, +} + + +ENTITY_ROLE_VALUE_MAP = { + 'Both': fab_cons.ENTITY_ROLE_BOTH, + 'Initiator': fab_cons.ENTITY_ROLE_INITIATOR, + 'Target': fab_cons.ENTITY_ROLE_TARGET, +} + +ENTITY_ROLE_VALUE_MAP_REV = utils.revert_dictionary(ENTITY_ROLE_VALUE_MAP) + + +ENTITY_TYPE_VALUE_MAP = { + 'Bridge': fab_cons.ENTITY_TYPE_PCI_BRIDGE, + 'DisplayController': fab_cons.ENTITY_TYPE_DISPLAY_CONTROLLER, + 'Drive': fab_cons.ENTITY_TYPE_DRIVE, + 'NetworkController': fab_cons.ENTITY_TYPE_NETWORK_CONTROLLER, + 'Processor': fab_cons.ENTITY_TYPE_PROCESSOR, + 'RootComplex': fab_cons.ENTITY_TYPE_ROOT_COMPLEX, + 'StorageExpander': fab_cons.ENTITY_TYPE_STORAGE_EXPANDER, + 'StorageInitiator': fab_cons.ENTITY_TYPE_STORAGE_INITIATOR, + 'Volume': fab_cons.ENTITY_TYPE_VOLUME, +} + +ENTITY_TYPE_VALUE_MAP_REV = utils.revert_dictionary(ENTITY_TYPE_VALUE_MAP) diff --git a/sushy/tests/unit/json_samples/endpoint.json b/sushy/tests/unit/json_samples/endpoint.json new file mode 100644 index 00000000..6e43ceb4 --- /dev/null +++ b/sushy/tests/unit/json_samples/endpoint.json @@ -0,0 +1,40 @@ +{ + "@odata.type":"#Endpoint.v1_1_0.Endpoint", + "Id":"Drive1", + "Name":"SAS Drive", + "Description":"The SAS Drive in Enclosure 2 Bay 0", + "EndpointProtocol":"SAS", + "ConnectedEntities":[ + { + "EntityType":"Drive", + "EntityRole":"Target", + "Identifiers":[ + { + "DurableNameFormat":"NAA", + "DurableName":"32ADF365C6C1B7C3" + } + ], + "Oem":{} + } + ], + "Links": + { + "MutuallyExclusiveEndpoints":[ + { + "@odata.id":"/redfish/v1/Fabrics/SAS/Endpoints/Enclosure2" + } + ], + "Ports":[ + { + "@odata.id":"/redfish/v1/Fabrics/SAS/Switches/Switch1/Ports/8" + }, + { + "@odata.id":"/redfish/v1/Fabrics/SAS/Switches/Switch2/Ports/8" + } + ], + "Oem":{} + }, + "Oem":{}, + "@odata.context":"/redfish/v1/$metadata#Endpoint.Endpoint", + "@odata.id":"/redfish/v1/Fabrics/SAS/Endpoints/Drive1" +} \ No newline at end of file diff --git a/sushy/tests/unit/json_samples/endpoint_collection.json b/sushy/tests/unit/json_samples/endpoint_collection.json new file mode 100644 index 00000000..6dd664f7 --- /dev/null +++ b/sushy/tests/unit/json_samples/endpoint_collection.json @@ -0,0 +1,13 @@ +{ + "@odata.type": "#EndpointCollection.EndpointCollection", + "Name": "Endpoint Collection", + "Members@odata.count": 1, + "Members": [ + { + "@odata.id": "/redfish/v1/Fabrics/SAS/Endpoints/Drive1" + } + ], + "Oem": {}, + "@odata.context": "/redfish/v1/$metadata#EndpointCollection.EndpointCollection", + "@odata.id": "/redfish/v1/Fabrics/SAS/Endpoints" +} diff --git a/sushy/tests/unit/resources/fabric/test_endpoint.py b/sushy/tests/unit/resources/fabric/test_endpoint.py new file mode 100644 index 00000000..e7a95d76 --- /dev/null +++ b/sushy/tests/unit/resources/fabric/test_endpoint.py @@ -0,0 +1,48 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json + +import mock + +import sushy +from sushy.resources.fabric import endpoint +from sushy.tests.unit import base + + +class EndpointTestCase(base.TestCase): + + def setUp(self): + super(EndpointTestCase, self).setUp() + self.conn = mock.Mock() + with open('sushy/tests/unit/json_samples/' + 'endpoint.json') as f: + self.conn.get.return_value.json.return_value = json.load(f) + self.fab_endpoint = endpoint.Endpoint( + self.conn, '/redfish/v1/Fabrics/SAS/Endpoints/Drive1', + redfish_version='1.0.2') + + def test__parse_atrtributes(self): + self.fab_endpoint._parse_attributes() + self.assertEqual('Drive1', self.fab_endpoint.identity) + self.assertEqual('SAS Drive', self.fab_endpoint.name) + self.assertEqual(sushy.PROTOCOL_TYPE_SAS, + self.fab_endpoint.endpoint_protocol) + self.assertEqual(sushy.ENTITY_TYPE_DRIVE, + self.fab_endpoint.connected_entities[0].entity_type) + self.assertEqual(sushy.ENTITY_ROLE_TARGET, + self.fab_endpoint.connected_entities[0].entity_role) + con_entity = self.fab_endpoint.connected_entities[0] + self.assertEqual(sushy.DURABLE_NAME_FORMAT_NAA, + con_entity.identifiers[0].durable_name_format) + self.assertEqual('32ADF365C6C1B7C3', + con_entity.identifiers[0].durable_name) diff --git a/sushy/tests/unit/resources/fabric/test_fabric.py b/sushy/tests/unit/resources/fabric/test_fabric.py index 4ac04816..792c3cb1 100644 --- a/sushy/tests/unit/resources/fabric/test_fabric.py +++ b/sushy/tests/unit/resources/fabric/test_fabric.py @@ -17,6 +17,7 @@ import json import mock import sushy +from sushy.resources.fabric import endpoint from sushy.resources.fabric import fabric from sushy.tests.unit import base @@ -41,11 +42,68 @@ class FabricTestCase(base.TestCase): self.assertEqual('SAS Fabric', self.fabric.name) self.assertEqual('A SAS Fabric with redundant switches.', self.fabric.description) - self.assertEqual(sushy.FABRIC_TYPE_SAS, + self.assertEqual(sushy.PROTOCOL_TYPE_SAS, self.fabric.fabric_type) self.assertEqual(sushy.STATE_ENABLED, self.fabric.status.state) self.assertEqual(sushy.HEALTH_OK, self.fabric.status.health) + def test_endpoints(self): + # | GIVEN | + with open('sushy/tests/unit/json_samples/' + 'endpoint_collection.json') as f: + endpoint_collection_return_value = json.load(f) + + with open('sushy/tests/unit/json_samples/' + 'endpoint.json') as f: + endpoint_return_value = json.load(f) + + self.conn.get.return_value.json.side_effect = [ + endpoint_collection_return_value, endpoint_return_value] + + # | WHEN | + actual_endpoints = self.fabric.endpoints + + # | THEN | + self.assertIsInstance(actual_endpoints, + endpoint.EndpointCollection) + self.assertEqual(actual_endpoints.name, 'Endpoint Collection') + + member = actual_endpoints.get_member( + '/redfish/v1/Fabrics/SAS/Endpoints/Drive1') + + self.assertEqual(member.name, "SAS Drive") + self.assertEqual(member.endpoint_protocol, sushy.PROTOCOL_TYPE_SAS) + + def test_endpoints_on_refresh(self): + # | GIVEN | + with open('sushy/tests/unit/json_samples/' + 'endpoint_collection.json') as f: + self.conn.get.return_value.json.return_value = json.load(f) + + # | WHEN & THEN | + endpts = self.fabric.endpoints + self.assertIsInstance(endpts, endpoint.EndpointCollection) + + # On refreshing the fabric instance... + with open('sushy/tests/unit/json_samples/fabric.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.fabric.invalidate() + self.fabric.refresh(force=False) + + # | WHEN & THEN | + self.assertTrue(endpts._is_stale) + + # | GIVEN | + with open('sushy/tests/unit/json_samples/' + 'endpoint_collection.json') as f: + self.conn.get.return_value.json.return_value = json.load(f) + + # | WHEN & THEN | + self.assertIsInstance(self.fabric.endpoints, + endpoint.EndpointCollection) + self.assertFalse(endpts._is_stale) + class FabricCollectionTestCase(base.TestCase):