Add DriveMetrics resource for RSD2.3

Change-Id: I00a162b4ee6405f53abf3f5030a7cbfb5345f075
This commit is contained in:
leizhang 2019-01-09 17:07:18 +08:00
parent 6c46d04f78
commit caa3c06add
6 changed files with 244 additions and 5 deletions

View File

@ -13,15 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
from sushy.resources import base
from sushy import utils
from rsd_lib.resources.v2_3.storage_service import drive_metrics
from rsd_lib import utils as rsd_lib_utils
LOG = logging.getLogger(__name__)
class LinksField(base.CompositeField):
chassis = base.Field('Chassis',
@ -151,6 +148,26 @@ class Drive(base.ResourceBase):
"""
super(Drive, self).__init__(connector, identity, redfish_version)
def _get_metrics_path(self):
"""Helper function to find the Metrics path"""
return utils.get_sub_resource_path_by(self,
['Links',
'Oem',
'Intel_RackScale',
'Metrics'])
@property
@utils.cache_it
def metrics(self):
"""Property to provide reference to `Metrics` instance
It is calculated once when it is queried for the first time. On
refresh, this property is reset.
"""
return drive_metrics.DriveMetrics(
self._conn, self._get_metrics_path(),
redfish_version=self.redfish_version)
class DriveCollection(base.ResourceCollectionBase):

View File

@ -0,0 +1,80 @@
# Copyright (c) 2019 Intel, Corp.
#
# 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 LifeTimeField(base.CompositeField):
unit_size_bytes = base.Field('UnitSizeBytes',
adapter=rsd_lib_utils.num_or_none)
"""The size of a unit in bytes used by UnitsRead and UnitsWritten"""
units_read = base.Field('UnitsRead')
"""The number of units of a read since reset """
units_written = base.Field('UnitsWritten')
"""The number of units of a written since reset"""
host_read_commands = base.Field('HostReadCommands')
"""The number of read commands completed by the disk controller"""
host_write_commands = base.Field('HostWriteCommands')
"""The number of write commands completed by the disk controller"""
power_cycles = base.Field('PowerCycles')
"""The number of power cycels of this drive"""
power_on_hours = base.Field('PowerOnHours')
"""The number of power-on hours of this drive"""
controller_busy_time_minutes = base.Field('ControllerBusyTimeMinutes')
"""The amount of time in minutes the driver controller is busy"""
class HealthDataField(base.CompositeField):
available_spare_percentage = base.Field('AvailableSparePercentage')
"""The percentage of the remaining spare capacity available"""
predicted_media_life_used_percent = base.Field(
'PredictedMediaLifeUsedPercent')
"""The percentage of life remaining in the driver's media"""
unsafe_shutdowns = base.Field('UnsafeShutdowns')
"""The number of unsafe shutdowns of this drive"""
media_errors = base.Field('MediaErrors')
"""The number of media and data integrity errors of this drive"""
class DriveMetrics(base.ResourceBase):
name = base.Field('Name')
"""Drive metrics name"""
identity = base.Field('Id')
"""Drive metrics id"""
description = base.Field('Description')
"""Drive metrics description"""
life_time = LifeTimeField('LifeTime')
"""The life time metrics for this drive"""
health_data = HealthDataField('HealthData')
"""The health data metrics for this drive"""
temperature_kelvin = base.Field('TemperatureKelvin')
"""The temperature in Kelvin degrees of this drive"""

View File

@ -53,7 +53,14 @@
"Chassis": {
"@odata.id": "/redfish/v1/Chassis/1"
},
"Oem": {},
"Oem": {
"Intel_RackScale": {
"@odata.type": "#Intel.Oem.DriveLinks",
"Metrics": {
"@odata.id": "/redfish/v1/Chassis/1/Drives/1/Metrics"
}
}
},
"Volumes": [],
"Endpoints": []
},

View File

