From 7372e555bd28b4f62790c46cc37c7a9702276b82 Mon Sep 17 00:00:00 2001 From: monokai <2536818783@qq.com> Date: Thu, 24 Jan 2019 14:58:15 +0800 Subject: [PATCH] Add metric report definition resource for RSD2.2 Change-Id: I009e7c04f14a13394b5d89f0677b4d4793345933 --- rsd_lib/resources/v2_2/telemetry/metric.py | 119 ++++++++ .../v2_2/telemetry/metric_definitions.py | 3 + .../telemetry/metric_report_definition.py | 177 +++++++++++ .../metric_report_definition_schemas.py | 60 ++++ rsd_lib/resources/v2_2/telemetry/telemetry.py | 17 ++ .../v2_2/metric_report_definition.json | 33 +++ .../metric_report_definition_collection.json | 12 + .../v2_2/telemetry/test_metric_definitions.py | 8 +- .../test_metric_report_definition.py | 276 ++++++++++++++++++ .../v2_2/telemetry/test_telemetry.py | 65 ++++- 10 files changed, 765 insertions(+), 5 deletions(-) create mode 100644 rsd_lib/resources/v2_2/telemetry/metric.py create mode 100644 rsd_lib/resources/v2_2/telemetry/metric_report_definition.py create mode 100644 rsd_lib/resources/v2_2/telemetry/metric_report_definition_schemas.py create mode 100644 rsd_lib/tests/unit/json_samples/v2_2/metric_report_definition.json create mode 100644 rsd_lib/tests/unit/json_samples/v2_2/metric_report_definition_collection.json create mode 100644 rsd_lib/tests/unit/resources/v2_2/telemetry/test_metric_report_definition.py diff --git a/rsd_lib/resources/v2_2/telemetry/metric.py b/rsd_lib/resources/v2_2/telemetry/metric.py new file mode 100644 index 0000000..24b1db2 --- /dev/null +++ b/rsd_lib/resources/v2_2/telemetry/metric.py @@ -0,0 +1,119 @@ +# 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. + +from sushy.resources import base + +from rsd_lib import utils as rsd_lib_utils + + +class DiscreteTriggerConditionField(base.ListField): + name = base.Field("Name") + """This property shall contain a name for the trigger""" + + trigger_value = base.Field("TriggerValue") + """This property contains the value that sets a trigger""" + + previous_value = base.Field("PreviousValue") + """If present, this property shall contain a previous value that shall be + used in evaluating the behavior of setting the trigger. + """ + + +class NumericTriggerConditionField(base.ListField): + name = base.Field("Name") + """This property shall contain a name for the trigger""" + + value = base.Field("Value", adapter=rsd_lib_utils.num_or_none) + """This property shall contain the value of the trigger""" + + direction_of_crossing = base.Field("DirectionOfCrossing") + """If present, this property shall contain the direction of crossing. If + not present, the direction is not relevant + """ + + +class TriggerConditionField(base.CompositeField): + dwell_interval = base.Field("DwellInterval") + """The value shall be an ISO 8601 conformant interval during which the + triggering state shall persist before the trigger is invoked. + """ + + trigger_type = base.Field("TriggerType") + """The value of this property shall specific the type of trigger""" + + discrete_trigger_conditions = DiscreteTriggerConditionField( + "DiscreteTriggerConditions") + """A Trigger condition based on TriggerDiscreteCondition""" + + filter_trigger_condition = base.Field("FilterTriggerCondition") + """A Trigger condition based on FilterTriggerCondition""" + + numeric_trigger_conditions = NumericTriggerConditionField( + "NumericTriggerConditions") + """A Trigger condition based on NumericTriggerConditions""" + + +class StatusField(base.CompositeField): + state = base.Field("State") + """The status state""" + + health = base.Field("Health") + """The status health""" + + +class Metric(base.ResourceBase): + identity = base.Field("Id") + """The metric identity""" + + name = base.Field('Name') + """The metric name""" + + description = base.Field('Description') + """The metric description""" + + metric_properties = base.Field("MetricProperties") + """The report definition metric properties""" + + collection_function = base.Field('CollectionFunction') + """If present, the value shall define the function to apply over the + collection duration + """ + + collection_duration = base.Field('CollectionDuration') + """This property shall not be present if MetricDefinition.Timescope=Point + or if MetricDefintion.Duration is present. If present, the value shall + be an ISO 8601 duration of the interval over which this metric value + shall be computed. + """ + + trigger_condition = TriggerConditionField("TriggerCondition") + """If present the values define conditions that shall be met before the + event is triggered. This trigger applies to all properties defined by + the value of the MetricPropertyDeclaration property in the associated + MetricDefinition and as constrained by the MetricScope property. + """ + + status = StatusField("Status") + """The report definition status""" + + def __init__(self, connector, identity, redfish_version=None): + """A class representing a Metric + + :param connector: A Connector instance + :param identity: The identity of the Metric resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(Metric, self).__init__(connector, identity, redfish_version) diff --git a/rsd_lib/resources/v2_2/telemetry/metric_definitions.py b/rsd_lib/resources/v2_2/telemetry/metric_definitions.py index 73eb53b..f68fc88 100644 --- a/rsd_lib/resources/v2_2/telemetry/metric_definitions.py +++ b/rsd_lib/resources/v2_2/telemetry/metric_definitions.py @@ -24,6 +24,9 @@ class MetricDefinition(base.ResourceBase): identity = base.Field('Id', required=True) """The CPUHealth metric definition identity string""" + description = base.Field('Description') + """The metric definition description""" + sensor_type = base.Field('SensorType') """The type of sensor""" diff --git a/rsd_lib/resources/v2_2/telemetry/metric_report_definition.py b/rsd_lib/resources/v2_2/telemetry/metric_report_definition.py new file mode 100644 index 0000000..d558d95 --- /dev/null +++ b/rsd_lib/resources/v2_2/telemetry/metric_report_definition.py @@ -0,0 +1,177 @@ +# 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. + +from jsonschema import validate +import logging + +from sushy import exceptions +from sushy.resources import base +from sushy import utils + +from rsd_lib.resources.v2_2.telemetry import metric +from rsd_lib.resources.v2_2.telemetry import metric_report_definition_schemas + + +LOG = logging.getLogger(__name__) + + +class ScheduleField(base.CompositeField): + recurrence_interval = base.Field("RecurrenceInterval") + """The schedule recurrence interval""" + + +class WildcardsField(base.ListField): + name = base.Field("Name") + """This property shall contain a name for a Wildcard for a key""" + + keys = base.Field("Keys") + """If the value is an empty string, then the server shall substitute every + current key. Each not empty key value shall be substituted for the + wildcard + """ + + +class StatusField(base.CompositeField): + state = base.Field("State") + """The status state""" + + health = base.Field("Health") + """The status health""" + + health_rollup = base.Field('HealthRollup') + """The status health_rollup""" + + +class MetricReportDefinition(base.ResourceBase): + identity = base.Field("Id") + """The metric report definition identity""" + + name = base.Field('Name') + """The metric report definition name""" + + description = base.Field('Description') + """The metric report definition description""" + + schedule = ScheduleField("Schedule") + """If present, A metric values collected starting at each scheduled + interval and for the time specified by Duration. No more than + Schedule.MaxOccurrences values shall be collected for this metric. If + not present, the corresponding metric values shall be collected when the + related metric report is retrieved. + """ + + metric_report_type = base.Field("MetricReportType") + """The value shall specify the collection type for the corresponding + metric values + """ + + collection_time_scope = base.Field("CollectionTimeScope") + """The value shall specify the time scope for collecting the corresponding + metric values + """ + + report_actions = base.Field("ReportActions") + """The value of this property shall specify the action to perform when the + metric report is generated. When a metric report is generated, place the + metric information in the resource specified by the MetricReport + property. The Volatile property will specify the behavior if + MetricReport resource already exists. + """ + + volatile = base.Field("Volatile") + """Entries in the resulting metric value properties are reused on each + scheduled interval + """ + + wildcards = WildcardsField("Wildcards") + """The property shall contain an array of wildcards and their replacements + strings, which are to appliced to the MetricProperties array property + """ + + status = StatusField("Status") + """The report definition status""" + + metric_properties = base.Field("MetricProperties") + """The report definition metric properties""" + + def __init__(self, connector, identity, redfish_version=None): + """A class representing a MetricReportDefinition + + :param connector: A Connector instance + :param identity: The identity of the MetricReportDefinition resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(MetricReportDefinition, self).__init__( + connector, identity, redfish_version) + + def _get_metrics_path(self): + """Helper function to find the metrics path""" + if 'Metrics' not in self.json: + raise exceptions.MissingAttributeError( + attribute='Metrics', resource=self.path) + + return utils.get_members_identities(self.json.get('Metrics')) + + @property + @utils.cache_it + def metrics(self): + """Property to provide collection to `Metric` + + It is calculated once the first time it is queried. On refresh, + this property is reset. + """ + return [metric.Metric( + self._conn, path, redfish_version=self.redfish_version) + for path in self._get_metrics_path()] + + def delete(self): + """Delete report definition""" + self._conn.delete(self.path) + + +class MetricReportDefinitionCollection(base.ResourceCollectionBase): + + @property + def _resource_type(self): + return MetricReportDefinition + + def __init__(self, connector, path, redfish_version=None): + """A class representing a ReportDefinitionCollection + + :param connector: A Connector instance + :param path: The canonical path to the ReportDefinition collection + resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(MetricReportDefinitionCollection, self).__init__( + connector, path, redfish_version) + + def create_metric_report_definition(self, metric_report_definition_req): + """Create a new report definition + + :param metric_report_definition_req: JSON for event subscription + :returns: The uri of the new event report definition + """ + target_uri = self._path + validate(metric_report_definition_req, + metric_report_definition_schemas.report_definition_req_schema) + + resp = self._conn.post(target_uri, data=metric_report_definition_req) + + report_definition_url = resp.headers['Location'] + LOG.info("report definition created at %s", report_definition_url) + return report_definition_url[report_definition_url.find(self._path):] diff --git a/rsd_lib/resources/v2_2/telemetry/metric_report_definition_schemas.py b/rsd_lib/resources/v2_2/telemetry/metric_report_definition_schemas.py new file mode 100644 index 0000000..1924088 --- /dev/null +++ b/rsd_lib/resources/v2_2/telemetry/metric_report_definition_schemas.py @@ -0,0 +1,60 @@ +# 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. + +report_definition_req_schema = { + 'type': 'object', + 'properties': { + 'Name': {'type': 'string'}, + 'Schedule': { + 'type': 'object', + 'properties': { + 'RecurrenceInterval': {'type': 'string'}, + } + }, + 'MetricReportType': { + 'type': 'string', + 'enum': ['OnChange', 'Periodic', 'OnRequests'] + }, + 'CollectionTimeScope': { + 'type': 'string', + 'enum': ['Point', 'Interval', 'StartupInterval'] + }, + 'ReportActions': { + 'type': 'array', + 'items': { + 'type': 'string', + 'enum': ['Transmit', 'Log'] + } + }, + 'MetricReport': { + 'type': 'object', + 'properties': { + '@odata.id': {'type': 'string'} + } + }, + 'Status': { + 'type': 'object', + 'properties': { + 'State': {'type': 'string'}, + 'Health': {'type': 'string'}, + 'HealthRollup': {'type': 'string'} + } + }, + 'MetricProperties': { + 'type': 'array', + 'items': {'type': 'string'} + } + }, + 'additionalProperties': False +} diff --git a/rsd_lib/resources/v2_2/telemetry/telemetry.py b/rsd_lib/resources/v2_2/telemetry/telemetry.py index 58cda6b..98bee40 100644 --- a/rsd_lib/resources/v2_2/telemetry/telemetry.py +++ b/rsd_lib/resources/v2_2/telemetry/telemetry.py @@ -17,6 +17,7 @@ from sushy.resources import base from sushy import utils from rsd_lib.resources.v2_2.telemetry import metric_definitions +from rsd_lib.resources.v2_2.telemetry import metric_report_definition from rsd_lib import utils as rsd_lib_utils @@ -61,3 +62,19 @@ class Telemetry(base.ResourceBase): return metric_definitions.MetricDefinitionsCollection( self._conn, self._get_metric_definitions_path(), redfish_version=self.redfish_version) + + def _get_metric_report_definitions_path(self): + """Helper function to find the metric report definitions path""" + return utils.get_sub_resource_path_by(self, 'MetricReportDefinitions') + + @property + @utils.cache_it + def metric_report_definitions(self): + """Property to provide reference to `MetricReportDefinitionCollection` + + It is calculated once the first time it is queried. On refresh, + this property is reset. + """ + return metric_report_definition.MetricReportDefinitionCollection( + self._conn, self._get_metric_definitions_path(), + redfish_version=self.redfish_version) diff --git a/rsd_lib/tests/unit/json_samples/v2_2/metric_report_definition.json b/rsd_lib/tests/unit/json_samples/v2_2/metric_report_definition.json new file mode 100644 index 0000000..15289cf --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/v2_2/metric_report_definition.json @@ -0,0 +1,33 @@ +{ + "@odata.context":"/redfish/v1/$metadata#MetricReportDefinition", + "@odata.type":"#MetricReportDefinition.1.0.0.MetricReportDefinition", + "@odata.id":"/redfish/v1/TelemetryService/MetricReportDefinitions/CPU1Metrics", + "Id":"CPUEventPublish", + "Name":"CPU1 Metric Publisher", + "Schedule":{ + "RecurrenceInterval":"PT1M" + }, + "MetricReportType":"Periodic", + "CollectionTimeScope":"Interval", + "ReportActions":[ + "Transmit", + "Log" + ], + "MetricReport":{ + "@odata.id":"/redfish/v1/TelemetryService/MetricReports/TransmitCPU1Metrics" + }, + "Metrics":[ + { + "@odata.id":"/redfish/v1/TelemetryService/FakeMetric" + } + ], + "Status":{ + "State":"Enabled", + "Health":"OK" + }, + "MetricProperties":[ + "/redfish/v1/Systems/System1/Processors/CPU1/Metrics#/BandwidthPercent", + "/redfish/v1/Systems/System1/Processors/CPU1/Metrics#/CPUHealth", + "/redfish/v1/Systems/System1/Processors/CPU1/Metrics#/TemperatureCelsius" + ] +} \ No newline at end of file diff --git a/rsd_lib/tests/unit/json_samples/v2_2/metric_report_definition_collection.json b/rsd_lib/tests/unit/json_samples/v2_2/metric_report_definition_collection.json new file mode 100644 index 0000000..11139c0 --- /dev/null +++ b/rsd_lib/tests/unit/json_samples/v2_2/metric_report_definition_collection.json @@ -0,0 +1,12 @@ +{ + "@odata.context":"/redfish/v1/$metadata#TelemetryService/MetricReportDefinitions/$entity", + "@odata.id":"/redfish/v1/TelemetryService/MetricReportDefinitions", + "@odata.type":"#MetricReportDefinitionCollection.MetricReportDefinitionCollection", + "Name":"MetricReportDefinition Collection", + "Members@odata.count":1, + "Members":[ + { + "@odata.id":"/redfish/v1/TelemetryService/MetricReportDefinitions/CPU1Metrics" + } + ] +} \ No newline at end of file diff --git a/rsd_lib/tests/unit/resources/v2_2/telemetry/test_metric_definitions.py b/rsd_lib/tests/unit/resources/v2_2/telemetry/test_metric_definitions.py index ef1a227..aa7b16d 100644 --- a/rsd_lib/tests/unit/resources/v2_2/telemetry/test_metric_definitions.py +++ b/rsd_lib/tests/unit/resources/v2_2/telemetry/test_metric_definitions.py @@ -38,10 +38,10 @@ class MetricDefinitionTestCase(testtools.TestCase): self.metric_definition_inst._parse_attributes() self.assertEqual('1.1.0', self.metric_definition_inst.redfish_version) - self.assertEqual('memoryTemperature', - self.metric_definition_inst.name) - self.assertEqual('1-md-6', - self.metric_definition_inst.identity) + self.assertEqual('memoryTemperature', self.metric_definition_inst.name) + self.assertEqual('1-md-6', self.metric_definition_inst.identity) + self.assertEqual('Metric Definition description', + self.metric_definition_inst.description) self.assertEqual('Numeric', self.metric_definition_inst.metric_type) self.assertEqual('Temperature', diff --git a/rsd_lib/tests/unit/resources/v2_2/telemetry/test_metric_report_definition.py b/rsd_lib/tests/unit/resources/v2_2/telemetry/test_metric_report_definition.py new file mode 100644 index 0000000..cac77af --- /dev/null +++ b/rsd_lib/tests/unit/resources/v2_2/telemetry/test_metric_report_definition.py @@ -0,0 +1,276 @@ +# 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 jsonschema +import mock +import testtools + +from sushy import exceptions + +from rsd_lib.resources.v2_2.telemetry import metric +from rsd_lib.resources.v2_2.telemetry import metric_report_definition +from rsd_lib.tests.unit.fakes import request_fakes + + +class ReportDefinitionTestCase(testtools.TestCase): + + def setUp(self): + super(ReportDefinitionTestCase, self).setUp() + self.conn = mock.Mock() + with open( + 'rsd_lib/tests/unit/json_samples/v2_2/' + 'metric_report_definition.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.metric_report_definition_inst = metric_report_definition.\ + MetricReportDefinition( + self.conn, + '/redfish/v1/TelemetryService/MetricReportDefinitions/' + 'CPU1Metrics', + redfish_version='1.1.0') + + def test__parse_attributes(self): + self.metric_report_definition_inst._parse_attributes() + self.assertEqual("CPUEventPublish", + self.metric_report_definition_inst.identity) + self.assertEqual("CPU1 Metric Publisher", + self.metric_report_definition_inst.name) + self.assertEqual(None, + self.metric_report_definition_inst.description) + self.assertEqual( + "PT1M", + self.metric_report_definition_inst.schedule.recurrence_interval) + self.assertEqual("Periodic", + self.metric_report_definition_inst.metric_report_type) + self.assertEqual( + "Interval", + self.metric_report_definition_inst.collection_time_scope) + self.assertEqual(['Transmit', 'Log'], + self.metric_report_definition_inst.report_actions) + self.assertEqual( + "Enabled", self.metric_report_definition_inst.status.state) + self.assertEqual( + "OK", self.metric_report_definition_inst.status.health) + self.assertEqual( + None, self.metric_report_definition_inst.status.health_rollup) + self.assertEqual(None, self.metric_report_definition_inst.volatile) + self.assertEqual(None, self.metric_report_definition_inst.wildcards) + self.assertEqual( + [ + "/redfish/v1/Systems/System1/Processors/CPU1/Metrics#" + "/BandwidthPercent", + "/redfish/v1/Systems/System1/Processors/CPU1/Metrics#" + "/CPUHealth", + "/redfish/v1/Systems/System1/Processors/CPU1/Metrics#" + "/TemperatureCelsius" + ], + self.metric_report_definition_inst.metric_properties) + + def test__get_metrics_path(self): + self.assertEqual( + ("/redfish/v1/TelemetryService/FakeMetric",), + self.metric_report_definition_inst._get_metrics_path()) + + def test__get_metrics_path_missing_systems_attr(self): + self.metric_report_definition_inst._json.pop('Metrics') + with self.assertRaisesRegex( + exceptions.MissingAttributeError, 'attribute Metrics'): + self.metric_report_definition_inst._get_metrics_path() + + def test_metrics(self): + # | GIVEN | + self.conn.get.return_value.json.reset_mock() + with open('rsd_lib/tests/unit/json_samples/v2_2/' + 'processor_metrics.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN | + actual_metrics = self.metric_report_definition_inst.metrics + # | THEN | + self.assertIsInstance(actual_metrics, list) + self.assertIsInstance(actual_metrics[0], metric.Metric) + 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_metrics, + self.metric_report_definition_inst.metrics) + self.conn.get.return_value.json.assert_not_called() + + def test_metrics_on_refresh(self): + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/v2_2/' + 'processor_metrics.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance( + self.metric_report_definition_inst.metrics, list) + self.assertIsInstance( + self.metric_report_definition_inst.metrics[0], metric.Metric) + + # On refreshing the telemetry service instance... + with open('rsd_lib/tests/unit/json_samples/v2_2/' + 'metric_report_definition.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.metric_report_definition_inst.invalidate() + self.metric_report_definition_inst.refresh(force=False) + + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/v2_2/' + 'processor_metrics.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance( + self.metric_report_definition_inst.metrics, list) + self.assertIsInstance( + self.metric_report_definition_inst.metrics[0], metric.Metric) + + def test_delete(self): + self.metric_report_definition_inst.delete() + self.metric_report_definition_inst._conn.delete.assert_called_once() + + +class ReportDefinitionCollectionTestCase(testtools.TestCase): + + def setUp(self): + super(ReportDefinitionCollectionTestCase, self).setUp() + self.conn = mock.Mock() + + with open('rsd_lib/tests/unit/json_samples/v2_2/' + 'metric_report_definition_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.conn.post.return_value = request_fakes.fake_request_post( + None, headers={ + "Location": "https://localhost:8443/redfish/v1/" + "TelemetryService/MetricReportDefinitions/1"}) + + self.report_definition_col = metric_report_definition.\ + MetricReportDefinitionCollection( + self.conn, + '/redfish/v1/TelemetryService/MetricReportDefinitions', + redfish_version='1.1.0') + + def test_parse_attributes(self): + self.report_definition_col._parse_attributes() + self.assertEqual("MetricReportDefinition Collection", self. + report_definition_col.name) + + @mock.patch.object( + metric_report_definition, 'MetricReportDefinition', autospec=True) + def test_get_member(self, mock_metric_report_definition): + self.report_definition_col.get_member( + '/redfish/v1/TelemetryService/MetricReportDefinitions/CPU1Metrics') + + mock_metric_report_definition.assert_called_once_with( + self.report_definition_col._conn, + '/redfish/v1/TelemetryService/MetricReportDefinitions/CPU1Metrics', + redfish_version=self.report_definition_col.redfish_version + ) + + @mock.patch.object( + metric_report_definition, 'MetricReportDefinition', autospec=True) + def test_get_members(self, mock_metric_report_definition): + members = self.report_definition_col.get_members() + self.assertEqual(mock_metric_report_definition.call_count, 1) + self.assertIsInstance(members, list) + self.assertEqual(1, len(members)) + + def test_create_report_definition_reqs(self): + reqs = { + 'Name': 'CPU1 Metric Publisher', + 'Schedule': { + 'RecurrenceInterval': 'PT1M' + }, + 'MetricReportType': 'Periodic', + 'CollectionTimeScope': 'Interval', + 'ReportActions': [ + 'Transmit', + 'Log' + ], + 'MetricReport': { + '@odata.id': '/redfish/v1/TelemetryService' + '/MetricReports/TransmitCPU1Metrics' + }, + 'Status': { + 'State': 'Enabled', + 'Health': 'OK' + }, + 'MetricProperties': [ + '/redfish/v1/Systems/System1/Processors/CPU1/Metrics' + '#/BandwidthPercent', + '/redfish/v1/Systems/System1/Processors/CPU1/Metrics' + '#/CPUHealth', + '/redfish/v1/Systems/System1/Processors/CPU1/Metrics' + '#/TemperatureCelsius' + ] + } + + result = self.report_definition_col.create_metric_report_definition( + reqs) + self.report_definition_col._conn.post.assert_called_once_with( + '/redfish/v1/TelemetryService/MetricReportDefinitions', + data=reqs) + self.assertEqual( + result, '/redfish/v1/TelemetryService/MetricReportDefinitions/1') + + def test_create_report_definition_invalid_reqs(self): + reqs = { + 'Name': 'CPU1 Metric Publisher', + 'Schedule': { + 'RecurrenceInterval': 'PT1M' + }, + 'MetricReportType': 'Periodic', + 'CollectionTimeScope': 'Interval', + 'ReportActions': [ + 'Transmit', + 'Log' + ], + 'MetricReport': { + '@odata.id': '/redfish/v1/TelemetryService/MetricReports' + '/TransmitCPU1Metrics' + }, + 'Status': { + 'State': 'Enabled', + 'Health': 'OK' + }, + 'MetricProperties': [ + '/redfish/v1/Systems/System1/Processors/CPU1/Metrics' + '#/BandwidthPercent', + '/redfish/v1/Systems/System1/Processors/CPU1/Metrics' + '#/CPUHealth', + '/redfish/v1/Systems/System1/Processors/CPU1/Metrics' + '#/TemperatureCelsius' + ] + } + + # Wrong format + report_definition_req = reqs.copy() + report_definition_req.update({'ReportActions': True}) + self.assertRaises( + jsonschema.exceptions.ValidationError, + self.report_definition_col.create_metric_report_definition, + report_definition_req) + + # Wrong additional fields + report_definition_req = reqs.copy() + report_definition_req['Additional'] = 'AdditionalField' + self.assertRaises( + jsonschema.exceptions.ValidationError, + self.report_definition_col.create_metric_report_definition, + report_definition_req) diff --git a/rsd_lib/tests/unit/resources/v2_2/telemetry/test_telemetry.py b/rsd_lib/tests/unit/resources/v2_2/telemetry/test_telemetry.py index d2730ad..18abfd7 100644 --- a/rsd_lib/tests/unit/resources/v2_2/telemetry/test_telemetry.py +++ b/rsd_lib/tests/unit/resources/v2_2/telemetry/test_telemetry.py @@ -20,6 +20,7 @@ import testtools from sushy import exceptions from rsd_lib.resources.v2_2.telemetry import metric_definitions +from rsd_lib.resources.v2_2.telemetry import metric_report_definition from rsd_lib.resources.v2_2.telemetry import telemetry @@ -78,7 +79,7 @@ class TelemetryTestCase(testtools.TestCase): self.telemetry_inst.metric_definitions) self.conn.get.return_value.json.assert_not_called() - def test_metrics_on_refresh(self): + def test_metrics_definitions_on_refresh(self): # | GIVEN | with open('rsd_lib/tests/unit/json_samples/v2_2/' 'metric_definitions.json', 'r') as f: @@ -102,3 +103,65 @@ class TelemetryTestCase(testtools.TestCase): # | WHEN & THEN | self.assertIsInstance(self.telemetry_inst.metric_definitions, metric_definitions.MetricDefinitionsCollection) + + def test__get_metric_report_definitions_path_path(self): + self.assertEqual( + '/redfish/v1/TelemetryService/MetricReportDefinitions', + self.telemetry_inst._get_metric_report_definitions_path()) + + def test__get_metric_report_definitions_path_missing_systems_attr(self): + self.telemetry_inst._json.pop('MetricReportDefinitions') + with self.assertRaisesRegex( + exceptions.MissingAttributeError, + 'attribute MetricReportDefinitions'): + self.telemetry_inst._get_metric_report_definitions_path() + + def test_metric_report_definitions(self): + # | GIVEN | + self.conn.get.return_value.json.reset_mock() + with open('rsd_lib/tests/unit/json_samples/v2_2/' + 'metric_report_definition_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN | + actual_report_definitions = \ + self.telemetry_inst.metric_report_definitions + # | THEN | + self.assertIsInstance( + actual_report_definitions, + metric_report_definition.MetricReportDefinitionCollection) + 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_report_definitions, + self.telemetry_inst.metric_report_definitions) + self.conn.get.return_value.json.assert_not_called() + + def test_metrics_report_definitions_on_refresh(self): + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/v2_2/' + 'metric_report_definition_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance( + self.telemetry_inst.metric_report_definitions, + metric_report_definition.MetricReportDefinitionCollection) + + # On refreshing the telemetry service instance... + with open('rsd_lib/tests/unit/json_samples/v2_2/' + 'telemetry_service.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.telemetry_inst.invalidate() + self.telemetry_inst.refresh(force=False) + + # | GIVEN | + with open('rsd_lib/tests/unit/json_samples/v2_2/' + 'metric_report_definition_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + # | WHEN & THEN | + self.assertIsInstance( + self.telemetry_inst.metric_report_definitions, + metric_report_definition.MetricReportDefinitionCollection)