From f74db6925de740a13c4879203a6899f9c3e7ef78 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 | 254 ++++++ tests/end2end/utils.py | 19 + zhmcclient/__init__.py | 1 + zhmcclient/_console.py | 13 + zhmcclient/_exceptions.py | 64 +- zhmcclient/_partition_link.py | 752 ++++++++++++++++++ zhmcclient/_session.py | 22 +- zhmcclient/_utils.py | 3 + 12 files changed, 1318 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..fe07bb5c --- /dev/null +++ b/tests/end2end/test_partition_link.py @@ -0,0 +1,254 @@ +# 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 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 + +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 = [] + + +@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 FICON-type 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) diff --git a/tests/end2end/utils.py b/tests/end2end/utils.py index a2dc9fb3..37001946 100644 --- a/tests/end2end/utils.py +++ b/tests/end2end/utils.py @@ -632,6 +632,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..9c95ea92 --- /dev/null +++ b/zhmcclient/_partition_link.py @@ -0,0 +1,752 @@ +# 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`. + + 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. + :exc:`~zhmcclient.PartitionLinkError`: Some partition link operations + have failed or are pending an SE restart. + """ + if properties is None: + properties = {} + + result = self.session.post( + uri=self._base_uri, + body=properties, + wait_for_completion=True) + + for op_result in result['operation-results']: + if op_result['operation-status'] != "attached": + raise PartitionLinkError(result['operation-results']) + + # 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): TBD + + 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) + # properties: + # 'devices-per-path' + # 'added-paths' + else: + bc = { + 'partition-uri': partition.uri, + } + bc.update(properties) + # properties: + # 'number-of-nics' + # 'nics' + 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,