Add missing attribute in MetricReportDefinition in RSD 2.2

Change-Id: Ia69592bd8f360eaa16e67ea821c995f94fdf1086
This commit is contained in:
Lin Yang 2019-05-26 10:57:51 -07:00
parent bb1396772d
commit ebdcb4ca45
2 changed files with 246 additions and 226 deletions

View File

@ -16,130 +16,141 @@
from jsonschema import validate
import logging
from sushy import exceptions
from sushy.resources import base
from sushy import utils
from rsd_lib import common as rsd_lib_common
from rsd_lib import base as rsd_lib_base
from rsd_lib.resources.v2_2.telemetry import metric
from rsd_lib.resources.v2_2.telemetry import metric_report
from rsd_lib.resources.v2_2.telemetry import metric_report_definition_schemas
from rsd_lib import utils as rsd_lib_utils
LOG = logging.getLogger(__name__)
class ScheduleField(base.CompositeField):
recurrence_interval = base.Field("RecurrenceInterval")
"""The schedule recurrence interval"""
"""Schedule field
Schedule a series of occurrences.
"""
class WildcardsField(base.ListField):
name = base.Field("Name")
"""This property shall contain a name for a Wildcard for a key"""
"""The Schedule name."""
lifetime = base.Field("Lifetime")
"""The time after provisioning when the schedule as a whole expires."""
max_occurrences = base.Field(
"MaxOccurrences", adapter=rsd_lib_utils.num_or_none
)
"""Maximum number of scheduled occurrences."""
initial_start_time = base.Field("InitialStartTime")
"""Time for initial occurrence."""
recurrence_interval = base.Field("RecurrenceInterval")
"""Distance until the next occurrences."""
enabled_days_of_week = base.Field("EnabledDaysOfWeek")
"""Days of the week when scheduled occurrences are enabled, for enabled
days of month and months of year.
"""
enabled_days_of_month = base.Field("EnabledDaysOfMonth")
"""Days of month when scheduled occurrences are enabled."""
enabled_months_of_year = base.Field("EnabledMonthsOfYear")
"""Months of year when scheduled occurrences are enabled."""
enabled_intervals = base.Field("EnabledIntervals")
"""Intervals when scheduled occurrences are enabled."""
class WildcardCollectionField(base.ListField):
name = base.Field("Name")
"""The name of Wildcard."""
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
"""An array of Key values to substitute for the wildcard."""
class MetricReportDefinition(rsd_lib_base.ResourceBase):
"""MetricReportDefinition resource class
A set of metrics that are collected periodically.
"""
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.
"""
"""A schedule for collecting metric values."""
metric_report_type = base.Field("MetricReportType")
"""The value shall specify the collection type for the corresponding
metric values
"""
"""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
"""
"""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.
"""This property specifies what action is perform when a metric report is
generated.
"""
volatile = base.Field("Volatile")
volatile = base.Field("Volatile", adapter=bool)
"""Entries in the resulting metric value properties are reused on each
scheduled interval
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 = rsd_lib_base.StatusField("Status")
"""This indicates the known state of the resource, such as if it is
enabled.
"""
status = rsd_lib_common.StatusField('Status')
"""The report definition status"""
wildcards = WildcardCollectionField("Wildcards")
"""Wildcards used to replace values in MetricProperties array property."""
metric_properties = base.Field("MetricProperties")
"""The report definition metric properties"""
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'))
"""A collection of URI that relates to the metric properties that will be
included in the metric report.
"""
@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.
It is calculated once when it is queried for the first time. 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 _get_metric_report_path(self):
"""Helper function to find the metric report path"""
return utils.get_sub_resource_path_by(self, 'MetricReport')
return [
metric.Metric(
self._conn, path, redfish_version=self.redfish_version
)
for path in utils.get_sub_resource_path_by(
self, "Metrics", is_collection=True
)
]
@property
@utils.cache_it
def metric_report(self):
"""Property to provide reference to `MetricReport` instance
It is calculated once the first time it is queried. On refresh,
this property is reset.
It is calculated once when it is queried for the first time. On
refresh, this property is reset.
"""
return metric_report.MetricReport(
self._conn, self._get_metric_report_path(),
redfish_version=self.redfish_version)
self._conn,
utils.get_sub_resource_path_by(self, "MetricReport"),
redfish_version=self.redfish_version,
)
def delete(self):
"""Delete report definition"""
self._conn.delete(self.path)
class MetricReportDefinitionCollection(base.ResourceCollectionBase):
class MetricReportDefinitionCollection(rsd_lib_base.ResourceCollectionBase):
@property
def _resource_type(self):
return MetricReportDefinition
@ -151,11 +162,13 @@ class MetricReportDefinitionCollection(base.ResourceCollectionBase):
: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)
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']
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):]

View File

@ -18,8 +18,6 @@ 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
from rsd_lib.resources.v2_2.telemetry import metric_report_definition
@ -27,46 +25,57 @@ 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:
"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')
"/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(
"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.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.metric_report_definition_inst.collection_time_scope,
)
self.assertEqual(
"Enabled", self.metric_report_definition_inst.status.state)
["Transmit", "Log"],
self.metric_report_definition_inst.report_actions,
)
self.assertEqual(
"OK", self.metric_report_definition_inst.status.health)
"Enabled", self.metric_report_definition_inst.status.state
)
self.assertEqual(
None, self.metric_report_definition_inst.status.health_rollup)
"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(
@ -76,26 +85,18 @@ class ReportDefinitionTestCase(testtools.TestCase):
"/redfish/v1/Systems/System1/Processors/CPU1/Metrics#"
"/CPUHealth",
"/redfish/v1/Systems/System1/Processors/CPU1/Metrics#"
"/TemperatureCelsius"
"/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_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()
self.metric_report_definition_inst.metric_properties,
)
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:
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
@ -108,144 +109,162 @@ class ReportDefinitionTestCase(testtools.TestCase):
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.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:
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, list)
self.assertIsInstance(
self.metric_report_definition_inst.metrics[0], metric.Metric)
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:
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:
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, list)
self.assertIsInstance(
self.metric_report_definition_inst.metrics[0], metric.Metric)
def test__get_metric_report_path_path(self):
self.assertEqual(
'/redfish/v1/TelemetryService/MetricReports/TransmitCPU1Metrics',
self.metric_report_definition_inst._get_metric_report_path())
def test__get_metric_report_path_missing_attr(self):
self.metric_report_definition_inst._json.pop('MetricReport')
with self.assertRaisesRegex(exceptions.MissingAttributeError,
'attribute MetricReport'):
self.metric_report_definition_inst._get_metric_report_path()
self.metric_report_definition_inst.metrics[0], metric.Metric
)
def test_metric_report(self):
# | GIVEN |
self.conn.get.return_value.json.reset_mock()
with open('rsd_lib/tests/unit/json_samples/v2_2/'
'metric_report.json', 'r') as f:
with open(
"rsd_lib/tests/unit/json_samples/v2_2/metric_report.json", "r"
) as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
# | WHEN |
actual_metric_report = self.metric_report_definition_inst.metric_report
# | THEN |
self.assertIsInstance(
actual_metric_report, metric_report.MetricReport)
self.assertIsInstance(actual_metric_report, metric_report.MetricReport)
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_metric_report,
self.metric_report_definition_inst.metric_report)
self.assertIs(
actual_metric_report,
self.metric_report_definition_inst.metric_report,
)
self.conn.get.return_value.json.assert_not_called()
def test_metric_report_on_refresh(self):
# | GIVEN |
with open('rsd_lib/tests/unit/json_samples/v2_2/'
'metric_report.json', 'r') as f:
with open(
"rsd_lib/tests/unit/json_samples/v2_2/metric_report.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.metric_report,
metric_report.MetricReport)
metric_report.MetricReport,
)
# On refreshing the telemetry service instance...
with open('rsd_lib/tests/unit/json_samples/v2_2/'
'telemetry_service.json', 'r') as f:
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.metric_report_definition_inst.invalidate()
self.metric_report_definition_inst.refresh(force=False)
# | GIVEN |
with open('rsd_lib/tests/unit/json_samples/v2_2/'
'metric_report.json', 'r') as f:
with open(
"rsd_lib/tests/unit/json_samples/v2_2/metric_report.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.metric_report,
metric_report.MetricReport)
metric_report.MetricReport,
)
def test_delete(self):
self.metric_report_definition_inst.delete()
self.metric_report_definition_inst._conn.delete.\
assert_called_once_with(self.metric_report_definition_inst.path)
assert_called_once_with(
self.metric_report_definition_inst.path
)
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:
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={
None,
headers={
"Location": "https://localhost:8443/redfish/v1/"
"TelemetryService/MetricReportDefinitions/1"})
"TelemetryService/MetricReportDefinitions/1"
},
)
self.report_definition_col = metric_report_definition.\
MetricReportDefinitionCollection(
self.conn,
'/redfish/v1/TelemetryService/MetricReportDefinitions',
redfish_version='1.1.0')
"/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
self.assertEqual(
"MetricReportDefinition Collection",
self.report_definition_col.name,
)
@mock.patch.object(
metric_report_definition, 'MetricReportDefinition', autospec=True)
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)
@ -254,84 +273,72 @@ class ReportDefinitionCollectionTestCase(testtools.TestCase):
def test_create_report_definition_reqs(self):
reqs = {
'Name': 'CPU1 Metric Publisher',
'Schedule': {
'RecurrenceInterval': 'PT1M'
"Name": "CPU1 Metric Publisher",
"Schedule": {"RecurrenceInterval": "PT1M"},
"MetricReportType": "Periodic",
"CollectionTimeScope": "Interval",
"ReportActions": ["Transmit", "Log"],
"MetricReport": {
"@odata.id": "/redfish/v1/TelemetryService"
"/MetricReports/TransmitCPU1Metrics"
},
'MetricReportType': 'Periodic',
'CollectionTimeScope': 'Interval',
'ReportActions': [
'Transmit',
'Log'
"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",
],
'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)
reqs
)
self.report_definition_col._conn.post.assert_called_once_with(
'/redfish/v1/TelemetryService/MetricReportDefinitions',
data=reqs)
"/redfish/v1/TelemetryService/MetricReportDefinitions", data=reqs
)
self.assertEqual(
result, '/redfish/v1/TelemetryService/MetricReportDefinitions/1')
result, "/redfish/v1/TelemetryService/MetricReportDefinitions/1"
)
def test_create_report_definition_invalid_reqs(self):
reqs = {
'Name': 'CPU1 Metric Publisher',
'Schedule': {
'RecurrenceInterval': 'PT1M'
"Name": "CPU1 Metric Publisher",
"Schedule": {"RecurrenceInterval": "PT1M"},
"MetricReportType": "Periodic",
"CollectionTimeScope": "Interval",
"ReportActions": ["Transmit", "Log"],
"MetricReport": {
"@odata.id": "/redfish/v1/TelemetryService/MetricReports"
"/TransmitCPU1Metrics"
},
'MetricReportType': 'Periodic',
'CollectionTimeScope': 'Interval',
'ReportActions': [
'Transmit',
'Log'
"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",
],
'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})
report_definition_req.update({"ReportActions": True})
self.assertRaises(
jsonschema.exceptions.ValidationError,
self.report_definition_col.create_metric_report_definition,
report_definition_req)
report_definition_req,
)
# Wrong additional fields
report_definition_req = reqs.copy()
report_definition_req['Additional'] = 'AdditionalField'
report_definition_req["Additional"] = "AdditionalField"
self.assertRaises(
jsonschema.exceptions.ValidationError,
self.report_definition_col.create_metric_report_definition,
report_definition_req)
report_definition_req,
)