Skip to content

Commit

Permalink
Added new z16 get sustainability data methods
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
andy-maier committed Jun 12, 2024
1 parent 2e50e01 commit 348ce15
Show file tree
Hide file tree
Showing 8 changed files with 793 additions and 2 deletions.
3 changes: 3 additions & 0 deletions changes/1511.feature.rst
Original file line number Diff line number Diff line change
@@ -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.
110 changes: 110 additions & 0 deletions examples/get_sustainability_data.py
Original file line number Diff line number Diff line change
@@ -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()
146 changes: 146 additions & 0 deletions tests/end2end/test_cpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 348ce15

Please sign in to comment.