@ -0,0 +1,25 @@
{
"@odata.context": "/redfish/v1/$metadata#Chassis/Members/1/Drive/Metrics/$entity",
"@odata.id": "/redfish/v1/Chassis/1/Drives/1/Metrics",
"@odata.type": "#DriveMetrics.v1_0_0.DriveMetrics",
"Name": "Drive Metrics for Drive",
"Description": "Metrics for Drive 1",
"Id": "Metrics",
"TemperatureKelvin": 318,
"LifeTime": {
"UnitSizeBytes": 512000,
"UnitsRead": 1640,
"UnitsWritten": 2,
"HostReadCommands": 12344,
"HostWriteCommands": 2323,
"PowerCycles": 244,
"PowerOnHours": 34566566,
"ControllerBusyTimeMinutes": 545465665656
},
"HealthData": {
"AvailableSparePercentage": 67,
"PredictedMediaLifeUsedPercent": 120,
"UnsafeShutdowns": 23,
"MediaErrors": 10
}
}

View File

@ -18,6 +18,7 @@ import mock
import testtools
from rsd_lib.resources.v2_3.storage_service import drive
from rsd_lib.resources.v2_3.storage_service import drive_metrics
class DriveTestCase(testtools.TestCase):
@ -77,6 +78,56 @@ class DriveTestCase(testtools.TestCase):
self.assertEqual((), self.drive_inst.links.volumes)
self.assertEqual((), self.drive_inst.links.endpoints)
def test_get_metrics_path(self):
expected = '/redfish/v1/Chassis/1/Drives/1/Metrics'
result = self.drive_inst._get_metrics_path()
self.assertEqual(expected, result)
def test_drive_metrics(self):
# | GIVEN |
self.conn.get.return_value.json.reset_mock()
with open('rsd_lib/tests/unit/json_samples/v2_3/'
'drive_metrics.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
# | WHEN |
actual_drive_metrics = self.drive_inst.metrics
# | THEN |
self.assertIsInstance(actual_drive_metrics,
drive_metrics.DriveMetrics)
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_drive_metrics,
self.drive_inst.metrics)
self.conn.get.return_value.json.assert_not_called()
def test_drive_metrics_on_refresh(self):
# | GIVEN |
with open('rsd_lib/tests/unit/json_samples/v2_3/'
'drive_metrics.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
# | WHEN & THEN |
self.assertIsInstance(self.drive_inst.metrics,
drive_metrics.DriveMetrics)
# On refreshing the chassis instance...
with open('rsd_lib/tests/unit/json_samples/v2_3/'
'drive.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.drive_inst.invalidate()
self.drive_inst.refresh(force=False)
# | GIVEN |
with open('rsd_lib/tests/unit/json_samples/v2_3/'
'drive_metrics.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
# | WHEN & THEN |
self.assertIsInstance(self.drive_inst.metrics,
drive_metrics.DriveMetrics)
class DriveCollectionTestCase(testtools.TestCase):

View File

@ -0,0 +1,59 @@
# 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_3.storage_service import drive_metrics
class DriveMetricsTestCase(testtools.TestCase):
def setUp(self):
super(DriveMetricsTestCase, self).setUp()
self.conn = mock.Mock()
with open('rsd_lib/tests/unit/json_samples/v2_3/drive_metrics.json',
'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.drive_metrics_inst = drive_metrics.DriveMetrics(
self.conn, '/redfish/v1/Chassis/1/Drives/1/Metrics',
redfish_version='1.0.2')
def test__parse_attributes(self):
self.drive_metrics_inst._parse_attributes()
self.assertEqual('Drive Metrics for Drive',
self.drive_metrics_inst.name)
self.assertEqual('Metrics for Drive 1',
self.drive_metrics_inst.description)
self.assertEqual('Metrics', self.drive_metrics_inst.identity)
self.assertEqual(318, self.drive_metrics_inst.temperature_kelvin)
life_time = self.drive_metrics_inst.life_time
self.assertEqual(512000, life_time.unit_size_bytes)
self.assertEqual(1640, life_time.units_read)
self.assertEqual(2, life_time.units_written)
self.assertEqual(12344, life_time.host_read_commands)
self.assertEqual(2323, life_time.host_write_commands)
self.assertEqual(244, life_time.power_cycles)
self.assertEqual(34566566, life_time.power_on_hours)
self.assertEqual(545465665656, life_time.controller_busy_time_minutes)
health_data = self.drive_metrics_inst.health_data
self.assertEqual(67, health_data.available_spare_percentage)
self.assertEqual(120, health_data.predicted_media_life_used_percent)
self.assertEqual(23, health_data.unsafe_shutdowns)
self.assertEqual(10, health_data.media_errors)