From 00b0fbfc12141fa0d4f5dbc0a3999119f3967252 Mon Sep 17 00:00:00 2001 From: monokai <2536818783@qq.com> Date: Thu, 17 Jan 2019 11:42:41 +0800 Subject: [PATCH] Add fabrics port resource for RSD2.1 Change-Id: I5c0fc9d08c67f2de75c9a2320ee2b46a4f8ec471 --- rsd_lib/resources/v2_1/fabric/port.py | 138 ++++++++++++++++++ rsd_lib/resources/v2_1/fabric/switch.py | 29 ++-- .../unit/json_samples/v2_1/fabrics_port.json | 52 +++++++ .../v2_1/fabrics_port_collection.json | 22 +++ .../unit/resources/v2_1/fabric/test_port.py | 112 ++++++++++++++ .../unit/resources/v2_1/fabric/test_switch.py | 57 +++++++- 6 files changed, 395 insertions(+), 15 deletions(-) create mode 100644 rsd_lib/resources/v2_1/fabric/port.py create mode 100644 rsd_lib/tests/unit/json_samples/v2_1/fabrics_port.json create mode 100644 rsd_lib/tests/unit/json_samples/v2_1/fabrics_port_collection.json create mode 100644 rsd_lib/tests/unit/resources/v2_1/fabric/test_port.py diff --git a/rsd_lib/resources/v2_1/fabric/port.py b/rsd_lib/resources/v2_1/fabric/port.py new file mode 100644 index 0000000..e783ec2 --- /dev/null +++ b/rsd_lib/resources/v2_1/fabric/port.py @@ -0,0 +1,138 @@ +# Copyright 2019 Intel, Inc. +# All Rights Reserved. +# +# 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 logging + +from sushy.resources import base + +from sushy import utils + +LOG = logging.getLogger(__name__) + + +class StatusField(base.CompositeField): + state = base.Field("State") + """The status state""" + + health = base.Field("Health") + """The status health""" + + +class PortResetField(base.CompositeField): + target = base.Field("target") + """The port reset target""" + + reset_type_redfish_allowable_values = base.Field("ResetType@Redfish." + "AllowableValues") + """The port reset type redfish allowable values""" + + +class ActionsField(base.CompositeField): + port_reset = PortResetField("#Port.Reset") + """The action port reset""" + + +class LinksField(base.CompositeField): + associated_endpoints = base.Field("AssociatedEndpoints", default=[], + adapter=utils.get_members_identities) + """The link associated endpoints""" + + +class IntelRackScaleField(base.CompositeField): + odata_type = base.Field("@odata.type") + """The Intel Rack Scale odata type""" + + pcie_connection_id = base.Field("PCIeConnectionId") + """The Intel Rack Scale PCIe Connection Id""" + + +class OemField(base.CompositeField): + intel_rackScale = IntelRackScaleField("Intel_RackScale") + """The oem intel rack scale""" + + +class Port(base.ResourceBase): + identity = base.Field("Id") + """The port identity string""" + + name = base.Field("Name") + """The port name""" + + description = base.Field("Description") + """The port description""" + + status = StatusField("Status") + """The port status""" + + port_id = base.Field("PortId") + """The port id""" + + port_protocol = base.Field("PortProtocol") + """The port protocol""" + + port_type = base.Field("PortType") + """The port type""" + + current_speed_gbps = base.Field("CurrentSpeedGbps") + """The port current speed Gbps""" + + width = base.Field("Width") + """The port width""" + + max_speed_gbps = base.Field("MaxSpeedGbps") + """The port max speed gbps""" + + actions = ActionsField("Actions") + """The port actions""" + + oem = OemField("Oem") + """The port oem""" + + links = LinksField("Links") + """The port links""" + + def __init__(self, connector, identity, redfish_version=None): + """A class representing a Port + + :param connector: A Connector instance + :param identity: The identity of the Port resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(Port, self).__init__(connector, identity, + redfish_version) + + def reset_port(self): + """A post method to reset port""" + + data = {"ResetType": "ForceRestart"} + target_uri = self.actions.port_reset.target + self._conn.post(target_uri, data=data) + + +class PortCollection(base.ResourceCollectionBase): + @property + def _resource_type(self): + return Port + + def __init__(self, connector, path, redfish_version=None): + """A class representing a Port + + :param connector: A Connector instance + :param path: The canonical path to the Port collection resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(PortCollection, self).__init__(connector, path, redfish_version) diff --git a/rsd_lib/resources/v2_1/fabric/switch.py b/rsd_lib/resources/v2_1/fabric/switch.py index bcc9521..46ce1bd 100644 --- a/rsd_lib/resources/v2_1/fabric/switch.py +++ b/rsd_lib/resources/v2_1/fabric/switch.py @@ -14,10 +14,10 @@ # under the License. import logging - from sushy.resources import base from sushy import utils +from rsd_lib.resources.v2_1.fabric import port from rsd_lib import utils as rsd_lib_utils LOG = logging.getLogger(__name__) @@ -29,10 +29,6 @@ class StatusField(base.CompositeField): health_rollup = base.Field('HealthRollUp') -class PortsField(base.CompositeField): - identity = base.Field('@odata.id') - - class LinksField(base.CompositeField): chassis = base.Field("Chassis", adapter=utils.get_members_identities) @@ -101,9 +97,6 @@ class Switch(base.ResourceBase): power_state = base.Field('PowerState') """The switch power state""" - ports = PortsField('Ports') - """The switch ports""" - links = LinksField("Links") """The switch links""" @@ -122,14 +115,28 @@ class Switch(base.ResourceBase): redfish_version) def reset_switch(self): - """A post method to reset switch + """A post method to reset switch""" - :returns: The uri of the acl rule - """ data = {"ResetType": "GracefulRestart"} target_uri = self.actions.switch_reset.target self._conn.post(target_uri, data=data) + def _get_ports_path(self): + """Helper function to find the network protocol path""" + return utils.get_sub_resource_path_by(self, 'Ports') + + @property + @utils.cache_it + def ports(self): + """Property to provide reference to ` Ports` instance + + It is calculated once when it is queried for the first time. On + refresh, this property is reset. + """ + return port.PortCollection( + self._conn, self._get_ports_path(), + redfish_version=self.redfish_version) + class SwitchCollection(base.ResourceCollectionBase): diff --git a/rsd_lib/tests/unit/json_samples/v2_1/fabrics_port.json b/rsd_lib/tests/unit/json_samples/v2_1/fabrics_port.json new file mode 100644 index 0000000..75b1f68 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/v2_1/fabrics_port.json @@ -0,0 +1,52 @@ +{ + "@odata.context":"/redfish/v1/$metadata#Port.Port", + "@odata.id":"/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Up1", + "@odata.type":"#Port.v1_0_0.Port", + "Id":"Up1", + "Name":"PCIe Upstream Port 1", + "Description":"PCIe Upstream Port 1", + "Status":{ + "State":"Enabled", + "Health":"OK" + }, + "PortId":"1", + "PortProtocol":"PCIe", + "PortType":"UpstreamPort", + "CurrentSpeedGbps":32, + "Width":4, + "MaxSpeedGbps":64, + "Actions":{ + "#Port.Reset":{ + "target":"/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Up1/Actions/PCIePort.Reset", + "ResetType@Redfish.AllowableValues":[ + "ForceOff", + "ForceRestart", + "ForceOn" + ] + }, + "Oem":{ + + } + }, + "Links":{ + "AssociatedEndpoints":[ + { + "@odata.id":"/redfish/v1/Fabrics/PCIe/Endpoints/HostRootComplex1" + } + ], + "ConnectedSwitches":[ + + ], + "ConnectedSwitchPorts":[ + + ] + }, + "Oem":{ + "Intel_RackScale":{ + "@odata.type":"#Intel.Oem.Port", + "PCIeConnectionId":[ + "XYZ1234567890" + ] + } + } +} diff --git a/rsd_lib/tests/unit/json_samples/v2_1/fabrics_port_collection.json b/rsd_lib/tests/unit/json_samples/v2_1/fabrics_port_collection.json new file mode 100644 index 0000000..361ca52 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/v2_1/fabrics_port_collection.json @@ -0,0 +1,22 @@ +{ + "@odata.context":"/redfish/v1/$metadata#PortCollection.PortCollection", + "@odata.id":"/redfish/v1/Fabrics/PCIe/Switches/1/Ports", + "@odata.type":"#PortCollection.PortCollection", + "Name":"PCIe Port Collection", + "Description":"PCIe Port Collection", + "Members@odata.count":4, + "Members":[ + { + "@odata.id":"/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Up1" + }, + { + "@odata.id":"/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Up2" + }, + { + "@odata.id":"/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Down1" + }, + { + "@odata.id":"/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Down2" + } + ] +} \ No newline at end of file diff --git a/rsd_lib/tests/unit/resources/v2_1/fabric/test_port.py b/rsd_lib/tests/unit/resources/v2_1/fabric/test_port.py new file mode 100644 index 0000000..1cf256d --- /dev/null +++ b/rsd_lib/tests/unit/resources/v2_1/fabric/test_port.py @@ -0,0 +1,112 @@ +# Copyright 2019 Intel, Inc. +# All Rights Reserved. +# +# 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 testtools + +from rsd_lib.resources.v2_1.fabric import port + + +class PortTestCase(testtools.TestCase): + + def setUp(self): + super(PortTestCase, self).setUp() + self.conn = mock.Mock() + with open('rsd_lib/tests/unit/json_samples/v2_1/fabrics_port.json', + 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.port_inst = port.Port( + self.conn, '/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Up1', + redfish_version='1.0.2') + + def test__parse_attributes(self): + self.port_inst._parse_attributes() + self.assertEqual("Up1", self.port_inst.identity) + self.assertEqual("PCIe Upstream Port 1", self.port_inst.name) + self.assertEqual("PCIe Upstream Port 1", self.port_inst.description) + self.assertEqual("Enabled", self.port_inst.status.state) + self.assertEqual("OK", self.port_inst.status.health) + self.assertEqual("1", self.port_inst.port_id) + self.assertEqual("PCIe", self.port_inst.port_protocol) + self.assertEqual("UpstreamPort", self.port_inst.port_type) + self.assertEqual(32, self.port_inst.current_speed_gbps) + self.assertEqual(4, self.port_inst.width) + self.assertEqual(64, self.port_inst.max_speed_gbps) + self.assertEqual("/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Up1" + "/Actions/PCIePort.Reset", self. + port_inst.actions.port_reset.target) + self.assertEqual(["ForceOff", "ForceRestart", "ForceOn"], + self.port_inst.actions.port_reset. + reset_type_redfish_allowable_values) + self.assertEqual(('/redfish/v1/Fabrics/PCIe/Endpoints/' + 'HostRootComplex1',), + self.port_inst.links.associated_endpoints) + self.assertEqual("#Intel.Oem.Port", + self.port_inst.oem.intel_rackScale.odata_type) + self.assertEqual(['XYZ1234567890'], + self.port_inst.oem.intel_rackScale.pcie_connection_id) + + def test_reset_port(self): + self.port_inst.reset_port() + self.port_inst._conn.post.assert_called_once_with( + '/redfish/v1/Fabrics/PCIe/Switches/1/' + 'Ports/Up1/Actions/PCIePort.Reset', + data={"ResetType": "ForceRestart"}) + + +class PortCollectionTestCase(testtools.TestCase): + + def setUp(self): + super(PortCollectionTestCase, self).setUp() + self.conn = mock.Mock() + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'fabrics_port_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + self.port_col = port.PortCollection( + self.conn, '/redfish/v1/Fabrics/PCIe/Switches/1/Ports', + redfish_version='1.0.2') + + def test__parse_attributes(self): + self.port_col._parse_attributes() + self.assertEqual("PCIe Port Collection", self.port_col.name) + self.assertEqual("1.0.2", self.port_col.redfish_version) + self.assertEqual(('/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Up1', + '/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Up2', + '/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Down1', + '/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Down2'), + self.port_col.members_identities) + + @mock.patch.object(port, 'Port', autospec=True) + def test_get_member(self, mock_port): + self.port_col.get_member( + '/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Up1') + mock_port.assert_called_once_with( + self.port_col._conn, + '/redfish/v1/Fabrics/PCIe/Switches/1/Ports/Up1', + redfish_version=self.port_col.redfish_version) + + @mock.patch.object(port, 'Port', autospec=True) + def test_get_members(self, mock_port): + members = self.port_col.get_members() + mock_port.assert_called_with( + self.port_col._conn, '/redfish/v1/Fabrics/PCIe/Switches/1/' + 'Ports/Down2', + redfish_version='1.0.2') + self.assertIsInstance(members, list) + self.assertEqual(mock_port.call_count, 4) + self.assertEqual(4, len(members)) diff --git a/rsd_lib/tests/unit/resources/v2_1/fabric/test_switch.py b/rsd_lib/tests/unit/resources/v2_1/fabric/test_switch.py index 34d40ba..5458f48 100644 --- a/rsd_lib/tests/unit/resources/v2_1/fabric/test_switch.py +++ b/rsd_lib/tests/unit/resources/v2_1/fabric/test_switch.py @@ -14,11 +14,11 @@ # under the License. import json - import mock -import testtools +from rsd_lib.resources.v2_1.fabric import port from rsd_lib.resources.v2_1.fabric import switch +import testtools class SwitchTestCase(testtools.TestCase): @@ -53,8 +53,6 @@ class SwitchTestCase(testtools.TestCase): self.assertEqual(97, self.switch_inst.total_switch_width) self.assertEqual(None, self.switch_inst.indicator_led) self.assertEqual('On', self.switch_inst.power_state) - self.assertEqual('/redfish/v1/Fabrics/PCIe/Switches/1/Ports', - self.switch_inst.ports.identity) self.assertEqual(('/redfish/v1/Chassis/PCIeSwitch1',), self. switch_inst. links.chassis) @@ -69,6 +67,57 @@ class SwitchTestCase(testtools.TestCase): '/redfish/v1/Fabrics/PCIe/Switches/1/Actions/Switch.Reset', data={"ResetType": "GracefulRestart"}) + def test__get_ports_path(self): + expected = '/redfish/v1/Fabrics/PCIe/Switches/1/Ports' + result = self.switch_inst._get_ports_path() + self.assertEqual(expected, result) + + def test_ports(self): + # | GIVEN | + self.conn.get.return_value.json.reset_mock() + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'fabrics_port.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN | + actual_ports = self.switch_inst.ports + # | THEN | + self.assertIsInstance(actual_ports, + port.PortCollection) + self.conn.get.return_value.json.assert_called_once_with() + + # reset mock + self.conn.get.return_value.json.reset_mock() + # | WHEN & THEN | + # tests for same object on invoking subsequently + self.assertIs(actual_ports, + self.switch_inst.ports) + self.conn.get.return_value.json.assert_not_called() + + def test_ports_on_refresh(self): + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'fabrics_port.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance(self.switch_inst.ports, + port.PortCollection) + + # On refreshing the manager instance... + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'switch.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.switch_inst.invalidate() + self.switch_inst.refresh(force=False) + + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'fabrics_port.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance(self.switch_inst.ports, + port.PortCollection) + class SwitchCollectionTestCase(testtools.TestCase):