From eab61e33bbe53ca7b7cc842b64662c5c67961afa Mon Sep 17 00:00:00 2001 From: Andreas Maier Date: Sat, 5 Oct 2024 04:16:27 +0200 Subject: [PATCH] Support for Partition Links Signed-off-by: Andreas Maier --- docs/appendix.rst | 5 + docs/resources.rst | 20 + examples/list_partition_links.py | 91 ++ .../partition_link_attached_partitions.py | 76 ++ tests/end2end/test_partition_link.py | 532 ++++++++++++ tests/end2end/utils.py | 49 ++ zhmcclient/__init__.py | 1 + zhmcclient/_console.py | 13 + zhmcclient/_exceptions.py | 64 +- zhmcclient/_partition_link.py | 786 ++++++++++++++++++ zhmcclient/_session.py | 22 +- zhmcclient/_utils.py | 3 + 12 files changed, 1660 insertions(+), 2 deletions(-) create mode 100755 examples/list_partition_links.py create mode 100755 examples/partition_link_attached_partitions.py create mode 100644 tests/end2end/test_partition_link.py create mode 100644 zhmcclient/_partition_link.py diff --git a/docs/appendix.rst b/docs/appendix.rst index 42e14786..b87bce69 100644 --- a/docs/appendix.rst +++ b/docs/appendix.rst @@ -489,6 +489,11 @@ Resources scoped to CPCs in DPM mode For details, see section :ref:`Partitions`. + Partition Link + A resource that interconnects two or more :term:`Partitions `, + using one of multiple interconnect technologies such as SMC-D, + Hipersockets, or CTC. + Port The physical connector port (jack) of an :term:`Adapter`. diff --git a/docs/resources.rst b/docs/resources.rst index 6c5ec429..d9203832 100644 --- a/docs/resources.rst +++ b/docs/resources.rst @@ -398,6 +398,26 @@ Storage Volume Templates :special-members: __str__ +.. _`Partition Links`: + +Partition Links +--------------- + +.. automodule:: zhmcclient._partition_link + +.. autoclass:: zhmcclient.PartitionLinkManager + :members: + :autosummary: + :autosummary-inherited-members: + :special-members: __str__ + +.. autoclass:: zhmcclient.PartitionLink + :members: + :autosummary: + :autosummary-inherited-members: + :special-members: __str__ + + .. _`Capacity Groups`: Capacity Groups diff --git a/examples/list_partition_links.py b/examples/list_partition_links.py new file mode 100755 index 00000000..8c7f59d8 --- /dev/null +++ b/examples/list_partition_links.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# Copyright 2024 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 lists partition links. +""" + +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 + +# Whether to list partition links with full properties +full_properties = False + +print(__doc__) + +print(f"Using HMC {nickname} at {host} with userid {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(f"Error: Cannot establish session with HMC {host}: " + f"{exc.__class__.__name__}: {exc}") + sys.exit(1) + +try: + client = zhmcclient.Client(session) + console = client.consoles.console + + print(f"\nListing partition links ...") + partition_links = console.partition_links.list( + full_properties=full_properties) + print(f"Number of partition links: {len(partition_links)}") + + print(f"\nAll partition links with short list of properties:\n") + for pl in partition_links: + pprint(dict(pl.properties)) + + print(f"\nPartition links with full list of properties for each type (if possible, complete):") + types_first = {} + types_complete = {} + for pl in partition_links: + pl_type = pl.get_property('type') + pl_state = pl.get_property('state') + if pl_type not in types_first: + types_first[pl_type] = pl + if pl_type not in types_complete and pl_state == 'complete': + types_complete[pl_type] = pl + for pl_type in ('hipersockets', 'smc-d', 'ctc'): + print(f"\nPartition link with type {pl_type}:\n") + try: + pl = types_complete[pl_type] + except KeyError: + try: + pl = types_first[pl_type] + except KeyError: + pl = None + if pl: + pl.pull_full_properties() + pprint(dict(pl.properties)) + +finally: + print("Logging off ...") + session.logoff() diff --git a/examples/partition_link_attached_partitions.py b/examples/partition_link_attached_partitions.py new file mode 100755 index 00000000..b1d24ec8 --- /dev/null +++ b/examples/partition_link_attached_partitions.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# Copyright 2024 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 lists the attached partitions of a partition link. +""" + +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 + +# Whether to list partition links with full properties +full_properties = False + +print(__doc__) + +print(f"Using HMC {nickname} at {host} with userid {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(f"Error: Cannot establish session with HMC {host}: " + f"{exc.__class__.__name__}: {exc}") + sys.exit(1) + +try: + client = zhmcclient.Client(session) + console = client.consoles.console + + print(f"\nListing partition links ...") + partition_links = console.partition_links.list( + full_properties=full_properties) + print(f"Number of partition links: {len(partition_links)}") + + print(f"\nAttached partitions for all partition links:") + for pl in partition_links: + pl_type = pl.get_property('type') + pl_state = pl.get_property('state') + print(f"\nPartition Link {pl.name!r}:") + print(f" Type: {pl_type}") + print(f" State: {pl_state}") + print(f" Attached partitions:") + parts = pl.list_attached_partitions() + for part in parts: + print(f" {part.name}") + +finally: + print("Logging off ...") + session.logoff() diff --git a/tests/end2end/test_partition_link.py b/tests/end2end/test_partition_link.py new file mode 100644 index 00000000..16187e6b --- /dev/null +++ b/tests/end2end/test_partition_link.py @@ -0,0 +1,532 @@ +# Copyright 2024 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. + +""" +End2end tests for partition links (on CPCs in DPM mode). + +These tests do not change any existing partition links or partitions, but +create, modify and delete test partition links and test partitions. +""" + + +import warnings +import random +import uuid +import time +from copy import copy +from pprint import pprint +import pytest +from requests.packages import urllib3 + +import zhmcclient +# pylint: disable=line-too-long,unused-import +from zhmcclient.testutils import hmc_definition, hmc_session # noqa: F401, E501 +from zhmcclient.testutils import dpm_mode_cpcs # noqa: F401, E501 +# pylint: enable=line-too-long,unused-import + +from .utils import skip_warn, pick_test_resources, TEST_PREFIX, \ + skipif_no_partition_link_feature, runtest_find_list, \ + runtest_get_properties, assert_properties + +urllib3.disable_warnings() + +# Properties in minimalistic PartitionLink objects (e.g. find_by_name()) +PARTLINK_MINIMAL_PROPS = ['object-uri', 'name'] + +# Properties in PartitionLink objects returned by list() without full props +PARTLINK_LIST_PROPS = ['object-uri', 'cpc-uri', 'name', 'state', 'type'] + +# Properties whose values can change between retrievals of PartitionLink objs +PARTLINK_VOLATILE_PROPS = [] + + +def replace_expressions(obj, replacements): + """ + Return a deep copy of the input object, with any string values that are + expressions containing replacement strings in 'replacements', with the + corresponding values from 'replacements'. + + This is used to be able to specify adapters and partitions in the testcases + as strings such as 'adapter_1' and then get them replaced with actual + adapter or partition objects. + + The function is called recursively while walking through the initially + provided properties dict. + + Parameters: + obj (dict or object): The input object. Initially, a dict with properties. + replacements (dict): Replacement values. + + Returns: + object: A deep copy of the input object, with any replacements made. + """ + if isinstance(obj, dict): + ret_obj = {} + for name, value in obj.items(): + ret_obj[name] = replace_expressions(value, replacements) + return ret_obj + + if isinstance(obj, list): + ret_obj = [] + for value in obj: + ret_obj.append(replace_expressions(value, replacements)) + return ret_obj + + if isinstance(obj, str): + ret_obj = obj + if any(map(lambda rep: rep in obj, replacements.keys())): + ret_obj = eval(f'{obj}', replacements) # pylint: disable=eval-used + return ret_obj + + return copy(obj) + + +@pytest.mark.parametrize( + "pl_type", [ + 'hipersockets', + 'smc-d', + 'ctc' + ]) +def test_partlink_find_list(dpm_mode_cpcs, pl_type): # noqa: F811 + # pylint: disable=redefined-outer-name + """ + Test list(), find(), findall(). + """ + 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 + skipif_no_partition_link_feature(cpc) + + console = cpc.manager.client.consoles.console + session = cpc.manager.session + hd = session.hmc_definition + + # Pick the partition links to test with + filter_args = { + 'cpc-uri': cpc.uri, + 'type': pl_type, + } + partlink_list = console.partition_links.list(filter_args=filter_args) + if not partlink_list: + skip_warn(f"No partition links of type {pl_type} associated to " + f"CPC {cpc.name} managed by HMC {hd.host}") + partlink_list = pick_test_resources(partlink_list) + + for partlink in partlink_list: + print(f"Testing on CPC {cpc.name} with {pl_type} partition link " + f"{partlink.name!r}") + runtest_find_list( + session, console.partition_links, partlink.name, 'name', + 'object-uri', PARTLINK_VOLATILE_PROPS, PARTLINK_MINIMAL_PROPS, + PARTLINK_LIST_PROPS) + + +@pytest.mark.parametrize( + "pl_type", [ + 'hipersockets', + 'smc-d', + 'ctc' + ]) +def test_partlink_property(dpm_mode_cpcs, pl_type): # noqa: F811 + # pylint: disable=redefined-outer-name + """ + Test property related methods + """ + 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 + skipif_no_partition_link_feature(cpc) + + console = cpc.manager.client.consoles.console + session = cpc.manager.session + hd = session.hmc_definition + + # Pick the partition links to test with + filter_args = { + 'cpc-uri': cpc.uri, + 'type': pl_type, + } + partlink_list = console.partition_links.list(filter_args=filter_args) + if not partlink_list: + skip_warn(f"No partition links of type {pl_type} associated to " + f"CPC {cpc.name} managed by HMC {hd.host}") + partlink_list = pick_test_resources(partlink_list) + + for partlink in partlink_list: + print(f"Testing on CPC {cpc.name} with {pl_type} partition link " + f"{partlink.name!r}") + + # Select a property that is not returned by list() + non_list_prop = 'description' + + runtest_get_properties(partlink.manager, non_list_prop) + + +@pytest.mark.parametrize( + "pl_type", [ + 'hipersockets', + 'smc-d', + 'ctc' + ]) +def test_partlink_crud(dpm_mode_cpcs, pl_type): # noqa: F811 + # pylint: disable=redefined-outer-name + """ + Test create, read, update and delete a partition link. + """ + 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 + skipif_no_partition_link_feature(cpc) + + print(f"Testing on CPC {cpc.name}") + + console = cpc.manager.client.consoles.console + partlink_name = TEST_PREFIX + ' test_partlink_crud partlink1' + partlink_name_new = partlink_name + ' new' + + # Ensure clean starting point + try: + partlink = console.partition_links.find(name=partlink_name) + except zhmcclient.NotFound: + pass + else: + warnings.warn( + f"Deleting test partition link from previous run: " + f"{partlink_name!r} on CPC {cpc.name}", UserWarning) + partlink.delete() + try: + partlink = console.partition_links.find(name=partlink_name_new) + except zhmcclient.NotFound: + pass + else: + warnings.warn( + "Deleting test partition link from previous run: " + f"{partlink_name_new!r} on CPC {cpc.name}", UserWarning) + partlink.delete() + + # Test creating the partition link. + # For Hipersockets and SMC-D-type partition links, we create the + # link with no partitions attached. + # For CTC-type partition links, we attach one partition. + + partlink_input_props = { + 'cpc-uri': cpc.uri, + 'name': partlink_name, + 'description': 'Test partition link for zhmcclient end2end tests', + 'type': pl_type, + } + + if pl_type == 'ctc': + # Provide one partition to attach to. + partitions = cpc.partitions.list(filter_args={'status': 'stopped'}) + if len(partitions) < 2: + pytest.skip(f"CPC {cpc.name} has no two stopped partitions " + "for CTC partition link creation") + part1, part2 = random.choices(partitions, k=2) + adapters = cpc.adapters.list(filter_args={'type': 'fc'}) + if not adapters: + pytest.skip(f"CPC {cpc.name} has no FC adapters for " + "CTC partition link creation") + adapter = random.choice(adapters) + path = { + 'adapter-port-uri': adapter.uri, + 'connecting-adapter-port-uri': adapter.uri, + } + partlink_input_props['partitions'] = [part1.uri, part2.uri] + partlink_input_props['paths'] = [path] + partlink_input_props['devices-per-path'] = 1 + + partlink_auto_props = { + 'cpc-name': cpc.name, + } + + # The code to be tested + partlink = console.partition_links.create(partlink_input_props) + + for pn, exp_value in partlink_input_props.items(): + assert partlink.properties[pn] == exp_value, ( + f"Unexpected value for property {pn!r} of partition link:\n" + f"{partlink.properties!r}") + partlink.pull_full_properties() + for pn, exp_value in partlink_input_props.items(): + assert partlink.properties[pn] == exp_value, ( + f"Unexpected value for property {pn!r} of partition link:\n" + f"{partlink.properties!r}") + for pn, exp_value in partlink_auto_props.items(): + assert pn in partlink.properties, ( + f"Automatically returned property {pn!r} is not in " + f"created partition link:\n{partlink!r}") + assert partlink.properties[pn] == exp_value, ( + f"Unexpected value for property {pn!r} of partition link:\n" + f"{partlink.properties!r}") + + # Test updating a property of the partition link + + new_desc = "Updated partition link description." + + # The code to be tested + partlink.update_properties(dict(description=new_desc)) + + assert partlink.properties['description'] == new_desc + partlink.pull_full_properties() + assert partlink.properties['description'] == new_desc + + # Test renaming the partition link + + # The code to be tested + partlink.update_properties(dict(name=partlink_name_new)) + + assert partlink.properties['name'] == partlink_name_new + partlink.pull_full_properties() + assert partlink.properties['name'] == partlink_name_new + with pytest.raises(zhmcclient.NotFound): + console.partition_links.find(name=partlink_name) + + # Test deleting the partition link + + # The code to be tested + partlink.delete() + + with pytest.raises(zhmcclient.NotFound): + console.partition_links.find(name=partlink_name_new) + + +PARTLINK_CREATE_DELETE_TESTCASES = [ + # testcases for the test_partlink_create() test function. + # Each list item is a testcase that is defined with the following + # tuple items: + # - desc (string): description of the testcase. + # - pl_type (string): Type of the partition link, as for its 'type' prop. + # - input_props (dict): Input properties for the create() method. + # The test function adds these properties automatically: + # - cpc-uri + # - name + # - type + # - exp_props (dict): Expected properties in the created partition link. + # The test function adds these properties automatically: + # - cpc-uri + # - name + # - type + # - exp_exc_type (class): Exception that is expected to be raised during + # PartitionLink.create(), or None. + ( + "Hipersockets with minimal input properties", + 'hipersockets', + {}, + { + 'bus-connections': [], + 'starting-device-number': '7400', # default + 'maximum-transmission-unit-size': 8, # default + }, + None + ), + ( + "SMC-D with minimal input properties", + 'smc-d', + {}, + { + 'bus-connections': [], + 'starting-fid': 4096, # default + }, + None + ), + ( + "CTC with minimal input properties", + 'ctc', + { + 'partitions': ['partition_1.uri', 'partition_2.uri'], + 'paths': [ + { + 'adapter-port-uri': 'adapter_1.uri', # Replaced + 'connecting-adapter-port-uri': 'adapter_1.uri', # Replaced + }, + ], + }, + { + 'partitions': ['partition_1.uri', 'partition_2.uri'], + 'paths': [ + { + 'starting-device-number': '4000', + # 'devices': [ + # { + # # ctc-endpoint + # } + # ], + 'adapter-port-info': { + 'adapter-uri': 'adapter_1.uri', # Replaced + 'adapter-name': 'adapter_1.name', # Replaced + }, + 'connecting-adapter-port-info': { + 'adapter-uri': 'adapter_1.uri', # Replaced + 'adapter-name': 'adapter_1.name', # Replaced + }, + }, + ], + 'devices-per-path': 4 # default + }, + None + ), +] + + +@pytest.mark.parametrize( + "desc, pl_type, input_props, exp_props, exp_exc_type", + PARTLINK_CREATE_DELETE_TESTCASES) +def test_partlink_create_delete( + dpm_mode_cpcs, # noqa: F811 + desc, pl_type, input_props, exp_props, exp_exc_type): + # pylint: disable=redefined-outer-name, exec-used, unused-argument + """ + Test creation of a partition link (and deletion, for cleanup) + """ + 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 + skipif_no_partition_link_feature(cpc) + + print(f"Testing on CPC {cpc.name}") + + console = cpc.manager.client.consoles.console + partlink_name = f"{TEST_PREFIX}_{uuid.uuid4().hex}" + replacements = {} # Replacements for testcase expressions + + # For CTC, get two FC adapters and two stopped partitions + if pl_type == 'ctc': + + adapters = cpc.adapters.list( + filter_args={'type': 'fc', 'state': 'online'}) + if len(adapters) < 2: + pytest.skip(f"CPC {cpc.name} has no two online FC adapters " + "for CTC partition link creation") + + adapter_1, adapter_2 = random.choices(adapters, k=2) + replacements['adapter_1'] = adapter_1 + replacements['adapter_2'] = adapter_2 + + partitions = cpc.partitions.list(filter_args={'status': 'stopped'}) + if len(partitions) < 2: + pytest.skip(f"CPC {cpc.name} has no two stopped partitions " + "for CTC partition link creation") + partition_1, partition_2 = random.choices(partitions, k=2) + replacements['partition_1'] = partition_1 + replacements['partition_2'] = partition_2 + + # Prepare the input properties for PartitionLink.create() + partlink_input_props = { + 'cpc-uri': cpc.uri, + 'name': partlink_name, + 'type': pl_type, + } + partlink_input_props.update(input_props) + + # Replace the variable names for adapters and partitions in the + # input properties with real data. + partlink_input_props = replace_expressions( + partlink_input_props, replacements) + + partlink = None + try: + + if exp_exc_type: + with pytest.raises(exp_exc_type): + + # The code to be tested + partlink = console.partition_links.create( + partlink_input_props) + + else: + try: + + # The code to be tested + partlink = console.partition_links.create( + partlink_input_props) + + except zhmcclient.PartitionLinkError as exc: + print("PartitionLinkError during PartitionLink.create(): " + f"{exc}") + print("Input properties of PartitionLink.create():") + pprint(partlink_input_props) + raise + except zhmcclient.HTTPError as exc: + print(f"HTTPError during PartitionLink.create(): {exc}") + print("Input properties of PartitionLink.create():") + pprint(partlink_input_props) + raise + + # Prepare the expected properties + partlink_exp_props = { + 'cpc-uri': cpc.uri, + 'name': partlink_name, + 'type': pl_type, + } + partlink_exp_props.update(exp_props) + + partlink_exp_props = replace_expressions( + partlink_exp_props, replacements) + + partlink.pull_full_properties() + + assert_properties(partlink.properties, partlink_exp_props) + + finally: + + # Cleanup, but also code to be tested + if partlink: + try: + partlink.delete() + except zhmcclient.HTTPError as exc: + print(f"HTTPError during PartitionLink.delete(): {exc}") + time.sleep(10) + print("Retrying PartitionLink.delete() ...") + partlink.delete() + + with pytest.raises(zhmcclient.NotFound): + console.partition_links.find(name=partlink_name) + + +def test_partlink_zzz_cleanup(dpm_mode_cpcs): # noqa: F811 + # pylint: disable=redefined-outer-name, exec-used, unused-argument + """ + Cleanup any created partition links + """ + 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 + skipif_no_partition_link_feature(cpc) + + print(f"Testing on CPC {cpc.name}") + + console = cpc.manager.client.consoles.console + + name_pattern = fr'{TEST_PREFIX}.*' + partlinks = console.partition_links.findall(name=name_pattern) + for partlink in partlinks: + + print("Deleting test partition link from a prior test run: " + f"{partlink.name!r} on CPC {cpc.name}") + + try: + partlink.delete() + except zhmcclient.HTTPError as exc: + print(f"HTTPError during PartitionLink.delete(): {exc}") diff --git a/tests/end2end/utils.py b/tests/end2end/utils.py index a2dc9fb3..606880e2 100644 --- a/tests/end2end/utils.py +++ b/tests/end2end/utils.py @@ -115,6 +115,36 @@ class End2endTestWarning(UserWarning): pass +def assert_properties(act_obj, exp_obj): + """ + Check that actual properties match expected properties. + + The expected properties may specify only a subset of the actual properties. + Only the expected properties are checked. + + The property values may have any type, including nested dictionaries and + lists. For nested dictionaries and lists, each item is matched recursively. + + Parameters: + act_obj (dict): The actual object. Initially, a dict with properties. + exp_obj (dict): The expected object. Initially, a dict with properties. + """ + if isinstance(exp_obj, dict): + for name, exp_value in exp_obj.items(): + assert name in act_obj, ( + f"Expected property {name!r} is not in actual properties:\n" + f"{act_obj!r}") + act_value = act_obj[name] + assert_properties(act_value, exp_value) + elif isinstance(exp_obj, list): + for i, exp_value in enumerate(exp_obj): + act_value = act_obj[i] + assert_properties(act_value, exp_value) + else: + assert act_obj == exp_obj, ( + f"Unexpected value: {act_obj!r}; Expected: {exp_obj!r}") + + def assert_res_props(res, exp_props, ignore_values=None, prop_names=None): """ Check the properties of a resource object. @@ -632,6 +662,25 @@ def _skipif_api_feature_not_on_cpc_and_hmc(feature, cpc): skip_warn(f"API feature {feature} not available on HMC {console.name}") +def skipif_no_partition_link_feature(cpc): + """ + Skip the test if the "dpm-smcd-partition-link-management" API feature is + not enabled for the specified CPC, or if the CPC does not yet support it. + + Note that the three partition-link related API features are always all + enabled or all disabled: + + * "dpm-smcd-partition-link-management" + * "dpm-hipersockets-partition-link-management" + * "dpm-ctc-partition-link-management" + """ + has_partition_link = has_api_feature( + "dpm-smcd-partition-link-management", cpc) + if not has_partition_link: + skip_warn("Partition link related API features not enabled or not " + f"supported on CPC {cpc.name}") + + def has_api_feature(feature, cpc): """ Returns True if the given API feature is available on the specified CPC diff --git a/zhmcclient/__init__.py b/zhmcclient/__init__.py index 4ea8591a..6df1ed16 100644 --- a/zhmcclient/__init__.py +++ b/zhmcclient/__init__.py @@ -57,6 +57,7 @@ from ._virtual_storage_resource import * # noqa: F401 from ._storage_group_template import * # noqa: F401 from ._storage_volume_template import * # noqa: F401 +from ._partition_link import * # noqa: F401 from ._capacity_group import * # noqa: F401 from ._certificates import * # noqa: F401 from ._os_console import * # noqa: F401 diff --git a/zhmcclient/_console.py b/zhmcclient/_console.py index 66c0cd65..c2e420e7 100644 --- a/zhmcclient/_console.py +++ b/zhmcclient/_console.py @@ -39,6 +39,7 @@ from ._group import GroupManager from ._utils import get_features from ._certificates import CertificateManager +from ._partition_link import PartitionLinkManager __all__ = ['ConsoleManager', 'Console'] @@ -205,6 +206,7 @@ def __init__(self, manager, uri, name=None, properties=None): # The manager objects for child resources (with lazy initialization): self._storage_groups = None self._storage_group_templates = None + self._partition_links = None self._users = None self._user_roles = None self._user_patterns = None @@ -238,6 +240,17 @@ def storage_group_templates(self): self._storage_group_templates = StorageGroupTemplateManager(self) return self._storage_group_templates + @property + def partition_links(self): + """ + :class:`~zhmcclient.PartitionLinkManager`: + Manager object for the Partition Links in scope of this Console. + """ + # We do here some lazy loading. + if not self._partition_links: + self._partition_links = PartitionLinkManager(self) + return self._partition_links + @property def users(self): """ diff --git a/zhmcclient/_exceptions.py b/zhmcclient/_exceptions.py index 8090999b..8d92bce4 100644 --- a/zhmcclient/_exceptions.py +++ b/zhmcclient/_exceptions.py @@ -29,7 +29,7 @@ 'SubscriptionNotFound', 'ConsistencyError', 'CeasedExistence', 'OSConsoleError', 'OSConsoleConnectedError', 'OSConsoleNotConnectedError', 'OSConsoleWebSocketError', - 'OSConsoleAuthError'] + 'OSConsoleAuthError', 'PartitionLinkError'] class Error(Exception): @@ -1656,3 +1656,65 @@ class OSConsoleAuthError(OSConsoleError): console. """ pass + + +class PartitionLinkError(Error): + # pylint: disable=redefined-builtin + """ + This exception indicates that an operation on a partition link has completed + its asynchronous operation with failed operation results or with pending + retries during SE restart. + + Derived from :exc:`~zhmcclient.Error`. + """ + + def __init__(self, operation_results): + """ + Parameters: + + operation_results (list of dict): + The 'operation-results' field of the job completion result. + + ``args[0]`` will be set to the ``msg`` parameter. + """ + op_msg_list = [] + for op_result in operation_results: + uri = op_result['partition-uri'] + status = op_result['operation-status'] + op_msg_list.append(f"operation status {status} for partition {uri}") + op_msg = ", ".join(op_msg_list) + msg = f"Partition link operation failed with: {op_msg}" + super().__init__(msg) + self._operation_results = operation_results + + @property + def operation_results(self): + """ + list of dict: The value of the 'operation-results' field of the job + completion result. + """ + return self._operation_results + + def __repr__(self): + """ + Return a string with the state of this exception object, for debug + purposes. + """ + return ( + f"{self.__class__.__name__}(" + f"message={self.args[0]!r}, " + f"operation_results={self._operation_results!r})") + + def str_def(self): + """ + :term:`string`: The exception as a string in a Python definition-style + format, e.g. for parsing by scripts: + + .. code-block:: text + + classname={}; message={}; operation_results={} + """ + return ( + f"classname={self.__class__.__name__!r}; " + f"message={self.args[0]!r}; " + f"operation_results={self._operation_results!r}") diff --git a/zhmcclient/_partition_link.py b/zhmcclient/_partition_link.py new file mode 100644 index 00000000..eeff3d77 --- /dev/null +++ b/zhmcclient/_partition_link.py @@ -0,0 +1,786 @@ +# Copyright 2024 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. + +""" +A :term:`Partition Link` interconnects two or more +:term:`Partitions ` that share the same network configuration. + +Partition Links have been introduced with SE 2.16. Conceptually, Partition Links +can connect different :term:`CPCs `. However, the implementation is +currently limited to the same DPM-enabled CPC. Currently, one Partition Link +resource is always associated with a single CPC at any time. + +Partition Links are based on different technologies, indicated in their +``type`` property: + +* ``smc-d`` - Shared Memory Communications - Direct Memory Access (SMC-D) + (Version 2 or later). + Support for this technology is indicated via :ref:`API feature ` + "dpm-smcd-partition-link-management". +* ``hipersockets`` - Hipersockets. + Support for this technology is indicated via :ref:`API feature ` + "dpm-hipersockets-partition-link-management". +* ``ctc`` - FICON Channel to Channel (CTC) interconnect, using corresponding + cabling between FICON adapters. + Support for this technology is indicated via :ref:`API feature ` + "dpm-ctc-partition-link-management". + +Partition Links have a ``state`` property that indicates their current +attachment state to any partitions: + +* ``complete`` - All requests for attaching or detaching the partition link to + or from partitions are complete. +* ``incomplete`` - All requests for attaching or detaching the partition link to + or from partitions are complete, but the partition link is attached to less + than 2 partitions. +* ``updating`` - Some requests for attaching or detaching the partition link to + or from partitions are incomplete. + +This section describes the interface for Partition Links using resource class +:class:`~zhmcclient.PartitionLink` and the corresponding manager class +:class:`~zhmcclient.PartitionLinkManager`. + +Because conceptually, Partition Links can connect different CPCs, this client +has designed :class:`~zhmcclient.PartitionLink` resources to be available +through the :class:`~zhmcclient.Console` resource, via its +:attr:`~zhmcclient.Console.partition_links` property. + +The earlier interfaces for Hipersockets are also supported: + +* represented as :class:`~zhmcclient.Adapter`, including support for creation + and deletion. +* Attachment to a partition is managed via creation and deletion of + :class:`~zhmcclient.Nic` resources on the partition. +""" + +import copy +import re + +from ._manager import BaseManager +from ._resource import BaseResource +from ._logging import logged_api_call +from ._utils import RC_PARTITION_LINK +from ._exceptions import PartitionLinkError + +__all__ = ['PartitionLinkManager', 'PartitionLink'] + + +class PartitionLinkManager(BaseManager): + """ + Manager providing access to the :term:`partition links ` of + the HMC. + + Derived from :class:`~zhmcclient.BaseManager`; see there for common methods + and attributes. + + Objects of this class are not directly created by the user; they are + accessible via the following instance variable: + + * :attr:`~zhmcclient.Console.partition_links` of a + :class:`~zhmcclient.Console` object. + + HMC/SE version requirements: + + * SE version >= 2.16.0 + * for technology-specific support, see the API features described in + :ref:`Partition Links` + """ + + def __init__(self, console): + # This function should not go into the docs. + # Parameters: + # console (:class:`~zhmcclient.Console`): + # CPC or HMC defining the scope for this manager. + + # Resource properties that are supported as filter query parameters. + # If the support for a resource property changes within the set of HMC + # versions that support this type of resource, this list must be set up + # for the version of the HMC this session is connected to. + query_props = [ + 'cpc-uri', + 'name', + 'state', + ] + + super().__init__( + resource_class=PartitionLink, + class_name=RC_PARTITION_LINK, + session=console.manager.session, + parent=console, + base_uri='/api/partition-links', + oid_prop='object-id', + uri_prop='object-uri', + name_prop='name', + query_props=query_props) + self._console = console + + @property + def console(self): + """ + :class:`~zhmcclient.Console`: The Console object representing the HMC. + """ + return self._console + + @logged_api_call + def list(self, full_properties=False, filter_args=None): + """ + List the partition links known to the HMC. + + Partition links for which the authenticated user does not have + object-access permission are not included. + + Any resource property may be specified in a filter argument. For + details about filter arguments, see :ref:`Filtering`. + + The listing of resources is handled in an optimized way: + + * If this manager is enabled for :ref:`auto-updating`, a locally + maintained resource list is used (which is automatically updated via + inventory notifications from the HMC) and the provided filter + arguments are applied. + + * Otherwise, if the filter arguments specify the resource name as a + single filter argument with a straight match string (i.e. without + regular expressions), an optimized lookup is performed based on a + locally maintained name-URI cache. + + * Otherwise, the HMC List operation is performed with the subset of the + provided filter arguments that can be handled on the HMC side and the + remaining filter arguments are applied on the client side on the list + result. + + This method performs the "List Partition Links" HMC operation. + + HMC/SE version requirements: + + * SE version >= 2.16.0 + * for technology-specific support, see the API features described in + :ref:`Partition Links` + + Authorization requirements: + + * Object-access permission to any partition links to be included in the + result. + + Parameters: + + full_properties (bool): + Controls that the full set of resource properties for each returned + partition link is being retrieved, vs. only the following short + set: "object-uri", "cpc-uri", "name", "state", and "type". + + filter_args (dict): + Filter arguments that narrow the list of returned resources to + those that match the specified filter arguments. For details, see + :ref:`Filtering`. + + `None` causes no filtering to happen. + + Returns: + + : A list of :class:`~zhmcclient.PartitionLink` objects. + + Raises: + + :exc:`~zhmcclient.HTTPError` + :exc:`~zhmcclient.ParseError` + :exc:`~zhmcclient.AuthError` + :exc:`~zhmcclient.ConnectionError` + """ + result_prop = 'partition-links' + list_uri = self._base_uri + return self._list_with_operation( + list_uri, result_prop, full_properties, filter_args, None) + + @logged_api_call + def create(self, properties=None): + """ + Create a partition link from the input properties. + + The input properties may specify an initial attachment of the partition + link to one or more partitions. + + Additional attachments to partitions and detachments from partitions + can be performed with + :meth:`~zhmcclient.PartitionLink.attach_to_partition` and + :meth:`~zhmcclient.PartitionLink.detach_from_partition`, and also with + the more generic :meth:`~zhmcclient.PartitionLink.update_properties`. + + For CTC-type partition links, it is required to specify the paths + describing the physical connectivity between the FICON adapters at + creation time, using the ``paths`` input property. + + The new partition link will be associated with the CPC identified by the + ``cpc-uri`` input property. + + This method performs the "Create Partition Link" HMC operation and + waits for completion of its asynchronous job. + + HMC/SE version requirements: + + * SE version >= 2.16.0 + * for technology-specific support, see the API features described in + :ref:`Partition Links` + + Authorization requirements: + + * Object-access permission to the CPC that will be associated with + the new partition link. + * Task permission to the "Create Partition Link" task. + * Object-access permission to all Partitions for the initially requested + attachments. + * Object-access permission to all FICON adapter objects used for the + CTC connections for the initially requested attachments. + + Parameters: + + properties (dict): Initial property values. + Allowable properties are defined in section 'Request body contents' + in section 'Create Partition Link' in the :term:`HMC API` book. + + The 'cpc-uri' property identifies the CPC to which the new + partition link will be associated, and is required to be specified + in this parameter. + + Returns: + + * If `wait_for_completion` is `True`, returns a + :class:`~zhmcclient.PartitionLink` object representing the new + partition link. + + * If `wait_for_completion` is `False`, returns a + :class:`~zhmcclient.Job` object representing the asynchronously + executing job on the HMC. + This job does not support cancellation. + + Raises: + + :exc:`~zhmcclient.HTTPError` + :exc:`~zhmcclient.ParseError` + :exc:`~zhmcclient.AuthError` + :exc:`~zhmcclient.ConnectionError` + :exc:`~zhmcclient.OperationTimeout`: The timeout expired while + waiting for completion of the operation. + """ + if properties is None: + properties = {} + + result = self.session.post( + uri=self._base_uri, + body=properties, + wait_for_completion=True) + + # The "Create Partition Link" operation does not return the object-uri + # of the new partition link in the response. however, it does return + # it in the "Location" header, and the session.post() method adds that + # to its return value as the "location-uri" field. + uri = result['location-uri'] + name = properties[self._name_prop] + props = copy.deepcopy(properties) + props[self._uri_prop] = uri + partition_link = PartitionLink(self, uri, name, props) + self._name_uri_cache.update(name, uri) + return partition_link + + +class PartitionLink(BaseResource): + """ + Representation of a :term:`partition link`. + + Derived from :class:`~zhmcclient.BaseResource`; see there for common + methods and attributes. + + Objects of this class are not directly created by the user; they are + returned from creation or list functions on their manager object + (in this case, :class:`~zhmcclient.PartitionLinkManager`). + + HMC/SE version requirements: + + * SE version >= 2.16.0 + * for technology-specific support, see the API features described in + :ref:`Partition Links` + """ + + def __init__(self, manager, uri, name=None, properties=None): + # This function should not go into the docs. + # manager (:class:`~zhmcclient.PartitionLinkManager`): + # Manager object for this resource object. + # uri (string): + # Canonical URI path of the resource. + # name (string): + # Name of the resource. + # properties (dict): + # Properties to be set for this resource object. May be `None` or + # empty. + assert isinstance(manager, PartitionLinkManager), ( + "PartitionLink init: Expected manager type " + f"{PartitionLinkManager}, got {type(manager)}") + super().__init__(manager, uri, name, properties) + # The manager objects for child resources (with lazy initialization): + self._cpc = None + + @property + def cpc(self): + """ + :class:`~zhmcclient.Cpc`: The :term:`CPC` to which this partition link + is associated. + + The returned :class:`~zhmcclient.Cpc` has only a minimal set of + properties populated. + """ + # We do here some lazy loading. + if not self._cpc: + cpc_uri = self.get_property('cpc-uri') + cpc_mgr = self.manager.console.manager.client.cpcs + self._cpc = cpc_mgr.resource_object(cpc_uri) + return self._cpc + + @logged_api_call + def list_attached_partitions(self, name=None, status=None): + """ + Return the partitions to which this partition link is currently + attached, optionally filtered by partition name and status. + + Properties of the endpoint objects of the partition links are not + returned by this method and can be obtained via the + :class:`~zhmcclient.PartitionLink` properties. + In case of Hipersockets, these properties are also available + on the NIC objects. In case of SMC-D and CTC, the HMC API does not + externalize corresponding endpoint objects other than as properties + of partition link objexts. + + This method performs the "Get Partition Link Properties" HMC operation, + if this Python object does not yet have the full properties. + + HMC/SE version requirements: + + * SE version >= 2.16.0 + * for technology-specific support, see the API features described in + :ref:`Partition Links` + + Authorization requirements: + + * Object-access permission to this partition link. + + Parameters: + + name (:term:`string`): Filter pattern (regular expression) + to limit returned partitions to those that have a matching + name. If `None`, no filtering for the partition name takes place. + + status (:term:`string`): Filter string to limit returned + partitions to those that have a matching status. The value + must be a valid partition 'status' property value. If `None`, no + filtering for the partition status takes place. + + Returns: + + List of :class:`~zhmcclient.Partition` objects representing the + partitions to which this partition link is currently attached, with + a minimal set of properties ('object-id', 'name'). + + Raises: + + :exc:`~zhmcclient.HTTPError` + :exc:`~zhmcclient.ParseError` + :exc:`~zhmcclient.AuthError` + :exc:`~zhmcclient.ConnectionError` + """ + part_list = [] + part_uri_list = [] + pl_type = self.get_property('type') + if pl_type == 'ctc': + for path in self.get_property('paths'): + for device in path.get('devices'): + for endpoint in device.get('endpoint-pair'): + part_uri = endpoint.get('partition-uri') + part_name = endpoint.get('partition-name') + part = self.cpc.partitions.resource_object( + part_uri, {'name': part_name}) + if name: + if not re.match(name, part_name): + continue + if status: + if part.get_property('status') != status: + continue + if part_uri not in part_uri_list: + part_uri_list.append(part_uri) + part_list.append(part) + else: + # Hipersockets or SMC-D + for bc in self.get_property('bus-connections'): + part_uri = bc.get('partition-uri') + part_name = bc.get('partition-name') + part = self.cpc.partitions.resource_object( + part_uri, {'name': part_name, 'nics': []}) + if name: + if not re.match(name, part_name): + continue + if status: + if part.get_property('status') != status: + continue + if part_uri not in part_uri_list: + part_uri_list.append(part_uri) + part_list.append(part) + return part_list + + def attach_to_partition( + self, partition, properties, wait_for_completion=True, + operation_timeout=None): + """ + Attach this partition link to a partition, creating corresponding + endpoints in the partition. + + This method performs the "Modify Partition Link" HMC operation and + by default waits for completion of its asynchronous job. + + HMC/SE version requirements: + + * SE version >= 2.16.0 + * for technology-specific support, see the API features described in + :ref:`Partition Links` + + Authorization requirements: + + * Object-access permission to this partition link. + + Parameters: + + partition (:class:`~zhmcclient.Partition`): The partition to which + this partition link is to be attached. + + properties (dict): Optional properties for controlling device + numbers and other attributes. + + For Hipersockets and SMC-D-type partition links, the following + properties can be used in this method. For a full description, + see the description of the "Modify Partition Link" operation + in the :term:`HMC API` book. The provided properties will become + an item in the ``added-connections`` array parameter, along with + the provided partition. The following properties can be specified: + + * ``number-of-added-nics`` - int - optional - Number of NICs to + define in the partition. + * ``added-nics`` - array of new-nic objects - optional: + + * ``device-numbers`` - array of string - optional - device + numbers + * ``fid`` - int - optional - SMCD only: FID + * ``vlan-id`` - int - optional - Hipersockets only: VLAN ID + * ``mac-address`` - string - optional - Hipersockets only: MAC + address + + For CTC-type partition links, the following properties can be used + in this method. For a full description, see the description of the + "Modify Partition Link" operation in the :term:`HMC API` book. + The provided properties will be added to the request payload of the + operation, along with the provided partition. The following + properties can be specified: + + * ``modified-paths`` - array of modified-ctc-path-info objects - + optional: + + * ``adapter-port-uri`` - URI - required - used to match path + * ``connecting-adapter-port-uri`` - URI - required - used to + match path + * ``devices`` - array of ctc-endpoint objects - optional: + + * ``endpoint-pair`` - array of two + ctc-partition-devices-endpoint objects - required: + + * ``partition-uri`` - URI - required - used to match + partition + * ``device-numbers`` - array of strings - required - device + numbers + + wait_for_completion (bool): + Boolean controlling whether this method should wait for completion + of the asynchronous job on the HMC. + + operation_timeout (:term:`number`): + Timeout in seconds, for waiting for completion of the asynchronous + job on the HMC. The special value 0 means that no timeout is set. + `None` means that the default async operation timeout of the + session is used. If the timeout expires when + `wait_for_completion=True`, a :exc:`~zhmcclient.OperationTimeout` + is raised. + + Returns: + + * If `wait_for_completion` is `True`, returns `None`. + + * If `wait_for_completion` is `False`, returns a + :class:`~zhmcclient.Job` object representing the asynchronously + executing job on the HMC. + This job does not support cancellation. + + Raises: + + :exc:`~zhmcclient.HTTPError` + :exc:`~zhmcclient.ParseError` + :exc:`~zhmcclient.AuthError` + :exc:`~zhmcclient.ConnectionError` + :exc:`~zhmcclient.OperationTimeout`: The timeout expired while + waiting for completion of the operation. + """ + body = {} + pl_type = self.get_property('type') + if pl_type == 'ctc': + body['added-partition-uris'] = [partition.uri] + body.update(properties) + else: + bc = { + 'partition-uri': partition.uri, + } + bc.update(properties) + body['added-connections'] = [bc] + + result = self.manager.session.post( + uri=f'{self.uri}/operations/modify', + resource=self, + body=body, + wait_for_completion=wait_for_completion, + operation_timeout=operation_timeout) + + if wait_for_completion: + for op_result in result['operation-results']: + if op_result['operation-status'] != "attached": + raise PartitionLinkError(result['operation-results']) + return None + + return result # Job + + def detach_from_partition( + self, partition, wait_for_completion=True, + operation_timeout=None): + """ + Detach this partition link from a partition, deleting all corresponding + endpoints in the partition. + + This method performs the "Modify Partition Link" HMC operation and + by default waits for completion of its asynchronous job. + + HMC/SE version requirements: + + * SE version >= 2.16.0 + * for technology-specific support, see the API features described in + :ref:`Partition Links` + + Authorization requirements: + + * Object-access permission to this partition link. + * Object-access permission to the partition to be detached. + * Task permission to the "Partition Link Details" task. + + Parameters: + + partition (:class:`~zhmcclient.Partition`): The partition from which + this partition link is to be detached. + + wait_for_completion (bool): + Boolean controlling whether this method should wait for completion + of the asynchronous job on the HMC. + + operation_timeout (:term:`number`): + Timeout in seconds, for waiting for completion of the asynchronous + job on the HMC. The special value 0 means that no timeout is set. + `None` means that the default async operation timeout of the + session is used. If the timeout expires when + `wait_for_completion=True`, a :exc:`~zhmcclient.OperationTimeout` + is raised. + + Returns: + + * If `wait_for_completion` is `True`, returns `None`. + + * If `wait_for_completion` is `False`, returns a + :class:`~zhmcclient.Job` object representing the asynchronously + executing job on the HMC. + This job does not support cancellation. + + Raises: + + :exc:`~zhmcclient.HTTPError` + :exc:`~zhmcclient.ParseError` + :exc:`~zhmcclient.AuthError` + :exc:`~zhmcclient.ConnectionError` + :exc:`~zhmcclient.OperationTimeout`: The timeout expired while + waiting for completion of the operation. + """ + body = { + 'removed-partition-uris': [partition.uri], + } + + result = self.manager.session.post( + uri=f'{self.uri}/operations/modify', + resource=self, + body=body, + wait_for_completion=wait_for_completion, + operation_timeout=operation_timeout) + + if wait_for_completion: + for op_result in result['operation-results']: + if op_result['operation-status'] != "detached": + raise PartitionLinkError(result['operation-results']) + return None + + return result # Job + + @logged_api_call + def delete(self, force_detach=False): + """ + Delete this partition link on the HMC. + + If there are active partitions to which the partition link is attached, + the operation will fail by default. The 'force_detach' parameter can be + used to forcefully detach the partition link from active partitions + before deleting it. + + This method performs the "Delete Partition Link" HMC operation and + waits for completion of its asynchronous job. + + HMC/SE version requirements: + + * SE version >= 2.16.0 + * for technology-specific support, see the API features described in + :ref:`Partition Links` + + Authorization requirements: + + * Object-access permission to this partition link. + * Object-access permission to all partitions that currently have this + partition link attached. + * Task permission to the "Delete Partition Link" task. + + Parameters: + + force_detach (bool): Controls what to do with active partitions + associated with this partition link. If True, such partitions + are detached forcefully. If False, the operation fails with + status code 409 (Conflict) and reason code 100. + + Raises: + + :exc:`~zhmcclient.HTTPError` + :exc:`~zhmcclient.ParseError` + :exc:`~zhmcclient.AuthError` + :exc:`~zhmcclient.ConnectionError` + :exc:`~zhmcclient.OperationTimeout`: The timeout expired while + waiting for completion of the operation. + """ + body = {} + if force_detach: + body['force-detach'] = True + + self.manager.session.post( + uri=f'{self.uri}/operations/delete', + resource=self, + body=body, + wait_for_completion=True) + + # pylint: disable=protected-access + self.manager._name_uri_cache.delete( + self.get_properties_local(self.manager._name_prop, None)) + self.cease_existence_local() + + @logged_api_call + def update_properties(self, properties): + """ + Update writeable properties of this partition link. + + This method can be used to attach and detach this partition link + to and from partitions, by modifying the appropriate resource + properties. + + This method serializes with other methods that access or change + properties on the same Python object. + + This method performs the "Modify Partition Link" HMC operation and + waits for completion of its asynchronous job. + + HMC/SE version requirements: + + * SE version >= 2.16.0 + * for technology-specific support, see the API features described in + :ref:`Partition Links` + + Authorization requirements: + + * Object-access permission to the CPC that will be associated with + the new partition link. + * Task permission to the "Create Partition Link" task. + * Object-access permission to all Partitions for the initially requested + attachments. + * Object-access permission to all FICON adapter objects used for the + CTC connections for the initially requested attachments. + + Parameters: + + properties (dict): New values for the properties to be updated. + Properties not to be updated are omitted. + Allowable properties are listed for operation + 'Modify Partition Link' in section 'Partition Link object' in the + :term:`HMC API` book. + + Raises: + + :exc:`~zhmcclient.HTTPError` + :exc:`~zhmcclient.ParseError` + :exc:`~zhmcclient.AuthError` + :exc:`~zhmcclient.ConnectionError` + :exc:`~zhmcclient.OperationTimeout`: The timeout expired while + waiting for completion of the operation. + """ + # pylint: disable=protected-access + result = self.manager.session.post( + uri=f'{self.uri}/operations/modify', + resource=self, + body=properties, + wait_for_completion=True) + + for op_result in result['operation-results']: + if op_result['operation-status'] \ + not in ("attached", "detached"): + raise PartitionLinkError(result['operation-results']) + + is_rename = self.manager._name_prop in properties + if is_rename: + # Delete the old name from the cache + self.manager._name_uri_cache.delete(self.name) + self.update_properties_local(copy.deepcopy(properties)) + if is_rename: + # Add the new name to the cache + self.manager._name_uri_cache.update(self.name, self.uri) + + def dump(self): + """ + Dump this PartitionLink resource with its properties as a resource + definition. + + The returned resource definition has the following format:: + + { + # Resource properties: + "properties": {...}, + } + + Returns: + + dict: Resource definition of this resource. + """ + + # Dump the resource properties + resource_dict = super().dump() + + return resource_dict diff --git a/zhmcclient/_session.py b/zhmcclient/_session.py index 7a5ee288..e264ec1f 100644 --- a/zhmcclient/_session.py +++ b/zhmcclient/_session.py @@ -1277,6 +1277,14 @@ def post(self, uri, resource=None, body=None, logon_required=True, HMC operation for a description of the members of the returned JSON object. + If the HMC operation response has a "Location" header, it is the + URI of the newly created resource. If the response does not + have an 'object-uri' or 'element-uri' field, this method adds the + URI from the "Location" header field to the response as the + 'location-uri' field. This is needed e.g. for the + "Create Partition Link" operation, because that operation does not + return the new URI in any response field. + * For asynchronous HMC operations with `wait_for_completion=False`: If this method returns, the asynchronous execution of the HMC @@ -1392,10 +1400,22 @@ def post(self, uri, resource=None, body=None, logon_required=True, # This is the most common case to return 202: An # asynchronous job has been started. result_object = _result_object(result) + try: + location_uri = result.headers['Location'] + except KeyError: + location_uri = None job_uri = result_object['job-uri'] job = Job(self, job_uri, 'POST', uri) if wait_for_completion: - return job.wait_for_completion(operation_timeout) + result = job.wait_for_completion(operation_timeout) + # The following addition of 'location-uri' from the + # Location header is for cases where a create operation + # does not return the new URI in the response. For example, + # the "Create Partition Link" operation does that. + if location_uri and 'element-uri' not in result and \ + 'object-uri' not in result: + result['location-uri'] = location_uri + return result return job if result.status_code == 403: diff --git a/zhmcclient/_utils.py b/zhmcclient/_utils.py index d3608a89..51483418 100644 --- a/zhmcclient/_utils.py +++ b/zhmcclient/_utils.py @@ -61,6 +61,7 @@ RC_VIRTUAL_TAPE_RESOURCE = 'virtual-tape-resource' RC_TAPE_LINK = 'tape-link' RC_TAPE_LIBRARY = 'tape-library' +RC_PARTITION_LINK = 'partition-link' RC_CERTIFICATE = 'certificate' # # For CPCs in classic mode: @@ -102,6 +103,7 @@ RC_USER, RC_LDAP_SERVER_DEFINITION, RC_CPC, # For unmanaged CPCs + RC_PARTITION_LINK, ) # Resource classes that are children of zhmcclient.Client (= top level) RC_CHILDREN_CLIENT = ( @@ -136,6 +138,7 @@ RC_VIRTUAL_TAPE_RESOURCE, RC_TAPE_LINK, RC_TAPE_LIBRARY, + RC_PARTITION_LINK, RC_RESET_ACTIVATION_PROFILE, RC_IMAGE_ACTIVATION_PROFILE, RC_LOAD_ACTIVATION_PROFILE,