From 4e29ceb539fb57489505960b502fbb1622edb9f4 Mon Sep 17 00:00:00 2001 From: Lin Yang Date: Mon, 17 Dec 2018 22:21:18 -0800 Subject: [PATCH] Add ThermalZone resource for RSD 2.1 Change-Id: Ia6cac7ab67029ddfb77bdc09daec50bba2928c77 --- .../resources/v2_1/chassis/thermal_zone.py | 151 ++++++++++++++++++ .../unit/json_samples/v2_1/thermal_zone.json | 66 ++++++++ .../v2_1/thermal_zone_collection.json | 12 ++ .../resources/v2_1/chassis/test_power_zone.py | 11 +- .../v2_1/chassis/test_thermal_zone.py | 139 ++++++++++++++++ 5 files changed, 373 insertions(+), 6 deletions(-) create mode 100644 rsd_lib/resources/v2_1/chassis/thermal_zone.py create mode 100644 rsd_lib/tests/unit/json_samples/v2_1/thermal_zone.json create mode 100644 rsd_lib/tests/unit/json_samples/v2_1/thermal_zone_collection.json create mode 100644 rsd_lib/tests/unit/resources/v2_1/chassis/test_thermal_zone.py diff --git a/rsd_lib/resources/v2_1/chassis/thermal_zone.py b/rsd_lib/resources/v2_1/chassis/thermal_zone.py new file mode 100644 index 0000000..ef517f0 --- /dev/null +++ b/rsd_lib/resources/v2_1/chassis/thermal_zone.py @@ -0,0 +1,151 @@ +# Copyright 2018 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. + +from sushy.resources import base + +from rsd_lib import utils as rsd_lib_utils + + +class StatusField(base.CompositeField): + state = base.Field('State') + health = base.Field('Health') + health_rollup = base.Field('HealthRollup') + + +class RackLocationField(base.CompositeField): + rack_units = base.Field('RackUnits') + """Indicates the rack unit type""" + + xlocation = base.Field('XLocation', adapter=rsd_lib_utils.int_or_none) + """The horizontal location within uLocation, from left to right + (1.. MAXIMUM) 0 indicate not available + """ + + ulocation = base.Field('ULocation', adapter=rsd_lib_utils.int_or_none) + """The index of the top-most U of the component, from top to bottom + (1.. MAXIMUM) 0 indicate not available + """ + + uheight = base.Field('UHeight', adapter=rsd_lib_utils.int_or_none) + """The height of managed zone, e.g. 8 for 8U, 16 for 16U""" + + +class FansField(base.ListField): + name = base.Field('Name') + """The Power Supply name""" + + reading_rpm = base.Field('ReadingRPM', adapter=rsd_lib_utils.int_or_none) + """Fan RPM reading""" + + status = StatusField('Status') + """The Fan status""" + + rack_location = RackLocationField('RackLocation') + """The Fan physical location""" + + +class TemperaturesField(base.ListField): + name = base.Field('Name') + """The Power Supply name""" + + reading_celsius = base.Field( + 'ReadingCelsius', adapter=rsd_lib_utils.int_or_none) + """Current value of the temperature sensor's reading""" + + physical_context = base.Field('PhysicalContext') + """Describes the area or device to which this temperature measurement + applies: + "Intake" - The intake point of the chassis + "Exhaust" - The exhaust point of the chassis + "Backplane" - A backplane within the chassis + "PowerSupply" - A power supply + "SystemBoard" - The system board (PCB) + "ComputeBay" - Within a compute bay + "PowerSupplyBay" - Within a power supply bay + """ + + status = StatusField('Status') + """The temperature sensors status""" + + +class ThermalZone(base.ResourceBase): + identity = base.Field('Id', required=True) + """The ThermalZone identity string""" + + name = base.Field('Name') + """The ThermalZone name""" + + description = base.Field('Description') + """The ThermalZone description""" + + status = StatusField('Status') + """The ThermalZone status""" + + rack_location = RackLocationField('RackLocation') + """The ThermalZone physical location""" + + presence = base.Field('Presence') + """Indicates the aggregated Power Supply Unit presence information + Aggregated Power Supply Unit presence format: Length of string indicate + total slot of Power Supply Units in PowerZone. + + For each byte the string: + "1" means present + "0" means not present + """ + + desired_speed_pwm = base.Field( + 'DesiredSpeedPWM', adapter=rsd_lib_utils.int_or_none) + """The desired FAN speed in current ThermalZone present in PWM unit""" + + desired_speed_rpm = base.Field( + 'DesiredSpeedRPM', adapter=rsd_lib_utils.int_or_none) + """The desired FAN speed in current ThermalZone present in RPM unit""" + + max_fans_supported = base.Field( + 'MaxFansSupported', adapter=rsd_lib_utils.int_or_none) + """Number of maximum fans that can be installed in a given Thermal Zone""" + + number_of_fans_present = base.Field( + 'NumberOfFansPresent', adapter=rsd_lib_utils.int_or_none) + """The existing number of fans in current ThermalZone""" + + volumetric_airflow = base.Field( + 'VolumetricAirflow', adapter=rsd_lib_utils.int_or_none) + """Rack Level PTAS Telemetry - Volumetric airflow in current ThermalZone""" + + fans = FansField('Fans') + """Details of the fans associated with this thermal zone""" + + temperatures = TemperaturesField('Temperatures') + """Array of temperature sensors""" + + +class ThermalZoneCollection(base.ResourceCollectionBase): + @property + def _resource_type(self): + return ThermalZone + + def __init__(self, connector, path, redfish_version=None): + """A class representing a ThermalZone Collection + + :param connector: A Connector instance + :param path: The canonical path to the power zone collection resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(ThermalZoneCollection, self).__init__(connector, + path, + redfish_version) diff --git a/rsd_lib/tests/unit/json_samples/v2_1/thermal_zone.json b/rsd_lib/tests/unit/json_samples/v2_1/thermal_zone.json new file mode 100644 index 0000000..88a2cdc --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/v2_1/thermal_zone.json @@ -0,0 +1,66 @@ +{ + "@odata.context": "/redfish/v1/$metadata#Chassis/Rack/ThermalZones/Members/$entity", + "@odata.type": "ThermalZone.v1_0_0.ThermalZone", + "@odata.id": "/redfish/v1/Chassis/Rack1/ThermalZones/1", + "Id": "1", + "Name": "thermal zone 1", + "Description": "thermal zone 1 description", + "RackLocation": { + "RackUnits": "OU", + "XLocation": 0, + "ULocation": 1, + "UHeight": 8 + }, + "Presence": "111100", + "DesiredSpeedPWM": 50, + "DesiredSpeedRPM": 3000, + "MaxFansSupported": 6, + "NumberOfFansPresent": 6, + "VolumetricAirflow": 80, + "Temperatures": [ + { + "Name": "Inlet Temperature", + "Status": { + "State": "Enabled", + "Health": "OK", + "HealthRollup": null + }, + "ReadingCelsius": 21, + "PhysicalContext": "Intake" + }, + { + "Name": "Outlet Temperature", + "Status": { + "State": "Enabled", + "Health": "OK", + "HealthRollup": null + }, + "ReadingCelsius": 35, + "PhysicalContext": "Exhaust" + } + ], + "Status": { + "State": "Enabled", + "Health": "OK", + "HealthRollup": null + }, + "Fans": [ + { + "Name": "Fan 1", + "Status": { + "State": "Enabled", + "Health": "OK", + "HealthRollup": null + }, + "ReadingRPM": 0, + "RackLocation": { + "RackUnits": "OU", + "XLocation": 0, + "ULocation": 1, + "UHeight": 8 + } + } + ], + "Actions": {}, + "Links": {} +} \ No newline at end of file diff --git a/rsd_lib/tests/unit/json_samples/v2_1/thermal_zone_collection.json b/rsd_lib/tests/unit/json_samples/v2_1/thermal_zone_collection.json new file mode 100644 index 0000000..9405650 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/v2_1/thermal_zone_collection.json @@ -0,0 +1,12 @@ +{ + "@odata.context": "/redfish/v1/$metadata#ThermalZoneCollection.ThermalZoneCollection", + "@odata.id": "/redfish/v1/Chassis/Rack1/ThermalZones", + "@odata.type": "#ThermalZoneCollection.ThermalZoneCollection", + "Name": "Thermal Zones Collection", + "Members@odata.count": 1, + "Members": [ + { + "@odata.id": "/redfish/v1/Chassis/Rack1/ThermalZones/Thermal1" + } + ] +} \ No newline at end of file diff --git a/rsd_lib/tests/unit/resources/v2_1/chassis/test_power_zone.py b/rsd_lib/tests/unit/resources/v2_1/chassis/test_power_zone.py index 4df90f6..3180758 100644 --- a/rsd_lib/tests/unit/resources/v2_1/chassis/test_power_zone.py +++ b/rsd_lib/tests/unit/resources/v2_1/chassis/test_power_zone.py @@ -1,4 +1,4 @@ -# Copyright 2018 99cloud, Inc. +# Copyright 2018 Intel, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -39,7 +39,6 @@ class PowerZoneTestCase(testtools.TestCase): self.assertEqual('power zone 1', self.power_zone_inst.name) self.assertEqual('power zone 1 description', self.power_zone_inst.description) - self.assertEqual('1', self.power_zone_inst.identity) self.assertEqual('Enabled', self.power_zone_inst.status.state) self.assertEqual('OK', self.power_zone_inst.status.health) self.assertEqual('OK', self.power_zone_inst.status.health_rollup) @@ -106,16 +105,16 @@ class PowerZoneCollectionTestCase(testtools.TestCase): self.power_zone_col.members_identities) @mock.patch.object(power_zone, 'PowerZone', autospec=True) - def test_get_member(self, mock_storage_subsystem): + def test_get_member(self, mock_power_zone): self.power_zone_col.get_member( '/redfish/v1/Chassis/Rack1/PowerZones/Power1') - mock_storage_subsystem.assert_called_once_with( + mock_power_zone.assert_called_once_with( self.power_zone_col._conn, '/redfish/v1/Chassis/Rack1/PowerZones/Power1', redfish_version=self.power_zone_col.redfish_version) @mock.patch.object(power_zone, 'PowerZone', autospec=True) - def test_get_members(self, mock_storage_subsystem): + def test_get_members(self, mock_power_zone): members = self.power_zone_col.get_members() calls = [ mock.call(self.power_zone_col._conn, @@ -123,6 +122,6 @@ class PowerZoneCollectionTestCase(testtools.TestCase): redfish_version=self.power_zone_col. redfish_version) ] - mock_storage_subsystem.assert_has_calls(calls) + mock_power_zone.assert_has_calls(calls) self.assertIsInstance(members, list) self.assertEqual(1, len(members)) diff --git a/rsd_lib/tests/unit/resources/v2_1/chassis/test_thermal_zone.py b/rsd_lib/tests/unit/resources/v2_1/chassis/test_thermal_zone.py new file mode 100644 index 0000000..b0f4f1e --- /dev/null +++ b/rsd_lib/tests/unit/resources/v2_1/chassis/test_thermal_zone.py @@ -0,0 +1,139 @@ +# Copyright 2018 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.chassis import thermal_zone + + +class ThermalZoneTestCase(testtools.TestCase): + + def setUp(self): + super(ThermalZoneTestCase, self).setUp() + self.conn = mock.Mock() + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'thermal_zone.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.thermal_zone_inst = thermal_zone.ThermalZone( + self.conn, '/redfish/v1/Chassis/Rack1/ThermalZones/Thermal1', + redfish_version='1.1.0') + + def test__parse_attributes(self): + self.thermal_zone_inst._parse_attributes() + self.assertEqual('1', self.thermal_zone_inst.identity) + self.assertEqual('thermal zone 1', self.thermal_zone_inst.name) + self.assertEqual('thermal zone 1 description', + self.thermal_zone_inst.description) + self.assertEqual('Enabled', self.thermal_zone_inst.status.state) + self.assertEqual('OK', self.thermal_zone_inst.status.health) + self.assertEqual(None, self.thermal_zone_inst.status.health_rollup) + self.assertEqual('OU', self.thermal_zone_inst.rack_location.rack_units) + self.assertEqual(0, self.thermal_zone_inst.rack_location.xlocation) + self.assertEqual(1, self.thermal_zone_inst.rack_location.ulocation) + self.assertEqual(8, self.thermal_zone_inst.rack_location.uheight) + self.assertEqual('111100', self.thermal_zone_inst.presence) + self.assertEqual(50, self.thermal_zone_inst.desired_speed_pwm) + self.assertEqual(3000, self.thermal_zone_inst.desired_speed_rpm) + self.assertEqual(6, self.thermal_zone_inst.max_fans_supported) + self.assertEqual(6, self.thermal_zone_inst.number_of_fans_present) + self.assertEqual(80, self.thermal_zone_inst.volumetric_airflow) + self.assertEqual( + 'Fan 1', self.thermal_zone_inst.fans[0].name) + self.assertEqual( + 0, self.thermal_zone_inst.fans[0].reading_rpm) + self.assertEqual( + 'Enabled', self.thermal_zone_inst.fans[0].status.state) + self.assertEqual( + 'OK', self.thermal_zone_inst.fans[0].status.health) + self.assertEqual( + None, self.thermal_zone_inst.fans[0].status.health_rollup) + self.assertEqual( + 'OU', + self.thermal_zone_inst.fans[0].rack_location.rack_units) + self.assertEqual( + 0, self.thermal_zone_inst.fans[0].rack_location.xlocation) + self.assertEqual( + 1, self.thermal_zone_inst.fans[0].rack_location.ulocation) + self.assertEqual( + 8, self.thermal_zone_inst.fans[0].rack_location.uheight) + self.assertEqual( + 'Inlet Temperature', self.thermal_zone_inst.temperatures[0].name) + self.assertEqual( + 'Enabled', self.thermal_zone_inst.temperatures[0].status.state) + self.assertEqual( + 'OK', self.thermal_zone_inst.temperatures[0].status.health) + self.assertEqual( + None, self.thermal_zone_inst.temperatures[0].status.health_rollup) + self.assertEqual( + 21, self.thermal_zone_inst.temperatures[0].reading_celsius) + self.assertEqual( + 'Intake', self.thermal_zone_inst.temperatures[0].physical_context) + self.assertEqual( + 'Outlet Temperature', self.thermal_zone_inst.temperatures[1].name) + self.assertEqual( + 'Enabled', self.thermal_zone_inst.temperatures[1].status.state) + self.assertEqual( + 'OK', self.thermal_zone_inst.temperatures[1].status.health) + self.assertEqual( + None, self.thermal_zone_inst.temperatures[1].status.health_rollup) + self.assertEqual( + 35, self.thermal_zone_inst.temperatures[1].reading_celsius) + self.assertEqual( + 'Exhaust', self.thermal_zone_inst.temperatures[1].physical_context) + + +class ThermalZoneCollectionTestCase(testtools.TestCase): + + def setUp(self): + super(ThermalZoneCollectionTestCase, self).setUp() + self.conn = mock.Mock() + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'thermal_zone_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + self.thermal_zone_col = thermal_zone.\ + ThermalZoneCollection( + self.conn, '/redfish/v1/Chassis/Rack1/ThermalZones', + redfish_version='1.1.0') + + def test__parse_attributes(self): + self.thermal_zone_col._parse_attributes() + self.assertEqual('1.1.0', self.thermal_zone_col.redfish_version) + self.assertEqual(('/redfish/v1/Chassis/Rack1/ThermalZones/Thermal1',), + self.thermal_zone_col.members_identities) + + @mock.patch.object(thermal_zone, 'ThermalZone', autospec=True) + def test_get_member(self, mock_thermal_zone): + self.thermal_zone_col.get_member( + '/redfish/v1/Chassis/Rack1/ThermalZones/Thermal1') + mock_thermal_zone.assert_called_once_with( + self.thermal_zone_col._conn, + '/redfish/v1/Chassis/Rack1/ThermalZones/Thermal1', + redfish_version=self.thermal_zone_col.redfish_version) + + @mock.patch.object(thermal_zone, 'ThermalZone', autospec=True) + def test_get_members(self, mock_thermal_zone): + members = self.thermal_zone_col.get_members() + calls = [ + mock.call(self.thermal_zone_col._conn, + '/redfish/v1/Chassis/Rack1/ThermalZones/Thermal1', + redfish_version=self.thermal_zone_col. + redfish_version) + ] + mock_thermal_zone.assert_has_calls(calls) + self.assertIsInstance(members, list) + self.assertEqual(1, len(members))