From 348ce157b16be843a6b9e0485536b1f80e3bfe40 Mon Sep 17 00:00:00 2001 From: Andreas Maier Date: Tue, 16 May 2023 06:40:04 +0200 Subject: [PATCH] Added new z16 get sustainability data methods Details: * Added support for getting new z16 environmental metrics about CPC and LPAR or partitions by adding 'get_sustainability_data()' methods to Cpc, Lpar, and Partition. * Added an example script get_sustainability_data.py that exercises the new methods for one CPC and one LPAR/partition. * Added end2end testcases for get_sustainability_data() for Cpc, Lpar, and Partition. Signed-off-by: Andreas Maier --- changes/1511.feature.rst | 3 + examples/get_sustainability_data.py | 110 +++++++++++++++++++ tests/end2end/test_cpc.py | 146 +++++++++++++++++++++++++ tests/end2end/test_lpar.py | 158 ++++++++++++++++++++++++++++ tests/end2end/test_partition.py | 158 ++++++++++++++++++++++++++++ zhmcclient/_cpc.py | 72 +++++++++++++ zhmcclient/_lpar.py | 74 ++++++++++++- zhmcclient/_partition.py | 74 ++++++++++++- 8 files changed, 793 insertions(+), 2 deletions(-) create mode 100644 changes/1511.feature.rst create mode 100755 examples/get_sustainability_data.py diff --git a/changes/1511.feature.rst b/changes/1511.feature.rst new file mode 100644 index 00000000..3a98781a --- /dev/null +++ b/changes/1511.feature.rst @@ -0,0 +1,3 @@ +Added support for getting new z16 environmental metrics about CPC and LPAR +or partitions by adding 'get_sustainability_data()' methods to Cpc, Lpar, +and Partition. diff --git a/examples/get_sustainability_data.py b/examples/get_sustainability_data.py new file mode 100755 index 00000000..f67ef49d --- /dev/null +++ b/examples/get_sustainability_data.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# Copyright 2023 IBM Corp. 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. + +""" +Example that gets the sustainability data of a CPC. +""" + +import sys +import requests.packages.urllib3 +from pprint import pprint + +import zhmcclient +from zhmcclient.testutils import hmc_definitions + +requests.packages.urllib3.disable_warnings() + +# Get HMC info from HMC inventory and vault files +hmc_def = hmc_definitions()[0] +nickname = hmc_def.nickname +host = hmc_def.host +userid = hmc_def.userid +password = hmc_def.password +verify_cert = hmc_def.verify_cert + +range = "last-day" +resolution = "fifteen-minutes" + +print(__doc__) + +print("Using HMC {} at {} with userid {} ...".format(nickname, host, userid)) + +print("Creating a session with the HMC ...") +try: + session = zhmcclient.Session( + host, userid, password, verify_cert=verify_cert) +except zhmcclient.Error as exc: + print("Error: Cannot establish session with HMC {}: {}: {}". + format(host, exc.__class__.__name__, exc)) + sys.exit(1) + +try: + client = zhmcclient.Client(session) + format_str = "{:<8} {:<6} {:<7} {:<16}" + rc = 0 + + cpcs = client.cpcs.list() + cpc = cpcs[0] + print('') + print('Getting sustainability metrics on CPC: {}'.format(cpc.name)) + print('Range: {}'.format(range)) + print('Resolution: {}'.format(resolution)) + try: + data = cpc.get_sustainability_data( + range=range, resolution=resolution) + except zhmcclient.Error as exc: + print("Error: {}".format(exc)) + rc = 1 + else: + print('') + print('CPC sustainability metrics:') + for metric_name, metric_array in data.items(): + print("{}:".format(metric_name)) + for dp in metric_array: + print(" {}: {}".format(dp['timestamp'], dp['data'])) + + if cpc.dpm_enabled: + parts = cpc.partitions.list() + part_str = "Partition" + else: + parts = cpc.lpars.list() + part_str = "LPAR" + part = parts[0] + print('') + print('Getting sustainability metrics on {}: {}'. + format(part_str, part.name)) + print('Range: {}'.format(range)) + print('Resolution: {}'.format(resolution)) + try: + data = part.get_sustainability_data( + range=range, resolution=resolution) + except zhmcclient.Error as exc: + print("Error: {}".format(exc)) + rc = 1 + else: + print('') + print('{} sustainability metrics:'.format(part_str)) + for metric_name, metric_array in data.items(): + print("{}:".format(metric_name)) + for dp in metric_array: + print(" {}: {}".format(dp['timestamp'], dp['data'])) + + if rc != 0: + print("Error happened - see above") + sys.exit(rc) + +finally: + print("Logging off ...") + session.logoff() diff --git a/tests/end2end/test_cpc.py b/tests/end2end/test_cpc.py index 95928460..c4b1406e 100644 --- a/tests/end2end/test_cpc.py +++ b/tests/end2end/test_cpc.py @@ -20,6 +20,7 @@ from __future__ import absolute_import, print_function +from datetime import timedelta, datetime, timezone import pytest from requests.packages import urllib3 import zhmcclient @@ -300,6 +301,151 @@ def test_cpc_export_dpm_config(dpm_mode_cpcs): # noqa: F811 cert_props['certificate'] cert.delete() + +TESTCASES_CPC_GET_SUSTAINABILITY_DATA = [ + # Test cases for test_cpc_get_sustainability_data(), each as a tuple with + # these items: + # * tc: Testcase short description + # * input_kwargs: kwargs to be used as input parameters for + # Cpc.get_sustainability_data() + # * exp_oldest: expected delta time from now to oldest data point, + # as timedelta + # * exp_delta: expected delta time between data points, as timedelta + ( + "Default values (range=last-week, resolution=one-hour)", + {}, + timedelta(days=7), + timedelta(hours=1), + ), + ( + "range=last-day, resolution=one-hour", + { + "range": "last-day", + "resolution": "one-hour", + }, + timedelta(hours=24), + timedelta(hours=1), + ), + ( + "range=last-day, resolution=fifteen-minutes", + { + "range": "last-day", + "resolution": "fifteen-minutes", + }, + timedelta(hours=24), + timedelta(minutes=15), + ), +] + +CPC_METRICS = { + # Metrics returned in "Get CPC Historical Sustainability Data" HMC + # operation. + # metric name: data type + "total-wattage": int, + "partition-wattage": int, + "infrastructure-wattage": int, + "unassigned-wattage": int, + "heat-load": int, + "heat-load-forced-air": int, + "processor-utilization": int, + "ambient-temperature": float, + "exhaust-heat-temperature": float, + "dew-point": float, + "ambient-humidity": int, +} + + +@pytest.mark.parametrize( + "tc, input_kwargs, exp_oldest, exp_delta", + TESTCASES_CPC_GET_SUSTAINABILITY_DATA) +def test_cpc_get_sustainability_data( + tc, input_kwargs, exp_oldest, exp_delta, all_cpcs): # noqa: F811 + # pylint: disable=redefined-outer-name,unused-argument + """ + Test for Cpc.get_sustainability_data(...) + """ + for cpc in all_cpcs: + + session = cpc.manager.session + hd = session.hmc_definition + + print("Testing with CPC {c}".format(c=cpc.name)) + + try: + + # The code to be tested + data = cpc.get_sustainability_data(**input_kwargs) + + except zhmcclient.HTTPError as exc: + if exc.http_status == 403 and exc.reason == 1: + skip_warn("HMC userid {u!r} is not authorized for task " + "'Environmental Dashboard' on HMC {h}". + format(u=hd.userid, h=hd.host)) + elif exc.http_status == 404 and exc.reason == 1: + skip_warn("CPC {c} on HMC {h} does not support feature: {e}". + format(c=cpc.name, h=hd.host, e=exc)) + else: + raise + + now_dt = datetime.now(timezone.utc) + exp_oldest_dt = now_dt - exp_oldest + + act_metric_names = set(data.keys()) + exp_metric_names = set(CPC_METRICS.keys()) + assert act_metric_names == exp_metric_names + + for metric_name, metric_array in data.items(): + metric_type = CPC_METRICS[metric_name] + + first_item = True + previous_dt = None + for dp_item in metric_array: + # We assume the order is oldest to newest + + assert 'data' in dp_item + assert 'timestamp' in dp_item + assert len(dp_item) == 2 + + dp_data = dp_item['data'] + dp_timestamp_dt = dp_item['timestamp'] + + assert isinstance(dp_data, metric_type), \ + "Invalid data type for metric {!r}".format(metric_name) + + if first_item: + first_item = False + + # Verify that the oldest timestamp is within a certain + # delta from the range start. + # There are cases where that is not satisfied, so we only + # issue only a warning (as opposed to failing). + delta_sec = abs((dp_timestamp_dt - exp_oldest_dt).seconds) + if delta_sec > 15 * 60: + print("Warning: Oldest data point of metric {!r} is " + "not within 15 minutes of range start: Oldest " + "data point: {}, Range start: {}, Delta: {} sec". + format(metric_name, dp_timestamp_dt, + exp_oldest_dt, delta_sec)) + else: + + # For second oldest timestamp on, verify that the delta + # to the previous data point is the requested resolution. + # There are cases where that is not satisfied, so we only + # issue only a warning (as opposed to failing). + tolerance_pct = 10 + delta_td = abs(dp_timestamp_dt - previous_dt) + if abs(delta_td.seconds - exp_delta.seconds) > \ + tolerance_pct / 100 * exp_delta.seconds: + print("Warning: Timestamp of a data point of metric " + "{!r} is not within expected delta of its " + "previous data point. Actual delta: {}, " + "Expected delta: {} (+/-{}%)". + format(metric_name, delta_td, exp_delta, + tolerance_pct)) + + previous_dt = dp_timestamp_dt + + # Read-only tests: # TODO: Test for get_wwpns(partitions) # TODO: Test for get_free_crypto_domains(crypto_adapters=None) diff --git a/tests/end2end/test_lpar.py b/tests/end2end/test_lpar.py index 04defa8d..7772e7d7 100644 --- a/tests/end2end/test_lpar.py +++ b/tests/end2end/test_lpar.py @@ -22,6 +22,7 @@ from __future__ import absolute_import, print_function import random +from datetime import timedelta, datetime, timezone import pdb import pytest from requests.packages import urllib3 @@ -749,3 +750,160 @@ def test_lpar_activate( logger.info("Cleanup: Setting 'load-at-activation' = %r in image " "profile %r", saved_auto_load, iap.name) set_resource_property(iap, 'load-at-activation', saved_auto_load) + + +TESTCASES_LPAR_GET_SUSTAINABILITY_DATA = [ + # Test cases for test_lpar_get_sustainability_data(), each as a tuple with + # these items: + # * tc: Testcase short description + # * input_kwargs: kwargs to be used as input parameters for + # Lpar.get_sustainability_data() + # * exp_oldest: expected delta time from now to oldest data point, + # as timedelta + # * exp_delta: expected delta time between data points, as timedelta + ( + "Default values (range=last-week, resolution=one-hour)", + {}, + timedelta(days=7), + timedelta(hours=1), + ), + ( + "range=last-day, resolution=one-hour", + { + "range": "last-day", + "resolution": "one-hour", + }, + timedelta(hours=24), + timedelta(hours=1), + ), + ( + "range=last-day, resolution=fifteen-minutes", + { + "range": "last-day", + "resolution": "fifteen-minutes", + }, + timedelta(hours=24), + timedelta(minutes=15), + ), +] + +LPAR_METRICS = { + # Metrics returned in "Get LPAR Historical Sustainability Data" HMC + # operation. + # metric name: data type + "wattage": int, + "processor-utilization": int, +} + + +@pytest.mark.parametrize( + "tc, input_kwargs, exp_oldest, exp_delta", + TESTCASES_LPAR_GET_SUSTAINABILITY_DATA) +def test_lpar_get_sustainability_data( + tc, input_kwargs, exp_oldest, exp_delta, + classic_mode_cpcs): # noqa: F811 + # pylint: disable=redefined-outer-name,unused-argument + """ + Test for Lpar.get_sustainability_data(...) + """ + if not classic_mode_cpcs: + pytest.skip("HMC definition does not include any CPCs in classic mode") + + for cpc in classic_mode_cpcs: + assert not cpc.dpm_enabled + + session = cpc.manager.session + hd = session.hmc_definition + + if hd.mock_file: + skip_warn("zhmcclient mock does not support " + "Lpar.get_sustainability_data()") + + # Pick the LPAR to test with + lpar_list = cpc.lpars.list() + if not lpar_list: + skip_warn("No LPARs on CPC {c} managed by HMC {h}". + format(c=cpc.name, h=hd.host)) + + # Pick a random LPAR to test with + lpar = random.choice(lpar_list) + + session = lpar.manager.session + hd = session.hmc_definition + + print("Testing with LPAR {n}".format(n=lpar.name)) + + try: + + # The code to be tested + data = lpar.get_sustainability_data(**input_kwargs) + + except zhmcclient.HTTPError as exc: + if exc.http_status == 403 and exc.reason == 1: + skip_warn("HMC userid {u!r} is not authorized for task " + "'Environmental Dashboard' on HMC {h}". + format(u=hd.userid, h=hd.host)) + elif exc.http_status == 404 and exc.reason == 1: + skip_warn("LPAR {c} on HMC {h} does not support " + "feature: {e}". + format(c=lpar.name, h=hd.host, e=exc)) + else: + raise + + now_dt = datetime.now(timezone.utc) + exp_oldest_dt = now_dt - exp_oldest + + act_metric_names = set(data.keys()) + exp_metric_names = set(LPAR_METRICS.keys()) + assert act_metric_names == exp_metric_names + + for metric_name, metric_array in data.items(): + metric_type = LPAR_METRICS[metric_name] + + first_item = True + previous_dt = None + for dp_item in metric_array: + # We assume the order is oldest to newest + + assert 'data' in dp_item + assert 'timestamp' in dp_item + assert len(dp_item) == 2 + + dp_data = dp_item['data'] + dp_timestamp_dt = dp_item['timestamp'] + + assert isinstance(dp_data, metric_type), \ + "Invalid data type for metric {!r}".format(metric_name) + + if first_item: + first_item = False + + # Verify that the oldest timestamp is within a certain + # delta from the range start. + # There are cases where that is not satisfied, so we only + # issue only a warning (as opposed to failing). + delta_sec = abs((dp_timestamp_dt - exp_oldest_dt).seconds) + if delta_sec > 15 * 60: + print("Warning: Oldest data point of metric {!r} is " + "not within 15 minutes of range start: Oldest " + "data point: {}, Range start: {}, Delta: {} sec". + format(metric_name, dp_timestamp_dt, + exp_oldest_dt, delta_sec)) + else: + + # For second oldest timestamp on, verify that the delta + # to the previous data point is the requested resolution. + # There are cases where that is not satisfied, so we only + # issue only a warning (as opposed to failing). + tolerance_pct = 10 + delta_td = abs(dp_timestamp_dt - previous_dt) + if abs(delta_td.seconds - exp_delta.seconds) > \ + tolerance_pct / 100 * exp_delta.seconds: + print("Warning: Timestamp of a data point of metric " + "{!r} is not within expected delta of its " + "previous data point. Actual delta: {}, " + "Expected delta: {} (+/-{}%)". + format(metric_name, delta_td, exp_delta, + tolerance_pct)) + + previous_dt = dp_timestamp_dt diff --git a/tests/end2end/test_partition.py b/tests/end2end/test_partition.py index 9662fe64..f7cc25ed 100644 --- a/tests/end2end/test_partition.py +++ b/tests/end2end/test_partition.py @@ -23,6 +23,7 @@ import warnings import random +from datetime import timedelta, datetime, timezone import pytest from requests.packages import urllib3 @@ -401,3 +402,160 @@ def test_console_list_permitted_partitions(desc, input_kwargs, exp_props, filter_args={'name': permitted_part_list[0].name, 'cpc-name': 'bad-cpc'})) assert len(permitted_partitions_filter_agrs) == 0 + + +TESTCASES_PART_GET_SUSTAINABILITY_DATA = [ + # Test cases for test_part_get_sustainability_data(), each as a tuple with + # these items: + # * tc: Testcase short description + # * input_kwargs: kwargs to be used as input parameters for + # Partition.get_sustainability_data() + # * exp_oldest: expected delta time from now to oldest data point, + # as timedelta + # * exp_delta: expected delta time between data points, as timedelta + ( + "Default values (range=last-week, resolution=one-hour)", + {}, + timedelta(days=7), + timedelta(hours=1), + ), + ( + "range=last-day, resolution=one-hour", + { + "range": "last-day", + "resolution": "one-hour", + }, + timedelta(hours=24), + timedelta(hours=1), + ), + ( + "range=last-day, resolution=fifteen-minutes", + { + "range": "last-day", + "resolution": "fifteen-minutes", + }, + timedelta(hours=24), + timedelta(minutes=15), + ), +] + +PART_METRICS = { + # Metrics returned in "Get Partition Historical Sustainability Data" HMC + # operation. + # metric name: data type + "wattage": int, + "processor-utilization": int, +} + + +@pytest.mark.parametrize( + "tc, input_kwargs, exp_oldest, exp_delta", + TESTCASES_PART_GET_SUSTAINABILITY_DATA) +def test_part_get_sustainability_data( + tc, input_kwargs, exp_oldest, exp_delta, + dpm_mode_cpcs): # noqa: F811 + # pylint: disable=redefined-outer-name,unused-argument + """ + Test for Partition.get_sustainability_data(...) + """ + if not dpm_mode_cpcs: + pytest.skip("HMC definition does not include any CPCs in DPM mode") + + for cpc in dpm_mode_cpcs: + assert cpc.dpm_enabled + + session = cpc.manager.session + hd = session.hmc_definition + + if hd.mock_file: + skip_warn("zhmcclient mock does not support " + "Partition.get_sustainability_data()") + + # Pick the partition to test with + part_list = cpc.partitions.list() + if not part_list: + skip_warn("No partitions on CPC {c} managed by HMC {h}". + format(c=cpc.name, h=hd.host)) + + # Pick a random partition to test with + part = random.choice(part_list) + + session = part.manager.session + hd = session.hmc_definition + + print("Testing with partition {n}".format(n=part.name)) + + try: + + # The code to be tested + data = part.get_sustainability_data(**input_kwargs) + + except zhmcclient.HTTPError as exc: + if exc.http_status == 403 and exc.reason == 1: + skip_warn("HMC userid {u!r} is not authorized for task " + "'Environmental Dashboard' on HMC {h}". + format(u=hd.userid, h=hd.host)) + elif exc.http_status == 404 and exc.reason == 1: + skip_warn("Partition {c} on HMC {h} does not support " + "feature: {e}". + format(c=part.name, h=hd.host, e=exc)) + else: + raise + + now_dt = datetime.now(timezone.utc) + exp_oldest_dt = now_dt - exp_oldest + + act_metric_names = set(data.keys()) + exp_metric_names = set(PART_METRICS.keys()) + assert act_metric_names == exp_metric_names + + for metric_name, metric_array in data.items(): + metric_type = PART_METRICS[metric_name] + + first_item = True + previous_dt = None + for dp_item in metric_array: + # We assume the order is oldest to newest + + assert 'data' in dp_item + assert 'timestamp' in dp_item + assert len(dp_item) == 2 + + dp_data = dp_item['data'] + dp_timestamp_dt = dp_item['timestamp'] + + assert isinstance(dp_data, metric_type), \ + "Invalid data type for metric {!r}".format(metric_name) + + if first_item: + first_item = False + + # Verify that the oldest timestamp is within a certain + # delta from the range start. + # There are cases where that is not satisfied, so we only + # issue only a warning (as opposed to failing). + delta_sec = abs((dp_timestamp_dt - exp_oldest_dt).seconds) + if delta_sec > 15 * 60: + print("Warning: Oldest data point of metric {!r} is " + "not within 15 minutes of range start: Oldest " + "data point: {}, Range start: {}, Delta: {} sec". + format(metric_name, dp_timestamp_dt, + exp_oldest_dt, delta_sec)) + else: + + # For second oldest timestamp on, verify that the delta + # to the previous data point is the requested resolution. + # There are cases where that is not satisfied, so we only + # issue only a warning (as opposed to failing). + tolerance_pct = 10 + delta_td = abs(dp_timestamp_dt - previous_dt) + if abs(delta_td.seconds - exp_delta.seconds) > \ + tolerance_pct / 100 * exp_delta.seconds: + print("Warning: Timestamp of a data point of metric " + "{!r} is not within expected delta of its " + "previous data point. Actual delta: {}, " + "Expected delta: {} (+/-{}%)". + format(metric_name, delta_td, exp_delta, + tolerance_pct)) + + previous_dt = dp_timestamp_dt diff --git a/zhmcclient/_cpc.py b/zhmcclient/_cpc.py index 476361d8..d775de65 100644 --- a/zhmcclient/_cpc.py +++ b/zhmcclient/_cpc.py @@ -68,6 +68,7 @@ from ._logging import logged_api_call from ._exceptions import ParseError, ConsistencyError from ._utils import get_features, \ + datetime_from_timestamp, timestamp_from_datetime, \ RC_CPC, RC_ADAPTER, RC_HBA, RC_NIC, RC_PARTITION, \ RC_NETWORK_PORT, RC_STORAGE_PORT, RC_STORAGE_TEMPLATE, RC_STORAGE_GROUP, \ RC_STORAGE_TEMPLATE_VOLUME, RC_STORAGE_VOLUME, RC_VIRTUAL_FUNCTION, \ @@ -2833,6 +2834,77 @@ def _convert_to_config(self, inventory_list, include_unused_adapters): return config_dict + @logged_api_call + def get_sustainability_data( + self, range="last-week", resolution="one-hour", + custom_range_start=None, custom_range_end=None): + # pylint: disable=redefined-builtin + """ + Get energy management related metrics for the CPC on a specific + historical time range. The metrics are returned as multiple data points + covering the requested time range with the requested resolution. + This method performs the "Get CPC Historical Sustainability Data" HMC + operation. + Authorization requirements: + * Object-access permission to this CPC + * Task permission to the "Environmental Dashboard" task + Parameters: + range (:term:`string`): + Time range for the requested data points, as follows: + * "last-day" - Last 24 hours. + * "last-week" - Last 7 days (default). + * "last-month" - Last 30 days. + * "last-three-months" - Last 90 days. + * "last-six-months" - Last 180 days. + * "last-year" - Last 365 days. + * "custom" - From `custom_range_start` to `custom_range_end`. + resolution (:term:`string`): + Resolution for the requested data points. This is the time interval + in between the data points. For systems where the + "environmental-metrics" feature is not available, the minimum + resolution is "one-hour". + The possible values are as follows: + * "fifteen-minutes" - 15 minutes. + * "one-hour" - 60 minutes (default). + * "one-day" - 24 hours. + * "one-week" - 7 days. + * "one-month" - 30 days. + custom_range_start (:class:`~py:datetime.datetime`): + Start of custom time range. Timezone-naive values are interpreted + using the local system time. Required if `range` is "custom". + custom_range_end (:class:`~py:datetime.datetime`): + End of custom time range. Timezone-naive values are interpreted + using the local system time. Required if `range` is "custom". + Returns: + dict: A dictionary with items as described for the response body + of the "Get CPC Historical Sustainability Data" HMC operation. + Timestamp fields are represented as timezone-aware + :class:`~py:datetime.datetime` objects. + Raises: + :exc:`~zhmcclient.HTTPError` + :exc:`~zhmcclient.ParseError` + :exc:`~zhmcclient.AuthError` + :exc:`~zhmcclient.ConnectionError` + """ + body = { + 'range': range, + 'resolution': resolution, + } + if range == "custom": + body['custom-range-start'] = \ + timestamp_from_datetime(custom_range_start) + body['custom-range-end'] = \ + timestamp_from_datetime(custom_range_end) + result = self.manager.session.post( + self.uri + '/operations/get-historical-sustainability-data', + body=body) + for field_array in result.values(): + for item in field_array: + if 'timestamp' in item: + item['timestamp'] = \ + datetime_from_timestamp(item['timestamp']) + return result + # Functions used by Cpc.export_dpm_configuration(). # Some of these functions were adapted from code in the diff --git a/zhmcclient/_lpar.py b/zhmcclient/_lpar.py index af389b14..aeb8b379 100644 --- a/zhmcclient/_lpar.py +++ b/zhmcclient/_lpar.py @@ -36,7 +36,7 @@ from ._constants import HMC_LOGGER_NAME from ._logging import get_logger, logged_api_call from ._utils import RC_LOGICAL_PARTITION, make_query_str, \ - warn_deprecated_parameter + warn_deprecated_parameter, datetime_from_timestamp, timestamp_from_datetime __all__ = ['LparManager', 'Lpar'] @@ -2165,3 +2165,75 @@ def unassign_certificate(self, certificate): self.manager.session.post( self.uri + '/operations/unassign-certificate', resource=self, body=body) + + @logged_api_call + def get_sustainability_data( + self, range="last-week", resolution="one-hour", + custom_range_start=None, custom_range_end=None): + # pylint: disable=redefined-builtin + """ + Get energy management related metrics for the LPAR on a specific + historical time range. The metrics are returned as multiple data points + covering the requested time range with the requested resolution. + This method performs the "Get LPAR Historical Sustainability Data" + HMC operation. + Authorization requirements: + * Object-access permission to this LPAR + * Task permission to the "Environmental Dashboard" task + Parameters: + range (:term:`string`): + Time range for the requested data points, as follows: + * "last-day" - Last 24 hours. + * "last-week" - Last 7 days (default). + * "last-month" - Last 30 days. + * "last-three-months" - Last 90 days. + * "last-six-months" - Last 180 days. + * "last-year" - Last 365 days. + * "custom" - From `custom_range_start` to `custom_range_end`. + resolution (:term:`string`): + Resolution for the requested data points. This is the time interval + in between the data points. For systems where the + "environmental-metrics" feature is not available, the minimum + resolution is "one-hour". + The possible values are as follows: + * "fifteen-minutes" - 15 minutes. + * "one-hour" - 60 minutes (default). + * "one-day" - 24 hours. + * "one-week" - 7 days. + * "one-month" - 30 days. + custom_range_start (:class:`~py:datetime.datetime`): + Start of custom time range. Timezone-naive values are interpreted + using the local system time. Required if `range` is "custom". + custom_range_end (:class:`~py:datetime.datetime`): + End of custom time range. Timezone-naive values are interpreted + using the local system time. Required if `range` is "custom". + Returns: + dict: A dictionary with items as described for the response body + of the "Get LPAR Historical Sustainability Data" HMC operation. + Timestamp fields are represented as timezone-aware + :class:`~py:datetime.datetime` objects. + Raises: + :exc:`~zhmcclient.HTTPError` + :exc:`~zhmcclient.ParseError` + :exc:`~zhmcclient.AuthError` + :exc:`~zhmcclient.ConnectionError` + """ + + body = { + 'range': range, + 'resolution': resolution, + } + if range == "custom": + body['custom-range-start'] = \ + timestamp_from_datetime(custom_range_start) + body['custom-range-end'] = \ + timestamp_from_datetime(custom_range_end) + result = self.manager.session.post( + self.uri + '/operations/get-historical-sustainability-data', + body=body) + for field_array in result.values(): + for item in field_array: + if 'timestamp' in item: + item['timestamp'] = \ + datetime_from_timestamp(item['timestamp']) + return result diff --git a/zhmcclient/_partition.py b/zhmcclient/_partition.py index d5177bcd..c09991e7 100644 --- a/zhmcclient/_partition.py +++ b/zhmcclient/_partition.py @@ -42,7 +42,8 @@ from ._hba import HbaManager from ._virtual_function import VirtualFunctionManager from ._logging import logged_api_call -from ._utils import RC_PARTITION, make_query_str +from ._utils import RC_PARTITION, make_query_str, datetime_from_timestamp, \ + timestamp_from_datetime __all__ = ['PartitionManager', 'Partition'] @@ -1492,3 +1493,74 @@ def dump(self): resource_dict['virtual_functions'] = virtual_functions return resource_dict + + @logged_api_call + def get_sustainability_data( + self, range="last-week", resolution="one-hour", + custom_range_start=None, custom_range_end=None): + # pylint: disable=redefined-builtin + """ + Get energy management related metrics for the partition on a specific + historical time range. The metrics are returned as multiple data points + covering the requested time range with the requested resolution. + This method performs the "Get Partition Historical Sustainability Data" + HMC operation. + Authorization requirements: + * Object-access permission to this partition + * Task permission to the "Environmental Dashboard" task + Parameters: + range (:term:`string`): + Time range for the requested data points, as follows: + * "last-day" - Last 24 hours. + * "last-week" - Last 7 days (default). + * "last-month" - Last 30 days. + * "last-three-months" - Last 90 days. + * "last-six-months" - Last 180 days. + * "last-year" - Last 365 days. + * "custom" - From `custom_range_start` to `custom_range_end`. + resolution (:term:`string`): + Resolution for the requested data points. This is the time interval + in between the data points. For systems where the + "environmental-metrics" feature is not available, the minimum + resolution is "one-hour". + The possible values are as follows: + * "fifteen-minutes" - 15 minutes. + * "one-hour" - 60 minutes (default). + * "one-day" - 24 hours. + * "one-week" - 7 days. + * "one-month" - 30 days. + custom_range_start (:class:`~py:datetime.datetime`): + Start of custom time range. Timezone-naive values are interpreted + using the local system time. Required if `range` is "custom". + custom_range_end (:class:`~py:datetime.datetime`): + End of custom time range. Timezone-naive values are interpreted + using the local system time. Required if `range` is "custom". + Returns: + dict: A dictionary with items as described for the response body + of the "Get Partition Historical Sustainability Data" HMC operation. + Timestamp fields are represented as timezone-aware + :class:`~py:datetime.datetime` objects. + Raises: + :exc:`~zhmcclient.HTTPError` + :exc:`~zhmcclient.ParseError` + :exc:`~zhmcclient.AuthError` + :exc:`~zhmcclient.ConnectionError` + """ + body = { + 'range': range, + 'resolution': resolution, + } + if range == "custom": + body['custom-range-start'] = \ + timestamp_from_datetime(custom_range_start) + body['custom-range-end'] = \ + timestamp_from_datetime(custom_range_end) + result = self.manager.session.post( + self.uri + '/operations/get-historical-sustainability-data', + body=body) + for field_array in result.values(): + for item in field_array: + if 'timestamp' in item: + item['timestamp'] = \ + datetime_from_timestamp(item['timestamp']) + return result