From 3764c4d8d0be1f2cc51350a608ab7006e7a70ffc Mon Sep 17 00:00:00 2001 From: Lin Yang Date: Mon, 17 Dec 2018 16:48:23 -0800 Subject: [PATCH] Add PowerZone resource for RSD 2.1 Change-Id: I3d16f8a3b7424d3211e35f749711d9e694bf9800 --- rsd_lib/resources/v2_1/chassis/power_zone.py | 146 ++++++++++++++++++ .../unit/json_samples/v2_1/power_zone.json | 50 ++++++ .../v2_1/power_zone_collection.json | 12 ++ .../resources/v2_1/chassis/test_power_zone.py | 128 +++++++++++++++ 4 files changed, 336 insertions(+) create mode 100644 rsd_lib/resources/v2_1/chassis/power_zone.py create mode 100644 rsd_lib/tests/unit/json_samples/v2_1/power_zone.json create mode 100644 rsd_lib/tests/unit/json_samples/v2_1/power_zone_collection.json create mode 100644 rsd_lib/tests/unit/resources/v2_1/chassis/test_power_zone.py diff --git a/rsd_lib/resources/v2_1/chassis/power_zone.py b/rsd_lib/resources/v2_1/chassis/power_zone.py new file mode 100644 index 0000000..1d91613 --- /dev/null +++ b/rsd_lib/resources/v2_1/chassis/power_zone.py @@ -0,0 +1,146 @@ +# 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 PowerSuppliesField(base.ListField): + name = base.Field('Name') + """The Power Supply name""" + + power_capacity_watts = base.Field( + 'PowerCapacityWatts', adapter=rsd_lib_utils.int_or_none) + """The maximum capacity of this Power Supply""" + + last_power_output_watts = base.Field( + 'LastPowerOutputWatts', adapter=rsd_lib_utils.int_or_none) + """The average power output of this Power Supply""" + + manufacturer = base.Field('Manufacturer') + """The manufacturer of this Power Supply""" + + model_number = base.Field('ModelNumber') + """The model number for this Power Supply""" + + firmware_revision = base.Field('FirmwareRevision') + """The firmware version for this Power Supply""" + + serial_number = base.Field('SerialNumber') + """The serial number for this Power Supply""" + + part_number = base.Field('PartNumber') + """The part number for this Power Supply""" + + status = StatusField('Status') + """The Power supply status""" + + rack_location = RackLocationField('RackLocation') + """The PowerZone physical location""" + + +class PowerZone(base.ResourceBase): + identity = base.Field('Id', required=True) + """The PowerZone identity string""" + + name = base.Field('Name') + """The PowerZone name""" + + description = base.Field('Description') + """The PowerZone description""" + + status = StatusField('Status') + """The PowerZone status""" + + rack_location = RackLocationField('RackLocation') + """The PowerZone physical location""" + + max_psus_supported = base.Field( + 'MaxPSUsSupported', adapter=rsd_lib_utils.int_or_none) + """The maximum number of Power Supply Units supported by PowerZone""" + + 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 + """ + + number_of_psus_present = base.Field( + 'NumberOfPSUsPresent', adapter=rsd_lib_utils.int_or_none) + """Indicates the number of existing Power Supply Units in PowerZone""" + + power_consumed_watts = base.Field( + 'PowerConsumedWatts', adapter=rsd_lib_utils.int_or_none) + """The total power consumption of PowerZone, sum of trays' + power consumption + """ + + power_output_watts = base.Field( + 'PowerOutputWatts', adapter=rsd_lib_utils.int_or_none) + """The total power production of PowerZone, sum of PSUs' output""" + + power_capacity_watts = base.Field( + 'PowerCapacityWatts', adapter=rsd_lib_utils.int_or_none) + """The maximum power capacity supported by PowerZone""" + + power_supplies = PowerSuppliesField('PowerSupplies') + """Details of the power supplies associated with this system or device""" + + +class PowerZoneCollection(base.ResourceCollectionBase): + @property + def _resource_type(self): + return PowerZone + + def __init__(self, connector, path, redfish_version=None): + """A class representing a PowerZone 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(PowerZoneCollection, self).__init__(connector, + path, + redfish_version) diff --git a/rsd_lib/tests/unit/json_samples/v2_1/power_zone.json b/rsd_lib/tests/unit/json_samples/v2_1/power_zone.json new file mode 100644 index 0000000..54d5b7e --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/v2_1/power_zone.json @@ -0,0 +1,50 @@ +{ + "@odata.context": "/redfish/v1/$metadata#Chassis/Rack/PowerZones/Members/$entity", + "@odata.id": "/redfish/v1/Chassis/Rack1/PowerZones/1", + "@odata.type": "PowerZone.v1_0_0.PowerZone", + "Id": "1", + "Name": "power zone 1", + "Description": "power zone 1 description", + "Status": { + "State": "Enabled", + "Health": "OK", + "HealthRollup": "OK" + }, + "RackLocation": { + "RackUnits": "OU", + "XLocation": 0, + "ULocation": 1, + "UHeight": 8 + }, + "MaxPSUsSupported": 6, + "Presence": "111111", + "NumberOfPSUsPresent": 6, + "PowerConsumedWatts": 2000, + "PowerOutputWatts": 2000, + "PowerCapacityWatts": 3000, + "PowerSupplies": [ + { + "Name": "Power supply 1", + "Status": { + "State": "Enabled", + "Health": "OK", + "HealthRollup": "OK" + }, + "RackLocation": { + "RackUnits": "OU", + "XLocation": 0, + "ULocation": 1, + "UHeight": 8 + }, + "SerialNumber": "", + "Manufacturer": "", + "ModelNumber": "", + "PartNumber": "", + "FirmwareRevision": "", + "PowerCapacityWatts": 300, + "LastPowerOutputWatts": 48 + } + ], + "Actions": {}, + "Links": {} +} \ No newline at end of file diff --git a/rsd_lib/tests/unit/json_samples/v2_1/power_zone_collection.json b/rsd_lib/tests/unit/json_samples/v2_1/power_zone_collection.json new file mode 100644 index 0000000..bf8df8c --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/v2_1/power_zone_collection.json @@ -0,0 +1,12 @@ +{ + "@odata.context": "/redfish/v1/$metadata#PowerZoneCollection.PowerZoneCollection", + "@odata.id": "/redfish/v1/Chassis/Rack1/PowerZones", + "@odata.type": "#PowerZoneCollection.PowerZoneCollection", + "Name": "Power Zones Collection", + "Members@odata.count": 1, + "Members": [ + { + "@odata.id": "/redfish/v1/Chassis/Rack1/PowerZones/Power1" + } + ] +} \ 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 new file mode 100644 index 0000000..4df90f6 --- /dev/null +++ b/rsd_lib/tests/unit/resources/v2_1/chassis/test_power_zone.py @@ -0,0 +1,128 @@ +# Copyright 2018 99cloud, 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 power_zone + + +class PowerZoneTestCase(testtools.TestCase): + + def setUp(self): + super(PowerZoneTestCase, self).setUp() + self.conn = mock.Mock() + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'power_zone.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.power_zone_inst = power_zone.PowerZone( + self.conn, '/redfish/v1/Chassis/Rack1/PowerZones/1', + redfish_version='1.1.0') + + def test__parse_attributes(self): + self.power_zone_inst._parse_attributes() + self.assertEqual('1', self.power_zone_inst.identity) + 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) + self.assertEqual('OU', self.power_zone_inst.rack_location.rack_units) + self.assertEqual(0, self.power_zone_inst.rack_location.xlocation) + self.assertEqual(1, self.power_zone_inst.rack_location.ulocation) + self.assertEqual(8, self.power_zone_inst.rack_location.uheight) + self.assertEqual(6, self.power_zone_inst.max_psus_supported) + self.assertEqual('111111', self.power_zone_inst.presence) + self.assertEqual(6, self.power_zone_inst.number_of_psus_present) + self.assertEqual(2000, self.power_zone_inst.power_consumed_watts) + self.assertEqual(2000, self.power_zone_inst.power_output_watts) + self.assertEqual(3000, self.power_zone_inst.power_capacity_watts) + self.assertEqual( + 'Power supply 1', self.power_zone_inst.power_supplies[0].name) + self.assertEqual( + 300, self.power_zone_inst.power_supplies[0].power_capacity_watts) + self.assertEqual( + 48, self.power_zone_inst.power_supplies[0].last_power_output_watts) + self.assertEqual( + '', self.power_zone_inst.power_supplies[0].manufacturer) + self.assertEqual( + '', self.power_zone_inst.power_supplies[0].model_number) + self.assertEqual( + '', self.power_zone_inst.power_supplies[0].firmware_revision) + self.assertEqual( + '', self.power_zone_inst.power_supplies[0].serial_number) + self.assertEqual( + '', self.power_zone_inst.power_supplies[0].part_number) + self.assertEqual( + 'Enabled', self.power_zone_inst.power_supplies[0].status.state) + self.assertEqual( + 'OK', self.power_zone_inst.power_supplies[0].status.health) + self.assertEqual( + 'OK', self.power_zone_inst.power_supplies[0].status.health_rollup) + self.assertEqual( + 'OU', + self.power_zone_inst.power_supplies[0].rack_location.rack_units) + self.assertEqual( + 0, self.power_zone_inst.power_supplies[0].rack_location.xlocation) + self.assertEqual( + 1, self.power_zone_inst.power_supplies[0].rack_location.ulocation) + self.assertEqual( + 8, self.power_zone_inst.power_supplies[0].rack_location.uheight) + + +class PowerZoneCollectionTestCase(testtools.TestCase): + + def setUp(self): + super(PowerZoneCollectionTestCase, self).setUp() + self.conn = mock.Mock() + with open('rsd_lib/tests/unit/json_samples/v2_1/' + 'power_zone_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + self.power_zone_col = power_zone.\ + PowerZoneCollection( + self.conn, '/redfish/v1/Chassis/Rack1/PowerZones', + redfish_version='1.1.0') + + def test__parse_attributes(self): + self.power_zone_col._parse_attributes() + self.assertEqual('1.1.0', self.power_zone_col.redfish_version) + self.assertEqual(('/redfish/v1/Chassis/Rack1/PowerZones/Power1',), + self.power_zone_col.members_identities) + + @mock.patch.object(power_zone, 'PowerZone', autospec=True) + def test_get_member(self, mock_storage_subsystem): + self.power_zone_col.get_member( + '/redfish/v1/Chassis/Rack1/PowerZones/Power1') + mock_storage_subsystem.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): + members = self.power_zone_col.get_members() + calls = [ + mock.call(self.power_zone_col._conn, + '/redfish/v1/Chassis/Rack1/PowerZones/Power1', + redfish_version=self.power_zone_col. + redfish_version) + ] + mock_storage_subsystem.assert_has_calls(calls) + self.assertIsInstance(members, list) + self.assertEqual(1, len(members))