././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1623327804.753634 zhmcclient-0.31.0/0000755000076500000240000000000000000000000014315 5ustar00maierastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/LICENSE0000644000076500000240000002363700000000000015335 0ustar00maierastaff00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623327803.0 zhmcclient-0.31.0/MANIFEST.in0000644000076500000240000000340600000000000016056 0ustar00maierastaff00000000000000# MANIFEST.in file generated by Makefile - DO NOT EDIT!! include setup.py include LICENSE include README.rst include requirements.txt include zhmcclient/__init__.py include zhmcclient/_activation_profile.py include zhmcclient/_adapter.py include zhmcclient/_capacity_group.py include zhmcclient/_client.py include zhmcclient/_console.py include zhmcclient/_constants.py include zhmcclient/_cpc.py include zhmcclient/_exceptions.py include zhmcclient/_hba.py include zhmcclient/_ldap_server_definition.py include zhmcclient/_logging.py include zhmcclient/_lpar.py include zhmcclient/_manager.py include zhmcclient/_metrics.py include zhmcclient/_nic.py include zhmcclient/_notification.py include zhmcclient/_partition.py include zhmcclient/_password_rule.py include zhmcclient/_port.py include zhmcclient/_resource.py include zhmcclient/_session.py include zhmcclient/_storage_group.py include zhmcclient/_storage_group_template.py include zhmcclient/_storage_volume.py include zhmcclient/_storage_volume_template.py include zhmcclient/_task.py include zhmcclient/_timestats.py include zhmcclient/_unmanaged_cpc.py include zhmcclient/_user.py include zhmcclient/_user_pattern.py include zhmcclient/_user_role.py include zhmcclient/_utils.py include zhmcclient/_version.py include zhmcclient/_virtual_function.py include zhmcclient/_virtual_storage_resource.py include zhmcclient/_virtual_switch.py include zhmcclient/debuginfo.py include zhmcclient/testutils/__init__.py include zhmcclient/testutils/cpc_fixtures.py include zhmcclient/testutils/hmc_definition_fixtures.py include zhmcclient/testutils/hmc_definitions.py include zhmcclient_mock/__init__.py include zhmcclient_mock/_hmc.py include zhmcclient_mock/_idpool.py include zhmcclient_mock/_session.py include zhmcclient_mock/_urihandler.py ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1623327804.7533324 zhmcclient-0.31.0/PKG-INFO0000644000076500000240000001725000000000000015417 0ustar00maierastaff00000000000000Metadata-Version: 2.1 Name: zhmcclient Version: 0.31.0 Summary: A pure Python client library for the IBM Z HMC Web Services API. Home-page: https://github.com/zhmcclient/python-zhmcclient Author: Juergen Leopold, Andreas Maier Author-email: leopoldj@de.ibm.com, maiera@de.ibm.com Maintainer: Andreas Maier, Kathir Velusamy Maintainer-email: maiera@de.ibm.com, kathir.velu@in.ibm.com License: Apache License, Version 2.0 Project-URL: Bug Tracker, https://github.com/zhmcclient/python-zhmcclient/issues Project-URL: Documentation, https://python-zhmcclient.readthedocs.io/en/stable/ Project-URL: Change Log, https://python-zhmcclient.readthedocs.io/en/stable/changes.html Platform: any Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* Description-Content-Type: text/x-rst License-File: LICENSE .. Copyright 2016-2017 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. .. zhmcclient - A pure Python client library for the IBM Z HMC Web Services API ============================================================================ .. PyPI download statistics are broken, but the new PyPI warehouse makes PyPI .. download statistics available through Google BigQuery .. (https://bigquery.cloud.google.com). .. Query to list package downloads by version: .. SELECT file.project, file.version, COUNT(*) as total_downloads, SUM(CASE WHEN REGEXP_EXTRACT(details.python, r"^([^\.]+\.[^\.]+)") = "2.6" THEN 1 ELSE 0 END) as py26_downloads, SUM(CASE WHEN REGEXP_EXTRACT(details.python, r"^([^\.]+\.[^\.]+)") = "2.7" THEN 1 ELSE 0 END) as py27_downloads, SUM(CASE WHEN REGEXP_EXTRACT(details.python, r"^([^\.]+)\.[^\.]+") = "3" THEN 1 ELSE 0 END) as py3_downloads, FROM TABLE_DATE_RANGE( [the-psf:pypi.downloads], TIMESTAMP("19700101"), CURRENT_TIMESTAMP() ) WHERE file.project = 'zhmcclient' GROUP BY file.project, file.version ORDER BY file.version DESC .. image:: https://img.shields.io/pypi/v/zhmcclient.svg :target: https://pypi.python.org/pypi/zhmcclient/ :alt: Version on Pypi .. # .. image:: https://img.shields.io/pypi/dm/zhmcclient.svg .. # :target: https://pypi.python.org/pypi/zhmcclient/ .. # :alt: Pypi downloads .. image:: https://github.com/zhmcclient/python-zhmcclient/workflows/test/badge.svg?branch=master :target: https://github.com/zhmcclient/python-zhmcclient/actions/ :alt: Actions status .. image:: https://readthedocs.org/projects/python-zhmcclient/badge/?version=latest :target: https://readthedocs.org/projects/python-zhmcclient/builds/ :alt: ReadTheDocs status .. image:: https://coveralls.io/repos/github/zhmcclient/python-zhmcclient/badge.svg?branch=master :target: https://coveralls.io/github/zhmcclient/python-zhmcclient?branch=master :alt: Coveralls status .. image:: https://codeclimate.com/github/zhmcclient/python-zhmcclient/badges/gpa.svg :target: https://codeclimate.com/github/zhmcclient/python-zhmcclient :alt: CodeClimate status .. contents:: Contents: :local: Overview ======== The zhmcclient package is a client library written in pure Python that interacts with the Web Services API of the Hardware Management Console (HMC) of `IBM Z`_ or `LinuxONE`_ machines. The goal of this package is to make the HMC Web Services API easily consumable for Python programmers. .. _IBM Z: http://www.ibm.com/systems/z/ .. _LinuxONE: http://www.ibm.com/systems/linuxone/ The HMC Web Services API is the access point for any external tools to manage the IBM Z or LinuxONE platform. It supports management of the lifecycle and configuration of various platform resources, such as partitions, CPU, memory, virtual switches, I/O adapters, and more. The zhmcclient package encapsulates both protocols supported by the HMC Web Services API: * REST over HTTPS for request/response-style operations driven by the client. Most of these operations complete synchronously, but some long-running tasks complete asynchronously. * JMS (Java Messaging Services) for notifications from the HMC to the client. This can be used to be notified about changes in the system, or about completion of asynchronous tasks started using REST. Installation ============ The quick way: .. code-block:: bash $ pip install zhmcclient For more details, see the `Installation section`_ in the documentation. .. _Installation section: http://python-zhmcclient.readthedocs.io/en/latest/intro.html#installation Quickstart =========== The following example code lists the machines (CPCs) managed by an HMC: .. code-block:: python #!/usr/bin/env python import zhmcclient import requests.packages.urllib3 requests.packages.urllib3.disable_warnings() # Set these variables for your environment: hmc_host = "" hmc_userid = "" hmc_password = "" session = zhmcclient.Session(hmc_host, hmc_userid, hmc_password) client = zhmcclient.Client(session) cpcs = client.cpcs.list() for cpc in cpcs: print(cpc) Possible output when running the script: .. code-block:: text Cpc(name=P000S67B, object-uri=/api/cpcs/fa1f2466-12df-311a-804c-4ed2cc1d6564, status=service-required) Documentation and Change Log ============================ For the latest released version on PyPI: * `Documentation`_ * `Change log`_ .. _Documentation: http://python-zhmcclient.readthedocs.io/en/latest/ .. _Change log: http://python-zhmcclient.readthedocs.io/en/latest/changes.html zhmc CLI ======== Before version 0.18.0 of the zhmcclient package, it contained the zhmc CLI. Starting with zhmcclient version 0.18.0, the zhmc CLI has been moved from this project into the new `zhmccli project`_. If your project uses the zhmc CLI, and you are upgrading the zhmcclient package from before 0.18.0 to 0.18.0 or later, your project will need to add the `zhmccli package`_ to its dependencies. .. _zhmccli project: https://github.com/zhmcclient/zhmccli .. _zhmccli package: https://pypi.python.org/pypi/zhmccli Contributing ============ For information on how to contribute to this project, see the `Development section`_ in the documentation. .. _Development section: http://python-zhmcclient.readthedocs.io/en/latest/development.html License ======= The zhmcclient package is licensed under the `Apache 2.0 License`_. .. _Apache 2.0 License: https://github.com/zhmcclient/python-zhmcclient/tree/master/LICENSE ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1621405388.0 zhmcclient-0.31.0/README.rst0000644000076500000240000001413400000000000016007 0ustar00maierastaff00000000000000.. Copyright 2016-2017 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. .. zhmcclient - A pure Python client library for the IBM Z HMC Web Services API ============================================================================ .. PyPI download statistics are broken, but the new PyPI warehouse makes PyPI .. download statistics available through Google BigQuery .. (https://bigquery.cloud.google.com). .. Query to list package downloads by version: .. SELECT file.project, file.version, COUNT(*) as total_downloads, SUM(CASE WHEN REGEXP_EXTRACT(details.python, r"^([^\.]+\.[^\.]+)") = "2.6" THEN 1 ELSE 0 END) as py26_downloads, SUM(CASE WHEN REGEXP_EXTRACT(details.python, r"^([^\.]+\.[^\.]+)") = "2.7" THEN 1 ELSE 0 END) as py27_downloads, SUM(CASE WHEN REGEXP_EXTRACT(details.python, r"^([^\.]+)\.[^\.]+") = "3" THEN 1 ELSE 0 END) as py3_downloads, FROM TABLE_DATE_RANGE( [the-psf:pypi.downloads], TIMESTAMP("19700101"), CURRENT_TIMESTAMP() ) WHERE file.project = 'zhmcclient' GROUP BY file.project, file.version ORDER BY file.version DESC .. image:: https://img.shields.io/pypi/v/zhmcclient.svg :target: https://pypi.python.org/pypi/zhmcclient/ :alt: Version on Pypi .. # .. image:: https://img.shields.io/pypi/dm/zhmcclient.svg .. # :target: https://pypi.python.org/pypi/zhmcclient/ .. # :alt: Pypi downloads .. image:: https://github.com/zhmcclient/python-zhmcclient/workflows/test/badge.svg?branch=master :target: https://github.com/zhmcclient/python-zhmcclient/actions/ :alt: Actions status .. image:: https://readthedocs.org/projects/python-zhmcclient/badge/?version=latest :target: https://readthedocs.org/projects/python-zhmcclient/builds/ :alt: ReadTheDocs status .. image:: https://coveralls.io/repos/github/zhmcclient/python-zhmcclient/badge.svg?branch=master :target: https://coveralls.io/github/zhmcclient/python-zhmcclient?branch=master :alt: Coveralls status .. image:: https://codeclimate.com/github/zhmcclient/python-zhmcclient/badges/gpa.svg :target: https://codeclimate.com/github/zhmcclient/python-zhmcclient :alt: CodeClimate status .. contents:: Contents: :local: Overview ======== The zhmcclient package is a client library written in pure Python that interacts with the Web Services API of the Hardware Management Console (HMC) of `IBM Z`_ or `LinuxONE`_ machines. The goal of this package is to make the HMC Web Services API easily consumable for Python programmers. .. _IBM Z: http://www.ibm.com/systems/z/ .. _LinuxONE: http://www.ibm.com/systems/linuxone/ The HMC Web Services API is the access point for any external tools to manage the IBM Z or LinuxONE platform. It supports management of the lifecycle and configuration of various platform resources, such as partitions, CPU, memory, virtual switches, I/O adapters, and more. The zhmcclient package encapsulates both protocols supported by the HMC Web Services API: * REST over HTTPS for request/response-style operations driven by the client. Most of these operations complete synchronously, but some long-running tasks complete asynchronously. * JMS (Java Messaging Services) for notifications from the HMC to the client. This can be used to be notified about changes in the system, or about completion of asynchronous tasks started using REST. Installation ============ The quick way: .. code-block:: bash $ pip install zhmcclient For more details, see the `Installation section`_ in the documentation. .. _Installation section: http://python-zhmcclient.readthedocs.io/en/latest/intro.html#installation Quickstart =========== The following example code lists the machines (CPCs) managed by an HMC: .. code-block:: python #!/usr/bin/env python import zhmcclient import requests.packages.urllib3 requests.packages.urllib3.disable_warnings() # Set these variables for your environment: hmc_host = "" hmc_userid = "" hmc_password = "" session = zhmcclient.Session(hmc_host, hmc_userid, hmc_password) client = zhmcclient.Client(session) cpcs = client.cpcs.list() for cpc in cpcs: print(cpc) Possible output when running the script: .. code-block:: text Cpc(name=P000S67B, object-uri=/api/cpcs/fa1f2466-12df-311a-804c-4ed2cc1d6564, status=service-required) Documentation and Change Log ============================ For the latest released version on PyPI: * `Documentation`_ * `Change log`_ .. _Documentation: http://python-zhmcclient.readthedocs.io/en/latest/ .. _Change log: http://python-zhmcclient.readthedocs.io/en/latest/changes.html zhmc CLI ======== Before version 0.18.0 of the zhmcclient package, it contained the zhmc CLI. Starting with zhmcclient version 0.18.0, the zhmc CLI has been moved from this project into the new `zhmccli project`_. If your project uses the zhmc CLI, and you are upgrading the zhmcclient package from before 0.18.0 to 0.18.0 or later, your project will need to add the `zhmccli package`_ to its dependencies. .. _zhmccli project: https://github.com/zhmcclient/zhmccli .. _zhmccli package: https://pypi.python.org/pypi/zhmccli Contributing ============ For information on how to contribute to this project, see the `Development section`_ in the documentation. .. _Development section: http://python-zhmcclient.readthedocs.io/en/latest/development.html License ======= The zhmcclient package is licensed under the `Apache 2.0 License`_. .. _Apache 2.0 License: https://github.com/zhmcclient/python-zhmcclient/tree/master/LICENSE ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/requirements.txt0000644000076500000240000000311400000000000017600 0ustar00maierastaff00000000000000# Pip requirements file for zhmcclient runtime dependencies. # # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. # Make sure that the package versions in minimum-constraints.txt are also # the minimum versions required in requirements.txt and dev-requirements.txt. # Direct dependencies (except pip, setuptools, wheel): # decorator 5.0.0 removed support for Python 2.7 and 3.4 decorator>=4.0.11,<5.0; python_version <= '3.4' # new BSD decorator>=4.0.11; python_version >= '3.5' # new BSD pytz>=2016.10 # MIT # requests 2.22.0 removed support for Python 3.4 requests>=2.20.1; python_version == '2.7' # Apache-2.0 requests>=2.20.1,<2.22.0; python_version == '3.4' # Apache-2.0 requests>=2.20.1; python_version >= '3.5' # Apache-2.0 six>=1.14.0 # MIT # stomp.py 5.0.0 (now deleted) and 6.0.0 removed support for Python 2.7, 3.4 and 3.5 # stomp.py 6.1.0 on Pypi contained older code than v6.1.0 in the repo -> will be yanked on Pypi # stomp.py 6.1.1 broke compatibility -> will be yanked on Pypi and re-released as 7.0.0 stomp.py>=4.1.23,<5.0.0; python_version <= '3.5' # Apache stomp.py>=4.1.23,<7.0.0,!=6.1.0,!=6.1.1; python_version >= '3.6' # Apache immutable-views>=0.6.0 # Indirect dependencies (commented out, only listed to document their license): # certifi # ISC, from requests>=2.20 # chardet # LGPL, from requests>=2.20 # docopt # MIT, from stomp.py>=4.1 # idna # BSD-like, from requests>=2.20 # urllib3 # MIT, from requests>=2.20 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1623327804.7537456 zhmcclient-0.31.0/setup.cfg0000644000076500000240000000004600000000000016136 0ustar00maierastaff00000000000000[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1621405388.0 zhmcclient-0.31.0/setup.py0000755000076500000240000001172100000000000016034 0ustar00maierastaff00000000000000#!/usr/bin/env python # Copyright 2016-2019 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. """ Setup script for zhmcclient project. """ import os import re import setuptools def get_version(version_file): """ Execute the specified version file and return the value of the __version__ global variable that is set in the version file. Note: Make sure the version file does not depend on any packages in the requirements list of this package (otherwise it cannot be executed in a fresh Python environment). """ with open(version_file, 'r') as fp: version_source = fp.read() _globals = {} exec(version_source, _globals) # pylint: disable=exec-used return _globals['__version__'] def get_requirements(requirements_file): """ Parse the specified requirements file and return a list of its non-empty, non-comment lines. The returned lines are without any trailing newline characters. """ with open(requirements_file, 'r') as fp: lines = fp.readlines() reqs = [] for line in lines: line = line.strip('\n') if not line.startswith('#') and line != '': reqs.append(line) return reqs def read_file(a_file): """ Read the specified file and return its content as one string. """ with open(a_file, 'r') as fp: content = fp.read() return content # pylint: disable=invalid-name requirements = get_requirements('requirements.txt') install_requires = [req for req in requirements if req and not re.match(r'[^:]+://', req)] dependency_links = [req for req in requirements if req and re.match(r'[^:]+://', req)] package_version = get_version(os.path.join('zhmcclient', '_version.py')) # Docs on setup(): # * https://docs.python.org/2.7/distutils/apiref.html? # highlight=setup#distutils.core.setup # * https://setuptools.readthedocs.io/en/latest/setuptools.html# # new-and-changed-setup-keywords # Explanations for the behavior of package_data, include_package_data, and # MANIFEST files: # * https://setuptools.readthedocs.io/en/latest/setuptools.html# # including-data-files # * https://stackoverflow.com/a/11848281/1424462 # * https://stackoverflow.com/a/14159430/1424462 setuptools.setup( name='zhmcclient', version=package_version, packages=[ 'zhmcclient', 'zhmcclient_mock' ], include_package_data=True, # Includes MANIFEST.in files into sdist (only) install_requires=install_requires, dependency_links=dependency_links, description='' 'A pure Python client library for the IBM Z HMC Web Services API.', long_description=read_file('README.rst'), long_description_content_type='text/x-rst', license='Apache License, Version 2.0', author='Juergen Leopold, Andreas Maier', author_email='leopoldj@de.ibm.com, maiera@de.ibm.com', maintainer='Andreas Maier, Kathir Velusamy', maintainer_email='maiera@de.ibm.com, kathir.velu@in.ibm.com', url='https://github.com/zhmcclient/python-zhmcclient', project_urls={ 'Bug Tracker': 'https://github.com/zhmcclient/python-zhmcclient/issues', 'Documentation': 'https://python-zhmcclient.readthedocs.io/en/stable/', 'Change Log': 'https://python-zhmcclient.readthedocs.io/en/stable/changes.html', }, options={'bdist_wheel': {'universal': True}}, zip_safe=True, # This package can safely be installed from a zip file platforms='any', # Keep these Python versions in sync with: # - Section "Supported environments" in docs/intro.rst # - Version checking in zhmcclient/_version.py python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1623327804.7474697 zhmcclient-0.31.0/zhmcclient/0000755000076500000240000000000000000000000016455 5ustar00maierastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1617707270.0 zhmcclient-0.31.0/zhmcclient/__init__.py0000644000076500000240000000472100000000000020572 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ zhmcclient - A pure Python client library for the IBM Z HMC Web Services API. For documentation, see TODO: Add link to RTD once available. """ from __future__ import absolute_import from ._version import * # noqa: F401 from ._constants import * # noqa: F401 from ._exceptions import * # noqa: F401 pylint: disable=redefined-builtin from ._manager import * # noqa: F401 from ._resource import * # noqa: F401 from ._logging import * # noqa: F401 from ._session import * # noqa: F401 from ._timestats import * # noqa: F401 from ._client import * # noqa: F401 from ._cpc import * # noqa: F401 from ._lpar import * # noqa: F401 from ._partition import * # noqa: F401 from ._activation_profile import * # noqa: F401 from ._adapter import * # noqa: F401 from ._nic import * # noqa: F401 from ._hba import * # noqa: F401 from ._virtual_function import * # noqa: F401 from ._virtual_switch import * # noqa: F401 from ._port import * # noqa: F401 from ._notification import * # noqa: F401 from ._metrics import * # noqa: F401 from ._utils import * # noqa: F401 from ._console import * # noqa: F401 from ._user import * # noqa: F401 from ._user_role import * # noqa: F401 from ._user_pattern import * # noqa: F401 from ._password_rule import * # noqa: F401 from ._task import * # noqa: F401 from ._ldap_server_definition import * # noqa: F401 from ._unmanaged_cpc import * # noqa: F401 from ._storage_group import * # noqa: F401 from ._storage_volume import * # noqa: F401 from ._virtual_storage_resource import * # noqa: F401 from ._storage_group_template import * # noqa: F401 from ._storage_volume_template import * # noqa: F401 from ._capacity_group import * # noqa: F401 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_activation_profile.py0000644000076500000240000002272500000000000023057 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ An :term:`Activation Profile` controls the activation of a :term:`CPC` or :term:`LPAR`. They are used to tailor the operation of a CPC and are stored in the Support Element associated with the CPC. Activation Profile resources are contained in CPC resources. Activation Profile resources only exist in CPCs that are not in DPM mode. TODO: If Reset Activation Profiles are used to determine the CPC mode, should they not exist in all CPC modes? There are three types of Activation Profiles: 1. Reset: The Reset Activation Profile defines for a CPC the mode in which the CPC licensed internal code will be loaded (e.g. DPM mode or classic mode) and how much central storage and expanded storage will be used. 2. Image: For CPCs in classic mode, each LPAR can have an Image Activation Profile. The Image Activation Profile determines the number of CPs that the LPAR will use and whether these CPs will be dedicated to the LPAR or shared. It also allows assigning the amount of central storage and expanded storage that will be used by each LPAR. 3. Load: For CPCs in classic mode, each LPAR can have a Load Activation Profile. The Load Activation Profile defines the channel address of the device that the operating system for that LPAR will be loaded (booted) from. """ from __future__ import absolute_import import copy from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['ActivationProfileManager', 'ActivationProfile'] class ActivationProfileManager(BaseManager): """ Manager providing access to the :term:`Activation Profiles ` of a particular type in a particular :term:`CPC` (the scoping CPC). Possible types of activation profiles are: * Reset Activation Profile * Image Activation Profile * Load Activation Profile 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 variables of a :class:`~zhmcclient.Cpc` object (in classic mode or ensemble mode): * :attr:`~zhmcclient.Cpc.reset_activation_profiles` * :attr:`~zhmcclient.Cpc.image_activation_profiles` * :attr:`~zhmcclient.Cpc.load_activation_profiles` """ def __init__(self, cpc, profile_type): # This function should not go into the docs. # Parameters: # cpc (:class:`~zhmcclient.Cpc`): # CPC defining the scope for this manager. # profile_type (string): # Type of Activation Profiles: # * `reset`: Reset Activation Profiles # * `image`: Image Activation Profiles # * `load`: Load Activation Profiles # 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 = [ 'name', ] super(ActivationProfileManager, self).__init__( resource_class=ActivationProfile, class_name='{}-activation-profile'.format(profile_type), session=cpc.manager.session, parent=cpc, base_uri='{}/{}-activation-profiles'.format(cpc.uri, profile_type), oid_prop='name', # This is an exception! uri_prop='element-uri', name_prop='name', query_props=query_props) self._profile_type = profile_type @property def cpc(self): """ :class:`~zhmcclient.Cpc`: :term:`CPC` defining the scope for this manager. """ return self._parent @property def profile_type(self): """ :term:`string`: Type of the Activation Profiles managed by this object: * ``'reset'`` - Reset Activation Profiles * ``'image'`` - Image Activation Profiles * ``'load'`` - Load Activation Profiles """ return self._profile_type @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the Activation Profiles of this CPC, of the profile type managed by this object. Authorization requirements: * Object-access permission to this CPC. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.ActivationProfile` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] resource_obj = self._try_optimized_lookup(filter_args) if resource_obj: resource_obj_list.append(resource_obj) # It already has full properties else: query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = self._profile_type + '-activation-profiles' uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list class ActivationProfile(BaseResource): """ Representation of an :term:`Activation Profile` of a particular type. 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.ActivationProfileManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.ActivationProfileManager`): # 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, ActivationProfileManager), \ "ActivationProfile init: Expected manager type %s, got %s" % \ (ActivationProfileManager, type(manager)) super(ActivationProfile, self).__init__(manager, uri, name, properties) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this Activation Profile. Authorization requirements: * Object-access permission to the CPC of this Activation Profile. * Task permission for the "Customize/Delete Activation Profiles" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model' in section ' activation profile' in the :term:`HMC API` book, where is the profile type of this object (e.g. Reset, Load, Image). Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) # Attempts to change the 'name' property will be rejected by the HMC, # so we don't need to update the name-to-URI cache. assert self.manager._name_prop not in properties self._properties.update(copy.deepcopy(properties)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_adapter.py0000644000076500000240000005172400000000000020617 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ An :term:`Adapter` is a physical adapter card (e.g. OSA-Express adapter, Crypto adapter) or a logical adapter (e.g. HiperSockets switch). Adapter resources are contained in :term:`CPC` resources. Adapters only exist in CPCs that are in DPM mode. There are four types of Adapters: 1. Network Adapters: Network adapters enable communication through different networking transport protocols. These network adapters are OSA-Express, HiperSockets and RoCE-Express. DPM automatically discovers OSA-Express and RoCE-Express adapters because they are physical cards that are installed on the CPC. In contrast, HiperSockets are logical adapters and must be created and configured by an administrator using the 'Create Hipersocket' operation (see create_hipersocket()). Network Interface Cards (NICs) provide a partition with access to networks. Each NIC represents a unique connection between the partition and a specific network adapter. 2. Storage Adapters: Fibre Channel connections provide high-speed connections between CPCs and storage devices. DPM automatically discovers any storage adapters installed on the CPC. Host bus adapters (HBAs) provide a partition with access to external storage area networks (SANs) and devices that are connected to a CPC. Each HBA represents a unique connection between the partition and a specific storage adapter. 3. Accelerator Adapters: Accelerator adapters provide specialized functions to improve performance or use of computer resource like the IBM System z Enterprise Data Compression (zEDC) feature. DPM automatically discovers accelerators that are installed on the CPC. An accelerator virtual function provides a partition with access to zEDC features that are installed on a CPC. Each virtual function represents a unique connection between the partition and a physical feature card. 4. Crypto Adapters: Crypto adapters provide cryptographic processing functions. DPM automatically discovers cryptographic features that are installed on the CPC. """ from __future__ import absolute_import import copy from ._manager import BaseManager from ._resource import BaseResource from ._port import PortManager from ._logging import logged_api_call from ._utils import repr_dict, repr_manager, repr_timestamp __all__ = ['AdapterManager', 'Adapter'] class AdapterManager(BaseManager): """ Manager providing access to the :term:`Adapters ` in a particular :term:`CPC`. 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 of a :class:`~zhmcclient.Cpc` object (in DPM mode): * :attr:`~zhmcclient.Cpc.adapters` """ def __init__(self, cpc): # This function should not go into the docs. # Parameters: # cpc (:class:`~zhmcclient.Cpc`): # CPC 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 = [ 'name', # The adapter-id property is supported for filtering, but due to # a firmware defect, adapters with a hex digit in their adapter-id # property are not found. Disabling the property causes it to # be handled via client-side filtering, so that mitigates the # defect. # TODO: Re-enable the property once the defect is fixed and the fix # is rolled out broadly enough. # 'adapter-id', 'adapter-family', 'type', 'status', ] super(AdapterManager, self).__init__( resource_class=Adapter, class_name='adapter', session=cpc.manager.session, parent=cpc, base_uri='/api/adapters', oid_prop='object-id', uri_prop='object-uri', name_prop='name', query_props=query_props) @property def cpc(self): """ :class:`~zhmcclient.Cpc`: :term:`CPC` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the Adapters in this CPC. Authorization requirements: * Object-access permission to this CPC. * Object-access permission to any Adapter to be included in the result. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.Adapter` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] resource_obj = self._try_optimized_lookup(filter_args) if resource_obj: resource_obj_list.append(resource_obj) # It already has full properties else: query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'adapters' uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create_hipersocket(self, properties): """ Create and configure a HiperSockets Adapter in this CPC. Authorization requirements: * Object-access permission to the scoping CPC. * Task permission to the "Create HiperSockets Adapter" task. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create Hipersocket' in the :term:`HMC API` book. Returns: :class:`~zhmcclient.Adapter`: The resource object for the new HiperSockets Adapter. The object will have its 'object-uri' property set as returned by the HMC, and will also have the input properties set. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.session.post(self.cpc.uri + '/adapters', body=properties) # There should not be overlaps, but just in case there are, the # returned props should overwrite the input props: props = copy.deepcopy(properties) props.update(result) name = props.get(self._name_prop, None) uri = props[self._uri_prop] adapter = Adapter(self, uri, name, props) self._name_uri_cache.update(name, uri) return adapter class Adapter(BaseResource): """ Representation of an :term:`Adapter`. Derived from :class:`~zhmcclient.BaseResource`; see there for common methods and attributes. For the properties of an Adapter, see section 'Data model' in section 'Adapter object' in the :term:`HMC API` book. 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.AdapterManager`). """ # Name of property for port URIs, dependent on adapter family port_uris_prop_by_family = { 'ficon': 'storage-port-uris', 'osa': 'network-port-uris', 'roce': 'network-port-uris', 'hipersockets': 'network-port-uris', 'cna': 'network-port-uris', 'cloud-network': 'network-port-uris', # for preliminary driver } # URI segment for port URIs, dependent on adapter family port_uri_segment_by_family = { 'ficon': 'storage-ports', 'osa': 'network-ports', 'roce': 'network-ports', 'hipersockets': 'network-ports', 'cna': 'network-ports', 'cloud-network': 'network-ports', # for preliminary driver } # Port type, dependent on adapter family port_type_by_family = { 'ficon': 'storage', 'osa': 'network', 'roce': 'network', 'hipersockets': 'network', 'cna': 'network', 'cloud-network': 'network', # for preliminary driver } def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.AdapterManager`): # 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, AdapterManager), \ "Adapter init: Expected manager type %s, got %s" % \ (AdapterManager, type(manager)) super(Adapter, self).__init__(manager, uri, name, properties) # The manager objects for child resources (with lazy initialization): self._ports = None self._port_uris_prop = None self._port_uri_segment = None @property def ports(self): """ :class:`~zhmcclient.PortManager`: Access to the :term:`Ports ` of this Adapter. """ # We do here some lazy loading. if not self._ports: family = self.get_property('adapter-family') try: port_type = self.port_type_by_family[family] except KeyError: port_type = None self._ports = PortManager(self, port_type) return self._ports @property def port_uris_prop(self): """ :term:`string`: Name of adapter property that specifies the adapter port URIs, or the empty string ('') for adapters without ports. For example, 'network-port-uris' for a network adapter. """ if self._port_uris_prop is None: family = self.get_property('adapter-family') try: self._port_uris_prop = self.port_uris_prop_by_family[family] except KeyError: self._port_uris_prop = '' return self._port_uris_prop @property def port_uri_segment(self): """ :term:`string`: Adapter type specific URI segment for adapter port URIs, or the empty string ('') for adapters without ports. For example, 'network-ports' for a network adapter. """ if self._port_uri_segment is None: family = self.get_property('adapter-family') try: self._port_uri_segment = self.port_uri_segment_by_family[ family] except KeyError: self._port_uri_segment = '' return self._port_uri_segment @property @logged_api_call def maximum_crypto_domains(self): """ Integer: The maximum number of crypto domains on this crypto adapter. The following table shows the maximum number of crypto domains for crypto adapters supported on IBM Z machine generations in DPM mode. The corresponding LinuxONE machine generations are listed in the notes below the table: ================= ========================= =============== Adapter type Machine generations Maximum domains ================= ========================= =============== Crypto Express 5S z14 (3) / z13 (1) 85 Crypto Express 5S z14-ZR1 (4) / z13s (2) 40 Crypto Express 6S z14 (3) 85 Crypto Express 6S z14-ZR1 (4) 40 ================= ========================= =============== Notes: (1) Supported for z13 and LinuxONE Emperor (2) Supported for z13s and LinuxONE Rockhopper (3) Supported for z14 and LinuxONE Emperor II (4) Supported for z14-ZR1 and LinuxONE Rockhopper II If this adapter is not a crypto adapter, `None` is returned. If the crypto adapter card type is not known, :exc:`ValueError` is raised. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` :exc:`ValueError`: Unknown crypto card type """ if self.get_property('adapter-family') != 'crypto': return None card_type = self.get_property('detected-card-type') if card_type.startswith('crypto-express-'): max_domains = self.manager.cpc.maximum_active_partitions else: raise ValueError("Unknown crypto card type: {!r}". format(card_type)) return max_domains @logged_api_call def delete(self): """ Delete this Adapter. The Adapter must be a HiperSockets Adapter. Authorization requirements: * Object-access permission to the HiperSockets Adapter to be deleted. * Task permission to the "Delete HiperSockets Adapter" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.delete(self.uri) self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this Adapter. Authorization requirements: * Object-access permission to the Adapter. * Task permission for the "Adapter Details" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model' in section 'Adapter object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) 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._properties.update(copy.deepcopy(properties)) if is_rename: # Add the new name to the cache self.manager._name_uri_cache.update(self.name, self.uri) def __repr__(self): """ Return a string with the state of this Adapter, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " _manager={_manager_classname} at 0x{_manager_id:08x},\n" " _uri={_uri!r},\n" " _full_properties={_full_properties!r},\n" " _properties_timestamp={_properties_timestamp},\n" " _properties={_properties},\n" " _ports(lazy)={_ports}\n" ")".format( classname=self.__class__.__name__, id=id(self), _manager_classname=self._manager.__class__.__name__, _manager_id=id(self._manager), _uri=self._uri, _full_properties=self._full_properties, _properties_timestamp=repr_timestamp( self._properties_timestamp), _properties=repr_dict(self._properties, indent=4), _ports=repr_manager(self._ports, indent=2), )) return ret @logged_api_call def change_crypto_type(self, crypto_type, zeroize=None): """ Reconfigures a cryptographic adapter to a different crypto type. This operation is only supported for cryptographic adapters. The cryptographic adapter must be varied offline before its crypto type can be reconfigured. Authorization requirements: * Object-access permission to this Adapter. * Task permission to the "Adapter Details" task. Parameters: crypto_type (:term:`string`): - ``"accelerator"``: Crypto Express5S Accelerator - ``"cca-coprocessor"``: Crypto Express5S CCA Coprocessor - ``"ep11-coprocessor"``: Crypto Express5S EP11 Coprocessor zeroize (bool): Specifies whether the cryptographic adapter will be zeroized when it is reconfigured to a crypto type of ``"accelerator"``. `None` means that the HMC-implemented default of `True` will be used. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = {'crypto-type': crypto_type} if zeroize is not None: body['zeroize'] = zeroize self.manager.session.post( self.uri + '/operations/change-crypto-type', body) @logged_api_call def change_adapter_type(self, adapter_type): """ Reconfigures an adapter from one type to another, or to ungonfigured. Currently, only storage adapters can be reconfigured, and their adapter type is the supported storage protocol (FCP vs. FICON). Storage adapter instances (i.e. :class:`~zhmcclient.Adapter` objects) represent daughter cards on a physical storage card. Current storage cards require both daughter cards to be configured to the same protocol, so changing the type of the targeted adapter will also change the type of the adapter instance that represents the other daughter card on the same physical card. Zhmcclient users that need to determine the related adapter instance can do so by finding the storage adapter with a matching first 9 characters (card ID and slot ID) of their `card-location` property values. The targeted adapter and its related adapter on the same storage card must not already have the desired adapter type, they must not be attached to any partition, and they must not have an adapter status of 'exceptions'. Authorization requirements: * Object-access permission to this Adapter. * Task permission to the "Configure Storage - System Programmer" task. Parameters: adapter_type (:term:`string`): - ``"fcp"``: FCP (Fibre Channel Protocol) - ``"fc"``: FICON (Fibre Connection) protocol - ``"not-configured"``: No adapter type configured Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = {'type': adapter_type} self.manager.session.post( self.uri + '/operations/change-adapter-type', body) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_capacity_group.py0000644000076500000240000002763500000000000022214 0ustar00maierastaff00000000000000# Copyright 2021 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:`Capacity Group` is a group of :term:`partitions ` that allows defining a capping for the total amount of physical processors consumed by the Partitions in the group. The Partitions must be defined with shared processors, and a Partition can be a member of at most one Capacity Group. Capacity Group resources are contained in CPC resources. Capacity Groups only exist in :term:`CPCs ` that are in DPM mode. """ from __future__ import absolute_import import copy from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['CapacityGroupManager', 'CapacityGroup'] class CapacityGroupManager(BaseManager): """ Manager providing access to the :term:`Capacity Groups ` in a particular :term:`CPC`. 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 of a :class:`~zhmcclient.Cpc` object (in DPM mode): * :attr:`~zhmcclient.Cpc.capacity_groups` """ def __init__(self, cpc): # This function should not go into the docs. # Parameters: # cpc (:class:`~zhmcclient.Cpc`): # CPC defining the scope for this manager. super(CapacityGroupManager, self).__init__( resource_class=CapacityGroup, class_name='capacity-group', session=cpc.manager.session, parent=cpc, base_uri='{}/capacity-groups'.format(cpc.uri), oid_prop='element-id', uri_prop='element-uri', name_prop='name', query_props=[], list_has_name=False) @property def cpc(self): """ :class:`~zhmcclient.Cpc`: :term:`CPC` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the Capacity Groups in this CPC. Authorization requirements: * Object-access permission to this CPC. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.CapacityGroup` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] resource_obj = self._try_optimized_lookup(filter_args) if resource_obj: resource_obj_list.append(resource_obj) # It already has full properties else: query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'capacity-groups' uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties): """ Create a Capacity Group in this CPC. The new Capacity Group initially has no partitions. Authorization requirements: * Object-access permission to this CPC. * Task permission to the "Manage Processor Sharing" task. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create Capacity Group' in the :term:`HMC API` book. Returns: CapacityGroup: The resource object for the new Capacity Group. The object will have its 'element-uri' property set as returned by the HMC, and will also have the input properties set. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.session.post(self.cpc.uri + '/capacity-groups', body=properties) # There should not be overlaps, but just in case there are, the # returned props should overwrite the input props: props = copy.deepcopy(properties) props.update(result) name = props.get(self._name_prop, None) uri = props[self._uri_prop] capacity_group = CapacityGroup(self, uri, name, props) self._name_uri_cache.update(name, uri) return capacity_group class CapacityGroup(BaseResource): """ Representation of a :term:`Capacity Group`. Derived from :class:`~zhmcclient.BaseResource`; see there for common methods and attributes. For the properties of a Capacity Group resource, see section 'Data model' in section 'Capacity Group element object' in the :term:`HMC API` book. 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.CapacityGroupManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.CapacityGroupManager`): # 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, CapacityGroupManager), \ "CapacityGroup init: Expected manager type %s, got %s" % \ (CapacityGroupManager, type(manager)) super(CapacityGroup, self).__init__(manager, uri, name, properties) @logged_api_call def delete(self): """ Delete this Capacity Group. The Capacity Group must not contain any Partitions. Authorization requirements: * Object-access permission to the CPC containing this Capacity Group. * Task permission to the "Manage Processor Sharing" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.delete(self._uri) self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this Capacity Group. Authorization requirements: * Object-access permission to the CPC containing this Capacity Group. * Task permission to the "Manage Processor Sharing" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model' in section 'Capacity Group element object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) 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._properties.update(copy.deepcopy(properties)) if is_rename: # Add the new name to the cache self.manager._name_uri_cache.update(self.name, self.uri) @logged_api_call def add_partition(self, partition): """ Add a Partition to this Capacity Group. Upon successful completion, the amount of processing capacity that could be used by this Partition becomes governed by the absolute cap values defined for this Capacity Group. A Partition cannot become a member of more than one Capacity Group. The Partition must be defined with shared processors and the Capacity Group must specify nonzero cap values for the processor types used by the Partition. The Partition must be on the same CPC as the Capacity Group and must not yet be a member of this (or any other) Capacity Group. Authorization requirements: * Object-access permission to the CPC containing this Capacity Group. * Task permission to the "Manage Processor Sharing" task. Parameters: partition (:class:`~zhmcclient.Partition`): The partition to be added. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = { 'partition-uri': partition.uri, } self.manager.session.post( self.uri + '/operations/add-partition', body=body) @logged_api_call def remove_partition(self, partition): """ Remove a Partition from this Capacity Group. Upon successful completion, the amount of processing capacity that could be used by this Partition is no longer governed by the absolute cap values defined for this Capacity Group. The Partition must be a member of this Capacity Group. Authorization requirements: * Object-access permission to the CPC containing this Capacity Group. * Task permission to the "Manage Processor Sharing" task. Parameters: partition (:class:`~zhmcclient.Partition`): The partition to be removed. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = { 'partition-uri': partition.uri, } self.manager.session.post( self.uri + '/operations/remove-partition', body=body) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/_client.py0000644000076500000240000001613700000000000020454 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ Client class: A client to an HMC. """ from __future__ import absolute_import import time from ._cpc import CpcManager from ._console import ConsoleManager from ._metrics import MetricsContextManager from ._logging import logged_api_call from ._exceptions import Error, OperationTimeout __all__ = ['Client'] class Client(object): """ A client to an HMC. This is the main class for users of this package. """ def __init__(self, session): """ Parameters: session (:class:`~zhmcclient.Session`): Session with the HMC. """ self._session = session self._cpcs = CpcManager(self) self._consoles = ConsoleManager(self) self._metrics_contexts = MetricsContextManager(self) self._api_version = None @property def session(self): """ :class:`~zhmcclient.Session`: Session with the HMC. """ return self._session @property def cpcs(self): """ :class:`~zhmcclient.CpcManager`: Manager object for the CPCs in scope of this client. This includes managed and unmanaged CPCs. """ return self._cpcs @property def consoles(self): """ :class:`~zhmcclient.ConsoleManager`: Manager object for the (one) Console representing the HMC this client is connected to. """ return self._consoles @property def metrics_contexts(self): """ :class:`~zhmcclient.MetricsContextManager`: Manager object for the :term:`Metrics Contexts ` in scope of this client (i.e. in scope of its HMC). """ return self._metrics_contexts @logged_api_call def version_info(self): """ Returns API version information for the HMC. This operation does not require authentication. Returns: :term:`HMC API version`: The HMC API version supported by the HMC. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.ConnectionError` """ if self._api_version is None: self.query_api_version() return self._api_version['api-major-version'],\ self._api_version['api-minor-version'] @logged_api_call def query_api_version(self): """ The Query API Version operation returns information about the level of Web Services API supported by the HMC. This operation does not require authentication. Returns: :term:`json object`: A JSON object with members ``api-major-version``, ``api-minor-version``, ``hmc-version`` and ``hmc-name``. For details about these properties, see section 'Response body contents' in section 'Query API Version' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.ConnectionError` """ version_resp = self._session.get('/api/version', logon_required=False) self._api_version = version_resp return self._api_version @logged_api_call def get_inventory(self, resources): """ Returns a JSON object with the requested resources and their properties, that are managed by the HMC. This method performs the 'Get Inventory' HMC operation. Parameters: resources (:term:`iterable` of :term:`string`): Resource classes and/or resource classifiers specifying the types of resources that should be included in the result. For valid values, see the 'Get Inventory' operation in the :term:`HMC API` book. Element resources of the specified resource types are automatically included as children (for example, requesting 'partition' includes all of its 'hba', 'nic' and 'virtual-function' element resources). Must not be `None`. Returns: :term:`JSON object`: The resources with their properties, for the requested resource classes and resource classifiers. Example: resource_classes = ['partition', 'adapter'] result_dict = client.get_inventory(resource_classes) Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.ConnectionError` """ uri = '/api/services/inventory' body = {'resources': resources} result = self.session.post(uri, body=body) return result @logged_api_call def wait_for_available(self, operation_timeout=None): """ Wait for the Console (HMC) this client is connected to, to become available. The Console is considered available if the :meth:`~zhmcclient.Client.query_api_version` method succeeds. If the Console does not become available within the operation timeout, an :exc:`~zhmcclient.OperationTimeout` exception is raised. Parameters: operation_timeout (:term:`number`): Timeout in seconds, when waiting for the Console to become available. 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, a :exc:`~zhmcclient.OperationTimeout` is raised. Raises: :exc:`~zhmcclient.OperationTimeout`: The timeout expired while waiting for the Console to become available. """ if operation_timeout is None: operation_timeout = \ self.session.retry_timeout_config.operation_timeout if operation_timeout > 0: start_time = time.time() while True: try: self.query_api_version() except Error: pass else: break if operation_timeout > 0: current_time = time.time() if current_time > start_time + operation_timeout: raise OperationTimeout( "Waiting for Console at {} to become available timed " "out (operation timeout: {} s)". format(self.session.host, operation_timeout), operation_timeout) time.sleep(10) # Avoid hot spin loop ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/_console.py0000644000076500000240000004627600000000000020647 0ustar00maierastaff00000000000000# Copyright 2017 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:`Console` resource represents an HMC. In a paired setup with primary and alternate HMC, each HMC is represented as a separate :term:`Console` resource. """ from __future__ import absolute_import import time from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call from ._utils import timestamp_from_datetime from ._storage_group import StorageGroupManager from ._storage_group_template import StorageGroupTemplateManager from ._user import UserManager from ._user_role import UserRoleManager from ._user_pattern import UserPatternManager from ._password_rule import PasswordRuleManager from ._task import TaskManager from ._ldap_server_definition import LdapServerDefinitionManager from ._unmanaged_cpc import UnmanagedCpcManager __all__ = ['ConsoleManager', 'Console'] class ConsoleManager(BaseManager): """ Manager providing access to the :term:`Console` representing the HMC this client is connected to. In a paired setup with primary and alternate HMC, each HMC is represented as a separate :term:`Console` resource. 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 of a :class:`~zhmcclient.Client` object: * :attr:`zhmcclient.Client.consoles` """ def __init__(self, client): # This function should not go into the docs. # Parameters: # client (:class:`~zhmcclient.Client`): # Client object for the HMC to be used. super(ConsoleManager, self).__init__( resource_class=Console, class_name='console', session=client.session, parent=None, base_uri='/api/console', oid_prop='object-id', uri_prop='object-uri', name_prop='name', query_props=None, list_has_name=False) self._client = client self._console = None @property def client(self): """ :class:`~zhmcclient.Client`: The client defining the scope for this manager. """ return self._client @property def console(self): """ :class:`~zhmcclient.Console`: The :term:`Console` representing the HMC this client is connected to. The returned object is cached, so it is looked up only upon first access to this property. The returned object has only the following properties set: * 'object-uri' Use :meth:`~zhmcclient.BaseResource.get_property` or :meth:`~zhmcclient.BaseResource.prop` to access any properties regardless of whether they are already set or first need to be retrieved. """ if self._console is None: self._console = self.list()[0] return self._console @logged_api_call def list(self, full_properties=True, filter_args=None): """ List the (one) :term:`Console` representing the HMC this client is connected to. Authorization requirements: * None Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only a short set consisting of 'object-uri'. filter_args (dict): This parameter exists for consistency with other list() methods and will be ignored. Returns: : A list of :class:`~zhmcclient.Console` objects, containing the one :term:`Console` representing the HMC this client is connected to. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ uri = self._base_uri # There is only one console object. if full_properties: props = self.session.get(uri) else: # Note: The Console resource's Object ID is not part of its URI. props = { self._uri_prop: uri, } resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) return [resource_obj] class Console(BaseResource): """ Representation of a :term:`Console`. 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.ConsoleManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.ConsoleManager`): # 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, ConsoleManager), \ "Console init: Expected manager type %s, got %s" % \ (ConsoleManager, type(manager)) super(Console, self).__init__(manager, uri, name, properties) # The manager objects for child resources (with lazy initialization): self._storage_groups = None self._storage_group_templates = None self._users = None self._user_roles = None self._user_patterns = None self._password_rules = None self._tasks = None self._ldap_server_definitions = None self._unmanaged_cpcs = None @property def storage_groups(self): """ :class:`~zhmcclient.StorageGroupManager`: Manager object for the Storage Groups in scope of this Console. """ # We do here some lazy loading. if not self._storage_groups: self._storage_groups = StorageGroupManager(self) return self._storage_groups @property def storage_group_templates(self): """ :class:`~zhmcclient.StorageGroupTemplateManager`: Manager object for the Storage Group Templates in scope of this Console. """ # We do here some lazy loading. if not self._storage_group_templates: self._storage_group_templates = StorageGroupTemplateManager(self) return self._storage_group_templates @property def users(self): """ :class:`~zhmcclient.UserManager`: Access to the :term:`Users ` in this Console. """ # We do here some lazy loading. if not self._users: self._users = UserManager(self) return self._users @property def user_roles(self): """ :class:`~zhmcclient.UserRoleManager`: Access to the :term:`User Roles ` in this Console. """ # We do here some lazy loading. if not self._user_roles: self._user_roles = UserRoleManager(self) return self._user_roles @property def user_patterns(self): """ :class:`~zhmcclient.UserPatternManager`: Access to the :term:`User Patterns ` in this Console. """ # We do here some lazy loading. if not self._user_patterns: self._user_patterns = UserPatternManager(self) return self._user_patterns @property def password_rules(self): """ :class:`~zhmcclient.PasswordRuleManager`: Access to the :term:`Password Rules ` in this Console. """ # We do here some lazy loading. if not self._password_rules: self._password_rules = PasswordRuleManager(self) return self._password_rules @property def tasks(self): """ :class:`~zhmcclient.TaskManager`: Access to the :term:`Tasks ` in this Console. """ # We do here some lazy loading. if not self._tasks: self._tasks = TaskManager(self) return self._tasks @property def ldap_server_definitions(self): """ :class:`~zhmcclient.LdapServerDefinitionManager`: Access to the :term:`LDAP Server Definitions ` in this Console. """ # We do here some lazy loading. if not self._ldap_server_definitions: self._ldap_server_definitions = LdapServerDefinitionManager(self) return self._ldap_server_definitions @property def unmanaged_cpcs(self): """ :class:`~zhmcclient.UnmanagedCpcManager`: Access to the unmanaged :term:`CPCs ` in this Console. """ # We do here some lazy loading. if not self._unmanaged_cpcs: self._unmanaged_cpcs = UnmanagedCpcManager(self) return self._unmanaged_cpcs @logged_api_call def restart(self, force=False, wait_for_available=True, operation_timeout=None): """ Restart the HMC represented by this Console object. Once the HMC is online again, this Console object, as well as any other resource objects accessed through this HMC, can continue to be used. An automatic re-logon will be performed under the covers, because the HMC restart invalidates the currently used HMC session. Authorization requirements: * Task permission for the "Shutdown/Restart" task. * "Remote Restart" must be enabled on the HMC. Parameters: force (bool): Boolean controlling whether the restart operation is processed when users are connected (`True`) or not (`False`). Users in this sense are local or remote GUI users. HMC WS API clients do not count as users for this purpose. wait_for_available (bool): Boolean controlling whether this method should wait for the HMC to become available again after the restart, as follows: * If `True`, this method will wait until the HMC has restarted and is available again. The :meth:`~zhmcclient.Client.query_api_version` method will be used to check for availability of the HMC. * If `False`, this method will return immediately once the HMC has accepted the request to be restarted. operation_timeout (:term:`number`): Timeout in seconds, for waiting for HMC availability after the restart. 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_available=True`, a :exc:`~zhmcclient.OperationTimeout` is raised. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` :exc:`~zhmcclient.OperationTimeout`: The timeout expired while waiting for the HMC to become available again after the restart. """ body = {'force': force} self.manager.session.post(self.uri + '/operations/restart', body=body) if wait_for_available: time.sleep(10) self.manager.client.wait_for_available( operation_timeout=operation_timeout) @logged_api_call def shutdown(self, force=False): """ Shut down and power off the HMC represented by this Console object. While the HMC is powered off, any Python resource objects retrieved from this HMC may raise exceptions upon further use. In order to continue using Python resource objects retrieved from this HMC, the HMC needs to be started again (e.g. by powering it on locally). Once the HMC is available again, Python resource objects retrieved from that HMC can continue to be used. An automatic re-logon will be performed under the covers, because the HMC startup invalidates the currently used HMC session. Authorization requirements: * Task permission for the "Shutdown/Restart" task. * "Remote Shutdown" must be enabled on the HMC. Parameters: force (bool): Boolean controlling whether the shutdown operation is processed when users are connected (`True`) or not (`False`). Users in this sense are local or remote GUI users. HMC WS API clients do not count as users for this purpose. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = {'force': force} self.manager.session.post(self.uri + '/operations/shutdown', body=body) @logged_api_call def make_primary(self): """ Change the role of the alternate HMC represented by this Console object to become the primary HMC. If that HMC is already the primary HMC, this method does not change its rols and succeeds. The HMC represented by this Console object must participate in a {primary, alternate} pairing. Authorization requirements: * Task permission for the "Manage Alternate HMC" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ self.manager.session.post(self.uri + '/operations/make-primary') @staticmethod def _time_query_parms(begin_time, end_time): """Return the URI query paramterer string for the specified begin time and end time.""" query_parms = [] if begin_time is not None: begin_ts = timestamp_from_datetime(begin_time) qp = 'begin-time={}'.format(begin_ts) query_parms.append(qp) if end_time is not None: end_ts = timestamp_from_datetime(end_time) qp = 'end-time={}'.format(end_ts) query_parms.append(qp) query_parms_str = '&'.join(query_parms) if query_parms_str: query_parms_str = '?' + query_parms_str return query_parms_str @logged_api_call def get_audit_log(self, begin_time=None, end_time=None): """ Return the console audit log entries, optionally filtered by their creation time. Authorization requirements: * Task permission to the "Audit and Log Management" task. Parameters: begin_time (:class:`~py:datetime.datetime`): Begin time for filtering. Log entries with a creation time older than the begin time will be omitted from the results. If `None`, no such filtering is performed (and the oldest available log entries will be included). end_time (:class:`~py:datetime.datetime`): End time for filtering. Log entries with a creation time newer than the end time will be omitted from the results. If `None`, no such filtering is performed (and the newest available log entries will be included). Returns: :term:`json object`: A JSON object with the log entries, as described in section 'Response body contents' of operation 'Get Console Audit Log' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ query_parms = self._time_query_parms(begin_time, end_time) uri = self.uri + '/operations/get-audit-log' + query_parms result = self.manager.session.get(uri) return result @logged_api_call def get_security_log(self, begin_time=None, end_time=None): """ Return the console security log entries, optionally filtered by their creation time. Authorization requirements: * Task permission to the "View Security Logs" task. Parameters: begin_time (:class:`~py:datetime.datetime`): Begin time for filtering. Log entries with a creation time older than the begin time will be omitted from the results. If `None`, no such filtering is performed (and the oldest available log entries will be included). end_time (:class:`~py:datetime.datetime`): End time for filtering. Log entries with a creation time newer than the end time will be omitted from the results. If `None`, no such filtering is performed (and the newest available log entries will be included). Returns: :term:`json object`: A JSON object with the log entries, as described in section 'Response body contents' of operation 'Get Console Security Log' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ query_parms = self._time_query_parms(begin_time, end_time) uri = self.uri + '/operations/get-security-log' + query_parms result = self.manager.session.get(uri) return result @logged_api_call def list_unmanaged_cpcs(self, name=None): """ List the unmanaged CPCs of this HMC. For details, see :meth:`~zhmcclient.UnmanagedCpc.list`. Authorization requirements: * None Parameters: name (:term:`string`): Regular expression pattern for the CPC name, as a filter that narrows the list of returned CPCs to those whose name property matches the specified pattern. `None` causes no filtering to happen, i.e. all unmanaged CPCs discovered by the HMC are returned. Returns: : A list of :class:`~zhmcclient.UnmanagedCpc` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ filter_args = dict() if name is not None: filter_args['name'] = name cpcs = self.unmanaged_cpcs.list(filter_args=filter_args) return cpcs ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/_constants.py0000644000076500000240000001136000000000000021203 0ustar00maierastaff00000000000000# Copyright 2017 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. """ Public constants. These constants are not meant to be changed by the user, they are made available for inspection and documentation purposes only. For technical reasons, the online documentation shows these constants in the ``zhmcclient._constants`` namespace, but they are also available in the ``zhmcclient`` namespace and should be used from there. """ __all__ = ['DEFAULT_CONNECT_TIMEOUT', 'DEFAULT_CONNECT_RETRIES', 'DEFAULT_HMC_PORT', 'DEFAULT_READ_TIMEOUT', 'DEFAULT_READ_RETRIES', 'DEFAULT_STOMP_PORT', 'DEFAULT_MAX_REDIRECTS', 'DEFAULT_OPERATION_TIMEOUT', 'DEFAULT_STATUS_TIMEOUT', 'DEFAULT_NAME_URI_CACHE_TIMETOLIVE', 'HMC_LOGGER_NAME', 'API_LOGGER_NAME', 'HTML_REASON_WEB_SERVICES_DISABLED', 'HTML_REASON_OTHER'] #: Default HTTP connect timeout in seconds, #: if not specified in the ``retry_timeout_config`` init argument to #: :class:`~zhmcclient.Session`. DEFAULT_CONNECT_TIMEOUT = 30 #: Default number of HTTP connect retries, #: if not specified in the ``retry_timeout_config`` init argument to #: :class:`~zhmcclient.Session`. DEFAULT_CONNECT_RETRIES = 3 #: Default HMC port number DEFAULT_HMC_PORT = 6794 #: Default HTTP read timeout in seconds, #: if not specified in the ``retry_timeout_config`` init argument to #: :class:`~zhmcclient.Session`. #: #: Note: The default value for this parameter has been increased to a large #: value in order to mitigate the behavior of the 'requests' module to #: retry HTTP methods even if they are not idempotent (e.g. DELETE). #: See zhmcclient `issue #249 #: `_. DEFAULT_READ_TIMEOUT = 3600 #: Default port on which the HMC issues JMS over STOMP messages. DEFAULT_STOMP_PORT = 61612 #: Default number of HTTP read retries, #: if not specified in the ``retry_timeout_config`` init argument to #: :class:`~zhmcclient.Session`. #: #: Note: The default value for this parameter has been set to 0 in order to #: mitigate the behavior of the 'requests' module to retry HTTP methods even if #: they are not idempotent (e.g. DELETE). #: See zhmcclient `issue #249 #: `_. DEFAULT_READ_RETRIES = 0 #: Default max. number of HTTP redirects, #: if not specified in the ``retry_timeout_config`` init argument to #: :class:`~zhmcclient.Session`. DEFAULT_MAX_REDIRECTS = 30 #: Default timeout in seconds for waiting for completion of an asynchronous #: HMC operation, #: if not specified in the ``retry_timeout_config`` init argument to #: :class:`~zhmcclient.Session`. #: #: This is used as a default value in asynchronous methods on #: resource objects (e.g. :meth:`zhmcclient.Partition.start`), in the #: :meth:`zhmcclient.Job.wait_for_completion` method, and in the #: low level method :meth:`zhmcclient.Session.post`. DEFAULT_OPERATION_TIMEOUT = 3600 #: Default timeout in seconds for waiting for completion of deferred status #: changes for LPARs, #: if not specified in the ``retry_timeout_config`` init argument to #: :class:`~zhmcclient.Session`. #: #: This is used as a default value in asynchronous methods #: of the :class:`~zhmcclient.Lpar` class that change its status (e.g. #: :meth:`zhmcclient.Lpar.activate`)). DEFAULT_STATUS_TIMEOUT = 900 #: Default time to the next automatic invalidation of the Name-URI cache of #: manager objects, in seconds since the last invalidation, #: if not specified in the ``retry_timeout_config`` init argument to #: :class:`~zhmcclient.Session`. #: #: The special value 0 means that no Name-URI cache is maintained (i.e. the #: caching is disabled). DEFAULT_NAME_URI_CACHE_TIMETOLIVE = 300 #: Name of the Python logger that logs HMC operations. HMC_LOGGER_NAME = 'zhmcclient.hmc' #: Name of the Python logger that logs zhmcclient API calls made by the user. API_LOGGER_NAME = 'zhmcclient.api' #: HTTP reason code: Web Services API is not enabled on the HMC. HTML_REASON_WEB_SERVICES_DISABLED = 900 #: HTTP reason code: Other HTML-formatted error response. Note that over time, #: there may be more specific reason codes introduced for such situations. HTML_REASON_OTHER = 999 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_cpc.py0000644000076500000240000016535600000000000017753 0ustar00maierastaff00000000000000# Copyright 2016-2017 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:`CPC` (Central Processor Complex) is a physical IBM Z or LinuxONE computer. A particular HMC can manage multiple CPCs and can discover other CPCs that are not managed by that HMC. Such other CPCs are called "unmanaged CPCs" and they may or may not be managed by another HMC. This section describes the interface for *managed* CPCs using resource class :class:`~zhmcclient.Cpc` and the corresponding manager class :class:`~zhmcclient.CpcManager`. The HMC can manage a range of old and new CPC generations. Some older CPC generations are not capable of supporting the HMC Web Services API; these older CPCs can be managed using the GUI of the HMC, but not through its Web Services API. Therefore, such older CPCs will not be exposed at the HMC Web Services API, and thus will not show up in the API of this Python package. TODO: List earliest CPC generation that supports the HMC Web Services API. A CPC can be in any of the following three modes: - DPM mode: Dynamic Partition Manager is enabled for the CPC. - Classic mode: The CPC does not have Dynamic Partition Manager enabled, and is not member of an ensemble. - Ensemble mode: The CPC is member of an ensemble. This Python client does not support the functionality that is specific to ensemble mode. The functionality supported at the HMC API and thus also for users of this Python client, depends on the mode in which the CPC currently is. If a particular functionality is available only in a specific mode, that is indicated in the description of the functionality. """ from __future__ import absolute_import import warnings import copy from ._manager import BaseManager from ._resource import BaseResource from ._lpar import LparManager from ._partition import PartitionManager from ._activation_profile import ActivationProfileManager from ._adapter import AdapterManager from ._virtual_switch import VirtualSwitchManager from ._capacity_group import CapacityGroupManager from ._logging import logged_api_call from ._exceptions import ParseError __all__ = ['CpcManager', 'Cpc'] class CpcManager(BaseManager): """ Manager providing access to the managed :term:`CPCs ` exposed by the HMC this client is connected to. 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 of a :class:`~zhmcclient.Client` object: * :attr:`~zhmcclient.Client.cpcs` """ def __init__(self, client): # This function should not go into the docs. # Parameters: # client (:class:`~zhmcclient.Client`): # Client object for the HMC to be used. # Resource properties that are supported as filter query parameters # (for server-side filtering). query_props = [ 'name', ] super(CpcManager, self).__init__( resource_class=Cpc, class_name='cpc', session=client.session, parent=None, base_uri='/api/cpcs', oid_prop='object-id', uri_prop='object-uri', name_prop='name', query_props=query_props) self._client = client @property def client(self): """ :class:`~zhmcclient.Client`: The client defining the scope for this manager. """ return self._client @property def console(self): """ :class:`~zhmcclient.Console`: The :term:`Console` representing the HMC this CPC is managed by. The returned object is cached, so it is looked up only upon first access to this property. The returned object has only the following properties set: * 'object-uri' Use :meth:`~zhmcclient.BaseResource.get_property` or :meth:`~zhmcclient.BaseResource.prop` to access any properties regardless of whether they are already set or first need to be retrieved. """ return self.client.consoles.console @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the CPCs managed by the HMC this client is connected to. Authorization requirements: * Object-access permission to any CPC to be included in the result. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.Cpc` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] resource_obj = self._try_optimized_lookup(filter_args) if resource_obj: resource_obj_list.append(resource_obj) # It already has full properties else: query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'cpcs' uri = '/api/{}{}'.format(resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list class Cpc(BaseResource): """ Representation of a managed :term:`CPC`. 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.CpcManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.CpcManager`): # 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, CpcManager), \ "Cpc init: Expected manager type %s, got %s" % \ (CpcManager, type(manager)) super(Cpc, self).__init__(manager, uri, name, properties) # The manager objects for child resources (with lazy initialization): self._lpars = None self._partitions = None self._adapters = None self._virtual_switches = None self._capacity_groups = None self._reset_activation_profiles = None self._image_activation_profiles = None self._load_activation_profiles = None @property def lpars(self): """ :class:`~zhmcclient.LparManager`: Access to the :term:`LPARs ` in this CPC. """ # We do here some lazy loading. if not self._lpars: self._lpars = LparManager(self) return self._lpars @property def partitions(self): """ :class:`~zhmcclient.PartitionManager`: Access to the :term:`Partitions ` in this CPC. """ # We do here some lazy loading. if not self._partitions: self._partitions = PartitionManager(self) return self._partitions @property def adapters(self): """ :class:`~zhmcclient.AdapterManager`: Access to the :term:`Adapters ` in this CPC. """ # We do here some lazy loading. if not self._adapters: self._adapters = AdapterManager(self) return self._adapters @property def virtual_switches(self): """ :class:`~zhmcclient.VirtualSwitchManager`: Access to the :term:`Virtual Switches ` in this CPC. """ # We do here some lazy loading. if not self._virtual_switches: self._virtual_switches = VirtualSwitchManager(self) return self._virtual_switches @property def capacity_groups(self): """ :class:`~zhmcclient.CapacityGroupManager`: Access to the :term:`Capacity Groups ` in this CPC. """ # We do here some lazy loading. if not self._capacity_groups: self._capacity_groups = CapacityGroupManager(self) return self._capacity_groups @property def vswitches(self): """ :class:`~zhmcclient.VirtualSwitchManager`: Access to the :term:`Virtual Switches ` in this CPC. **Deprecated:** This attribute is deprecated and using it will cause a :exc:`~py:exceptions.DeprecationWarning` to be issued. Use :attr:`~zhmcclient.Cpc.virtual_switches` instead. """ warnings.warn( "Use of the vswitches attribute on zhmcclient.Cpc objects is " "deprecated; use the virtual_switches attribute instead", DeprecationWarning) return self.virtual_switches @property def reset_activation_profiles(self): """ :class:`~zhmcclient.ActivationProfileManager`: Access to the :term:`Reset Activation Profiles ` in this CPC. """ # We do here some lazy loading. if not self._reset_activation_profiles: self._reset_activation_profiles = \ ActivationProfileManager(self, profile_type='reset') return self._reset_activation_profiles @property def image_activation_profiles(self): """ :class:`~zhmcclient.ActivationProfileManager`: Access to the :term:`Image Activation Profiles ` in this CPC. """ # We do here some lazy loading. if not self._image_activation_profiles: self._image_activation_profiles = \ ActivationProfileManager(self, profile_type='image') return self._image_activation_profiles @property def load_activation_profiles(self): """ :class:`~zhmcclient.ActivationProfileManager`: Access to the :term:`Load Activation Profiles ` in this CPC. """ # We do here some lazy loading. if not self._load_activation_profiles: self._load_activation_profiles = \ ActivationProfileManager(self, profile_type='load') return self._load_activation_profiles @property @logged_api_call def dpm_enabled(self): """ bool: Indicates whether this CPC is currently in DPM mode (Dynamic Partition Manager mode). If the CPC is not currently in DPM mode, or if the CPC does not support DPM mode (i.e. before z13), `False` is returned. Authorization requirements: * Object-access permission to this CPC. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ return self.prop('dpm-enabled', False) # Note: From HMC API version 2.24 on (i.e. starting with 8561), the Cpc # object supports a 'maximum-partitions' property, but only in DPM mode. # Therefore, we need to continue maintaining max partitions for all future # machine types. # Machine types with same max partitions for all models: _MAX_PARTITIONS_BY_MACHINE_TYPE = { '2817': 60, # z196 '2818': 30, # z114 '2827': 60, # zEC12 '2828': 30, # zBC12 '2964': 85, # z13 / Emperor '2965': 40, # z13s / Rockhopper '3906': 85, # z14 / Emperor II '3907': 40, # z14-ZR1 / Rockhopper II } # Machine types with different max partitions across their models: _MAX_PARTITIONS_BY_MACHINE_TYPE_MODEL = { ('8561', 'T01'): 85, # z15 ('8561', 'LT1'): 85, # z15 ('8562', 'GT2'): 85, # z15 (85 is an exception for 8562) ('8562', 'T02'): 40, # z15 ('8562', 'LT2'): 40, # z15 } @property @logged_api_call def maximum_active_partitions(self): """ Integer: The maximum number of active logical partitions or partitions of this CPC. The following table shows the maximum number of active logical partitions or partitions by machine generations supported at the HMC API: ========================= ================== Machine generation Maximum partitions ========================= ================== z196 60 z114 30 zEC12 60 zBC12 30 z13 / Emperor 85 z13s / Rockhopper 40 z14 / Emperor II 85 z14-ZR1 / -LR1 40 z15-T01 / -LT1 / -GT2 85 z15-T02 / -LT2 40 ========================= ================== Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` :exc:`ValueError`: Unknown machine type """ machine_type = self.get_property('machine-type') machine_model = self.get_property('machine-model') try: return self._MAX_PARTITIONS_BY_MACHINE_TYPE[machine_type] except KeyError: pass try: return self._MAX_PARTITIONS_BY_MACHINE_TYPE_MODEL[ (machine_type, machine_model)] except KeyError: pass try: return self.get_property('maximum-partitions') except KeyError: new_exc = ValueError("Unknown machine type/model: {}-{}". format(machine_type, machine_model)) new_exc.__cause__ = None raise new_exc # ValueError @logged_api_call def feature_enabled(self, feature_name): """ Indicates whether the specified feature is enabled for this CPC. The HMC must generally support features, and the specified feature must be available for the CPC. For a list of available features, see section "Features" in the :term:`HMC API`, or use the :meth:`feature_info` method. Authorization requirements: * Object-access permission to this CPC. Parameters: feature_name (:term:`string`): The name of the feature. Returns: bool: `True` if the feature is enabled, or `False` if the feature is disabled (but available). Raises: :exc:`ValueError`: Features are not supported on the HMC. :exc:`ValueError`: The specified feature is not available for the CPC. :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ feature_list = self.prop('available-features-list', None) if feature_list is None: raise ValueError("Firmware features are not supported on CPC %s" % self.name) for feature in feature_list: if feature['name'] == feature_name: break else: raise ValueError("Firmware feature %s is not available on CPC %s" % (feature_name, self.name)) return feature['state'] # pylint: disable=undefined-loop-variable @logged_api_call def feature_info(self): """ Returns information about the features available for this CPC. Authorization requirements: * Object-access permission to this CPC. Returns: :term:`iterable`: An iterable where each item represents one feature that is available for this CPC. Each item is a dictionary with the following items: * `name` (:term:`unicode string`): Name of the feature. * `description` (:term:`unicode string`): Short description of the feature. * `state` (bool): Enablement state of the feature (`True` if the enabled, `False` if disabled). Raises: :exc:`ValueError`: Features are not supported on the HMC. :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ feature_list = self.prop('available-features-list', None) if feature_list is None: raise ValueError("Firmware features are not supported on CPC %s" % self.name) return feature_list @logged_api_call def update_properties(self, properties): """ Update writeable properties of this CPC. Authorization requirements: * Object-access permission to this CPC. * Task permission for the "CPC Details" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model' in section 'CPC' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) # Attempts to change the 'name' property will be rejected by the HMC, # so we don't need to update the name-to-URI cache. assert self.manager._name_prop not in properties self._properties.update(copy.deepcopy(properties)) @logged_api_call def start(self, wait_for_completion=True, operation_timeout=None): """ Start this CPC, using the HMC operation "Start CPC". Authorization requirements: * Object-access permission to this CPC. * Task permission for the "Start (start a single DPM system)" task. Parameters: wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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: `None` or :class:`~zhmcclient.Job`: 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. 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. """ result = self.manager.session.post( self.uri + '/operations/start', wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) return result @logged_api_call def stop(self, wait_for_completion=True, operation_timeout=None): """ Stop this CPC, using the HMC operation "Stop CPC". Authorization requirements: * Object-access permission to this CPC. * Task permission for the "Stop (stop a single DPM system)" task. Parameters: wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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: `None` or :class:`~zhmcclient.Job`: 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. 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. """ result = self.manager.session.post( self.uri + '/operations/stop', wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) return result @logged_api_call def import_profiles(self, profile_area, wait_for_completion=True, operation_timeout=None): """ Import activation profiles and/or system activity profiles for this CPC from the SE hard drive into the CPC using the HMC operation "Import Profiles". This operation is not permitted when the CPC is in DPM mode. Authorization requirements: * Object-access permission to this CPC. * Task permission for the "CIM Actions ExportSettingsData" task. Parameters: profile_area (int): The numbered hard drive area (1-4) from which the profiles are imported. wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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: `None` or :class:`~zhmcclient.Job`: 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. 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 = {'profile-area': profile_area} result = self.manager.session.post( self.uri + '/operations/import-profiles', body, wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) return result @logged_api_call def export_profiles(self, profile_area, wait_for_completion=True, operation_timeout=None): """ Export activation profiles and/or system activity profiles from this CPC to the SE hard drive using the HMC operation "Export Profiles". This operation is not permitted when the CPC is in DPM mode. Authorization requirements: * Object-access permission to this CPC. * Task permission for the "CIM Actions ExportSettingsData" task. Parameters: profile_area (int): The numbered hard drive area (1-4) to which the profiles are exported. Any existing data is overwritten. wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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: `None` or :class:`~zhmcclient.Job`: 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. 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 = {'profile-area': profile_area} result = self.manager.session.post( self.uri + '/operations/export-profiles', body, wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) return result @logged_api_call def get_wwpns(self, partitions): """ Return the WWPNs of the host ports (of the :term:`HBAs `) of the specified :term:`Partitions ` of this CPC. This method performs the HMC operation "Export WWPN List". Authorization requirements: * Object-access permission to this CPC. * Object-access permission to the Partitions designated by the "partitions" parameter. * Task permission for the "Export WWPNs" task. Parameters: partitions (:term:`iterable` of :class:`~zhmcclient.Partition`): :term:`Partitions ` to be used. Returns: A list of items for each WWPN, where each item is a dict with the following keys: * 'partition-name' (string): Name of the :term:`Partition`. * 'adapter-id' (string): ID of the :term:`FCP Adapter`. * 'device-number' (string): Virtual device number of the :term:`HBA`. * 'wwpn' (string): WWPN of the HBA. Raises: :exc:`~zhmcclient.HTTPError`: See the HTTP status and reason codes of operation "Export WWPN List" in the :term:`HMC API` book. :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = {'partitions': [p.uri for p in partitions]} result = self.manager.session.post(self._uri + '/operations/' 'export-port-names-list', body=body) # Parse the returned comma-separated string for each WWPN into a dict: wwpn_list = [] dict_keys = ('partition-name', 'adapter-id', 'device-number', 'wwpn') for wwpn_item in result['wwpn-list']: dict_values = wwpn_item.split(',') wwpn_list.append(dict(zip(dict_keys, dict_values))) return wwpn_list @logged_api_call def get_free_crypto_domains(self, crypto_adapters=None): """ Return a list of crypto domains that are free for usage on a list of crypto adapters in this CPC. A crypto domain is considered free for usage if it is not assigned to any defined partition of this CPC in access mode 'control-usage' on any of the specified crypto adapters. For this test, all currently defined partitions of this CPC are checked, regardless of whether or not they are active. This ensures that a crypto domain that is found to be free for usage can be assigned to a partition for 'control-usage' access to the specified crypto adapters, without causing a crypto domain conflict when activating that partition. Note that a similar notion of free domains does not exist for access mode 'control', because a crypto domain on a crypto adapter can be in control access by multiple active partitions. This method requires the CPC to be in DPM mode. **Example:** .. code-block:: text crypto domains adapters 0 1 2 3 +---+---+---+---+ c1 |A,c|a,c| | C | +---+---+---+---+ c2 |b,c|B,c| B | C | +---+---+---+---+ In this example, the CPC has two crypto adapters c1 and c2. For simplicity of the example, we assume these crypto adapters support only 4 crypto domains. Partition A uses only adapter c1 and has domain 0 in 'control-usage' access (indicated by an upper case letter 'A' in the corresponding cell) and has domain 1 in 'control' access (indicated by a lower case letter 'a' in the corresponding cell). Partition B uses only adapter c2 and has domain 0 in 'control' access and domains 1 and 2 in 'control-usage' access. Partition C uses both adapters, and has domains 0 and 1 in 'control' access and domain 3 in 'control-usage' access. The domains free for usage in this example are shown in the following table, for each combination of crypto adapters to be investigated: =============== ====================== crypto_adapters domains free for usage =============== ====================== c1 1, 2 c2 0 c1, c2 (empty list) =============== ====================== **Experimental:** This method has been added in v0.14.0 and is currently considered experimental. Its interface may change incompatibly. Once the interface remains stable, this experimental marker will be removed. Authorization requirements: * Object-access permission to this CPC. * Object-access permission to all of its Partitions. * Object-access permission to all of its crypto Adapters. Parameters: crypto_adapters (:term:`iterable` of :class:`~zhmcclient.Adapter`): The crypto :term:`Adapters ` to be investigated. `None` means to investigate all crypto adapters of this CPC. Returns: A sorted list of domain index numbers (integers) of the crypto domains that are free for usage on the specified crypto adapters. Returns `None`, if ``crypto_adapters`` was an empty list or if ``crypto_adapters`` was `None` and the CPC has no crypto adapters. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ if crypto_adapters is None: crypto_adapters = self.adapters.findall(type='crypto') if not crypto_adapters: # No crypto adapters were specified or defaulted. return None # We determine the maximum number of crypto domains independently # of the partitions, because (1) it is possible that no partition # has a crypto configuration and (2) further down we want the inner # loop to be on the crypto adapters because accessing them multiple # times does not drive additional HMC operations. max_domains = None # maximum number of domains across all adapters for ca in crypto_adapters: if max_domains is None: max_domains = ca.maximum_crypto_domains else: max_domains = min(ca.maximum_crypto_domains, max_domains) used_domains = set() # Crypto domains used in control-usage mode partitions = self.partitions.list(full_properties=True) for partition in partitions: crypto_config = partition.get_property('crypto-configuration') if crypto_config: adapter_uris = crypto_config['crypto-adapter-uris'] domain_configs = crypto_config['crypto-domain-configurations'] for ca in crypto_adapters: if ca.uri in adapter_uris: used_adapter_domains = list() for dc in domain_configs: if dc['access-mode'] == 'control-usage': used_adapter_domains.append(dc['domain-index']) used_domains.update(used_adapter_domains) all_domains = set(range(0, max_domains)) free_domains = all_domains - used_domains return sorted(list(free_domains)) @logged_api_call def set_power_save(self, power_saving, wait_for_completion=True, operation_timeout=None): """ Set the power save setting of this CPC. The current power save setting in effect for a CPC is described in the "cpc-power-saving" property of the CPC. This method performs the HMC operation "Set CPC Power Save". It requires that the feature "Automate/advanced management suite" (FC 0020) is installed and enabled, and fails otherwise. This method will also fail if the CPC is under group control. Whether a CPC currently allows this method is described in the "cpc-power-save-allowed" property of the CPC. Authorization requirements: * Object-access permission to this CPC. * Task permission for the "Power Save" task. Parameters: power_saving (:term:`string`): The new power save setting, with the possible values: * "high-performance" - The power consumption and performance of the CPC are not reduced. This is the default setting. * "low-power" - Low power consumption for all components of the CPC enabled for power saving. * "custom" - Components may have their own settings changed individually. No component settings are actually changed when this mode is entered. wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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: `None` or :class:`~zhmcclient.Job`: 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. Raises: :exc:`~zhmcclient.HTTPError`: See the HTTP status and reason codes of operation "Set CPC Power Save" in the :term:`HMC API` book. :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` :exc:`~zhmcclient.OperationTimeout`: The timeout expired while waiting for completion of the operation. """ body = {'power-saving': power_saving} result = self.manager.session.post( self.uri + '/operations/set-cpc-power-save', body, wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) if wait_for_completion: # The HMC API book does not document what the result data of the # completed job is. It turns out that the completed job has this # dictionary as its result data: # {'message': 'Operation executed successfully'} # We transform that to None. return None return result @logged_api_call def set_power_capping(self, power_capping_state, power_cap=None, wait_for_completion=True, operation_timeout=None): """ Set the power capping settings of this CPC. The power capping settings of a CPC define whether or not the power consumption of the CPC is limited and if so, what the limit is. Use this method to limit the peak power consumption of a CPC, or to remove a power consumption limit for a CPC. The current power capping settings in effect for a CPC are described in the "cpc-power-capping-state" and "cpc-power-cap-current" properties of the CPC. This method performs the HMC operation "Set CPC Power Capping". It requires that the feature "Automate/advanced management suite" (FC 0020) is installed and enabled, and fails otherwise. This method will also fail if the CPC is under group control. Whether a CPC currently allows this method is described in the "cpc-power-cap-allowed" property of the CPC. Authorization requirements: * Object-access permission to this CPC. * Task permission for the "Power Capping" task. Parameters: power_capping_state (:term:`string`): The power capping state to be set, with the possible values: * "disabled" - The power cap of the CPC is not set and the peak power consumption is not limited. This is the default setting. * "enabled" - The peak power consumption of the CPC is limited to the specified power cap value. * "custom" - Individually configure the components for power capping. No component settings are actually changed when this mode is entered. power_cap (:term:`integer`): The power cap value to be set, as a power consumption in Watt. This parameter is required not to be `None` if `power_capping_state="enabled"`. The specified value must be between the values of the CPC properties "cpc-power-cap-minimum" and "cpc-power-cap-maximum". wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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: `None` or :class:`~zhmcclient.Job`: 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. Raises: :exc:`~zhmcclient.HTTPError`: See the HTTP status and reason codes of operation "Set CPC Power Save" in the :term:`HMC API` book. :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` :exc:`~zhmcclient.OperationTimeout`: The timeout expired while waiting for completion of the operation. """ body = {'power-capping-state': power_capping_state} if power_cap is not None: body['power-cap-current'] = power_cap result = self.manager.session.post( self.uri + '/operations/set-cpc-power-capping', body, wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) if wait_for_completion: # The HMC API book does not document what the result data of the # completed job is. Just in case there is similar behavior to the # "Set CPC Power Save" operation, we transform that to None. # TODO: Verify job result of a completed "Set CPC Power Capping". return None return result @logged_api_call def get_energy_management_properties(self): """ Return the energy management properties of the CPC. The returned energy management properties are a subset of the properties of the CPC resource, and are also available as normal properties of the CPC resource. In so far, there is no new data provided by this method. However, because only a subset of the properties is returned, this method is faster than retrieving the complete set of CPC properties (e.g. via :meth:`~zhmcclient.BaseResource.pull_full_properties`). This method performs the HMC operation "Get CPC Energy Management Data", and returns only the energy management properties for this CPC from the operation result. Note that in non-ensemble mode of a CPC, the HMC operation result will only contain data for the CPC alone. It requires that the feature "Automate/advanced management suite" (FC 0020) is installed and enabled, and returns empty values for most properties, otherwise. Authorization requirements: * Object-access permission to this CPC. Returns: dict: A dictionary of properties of the CPC that are related to energy management. For details, see section "Energy management related additional properties" in the data model for the CPC resource in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError`: See the HTTP status and reason codes of operation "Get CPC Energy Management Data" in the :term:`HMC API` book. :exc:`~zhmcclient.ParseError`: Also raised by this method when the JSON response could be parsed but contains inconsistent data. :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.manager.session.get(self.uri + '/energy-management-data') em_list = result['objects'] if len(em_list) != 1: uris = [em_obj['object-uri'] for em_obj in em_list] raise ParseError("Energy management data returned for no resource " "or for more than one resource: %r" % uris) em_cpc_obj = em_list[0] if em_cpc_obj['object-uri'] != self.uri: raise ParseError("Energy management data returned for an " "unexpected resource: %r" % em_cpc_obj['object-uri']) if em_cpc_obj['error-occurred']: raise ParseError("Errors occurred when retrieving energy " "management data for CPC. Operation result: %r" % result) cpc_props = em_cpc_obj['properties'] return cpc_props @logged_api_call def list_associated_storage_groups( self, full_properties=False, filter_args=None): """ Return the :term:`storage groups ` that are associated to this CPC. If the CPC does not support the "dpm-storage-management" feature, or does not have it enabled, an empty list is returned. Storage groups for which the authenticated user does not have object-access permission are not included. Authorization requirements: * Object-access permission to any storage groups to be included in the result. Parameters: full_properties (bool): Controls that the full set of resource properties for each returned storage group is being retrieved, vs. only the following short set: "object-uri", "cpc-uri", "name", "fulfillment-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. The 'cpc-uri' property is automatically added to the filter arguments and must not be specified in this parameter. Returns: : A list of :class:`~zhmcclient.StorageGroup` objects. Raises: ValueError: The filter_args parameter specifies the 'cpc-uri' property :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ if filter_args is None: filter_args = {} else: filter_args = filter_args.copy() if 'cpc-uri' in filter_args: raise ValueError( "The filter_args parameter specifies the 'cpc-uri' property " "with value: %s" % filter_args['cpc-uri']) filter_args['cpc-uri'] = self.uri sg_list = self.manager.console.storage_groups.list( full_properties, filter_args) return sg_list @logged_api_call def validate_lun_path(self, host_wwpn, host_port, wwpn, lun): """ Validate if an FCP storage volume on an actual storage subsystem is reachable from this CPC, through a specified host port and using a specified host WWPN. This method performs the "Validate LUN Path" HMC operation. If the volume is reachable, the method returns. If the volume is not reachable (and no other errors occur), an :exc:`~zhmcclient.HTTPError` is raised, and its :attr:`~zhmcclient.HTTPError.reason` property indicates the reason as follows: * 484: Target WWPN cannot be reached. * 485: Target WWPN can be reached, but LUN cannot be reached. The CPC must have the "dpm-storage-management" feature enabled. Parameters: host_wwpn (:term:`string`): World wide port name (WWPN) of the host (CPC), as a hexadecimal number of up to 16 characters in any lexical case. This may be the WWPN of the physical storage port, or a WWPN of a virtual HBA. In any case, it must be the kind of WWPN that is used for zoning and LUN masking in the SAN. host_port (:class:`~zhmcclient.Port`): Storage port on the CPC that will be used for validating reachability. wwpn (:term:`string`): World wide port name (WWPN) of the FCP storage subsystem containing the storage volume, as a hexadecimal number of up to 16 characters in any lexical case. lun (:term:`string`): Logical Unit Number (LUN) of the storage volume within its FCP storage subsystem, as a hexadecimal number of up to 16 characters in any lexical case. Authorization requirements: * Object-access permission to the storage group owning this storage volume. * Task permission to the "Configure Storage - Storage Administrator" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # The operation requires exactly 16 characters in lower case host_wwpn_16 = format(int(host_wwpn, 16), '016x') wwpn_16 = format(int(wwpn, 16), '016x') lun_16 = format(int(lun, 16), '016x') body = { 'host-world-wide-port-name': host_wwpn_16, 'adapter-port-uri': host_port.uri, 'target-world-wide-port-name': wwpn_16, 'logical-unit-number': lun_16, } self.manager.session.post( self.uri + '/operations/validate-lun-path', body=body) @logged_api_call def add_temporary_capacity( self, record_id, software_model=None, processor_info=None, test=False, force=False): """ Add temporary processors to the CPC or increase temporary model capacity of the CPC. This method performs the "Add Temporary Capacity" HMC operation. If the request would exceed the processor capacity that is installed on the CPC or the limits of the capacity record, the operation will fail, unless the `force` parameter is `True`. Parameters: record_id (:term:`string`): The ID of the capacity record to be used for any updates of the processor capacity. software_model (:term:`string`): The name of the software model to be activated for the CPC. This must be one of the software models defined within the specified capacity record. The software model implies the number of general purpose processors that will be active once the operation succeeds. If `None`, the software model and the number of general purpose processors of the CPC will remain unchanged. processor_info (dict): The number of specialty processors to be added to the CPC. If `None`, the number of specialty processors of the CPC will remain unchanged. Each item in the dictionary identifies the number of one type of specialty processor. The key of the item must be a string specifying the type of specialty processor ('aap', 'cbp', 'icf', 'ifl', 'iip', 'sap'), and the value of the item must be an integer specifying the number of processors of that type to be added. If an item for a type of specialty processor is not provided, or if the value of the item is `None`, the number of specialty processors of that type will remain unchanged. test (bool): Indicates whether test (`True`) or real (`False`) resources should be activated. Test resources are automatically deactivated after 24h. force (bool): `True` indicates that the operation should proceed if not enough processors are available. `True` is permitted only for CBU, CPE and loaner capacity records. Authorization requirements: * Object-access permission to the CPC. * Task permission to the "Perform Model Conversion" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = { 'record-id': record_id, 'force': force, 'test': test, } if software_model: body['software-model'] = software_model if processor_info: pi = [] for ptype, pvalue in processor_info.items(): pi_item = { 'processor-type': ptype, } if pvalue is not None: pi_item['num-processor-steps'] = pvalue pi.append(pi_item) body['processor-info'] = pi self.manager.session.post( self.uri + '/operations/add-temp-capacity', body=body) @logged_api_call def remove_temporary_capacity( self, record_id, software_model=None, processor_info=None): """ Remove temporary processors from the CPC or decrease temporary model capacity of the CPC. This method performs the "Remove Temporary Capacity" HMC operation. You can only remove activated resources for the specific offering. You cannot remove dedicated engines or the last processor of a processor type. If you remove resources back to the base configuration, the capacity record activation is completed. That is, if you remove the last temporary processor, your capacity record is deactivated. For a CBU and On/Off CoD record, to add resources again, you must use another :meth:`add_temporary_capacity` operation. For an On/Off CoD test or CPE record, once the record is deactivated, it is no longer available for use. You can then delete the record. After removal of the resources, the capacity record remains as an installed record. If you want a record deleted, you must manually delete the record on the "Installed Records" page in the HMC GUI. Parameters: record_id (:term:`string`): The ID of the capacity record to be used for any updates of the processor capacity. software_model (:term:`string`): The name of the software model to be activated for the CPC. This must be one of the software models defined within the specified capacity record. The software model implies the number of general purpose processors that will be active once the operation succeeds. If `None`, the software model and the number of general purpose processors of the CPC will remain unchanged. processor_info (dict): The number of specialty processors to be removed from the CPC. If `None`, the number of specialty processors of the CPC will remain unchanged. Each item in the dictionary identifies the number of one type of specialty processor. The key of the item must be a string specifying the type of specialty processor ('aap', 'cbp', 'icf', 'ifl', 'iip', 'sap'), and the value of the item must be an integer specifying the number of processors of that type to be removed. If an item for a type of specialty processor is not provided, or if the value of the item is `None`, the number of specialty processors of that type will remain unchanged. Authorization requirements: * Object-access permission to the CPC. * Task permission to the "Perform Model Conversion" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = { 'record-id': record_id, } if software_model: body['software-model'] = software_model if processor_info: pi = [] for ptype, pvalue in processor_info.items(): pi_item = { 'processor-type': ptype, } if pvalue is not None: pi_item['num-processor-steps'] = pvalue pi.append(pi_item) body['processor-info'] = pi self.manager.session.post( self.uri + '/operations/remove-temp-capacity', body=body) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623169682.0 zhmcclient-0.31.0/zhmcclient/_exceptions.py0000644000076500000240000012657200000000000021364 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ Exceptions that can be raised by the client. """ import re __all__ = ['Error', 'ConnectionError', 'ConnectTimeout', 'ReadTimeout', 'RetriesExceeded', 'AuthError', 'ClientAuthError', 'ServerAuthError', 'ParseError', 'VersionError', 'HTTPError', 'OperationTimeout', 'StatusTimeout', 'NoUniqueMatch', 'NotFound', 'MetricsResourceNotFound', 'NotificationError', 'NotificationJMSError', 'NotificationParseError'] class Error(Exception): """ Abstract base class for exceptions specific to this package. Exceptions of this class are not raised; only derived exceptions are raised. Derived from :exc:`~py:exceptions.Exception`. """ def str_def(self): """ Interface definition for the corresponding method derived exception classes. :term:`string`: The exception as a string in a Python definition-style format, e.g. for parsing by scripts. For the exact format returned by derived exception classes, see the same-named methods there. """ raise NotImplementedError class ConnectionError(Error): # pylint: disable=redefined-builtin """ This exception indicates a problem with the connection to the HMC, below the HTTP level. HTTP errors are indicated via :exc:`~zhmcclient.HTTPError`. A retry by the user code is not likely to be successful, unless connect or read retries had been disabled when creating the session (see :class:`~zhmcclient.Session`). Even though this class has exceptions derived from it, exceptions of this class may also be raised (if no other derived class matches the circumstances). Derived from :exc:`~zhmcclient.Error`. """ def __init__(self, msg, details): """ Parameters: msg (:term:`string`): A human readable message describing the problem. details (Exception): The original exception describing details about the error. ``args[0]`` will be set to the ``msg`` parameter. """ super(ConnectionError, self).__init__(msg) self._details = details @property def details(self): """ The original exception caught by this package, providing more information about the problem. This will be one of the following exceptions: * Any exception derived from :exc:`requests.exceptions.RequestException`. """ return self._details def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(message={!r})". \ format(self.__class__.__name__, self.args[0]) 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={}; """ return "classname={!r}; message={!r};". \ format(self.__class__.__name__, self.args[0]) class ConnectTimeout(ConnectionError): """ This exception indicates that a connection to the HMC timed out after exhausting the connect retries (see :attr:`zhmcclient.RetryTimeoutConfig.connect_retries`). Further retrying by the user code is not likely to be successful, unless connect retries had been disabled when creating the session (see :class:`~zhmcclient.Session`). Derived from :exc:`~zhmcclient.ConnectionError`. """ def __init__(self, msg, details, connect_timeout, connect_retries): """ Parameters: msg (:term:`string`): A human readable message describing the problem. details (Exception): The original exception describing details about the error. connect_timeout (:term:`integer`): The connect timeout in seconds. connect_retries (:term:`integer`): The number of connect retries. ``args[0]`` will be set to the ``msg`` parameter. """ super(ConnectTimeout, self).__init__(msg, details) self._connect_timeout = connect_timeout self._connect_retries = connect_retries @property def connect_timeout(self): """ :term:`integer`: The connect timeout in seconds. """ return self._connect_timeout @property def connect_retries(self): """ :term:`integer`: The number of connect retries. """ return self._connect_retries def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(message={!r}, connect_timeout={!r}, " \ "connect_retries={!r})". \ format(self.__class__.__name__, self.args[0], self.connect_timeout, self.connect_retries) 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={}; connect_timeout={}; connect_retries={}; message={}; """ # noqa: E501 return "classname={!r}; connect_timeout={!r}; connect_retries={!r}; " \ "message={!r};". \ format(self.__class__.__name__, self.connect_timeout, self.connect_retries, self.args[0]) class ReadTimeout(ConnectionError): """ This exception indicates that reading an HTTP response from the HMC timed out after exhausting the read retries (see :attr:`zhmcclient.RetryTimeoutConfig.read_retries`). Further retrying by the user code is not likely to be successful, unless read retries had been disabled when creating the session (see :class:`~zhmcclient.Session`). Derived from :exc:`~zhmcclient.ConnectionError`. """ def __init__(self, msg, details, read_timeout, read_retries): """ Parameters: msg (:term:`string`): A human readable message describing the problem. details (Exception): The original exception describing details about the error. read_timeout (:term:`integer`): The read timeout in seconds. read_retries (:term:`integer`): The number of read retries. ``args[0]`` will be set to the ``msg`` parameter. """ super(ReadTimeout, self).__init__(msg, details) self._read_timeout = read_timeout self._read_retries = read_retries @property def read_timeout(self): """ :term:`integer`: The read timeout in seconds. """ return self._read_timeout @property def read_retries(self): """ :term:`integer`: The number of read retries. """ return self._read_retries def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(message={!r}, read_timeout={!r}, read_retries={!r})". \ format(self.__class__.__name__, self.args[0], self.read_timeout, self.read_retries) 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={}; read_timeout={}; read_retries={}; message={}; """ return "classname={!r}; read_timeout={!r}; read_retries={!r}; " \ "message={!r};". \ format(self.__class__.__name__, self.read_timeout, self.read_retries, self.args[0]) class RetriesExceeded(ConnectionError): """ This exception indicates that the maximum number of retries for connecting to the HMC, sending HTTP requests or reading HTTP responses was exceeded, for reasons other than connect timeouts (see :exc:`~zhmcclient.ConnectTimeout`) or read timeouts (see :exc:`~zhmcclient.ReadTimeout`). Further retrying by the user code is not likely to be successful, unless connect or read retries had been disabled when creating the session (see :class:`~zhmcclient.Session`). Derived from :exc:`~zhmcclient.ConnectionError`. """ def __init__(self, msg, details, connect_retries): """ Parameters: msg (:term:`string`): A human readable message describing the problem. details (Exception): The original exception describing details about the error. connect_retries (:term:`integer`): The number of connect retries. ``args[0]`` will be set to the ``msg`` parameter. """ super(RetriesExceeded, self).__init__(msg, details) self._connect_retries = connect_retries @property def connect_retries(self): """ :term:`integer`: The number of connect retries. """ return self._connect_retries def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(message={!r}, connect_retries={!r})". \ format(self.__class__.__name__, self.args[0], self.connect_retries) 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={}; connect_retries={}; message={}; """ return "classname={!r}; connect_retries={!r}; message={!r};". \ format(self.__class__.__name__, self.connect_retries, self.args[0]) class AuthError(Error): # pylint: disable=abstract-method """ This exception indicates erors related to authentication. Exceptions of this class are not raised; only derived exceptions are raised. Derived from :exc:`~zhmcclient.Error`. """ pass class ClientAuthError(AuthError): """ This exception indicates an authentication related problem detected on the client side. Derived from :exc:`~zhmcclient.AuthError`. """ def __init__(self, msg): # pylint: disable=useless-super-delegation """ Parameters: msg (:term:`string`): A human readable message describing the problem. ``args[0]`` will be set to the ``msg`` parameter. """ super(ClientAuthError, self).__init__(msg) def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(message={!r})". \ format(self.__class__.__name__, self.args[0]) 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={}; """ return "classname={!r}; message={!r};". \ format(self.__class__.__name__, self.args[0]) class ServerAuthError(AuthError): """ This exception indicates an authentication error with the HMC. Derived from :exc:`~zhmcclient.AuthError`. """ def __init__(self, msg, details): """ Parameters: msg (:term:`string`): A human readable message describing the problem. details (Exception): The :exc:`~zhmcclient.HTTPError` exception describing the error returned by the HMC. ``args[0]`` will be set to the ``msg`` parameter. """ super(ServerAuthError, self).__init__(msg) assert isinstance(details, HTTPError) self._details = details @property def details(self): """ The original exception describing details about the error. This may be one of the following exceptions: * :exc:`~zhmcclient.HTTPError` """ return self._details def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(message={!r}, details.request_method={!r}, " \ "details.request_uri={!r}, details.http_status={!r}, " \ "details.reason={!r})". \ format(self.__class__.__name__, self.args[0], self.details.request_method, self.details.request_uri, self.details.http_status, self.details.reason) def str_def(self): # pylint: disable=line-too-long """ :term:`string`: The exception as a string in a Python definition-style format, e.g. for parsing by scripts: .. code-block:: text classname={}; request_method={}; request_uri={}; http_status={}; reason={}; message={}; """ # noqa: E501 # pylint: enable=line-too-long return "classname={!r}; request_method={!r}; request_uri={!r}; " \ "http_status={!r}; reason={!r}; message={!r};". \ format(self.__class__.__name__, self.details.request_method, self.details.request_uri, self.details.http_status, self.details.reason, self.args[0]) class ParseError(Error): """ This exception indicates a parsing error while processing the JSON payload in a response from the HMC. Derived from :exc:`~zhmcclient.Error`. The error location within the payload is automatically determined by parsing the error message for the pattern: .. code-block:: text : line {line} column {column} """ def __init__(self, msg): """ Parameters: msg (:term:`string`): A human readable message describing the problem. This should be the message of the `ValueError` exception raised by methods of the :class:`py:json.JSONDecoder` class. ``args[0]`` will be set to the ``msg`` parameter. """ super(ParseError, self).__init__(msg) self._line = None self._column = None if msg: m = re.search(r': line ([0-9]+) column ([0-9]+) ', msg) if m: self._line = int(m.group(1)) self._column = int(m.group(2)) @property def line(self): """ :term:`integer`: The 1-based line number of the error location within the JSON payload. `None` indicates that the error location is not available. """ return self._line @property def column(self): """ :term:`integer`: The 1-based column number of the error location within the JSON payload. `None` indicates that the error location is not available. """ return self._column def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(message={!r}, line={!r}, column={!r})". \ format(self.__class__.__name__, self.args[0], self.line, self.column) 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={}; line={}; column={}; message={}; """ return "classname={!r}; line={!r}; column={!r}; message={!r};". \ format(self.__class__.__name__, self.line, self.column, self.args[0]) class VersionError(Error): """ This exception indicates that a function of the client requires a minimum HMC API version which is not supported by the HMC. Derived from :exc:`~zhmcclient.Error`. """ def __init__(self, msg, min_api_version, api_version): """ Parameters: msg (:term:`string`): A human readable message describing the problem. min_api_version (:term:`HMC API version`): The minimum HMC API version required to perform the function that raised this exception. api_version (:term:`HMC API version`): The actual HMC API version supported by the HMC. ``args[0]`` will be set to the ``msg`` parameter. """ super(VersionError, self).__init__(msg) self._min_api_version = min_api_version self._api_version = api_version @property def min_api_version(self): """ :term:`HMC API version`: The minimum HMC API version required to perform the function that raised this exception. """ return self._min_api_version @property def api_version(self): """ :term:`HMC API version`: The actual HMC API version supported by the HMC. """ return self._api_version def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(message={!r}, min_api_version={!r}, api_version={!r})". \ format(self.__class__.__name__, self.args[0], self.min_api_version, self.api_version) 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={}; min_api_version={}; api_version={}; message={}; """ return "classname={!r}; min_api_version={!r}; api_version={!r}; " \ "message={!r};". \ format(self.__class__.__name__, self.min_api_version, self.api_version, self.args[0]) class HTTPError(Error): """ This exception indicates that the HMC returned an HTTP response with a bad HTTP status code. Derived from :exc:`~zhmcclient.Error`. """ def __init__(self, body): """ Parameters: body (:term:`json object`): Body of the HTTP error response. ``args[0]`` will be set to the 'message' item of the body, or to `None` if not present. """ msg = body.get('message', None) super(HTTPError, self).__init__(msg) self._body = body @property def http_status(self): """ :term:`integer`: Numeric HTTP status code (e.g. 500). See :term:`RFC2616` for a list of HTTP status codes and reason phrases. """ return self._body.get('http-status', None) @property def reason(self): """ :term:`integer`: Numeric HMC reason code. HMC reason codes provide more details as to the nature of the error than is provided by the HTTP status code. This HMC reason code is treated as a sub-code of the HTTP status code and thus must be used in conjunction with the HTTP status code to determine the error condition. Standard HMC reason codes that apply across the entire API are described in section 'Common request validation reason codes' in the :term:`HMC API` book. Additional operation-specific reason codes may also be documented in the description of specific API operations in the :term:`HMC API` book. The articial reason code 999 is used when the response from the HMC contains an HTML-formatted error message. """ return self._body.get('reason', None) @property def message(self): """ :term:`string`: Message describing the error. This message is not currently localized. """ return self._body.get('message', None) @property def request_method(self): """ :term:`string`: The HTTP method (DELETE, GET, POST, PUT) that caused this error response. """ return self._body.get('request-method', None) @property def request_uri(self): """ :term:`string`: The URI that caused this error response. """ return self._body.get('request-uri', None) @property def request_query_parms(self): """ List of query-parm-info objects: URI query parameters specified on the request. Each query-parm-info object identifies a single query parameter by its name and includes its value(s). An empty list, if the request did not specify any query parameters. """ return self._body.get('request-query-parms', None) @property def request_headers(self): """ header-info object: HTTP headers specified on the request. An empty list, if the request did not specify any HTTP headers. """ return self._body.get('request-headers', None) @property def request_authenticated_as(self): """ :term:`string`: Name of the HMC user associated with the API session under which the request was issued. `None`, if the request was issued without an established session or there is no HMC user bound to the session. """ return self._body.get('request-authenticated-as', None) @property def request_body(self): """ The request body, in the form of a JSON document. Note that, since it is in the form of a JSON document, this may not be exactly what was submitted by the API client program, but it is semantically equivalent. If the request body could not be parsed or some other error prevented the creation of a JSON document from the request body, this property is `None` and the request body is instead available in the :attr:`~zhmcclient.HTTPError.request_body_as_string` property. """ return self._body.get('request-body', None) @property def request_body_as_string(self): """ :term:`string`: The complete request body, or some portion of the request body, exactly as it was submitted by the API client program, if the :attr:`~zhmcclient.HTTPError.request_body` property is `None`. Otherwise, `None`. The :attr:`~zhmcclient.HTTPError.request_body_as_string_partial` property indicates whether the complete request body is provided in this property. """ return self._body.get('request-body-as-string', None) @property def request_body_as_string_partial(self): """ :class:`py:bool`: Indicates whether the :attr:`~zhmcclient.HTTPError.request_body_as_string` property contains only part of the request body (`True`) or the entire request body (`False`). `None`, if the :attr:`~zhmcclient.HTTPError.request_body_as_string` property is `None`. """ return self._body.get('request-body-as-string-partial', None) @property def stack(self): """ :term:`string`: Internal HMC diagnostic information for the error. This field is supplied only on selected 5xx HTTP status codes. `None`, if not supplied. """ return self._body.get('stack', None) @property def error_details(self): """ :term:`string`: A nested object that provides additional operation-specific error information. This field is provided by selected operations, and the format of the nested object is as described by that operation. """ return self._body.get('error-details', None) def __str__(self): """ Return a human readable string representation of this exception object. """ stack_txt = ' stack={!r}'.format(self.stack) if self.stack else '' return "{},{}: {} [{} {}]{}".\ format(self.http_status, self.reason, self.message, self.request_method, self.request_uri, stack_txt) def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(http_status={!r}, reason={!r}, message={!r}, " \ "request_method={!r}, request_uri={!r}, stack={!r}, ...)". \ format(self.__class__.__name__, self.http_status, self.reason, self.message, self.request_method, self.request_uri, self.stack) def str_def(self): # pylint: disable=line-too-long """ :term:`string`: The exception as a string in a Python definition-style format, e.g. for parsing by scripts: .. code-block:: text classname={}; request_method={}; request_uri={}; http_status={}; reason={}; message={}; """ # noqa: E501 # pylint: enable=line-too-long return "classname={!r}; request_method={!r}; request_uri={!r}; " \ "http_status={!r}; reason={!r}; message={!r};". \ format(self.__class__.__name__, self.request_method, self.request_uri, self.http_status, self.reason, self.args[0]) class OperationTimeout(Error): """ This exception indicates that the waiting for completion of an asynchronous HMC operation has timed out. Derived from :exc:`~zhmcclient.Error`. """ def __init__(self, msg, operation_timeout): """ Parameters: msg (:term:`string`): A human readable message describing the problem. operation_timeout (:term:`integer`): The operation timeout in seconds. ``args[0]`` will be set to the ``msg`` parameter. """ super(OperationTimeout, self).__init__(msg) self._operation_timeout = operation_timeout @property def operation_timeout(self): """ :term:`integer`: The operation timeout in seconds. """ return self._operation_timeout def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(message={!r}, operation_timeout={!r})". \ format(self.__class__.__name__, self.args[0], self.operation_timeout) 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={}; operation_timeout={}; message={}; """ return "classname={!r}; operation_timeout={!r}; message={!r};". \ format(self.__class__.__name__, self.operation_timeout, self.args[0]) class StatusTimeout(Error): """ This exception indicates that the waiting for reaching a desired LPAR or Partition status has timed out. The possible status values for an LPAR are: * ``"not-activated"`` - The LPAR is not active. * ``"not-operating"`` - The LPAR is active but no operating system is running in the LPAR. * ``"operating"`` - The LPAR is active and an operating system is running in the LPAR. * ``"exceptions"`` - The LPAR or its CPC has one or more unusual conditions. The possible status values for a Partition are described in the 'status' property of the data model for the partition resource in the :term:`HMC API` book. Derived from :exc:`~zhmcclient.Error`. """ def __init__(self, msg, actual_status, desired_statuses, status_timeout): """ Parameters: msg (:term:`string`): A human readable message describing the problem. actual_status (:term:`string`): The actual status (at the point in time when the status timeout expired). desired_statuses (iterable of :term:`string`): The desired status values that were supposed to be reached. status_timeout (:term:`number`): The status timeout (in seconds) that has expired. ``args[0]`` will be set to the ``msg`` parameter. """ super(StatusTimeout, self).__init__(msg) self._actual_status = actual_status self._desired_statuses = desired_statuses self._status_timeout = status_timeout @property def actual_status(self): """ :term:`string`: The actual status (at the point in time when the status timeout expired). """ return self._actual_status @property def desired_statuses(self): """ iterable of :term:`string`: The desired status values that were supposed to be reached. """ return self._desired_statuses @property def status_timeout(self): """ :term:`number`: The status timeout (in seconds) that has expired. """ return self._status_timeout def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(message={!r}, actual_status={!r}, desired_statuses={!r}, " \ "status_timeout={!r})". \ format(self.__class__.__name__, self.args[0], self.actual_status, self.desired_statuses, self.status_timeout) def str_def(self): # pylint: disable=line-too-long """ :term:`string`: The exception as a string in a Python definition-style format, e.g. for parsing by scripts: .. code-block:: text classname={}; actual_status={}; desired_statuses={}; status_timeout={}; message={}; """ # noqa: E501 # pylint: enable=line-too-long return "classname={!r}; actual_status={!r}; desired_statuses={!r}; " \ "status_timeout={!r}; message={!r};". \ format(self.__class__.__name__, self.actual_status, self.desired_statuses, self.status_timeout, self.args[0]) class NoUniqueMatch(Error): """ This exception indicates that more than one resource matched the filter arguments. Derived from :exc:`~zhmcclient.Error`. """ def __init__(self, filter_args, manager, resources): """ Parameters: filter_args (dict): Dictionary of filter arguments by which the resource was attempted to be found. Keys are the resource property names, values are the match values for that property. manager (:class:`~zhmcclient.BaseManager`): The manager of the resource, in whose scope the resource was attempted to be found. Must not be `None`. resources (:term:`iterable` of :class:`~zhmcclient.BaseResource`): The resources that did match the filter. Must not be `None`. ``args[0]`` will be set to an exception message that is automatically constructed from the input parameters. """ parent = manager.parent if parent: in_str = " in {} {!r}". \ format(parent.__class__.__name__, parent.name) else: in_str = "" resource_uris = [r.uri for r in resources] msg = "Found more than one {} using filter arguments {!r}{}, with " \ "URIs: {!r}". \ format(manager.resource_class.__name__, filter_args, in_str, resource_uris) super(NoUniqueMatch, self).__init__(msg) self._filter_args = filter_args self._manager = manager self._resources = list(resources) self._resource_uris = resource_uris @property def filter_args(self): """ dict: Dictionary of filter arguments by which the resource was attempted to be found. Keys are the resource property names, values are the match values for that property. """ return self._filter_args @property def manager(self): """ :class:`~zhmcclient.BaseManager`: The manager of the resource, in whose scope the resource was attempted to be found. """ return self._manager @property def resources(self): """ List of :class:`~zhmcclient.BaseResource`: The resources that matched the filter. """ return self._resources @property def resource_uris(self): """ List of URIs of the resources that matched the filter. """ return self._resource_uris def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ parent = self.manager.parent return "{}(message={!r}, resource_classname={!r}, filter_args={!r}, " \ "parent_classname={!r}, parent_name={!r}, " \ "resource_uris={!r})". \ format(self.__class__.__name__, self.args[0], self.manager.resource_class.__name__, self.filter_args, parent.__class__.__name__ if parent else None, parent.name if parent else None, self.resource_uris) def str_def(self): # pylint: disable=line-too-long """ :term:`string`: The exception as a string in a Python definition-style format, e.g. for parsing by scripts: .. code-block:: text classname={}; resource_classname={}; filter_args={}; parent_classname={}; manager_name={}; message={}; resource_uris={} """ # noqa: E501 # pylint: enable=line-too-long parent = self.manager.parent return "classname={!r}; resource_classname={!r}; filter_args={!r}; " \ "parent_classname={!r}; parent_name={!r}; message={!r}; " \ "resource_uris={!r}". \ format(self.__class__.__name__, self.manager.resource_class.__name__, self.filter_args, parent.__class__.__name__ if parent else None, parent.name if parent else None, self.args[0], self.resource_uris) class NotFound(Error): """ This exception indicates that a resource was not found. Derived from :exc:`~zhmcclient.Error`. """ def __init__(self, filter_args, manager): """ Parameters: filter_args (dict): Dictionary of filter arguments by which the resource was attempted to be found. Keys are the resource property names, values are the match values for that property. manager (:class:`~zhmcclient.BaseManager`): The manager of the resource, in whose scope the resource was attempted to be found. Must not be `None`. ``args[0]`` will be set to an exception message that is automatically constructed from the input parameters. """ parent = manager.parent if parent: in_str = " in {} {!r}". \ format(parent.__class__.__name__, parent.name) else: in_str = "" if filter_args and len(filter_args) == 1 and \ manager._name_prop in filter_args: msg = "Could not find {} {!r}{}.". \ format(manager.resource_class.__name__, filter_args[manager._name_prop], in_str) else: msg = "Could not find {} using filter arguments {!r}{}.".\ format(manager.resource_class.__name__, filter_args, in_str) super(NotFound, self).__init__(msg) self._filter_args = filter_args self._manager = manager @property def filter_args(self): """ dict: Dictionary of filter arguments by which the resource was attempted to be found. Keys are the resource property names, values are the match values for that property. """ return self._filter_args @property def manager(self): """ :class:`~zhmcclient.BaseManager`: The manager of the resource, in whose scope the resource was attempted to be found. """ return self._manager def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ parent = self.manager.parent return "{}(message={!r}, resource_classname={!r}, filter_args={!r}, " \ "parent_classname={!r}, parent_name={!r})". \ format(self.__class__.__name__, self.args[0], self.manager.resource_class.__name__, self.filter_args, parent.__class__.__name__ if parent else None, parent.name if parent else None) def str_def(self): # pylint: disable=line-too-long """ :term:`string`: The exception as a string in a Python definition-style format, e.g. for parsing by scripts: .. code-block:: text classname={}; resource_classname={}; filter_args={}; parent_classname={}; parent_name={}; message={}; """ # noqa: E501 # pylint: enable=line-too-long parent = self.manager.parent return "classname={!r}; resource_classname={!r}; filter_args={!r}; " \ "parent_classname={!r}; parent_name={!r}; message={!r};". \ format(self.__class__.__name__, self.manager.resource_class.__name__, self.filter_args, parent.__class__.__name__ if parent else None, parent.name if parent else None, self.args[0]) class MetricsResourceNotFound(Error): # pylint: disable=redefined-builtin """ This exception indicates that the resource referenced by a metric object value was not found on the HMC. Derived from :exc:`~zhmcclient.Error`. """ def __init__(self, msg, resource_class, managers): """ Parameters: msg (:term:`string`): A human readable message describing the problem. resource_class (:class:`~zhmcclient.BaseResource`): The zhmcclient resource class of the resource that was not found. managers (list of :class:`~zhmcclient.BaseManager`): List of zhmcclient resource managers that were searched for the resource. ``args[0]`` will be set to the ``msg`` parameter. """ super(MetricsResourceNotFound, self).__init__(msg) self._resource_class = resource_class self._managers = managers @property def resource_class(self): """ The zhmcclient resource class of the resource that was not found. """ return self._resource_class @property def managers(self): """ List of zhmcclient resource managers that were searched for the resource """ return self._managers def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(message={!r}". \ format(self.__class__.__name__, self.args[0]) 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={} """ return "classname={!r}; message={!r}". \ format(self.__class__.__name__, self.args[0]) class NotificationError(Error): """ Abstract base class for exceptions raised by :class:`~zhmcclient.NotificationListener`. Exceptions of this class are not raised; only derived exceptions are raised. Derived from :exc:`~zhmcclient.Error`. """ def str_def(self): """ Interface definition for the corresponding method derived exception classes. :term:`string`: The exception as a string in a Python definition-style format, e.g. for parsing by scripts. For the exact format returned by derived exception classes, see the same-named methods there. """ raise NotImplementedError class NotificationJMSError(NotificationError): """ This exception indicates that a JMS error was returned by the HMC in the notification protocol. Derived from :exc:`~zhmcclient.NotificationError`. """ def __init__(self, msg, jms_headers, jms_message): """ Parameters: msg (:term:`string`): A human readable message describing the problem. This is either the 'message' item from the JMS headers if present, or a generic message. jms_headers (dict): The JMS headers returned by the HMC. jms_message (:term:`string`): The JMS message body returned by the HMC. ``args[0]`` will be set to the ``msg`` parameter. """ super(NotificationJMSError, self).__init__(msg) self._jms_headers = jms_headers self._jms_message = jms_message @property def jms_headers(self): """ dict: The JMS headers returned by the HMC. """ return self._jms_headers @property def jms_message(self): """ :term:`string`: The JMS message body returned by the HMC. """ return self._jms_message def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(message={!r}, jms_headers={!r}, jms_message={!r})". \ format(self.__class__.__name__, self.args[0], self.jms_headers, self.jms_message) 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={} """ return "classname={!r}; message={!r};". \ format(self.__class__.__name__, self.args[0]) class NotificationParseError(NotificationError): """ This exception indicates that the message body of a JMS message could not be parsed as JSON format. Derived from :exc:`~zhmcclient.NotificationError`. """ def __init__(self, msg, jms_message): """ Parameters: msg (:term:`string`): A human readable message describing the problem. jms_message (:term:`string`): The JMS message body returned by the HMC. ``args[0]`` will be set to the ``msg`` parameter. """ super(NotificationParseError, self).__init__(msg) self._jms_message = jms_message @property def jms_message(self): """ :term:`string`: The JMS message body returned by the HMC. """ return self._jms_message def __repr__(self): """ Return a string with the state of this exception object, for debug purposes. """ return "{}(message={!r}, jms_message={!r})". \ format(self.__class__.__name__, self.args[0], self.jms_message) 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={} """ return "classname={!r}; message={!r};". \ format(self.__class__.__name__, self.args[0]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_hba.py0000644000076500000240000002627500000000000017734 0ustar00maierastaff00000000000000# Copyright 2016-2017 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:`HBA` (Host Bus Adapter) is a logical entity that provides a :term:`Partition` with access to external storage area networks (SANs) through an :term:`FCP Adapter`. More specifically, an HBA connects a Partition with an :term:`Adapter Port` on an FCP Adapter. HBA resources are contained in Partition resources. HBA resources only exist in :term:`CPCs ` that are in DPM mode and when the "dpm-storage-management" feature is not enabled. See section :ref:`Storage Groups` for details. When the "dpm-storage-management" feature is enabled, :term:`virtual HBAs ` are represented as :term:`Virtual Storage Resource` resources. """ from __future__ import absolute_import import copy from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['HbaManager', 'Hba'] class HbaManager(BaseManager): """ Manager providing access to the :term:`HBAs ` in a particular :term:`Partition`. 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 of a :class:`~zhmcclient.Partition` object (in DPM mode): * :attr:`~zhmcclient.Partition.hbas` Note that this instance variable will be `None` if the "dpm-storage-management" feature is enabled. """ def __init__(self, partition): # This function should not go into the docs. # Parameters: # partition (:class:`~zhmcclient.Partition`): # Partition defining the scope for this manager. super(HbaManager, self).__init__( resource_class=Hba, class_name='hba', session=partition.manager.session, parent=partition, base_uri='{}/hbas'.format(partition.uri), oid_prop='element-id', uri_prop='element-uri', name_prop='name', query_props=[], list_has_name=False) @property def partition(self): """ :class:`~zhmcclient.Partition`: :term:`Partition` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the HBAs in this Partition. The returned HBAs have only the 'element-uri' property set. Filtering is supported only for the 'element-uri' property. Authorization requirements: * Object-access permission to this Partition. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.Hba` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] uris = self.partition.get_property('hba-uris') if uris: for uri in uris: resource_obj = self.resource_class( manager=self, uri=uri, name=None, properties=None) if self._matches_filters(resource_obj, filter_args): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties): """ Create and configure an HBA in this Partition. The HBA must be backed by an adapter port on an FCP adapter. The backing adapter port is specified in the "properties" parameter of this method by setting the "adapter-port-uri" property to the URI of the backing adapter port. The value for the "adapter-port-uri" property can be determined from a given adapter name and port index as shown in the following example code (omitting any error handling): .. code-block:: python partition = ... # Partition object for the new HBA adapter_name = 'FCP #1' # name of adapter with backing port adapter_port_index = 0 # port index of backing port adapter = partition.manager.cpc.adapters.find(name=adapter_name) port = adapter.ports.find(index=adapter_port_index) properties['adapter-port-uri'] = port.uri Authorization requirements: * Object-access permission to this Partition. * Object-access permission to the backing Adapter for the new HBA. * Task permission to the "Partition Details" task. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create HBA' in the :term:`HMC API` book. Returns: Hba: The resource object for the new HBA. The object will have its 'element-uri' property set as returned by the HMC, and will also have the input properties set. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.session.post(self.partition.uri + '/hbas', body=properties) # There should not be overlaps, but just in case there are, the # returned props should overwrite the input props: props = copy.deepcopy(properties) props.update(result) name = props.get(self._name_prop, None) uri = props[self._uri_prop] hba = Hba(self, uri, name, props) self._name_uri_cache.update(name, uri) return hba class Hba(BaseResource): """ Representation of an :term:`HBA`. Derived from :class:`~zhmcclient.BaseResource`; see there for common methods and attributes. For the properties of an HBA resource, see section 'Data model - HBA Element Object' in section 'Partition object' in the :term:`HMC API` book. 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.HbaManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # Parameters: # manager (:class:`~zhmcclient.HbaManager`): # 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, HbaManager), \ "Hba init: Expected manager type %s, got %s" % \ (HbaManager, type(manager)) super(Hba, self).__init__(manager, uri, name, properties) @logged_api_call def delete(self): """ Delete this HBA. Authorization requirements: * Object-access permission to the Partition containing this HBA. * Task permission to the "Partition Details" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.delete(self._uri) self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this HBA. Authorization requirements: * Object-access permission to the Partition containing this HBA. * **TBD: Verify:** Object-access permission to the backing Adapter for this HBA. * Task permission to the "Partition Details" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model - HBA Element Object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) 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._properties.update(copy.deepcopy(properties)) if is_rename: # Add the new name to the cache self.manager._name_uri_cache.update(self.name, self.uri) @logged_api_call def reassign_port(self, port): """ Reassign this HBA to a new underlying :term:`FCP port`. This method performs the HMC operation "Reassign Storage Adapter Port". Authorization requirements: * Object-access permission to the Partition containing this HBA. * Object-access permission to the Adapter with the new Port. * Task permission to the "Partition Details" task. Parameters: port (:class:`~zhmcclient.Port`): :term:`FCP port` to be used. Raises: :exc:`~zhmcclient.HTTPError`: See the HTTP status and reason codes of operation "Reassign Storage Adapter Port" in the :term:`HMC API` book. :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = {'adapter-port-uri': port.uri} self.manager.session.post( self._uri + '/operations/reassign-storage-adapter-port', body=body) self._properties.update(body) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_ldap_server_definition.py0000644000076500000240000002240700000000000023711 0ustar00maierastaff00000000000000# Copyright 2017 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:`LDAP Server Definition` resource represents a definition that contains information about an LDAP server that may be used for HMC user authentication purposes. """ from __future__ import absolute_import import copy from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['LdapServerDefinitionManager', 'LdapServerDefinition'] class LdapServerDefinitionManager(BaseManager): """ Manager providing access to the :term:`LDAP Server Definition` resources of a 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 of a :class:`~zhmcclient.Console` object: * :attr:`zhmcclient.Console.ldap_server_definitions` """ def __init__(self, console): # This function should not go into the docs. # Parameters: # console (:class:`~zhmcclient.Console`): # Console object representing the HMC. # 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 = [ 'name', ] super(LdapServerDefinitionManager, self).__init__( resource_class=LdapServerDefinition, class_name='ldap-server-definition', session=console.manager.session, parent=console, base_uri='/api/console/ldap-server-definitions', oid_prop='element-id', uri_prop='element-uri', name_prop='name', query_props=query_props) @property def console(self): """ :class:`~zhmcclient.Console`: :term:`Console` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=True, filter_args=None): """ List the :term:`LDAP Server Definition` resources representing the definitions of LDAp servers in this HMC. Authorization requirements: * User-related-access permission to the LDAP Server Definition objects included in the result, or task permission to the "Manage LDAP Server Definitions" task. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.LdapServerDefinition` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'ldap-server-definitions' uri = '{}/{}{}'.format(self.console.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties): """ Create a new LDAP Server Definition in this HMC. Authorization requirements: * Task permission to the "Manage LDAP Server Definitions" task. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create LDAP Server Definition' in the :term:`HMC API` book. Returns: LdapServerDefinition: The resource object for the new LDAP Server Definition. The object will have its 'object-uri' property set as returned by the HMC, and will also have the input properties set. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.session.post( self.console.uri + '/ldap-server-definitions', body=properties) # There should not be overlaps, but just in case there are, the # returned props should overwrite the input props: props = copy.deepcopy(properties) props.update(result) name = props.get(self._name_prop, None) uri = props[self._uri_prop] ldap_server_definition = LdapServerDefinition(self, uri, name, props) self._name_uri_cache.update(name, uri) return ldap_server_definition class LdapServerDefinition(BaseResource): """ Representation of a :term:`LDAP Server Definition`. 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.LdapServerDefinitionManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.LdapServerDefinitionManager`): # 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, LdapServerDefinitionManager), \ "Console init: Expected manager type %s, got %s" % \ (LdapServerDefinitionManager, type(manager)) super(LdapServerDefinition, self).__init__( manager, uri, name, properties) @logged_api_call def delete(self): """ Delete this LDAP Server Definition. Authorization requirements: * Task permission to the "Manage LDAP Server Definitions" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.delete(self.uri) self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this LDAP Server Definitions. Authorization requirements: * Task permission to the "Manage LDAP Server Definitions" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model' in section 'LDAP Server Definition object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) # The name of LDAP Server Definitions cannot be updated. An attempt to # do so should cause HTTPError to be raised in the POST above, so we # assert that here, because we omit the extra code for handling name # updates: assert self.manager._name_prop not in properties self._properties.update(copy.deepcopy(properties)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/_logging.py0000644000076500000240000002142100000000000020614 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ The zhmcclient supports logging using the standard Python :mod:`py:logging` module, using standard Python :class:`~py:logging.Logger` objects with these names: * 'zhmcclient.api' for user-issued calls to zhmcclient API functions, at the debug level. Internal calls to API functions are not logged. * 'zhmcclient.hmc' for interactions between zhmcclient and the HMC, at the debug level. For HMC operations and API calls that contain the HMC password or HMC session tokens, the password is hidden in the log message by replacing it with a few '*' characters. All these loggers have a null-handler (see :class:`~py:logging.NullHandler`) and have no log formatter (see :class:`~py:logging.Formatter`). As a result, the loggers are silent by default. If you want to turn on logging, add a log handler (see :meth:`~py:logging.Logger.addHandler`, and :mod:`py:logging.handlers` for the handlers included with Python) and set the log level (see :meth:`~py:logging.Logger.setLevel`, and :ref:`py:levels` for the defined levels). If you want to change the default log message format, use :meth:`~py:logging.Handler.setFormatter`. Its ``form`` parameter is a format string with %-style placeholders for the log record attributes (see Python section :ref:`py:logrecord-attributes`). Examples: * To output the log records for all HMC operations to ``stdout`` in a particular format, do this:: import logging handler = logging.StreamHandler() format_string = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' handler.setFormatter(logging.Formatter(format_string)) logger = logging.getLogger('zhmcclient.hmc') logger.addHandler(handler) logger.setLevel(logging.DEBUG) * This example uses the :func:`~py:logging.basicConfig` convenience function that sets the same format and level as in the previous example, but for the root logger. Therefore, it will output all log records, not just from this package:: import logging format_string = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' logging.basicConfig(format=format_string, level=logging.DEBUG) """ import logging import inspect try: from decorator import decorate # decorate >= 4.0 except ImportError: from decorator import decorator # decorate < 4.0 from ._constants import API_LOGGER_NAME def log_escaped(string): """ Return the escaped input string, for use in log messages. """ return string.replace('\n', ' ').replace(' ', ' ').replace(' ', ' ').\ replace(' ', ' ') def get_logger(name): """ Return a :class:`~py:logging.Logger` object with the specified name. A :class:`~py:logging.NullHandler` handler is added to the logger if it does not have any handlers yet and if it is not the Python root logger. This prevents the propagation of log requests up the Python logger hierarchy, and therefore causes this package to be silent by default. """ logger = logging.getLogger(name) if name != '' and not logger.handlers: logger.addHandler(logging.NullHandler()) return logger def logged_api_call(func): """ Function decorator that causes the decorated API function or method to log calls to itself to a logger. The logger's name is the dotted module name of the module defining the decorated function (e.g. 'zhmcclient._cpc'). Parameters: func (function object): The original function being decorated. Returns: function object: The function wrappering the original function being decorated. Raises: TypeError: The @logged_api_call decorator must be used on a function or method (and not on top of the @property decorator). """ # Note that in this decorator function, we are in a module loading context, # where the decorated functions are being defined. When this decorator # function is called, its call stack represents the definition of the # decorated functions. Not all global definitions in the module have been # defined yet, and methods of classes that are decorated with this # decorator are still functions at this point (and not yet methods). module = inspect.getmodule(func) if not inspect.isfunction(func) or not hasattr(module, '__name__'): raise TypeError("The @logged_api_call decorator must be used on a " "function or method (and not on top of the @property " "decorator)") try: # We avoid the use of inspect.getouterframes() because it is slow, # and use the pointers up the stack frame, instead. this_frame = inspect.currentframe() # this decorator function here apifunc_frame = this_frame.f_back # the decorated API function apifunc_owner = inspect.getframeinfo(apifunc_frame)[2] finally: # Recommended way to deal with frame objects to avoid ref cycles del this_frame del apifunc_frame # TODO: For inner functions, show all outer levels instead of just one. if apifunc_owner == '': # The decorated API function is defined globally (at module level) apifunc_str = '{func}()'.format(func=func.__name__) else: # The decorated API function is defined in a class or in a function apifunc_str = '{owner}.{func}()'.format(owner=apifunc_owner, func=func.__name__) logger = get_logger(API_LOGGER_NAME) def is_external_call(): """ Return a boolean indicating whether the call to the decorated API function is an external call (vs. b eing an internal call). """ try: # We avoid the use of inspect.getouterframes() because it is slow, # and use the pointers up the stack frame, instead. log_it_frame = inspect.currentframe() # this log_it() function log_api_call_frame = log_it_frame.f_back # the log_api_call() func apifunc_frame = log_api_call_frame.f_back # the decorated API func apicaller_frame = apifunc_frame.f_back # caller of API function apicaller_module = inspect.getmodule(apicaller_frame) if apicaller_module is None: apicaller_module_name = "" else: apicaller_module_name = apicaller_module.__name__ finally: # Recommended way to deal with frame objects to avoid ref cycles del log_it_frame del log_api_call_frame del apifunc_frame del apicaller_frame del apicaller_module # Log only if the caller is not from the zhmcclient package return apicaller_module_name.split('.')[0] != 'zhmcclient' def log_api_call(func, *args, **kwargs): """ Log entry to and exit from the decorated function, at the debug level. Note that this wrapper function is called every time the decorated function/method is called, but that the log message only needs to be constructed when logging for this logger and for this log level is turned on. Therefore, we do as much as possible in the decorator function, plus we use %-formatting and lazy interpolation provided by the log functions, in order to save resources in this function here. Parameters: func (function object): The decorated function. *args: Any positional arguments for the decorated function. **kwargs: Any keyword arguments for the decorated function. """ # Note that in this function, we are in the context where the # decorated function is actually called. _log_it = is_external_call() and logger.isEnabledFor(logging.DEBUG) if _log_it: logger.debug("Called: {}, args: {:.500}, kwargs: {:.500}". format(apifunc_str, log_escaped(repr(args)), log_escaped(repr(kwargs)))) result = func(*args, **kwargs) if _log_it: logger.debug("Return: {}, result: {:.1000}". format(apifunc_str, log_escaped(repr(result)))) return result if 'decorate' in globals(): return decorate(func, log_api_call) return decorator(log_api_call, func) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_lpar.py0000644000076500000240000014663400000000000020142 0ustar00maierastaff00000000000000# Copyright 2016-2017 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:`LPAR` (Logical Partition) is a subset of the hardware resources of a :term:`CPC` in classic mode (or ensemble mode), virtualized as a separate computer. LPARs cannot be created or deleted by the user; they can only be listed. LPAR resources are contained in CPC resources. LPAR resources only exist in CPCs that are in classic mode (or ensemble mode). CPCs in DPM mode have :term:`Partition` resources, instead. """ from __future__ import absolute_import import time import copy from ._manager import BaseManager from ._resource import BaseResource from ._exceptions import StatusTimeout from ._logging import logged_api_call __all__ = ['LparManager', 'Lpar'] class LparManager(BaseManager): """ Manager providing access to the :term:`LPARs ` in a particular :term:`CPC`. 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 of a :class:`~zhmcclient.Cpc` object (in DPM mode): * :attr:`~zhmcclient.Cpc.lpars` """ def __init__(self, cpc): # This function should not go into the docs. # Parameters: # cpc (:class:`~zhmcclient.Cpc`): # CPC 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 = [ 'name', ] super(LparManager, self).__init__( resource_class=Lpar, class_name='logical-partition', session=cpc.manager.session, parent=cpc, base_uri='/api/logical-partitions', oid_prop='object-id', uri_prop='object-uri', name_prop='name', query_props=query_props) @property def cpc(self): """ :class:`~zhmcclient.Cpc`: :term:`CPC` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the LPARs in this CPC. Authorization requirements: * Object-access permission to this CPC. * Object-access permission to any LPAR to be included in the result. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.Lpar` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] resource_obj = self._try_optimized_lookup(filter_args) if resource_obj: resource_obj_list.append(resource_obj) # It already has full properties else: query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'logical-partitions' uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list class Lpar(BaseResource): """ Representation of an :term:`LPAR`. 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.LparManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.LparManager`): # 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, LparManager), \ "Lpar init: Expected manager type %s, got %s" % \ (LparManager, type(manager)) super(Lpar, self).__init__(manager, uri, name, properties) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this LPAR. Authorization requirements: * Object-access permission to this LPAR. * Task permission for the "Change Object Definition" task. * Object-access permission to the CPC of this LPAR. * For an LPAR whose activation-mode is "zaware", task permission for the "Firmware Details" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model' in section 'Logical Partition object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) # Attempts to change the 'name' property will be rejected by the HMC, # so we don't need to update the name-to-URI cache. assert self.manager._name_prop not in properties self._properties.update(copy.deepcopy(properties)) @logged_api_call def activate(self, wait_for_completion=True, operation_timeout=None, status_timeout=None, allow_status_exceptions=False, activation_profile_name=None, force=False): """ Activate (start) this LPAR, using the HMC operation "Activate Logical Partition". This HMC operation has deferred status behavior: If the asynchronous job on the HMC is complete, it takes a few seconds until the LPAR status has reached the desired value. If `wait_for_completion=True`, this method repeatedly checks the status of the LPAR after the HMC operation has completed, and waits until the status is in the desired state "not-operating" (which indicates that the LPAR is active but no operating system is running), or "operating", or if `allow_status_exceptions` was set additionally in the state "exceptions". Authorization requirements: * Object-access permission to the CPC containing this LPAR. * Object-access permission to this LPAR. * Task permission for the "Activate" task. Parameters: wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation, and for the status becoming "not-operating" or "operating" (or in addition "exceptions", if `allow_status_exceptions` was set. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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. status_timeout (:term:`number`): Timeout in seconds, for waiting that the status of the LPAR has reached the desired status, after the HMC operation has completed. 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.StatusTimeout` is raised. allow_status_exceptions (bool): Boolean controlling whether LPAR status "exceptions" is considered an additional acceptable end status when `wait_for_completion` is set. activation_profile_name (:term:`string`): Name of the image :class:`ActivationProfile` to use for activation. `None` means that the activation profile specified in the `next-activation-profile-name` property of the LPAR is used. force (bool): Boolean controlling whether this operation is permitted when the LPAR is in the "operating" status. TBD: What will happen with the LPAR in that case (deactivated then activated? nothing?) Returns: `None` or :class:`~zhmcclient.Job`: 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. 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.StatusTimeout`: The timeout expired while waiting for the desired LPAR status. """ body = {} if activation_profile_name: body['activation-profile-name'] = activation_profile_name if force: body['force'] = force result = self.manager.session.post( self.uri + '/operations/activate', body, wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) if wait_for_completion: statuses = ["not-operating", "operating"] if allow_status_exceptions: statuses.append("exceptions") self.wait_for_status(statuses, status_timeout) return result @logged_api_call def deactivate(self, wait_for_completion=True, operation_timeout=None, status_timeout=None, allow_status_exceptions=False, force=False): """ De-activate (stop) this LPAR, using the HMC operation "Deactivate Logical Partition". This HMC operation has deferred status behavior: If the asynchronous job on the HMC is complete, it takes a few seconds until the LPAR status has reached the desired value. If `wait_for_completion=True`, this method repeatedly checks the status of the LPAR after the HMC operation has completed, and waits until the status is in the desired state "not-activated", or if `allow_status_exceptions` was set additionally in the state "exceptions". Authorization requirements: * Object-access permission to the CPC containing this LPAR. * Object-access permission to this LPAR. * Task permission for the "Deactivate" task. Parameters: wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation, and for the status becoming "non-activated" (or in addition "exceptions", if `allow_status_exceptions` was set. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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. status_timeout (:term:`number`): Timeout in seconds, for waiting that the status of the LPAR has reached the desired status, after the HMC operation has completed. 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.StatusTimeout` is raised. allow_status_exceptions (bool): Boolean controlling whether LPAR status "exceptions" is considered an additional acceptable end status when `wait_for_completion` is set. force (bool): Boolean controlling whether this operation is permitted when the LPAR is in the "operating" status. TBD: What will happen with the LPAR in that case (deactivated then activated? nothing?) Returns: `None` or :class:`~zhmcclient.Job`: 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. 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.StatusTimeout`: The timeout expired while waiting for the desired LPAR status. """ body = {} if force: body['force'] = force result = self.manager.session.post( self.uri + '/operations/deactivate', body, wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) if wait_for_completion: statuses = ["not-activated"] if allow_status_exceptions: statuses.append("exceptions") self.wait_for_status(statuses, status_timeout) return result @logged_api_call def scsi_load(self, load_address, wwpn, lun, load_parameter=None, disk_partition_id=None, operating_system_specific_load_parameters=None, boot_record_logical_block_address=None, force=False, wait_for_completion=True, operation_timeout=None, status_timeout=None, allow_status_exceptions=False, secure_boot=False): # pylint: disable=invalid-name """ Load (boot) this LPAR from a designated SCSI device, using the HMC operation "SCSI Load". This HMC operation has deferred status behavior: If the asynchronous job on the HMC is complete, it takes a few seconds until the LPAR status has reached the desired value. If `wait_for_completion=True`, this method repeatedly checks the status of the LPAR after the HMC operation has completed, and waits until the status is in the desired state "operating", or if `allow_status_exceptions` was set additionally in the state "exceptions". Authorization requirements: * Object-access permission to the CPC containing this LPAR. * Object-access permission to this LPAR. * Task permission for the "SCSI Load" task. Parameters: load_address (:term:`string`): Device number of the boot device. wwpn (:term:`string`): Worldwide port name (WWPN) of the target SCSI device to be used for this operation, in hexadecimal. lun (:term:`string`): Hexadecimal logical unit number (LUN) to be used for the SCSI Load. load_parameter (:term:`string`): Optional load control string. If empty string or `None`, it is not passed to the HMC. disk_partition_id (:term:`integer`): Optional disk-partition-id (also called the boot program selector) to be used for the SCSI Load. If `None`, it is not passed to the HMC. operating_system_specific_load_parameters (:term:`string`): Optional operating system specific load parameters to be used for the SCSI Load. boot_record_logical_block_address (:term:`string`): Optional hexadecimal boot record logical block address to be used for the SCSI Load. force (bool): Boolean controlling whether this operation is permitted when the LPAR is in the "operating" status. wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation, and for the status becoming "operating" (or in addition "exceptions", if `allow_status_exceptions` was set. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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. status_timeout (:term:`number`): Timeout in seconds, for waiting that the status of the LPAR has reached the desired status, after the HMC operation has completed. 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.StatusTimeout` is raised. allow_status_exceptions (bool): Boolean controlling whether LPAR status "exceptions" is considered an additional acceptable end status when `wait_for_completion` is set. secure_boot (bool): Bollean controlling whether the system checks the software signature of what is loaded against what the distributor signed it with. Requires z15 or later. Returns: `None` or :class:`~zhmcclient.Job`: 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. 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.StatusTimeout`: The timeout expired while waiting for the desired LPAR status. """ body = {} body['load-address'] = load_address body['world-wide-port-name'] = wwpn body['logical-unit-number'] = lun if load_parameter: body['load-parameter'] = load_parameter if disk_partition_id is not None: body['disk-partition-id'] = disk_partition_id if operating_system_specific_load_parameters: body['operating-system-specific-load-parameters'] = \ operating_system_specific_load_parameters if boot_record_logical_block_address: body['boot-record-logical-block-address'] = \ boot_record_logical_block_address if force: body['force'] = force if secure_boot: body['secure-boot'] = secure_boot result = self.manager.session.post( self.uri + '/operations/scsi-load', body, wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) if wait_for_completion: statuses = ["operating"] if allow_status_exceptions: statuses.append("exceptions") self.wait_for_status(statuses, status_timeout) return result @logged_api_call def scsi_dump(self, load_address, wwpn, lun, load_parameter=None, disk_partition_id=None, operating_system_specific_load_parameters=None, boot_record_logical_block_address=None, os_ipl_token=None, wait_for_completion=True, operation_timeout=None, status_timeout=None, allow_status_exceptions=False, force=False): # pylint: disable=invalid-name """ Load a standalone dump program from a designated SCSI device in this LPAR, using the HMC operation "SCSI Dump". This HMC operation has deferred status behavior: If the asynchronous job on the HMC is complete, it takes a few seconds until the LPAR status has reached the desired value. If `wait_for_completion=True`, this method repeatedly checks the status of the LPAR after the HMC operation has completed, and waits until the status is in the desired state "operating", or if `allow_status_exceptions` was set additionally in the state "exceptions". Authorization requirements: * Object-access permission to the CPC containing this LPAR. * Object-access permission to this LPAR. * Task permission for the "SCSI Dump" task. Parameters: load_address (:term:`string`): Device number of the boot device. wwpn (:term:`string`): Worldwide port name (WWPN) of the target SCSI device to be used for this operation, in hexadecimal. lun (:term:`string`): Hexadecimal logical unit number (LUN) to be used for the SCSI Load. load_parameter (:term:`string`): Optional load control string. If empty string or `None`, it is not passed to the HMC. disk_partition_id (:term:`integer`): Optional disk-partition-id (also called the boot program selector) to be used for the SCSI Load. If `None`, it is not passed to the HMC. operating_system_specific_load_parameters (:term:`string`): Optional operating system specific load parameters to be used for the SCSI Load. boot_record_logical_block_address (:term:`string`): Optional hexadecimal boot record logical block address to be used for the SCSI Load. os_ipl_token (:term:`string`): Optional hexadecimal value to be used for the SCSI dump. wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation, and for the status becoming "operating" (or in addition "exceptions", if `allow_status_exceptions` was set. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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. status_timeout (:term:`number`): Timeout in seconds, for waiting that the status of the LPAR has reached the desired status, after the HMC operation has completed. 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.StatusTimeout` is raised. allow_status_exceptions (bool): Boolean controlling whether LPAR status "exceptions" is considered an additional acceptable end status when `wait_for_completion` is set. force (bool): Boolean controlling whether this operation is permitted when the LPAR is in the "operating" status. Returns: `None` or :class:`~zhmcclient.Job`: 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. 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.StatusTimeout`: The timeout expired while waiting for the desired LPAR status. """ body = {} body['load-address'] = load_address body['world-wide-port-name'] = wwpn body['logical-unit-number'] = lun if load_parameter: body['load-parameter'] = load_parameter if disk_partition_id is not None: body['disk-partition-id'] = disk_partition_id if operating_system_specific_load_parameters: body['operating-system-specific-load-parameters'] = \ operating_system_specific_load_parameters if boot_record_logical_block_address: body['boot-record-logical-block-address'] = \ boot_record_logical_block_address if os_ipl_token is not None: body['os-ipl-token'] = os_ipl_token if force: body['force'] = force result = self.manager.session.post( self.uri + '/operations/scsi-dump', body, wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) if wait_for_completion: statuses = ["operating"] if allow_status_exceptions: statuses.append("exceptions") self.wait_for_status(statuses, status_timeout) return result @logged_api_call def load(self, load_address=None, load_parameter=None, clear_indicator=True, store_status_indicator=False, wait_for_completion=True, operation_timeout=None, status_timeout=None, allow_status_exceptions=False, force=False): """ Load (boot) this LPAR from a load address (boot device), using the HMC operation "Load Logical Partition". This HMC operation has deferred status behavior: If the asynchronous job on the HMC is complete, it takes a few seconds until the LPAR status has reached the desired value. If `wait_for_completion=True`, this method repeatedly checks the status of the LPAR after the HMC operation has completed, and waits until the status is in the desired state "operating", or if `allow_status_exceptions` was set additionally in the state "exceptions". Authorization requirements: * Object-access permission to the CPC containing this LPAR. * Object-access permission to this LPAR. * Task permission for the "Load" task. Parameters: load_address (:term:`string`): Device number of the boot device. Up to z13, this parameter is required. Starting with z14, this parameter is optional and defaults to the load address specified in the 'last-used-load-address' property of the Lpar. load_parameter (:term:`string`): Optional load control string. If empty string or `None`, it is not passed to the HMC. clear_indicator (bool): Optional boolean controlling whether the memory should be cleared before performing the load or not cleared. The default value is `True`. store_status_indicator (bool): Optional boolean controlling whether the status should be stored before performing the Load. The default value is `False`. wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation, and for the status becoming "operating" (or in addition "exceptions", if `allow_status_exceptions` was set. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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. status_timeout (:term:`number`): Timeout in seconds, for waiting that the status of the LPAR has reached the desired status, after the HMC operation has completed. 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.StatusTimeout` is raised. allow_status_exceptions (bool): Boolean controlling whether LPAR status "exceptions" is considered an additional acceptable end status when `wait_for_completion` is set. force (bool): Boolean controlling whether this operation is permitted when the LPAR is in the "operating" status. TBD: What will happen with the LPAR in that case (deactivated then activated? nothing?) Returns: `None` or :class:`~zhmcclient.Job`: 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. 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.StatusTimeout`: The timeout expired while waiting for the desired LPAR status. """ body = {} if load_address: body['load-address'] = load_address if load_parameter: body['load-parameter'] = load_parameter if force: body['force'] = force if not clear_indicator: body['clear-indicator'] = clear_indicator if store_status_indicator: body['store-status-indicator'] = store_status_indicator result = self.manager.session.post( self.uri + '/operations/load', body, wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) if wait_for_completion: statuses = ["operating"] if allow_status_exceptions: statuses.append("exceptions") self.wait_for_status(statuses, status_timeout) return result @logged_api_call def stop(self, wait_for_completion=True, operation_timeout=None, status_timeout=None, allow_status_exceptions=False): """ Stop this LPAR, using the HMC operation "Stop Logical Partition". The stop operation stops the processors from processing instructions. This HMC operation has deferred status behavior: If the asynchronous job on the HMC is complete, it takes a few seconds until the LPAR status has reached the desired value. If `wait_for_completion=True`, this method repeatedly checks the status of the LPAR after the HMC operation has completed, and waits until the status is in the desired state "operating", or if `allow_status_exceptions` was set additionally in the state "exceptions". Authorization requirements: * Object-access permission to the CPC containing this LPAR. * Object-access permission to this LPAR. * Task permission for the "Stop" task. Parameters: wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation, and for the status becoming "operating" (or in addition "exceptions", if `allow_status_exceptions` was set. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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. status_timeout (:term:`number`): Timeout in seconds, for waiting that the status of the LPAR has reached the desired status, after the HMC operation has completed. 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.StatusTimeout` is raised. allow_status_exceptions (bool): Boolean controlling whether LPAR status "exceptions" is considered an additional acceptable end status when `wait_for_completion` is set. Returns: `None` or :class:`~zhmcclient.Job`: 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. 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.StatusTimeout`: The timeout expired while waiting for the desired LPAR status. """ body = {} result = self.manager.session.post( self.uri + '/operations/stop', body, wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) if wait_for_completion: statuses = ["operating"] if allow_status_exceptions: statuses.append("exceptions") self.wait_for_status(statuses, status_timeout) return result @logged_api_call def reset_clear(self, force=False, wait_for_completion=True, operation_timeout=None, status_timeout=None, allow_status_exceptions=False): """ Initialize this LPAR by clearing its pending interruptions, resetting its channel subsystem, and resetting its processors, using the HMC operation "Reset Clear". This HMC operation has deferred status behavior: If the asynchronous job on the HMC is complete, it takes a few seconds until the LPAR status has reached the desired value. If `wait_for_completion=True`, this method repeatedly checks the status of the LPAR after the HMC operation has completed, and waits until the status is in the desired state "operating", or if `allow_status_exceptions` was set additionally in the state "exceptions". Authorization requirements: * Object-access permission to the CPC containing this LPAR. * Object-access permission to this LPAR. * Task permission for the "Reset Clear" task. Parameters: force (bool): Boolean controlling whether this operation is permitted when the LPAR is in the "operating" status. The default is `False`. wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation, and for the status becoming "operating" (or in addition "exceptions", if `allow_status_exceptions` was set. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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. status_timeout (:term:`number`): Timeout in seconds, for waiting that the status of the LPAR has reached the desired status, after the HMC operation has completed. 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.StatusTimeout` is raised. allow_status_exceptions (bool): Boolean controlling whether LPAR status "exceptions" is considered an additional acceptable end status when `wait_for_completion` is set. Returns: `None` or :class:`~zhmcclient.Job`: 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. 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.StatusTimeout`: The timeout expired while waiting for the desired LPAR status. """ body = {} if force: body['force'] = force result = self.manager.session.post( self.uri + '/operations/reset-clear', body, wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) if wait_for_completion: statuses = ["operating"] if allow_status_exceptions: statuses.append("exceptions") self.wait_for_status(statuses, status_timeout) return result @logged_api_call def open_os_message_channel(self, include_refresh_messages=True): """ Open a JMS message channel to this LPAR's operating system, returning the string "topic" representing the message channel. Parameters: include_refresh_messages (bool): Boolean controlling whether refresh operating systems messages should be sent, as follows: * If `True`, refresh messages will be recieved when the user connects to the topic. The default. * If `False`, refresh messages will not be recieved when the user connects to the topic. Returns: :term:`string`: Returns a string representing the os-message-notification JMS topic. The user can connect to this topic to start the flow of operating system messages. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = {'include-refresh-messages': include_refresh_messages} result = self.manager.session.post( self.uri + '/operations/open-os-message-channel', body) return result['topic-name'] @logged_api_call def send_os_command(self, os_command_text, is_priority=False): """ Send a command to the operating system running in this LPAR. Parameters: os_command_text (string): The text of the operating system command. is_priority (bool): Boolean controlling whether this is a priority operating system command, as follows: * If `True`, this message is treated as a priority operating system command. * If `False`, this message is not treated as a priority operating system command. The default. Returns: None Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = {'is-priority': is_priority, 'operating-system-command-text': os_command_text} self.manager.session.post( self.uri + '/operations/send-os-cmd', body) @logged_api_call def psw_restart(self, wait_for_completion=True, operation_timeout=None, status_timeout=None, allow_status_exceptions=False): """ Restart this LPAR, using the HMC operation "PSW Restart". This HMC operation has deferred status behavior: If the asynchronous job on the HMC is complete, it takes a few seconds until the LPAR status has reached the desired value. If `wait_for_completion=True`, this method repeatedly checks the status of the LPAR after the HMC operation has completed, and waits until the status is in the desired state "operating", or if `allow_status_exceptions` was set additionally in the state "exceptions". Authorization requirements: * Object-access permission to the CPC containing this LPAR. * Object-access permission to this LPAR. * Task permission for the "PSW Restart" task. Parameters: wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation, and for the status becoming "operating" (or in addition "exceptions", if `allow_status_exceptions` was set. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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. status_timeout (:term:`number`): Timeout in seconds, for waiting that the status of the LPAR has reached the desired status, after the HMC operation has completed. 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.StatusTimeout` is raised. allow_status_exceptions (bool): Boolean controlling whether LPAR status "exceptions" is considered an additional acceptable end status when `wait_for_completion` is set. Returns: `None` or :class:`~zhmcclient.Job`: 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. 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.StatusTimeout`: The timeout expired while waiting for the desired LPAR status. """ body = {} result = self.manager.session.post( self.uri + '/operations/psw-restart', body, wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) if wait_for_completion: statuses = ["operating"] if allow_status_exceptions: statuses.append("exceptions") self.wait_for_status(statuses, status_timeout) return result @logged_api_call def wait_for_status(self, status, status_timeout=None): """ Wait until the status of this LPAR has a desired value. Parameters: status (:term:`string` or iterable of :term:`string`): Desired LPAR status or set of status values to reach; one or more of the following values: * ``"not-activated"`` - The LPAR is not active. * ``"not-operating"`` - The LPAR is active but no operating system is running in the LPAR. * ``"operating"`` - The LPAR is active and an operating system is running in the LPAR. * ``"exceptions"`` - The LPAR or its CPC has one or more unusual conditions. Note that the description of LPAR status values in the :term:`HMC API` book (as of its version 2.13.1) is partly confusing. status_timeout (:term:`number`): Timeout in seconds, for waiting that the status of the LPAR has reached one of the desired status values. The special value 0 means that no timeout is set. `None` means that the default status timeout will be used. If the timeout expires , a :exc:`~zhmcclient.StatusTimeout` is raised. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` :exc:`~zhmcclient.StatusTimeout`: The timeout expired while waiting for the desired LPAR status. """ if status_timeout is None: status_timeout = \ self.manager.session.retry_timeout_config.status_timeout if status_timeout > 0: end_time = time.time() + status_timeout if isinstance(status, (list, tuple)): statuses = status else: statuses = [status] while True: # Fastest way to get actual status value: lpars = self.manager.cpc.lpars.list( filter_args={'name': self.name}) assert len(lpars) == 1 this_lpar = lpars[0] actual_status = this_lpar.get_property('status') if actual_status in statuses: return if status_timeout > 0 and time.time() > end_time: raise StatusTimeout( "Waiting for LPAR {} to reach status(es) '{}' timed out " "after {} s - current status is '{}'". format(self.name, statuses, status_timeout, actual_status), actual_status, statuses, status_timeout) time.sleep(1) # Avoid hot spin loop ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/_manager.py0000644000076500000240000010211400000000000020577 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ Base definitions for resource manager classes. Resource manager classes exist for each resource type and are helper classes that provide functionality common for the resource type. Resource manager objects are not necessarily singleton objects, because they have a scope of a certain set of resource objects. For example, the resource manager object for LPARs exists once for each CPC managed by the HMC, and the resource object scope of each LPAR manager object is the set of LPARs in that CPC. """ from __future__ import absolute_import import re from datetime import datetime, timedelta import warnings import six from ._logging import logged_api_call from ._exceptions import NotFound, NoUniqueMatch, HTTPError from ._utils import repr_list, append_query_parms __all__ = ['BaseManager'] class _NameUriCache(object): """ A Name-URI cache, that caches the mapping between resource names and resource URIs. It supports looking up resource URIs by resource names. This class is used by the implementation of manager classes, and is not part of the external API. """ def __init__(self, manager, timetolive): """ Parameters: manager (BaseManager): Manager that holds this Name-URI cache. The manager object is expected to have a ``list()`` method, which is used to list the resources of that manager, in order to fill this cache. timetolive (number): Time in seconds until the cache will invalidate itself automatically, since it was last invalidated. """ self._manager = manager self._timetolive = timetolive # The cached data, as a dictionary with: # Key (string): Name of a resource (unique within its parent resource) # Value (string): URI of that resource self._uris = {} # Point in time when the cache was last invalidated self._invalidated = datetime.now() def get(self, name): """ Get the resource URI for a specified resource name. If an entry for the specified resource name does not exist in the Name-URI cache, the cache is refreshed from the HMC with all resources of the manager holding this cache. If an entry for the specified resource name still does not exist after that, ``NotFound`` is raised. """ self.auto_invalidate() try: return self._uris[name] except KeyError: self.refresh() try: return self._uris[name] except KeyError: # pylint: disable=protected-access new_exc = NotFound( {self._manager._name_prop: name}, self._manager) new_exc.__cause__ = None raise new_exc # zhmcclient.NotFound def auto_invalidate(self): """ Invalidate the cache if the current time is past the time to live. """ current = datetime.now() if current > self._invalidated + timedelta(seconds=self._timetolive): self.invalidate() def invalidate(self): """ Invalidate the cache. This empties the cache and sets the time of last invalidation to the current time. """ self._uris = {} self._invalidated = datetime.now() def refresh(self): """ Refresh the Name-URI cache from the HMC. This is done by invalidating the cache, listing the resources of this manager from the HMC, and populating the cache with that information. """ # pylint: disable=protected-access self.invalidate() full = not self._manager._list_has_name res_list = self._manager.list(full_properties=full) self.update_from(res_list) def update_from(self, res_list): """ Update the Name-URI cache from the provided resource list. This is done by going through the resource list and updating any cache entries for non-empty resource names in that list. Other cache entries remain unchanged. """ # pylint: disable=protected-access for res in res_list: # We access the properties dictionary, in order to make sure # we don't drive additional HMC interactions. name = res.properties.get(self._manager._name_prop, None) uri = res.properties.get(self._manager._uri_prop, None) self.update(name, uri) def update(self, name, uri): """ Update or create the entry for the specified resource name in the Name-URI cache, and set it to the specified URI. If the specified name is `None` or the empty string, do nothing. """ if name: self._uris[name] = uri def delete(self, name): """ Delete the entry for the specified resource name from the Name-URI cache. If the specified name is `None` or the empty string, or if an entry for the specified name does not exist, do nothing. """ if name: try: del self._uris[name] except KeyError: pass class BaseManager(object): """ Abstract base class for manager classes (e.g. :class:`~zhmcclient.CpcManager`). It defines the interface for the derived manager classes, and implements methods that have a common implementation for the derived manager classes. Objects of derived manager classes should not be created by users of this package by simply instantiating them. Instead, such objects are created by this package as instance variables of :class:`~zhmcclient.Client` and other resource objects, e.g. :attr:`~zhmcclient.Client.cpcs`. For this reason, the `__init__()` method of this class and of its derived manager classes are considered internal interfaces and their parameters are not documented and may change incompatibly. """ def __init__(self, resource_class, class_name, session, parent, base_uri, oid_prop, uri_prop, name_prop, query_props, list_has_name=True): # This method intentionally has no docstring, because it is internal. # # Parameters: # resource_class (class): # Python class for the resources of this manager. # Must not be `None`. # class_name (string): # Resource class name (e.g. 'cpc' for a CPC resource). Must # be the value of the 'class' property of the resource. # Must not be `None`. # session (:class:`~zhmcclient.Session`): # Session for this manager. # Must not be `None`. # parent (subclass of :class:`~zhmcclient.BaseResource`): # Parent resource defining the scope for this manager. # `None`, if the manager has no parent, i.e. when it manages # top-level resources (e.g. CPC). # base_uri (string): # Base URI of the resources of this manager. The base URI has no # trailing slash and becomes the resource URI by appending '/' and # the value of the property specified in 'oid_prop'. # Must not be `None`. # oid_prop (string): # Name of the resource property whose value is appended to the # base URI to form the resource URI (e.g. 'object-id' or # 'element-id'). # Must not be `None`. # uri_prop (string): # Name of the resource property that is the canonical URI path of # the resource (e.g. 'object-uri' or 'element-uri'). # Must not be `None`. # name_prop (string): # Name of the resource property that is the name of the resource # (e.g. 'name'). # Must not be `None`. # query_props (iterable of strings): # List of names of resource properties that are supported as filter # query parameters in HMC list operations for this type of resource # (i.e. for server-side filtering). # May be `None`. # If the support for a resource property changes within the set of # HMC versions that support this type of resource, this list must # represent the version of the HMC this session is connected to. # list_has_name (bool): # Indicates whether the list() method for the resource populates # the name property (i.e. name_prop). For example, for NICs the # list() method returns minimalistic Nic objects without name. # We want to surface precondition violations as early as possible, # so we test those that are not surfaced through the init code: assert resource_class is not None assert class_name is not None assert session is not None assert base_uri is not None assert oid_prop is not None assert uri_prop is not None assert name_prop is not None self._resource_class = resource_class self._class_name = class_name self._session = session self._parent = parent self._base_uri = base_uri self._oid_prop = oid_prop self._uri_prop = uri_prop self._name_prop = name_prop self._query_props = query_props self._list_has_name = list_has_name self._name_uri_cache = _NameUriCache( self, session.retry_timeout_config.name_uri_cache_timetolive) def __repr__(self): """ Return a string with the state of this manager object, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " _resource_class={_resource_class!r},\n" " _class_name={_class_name!r},\n" " _session={_session_classname} at 0x{_session_id:08x},\n" " _parent={_parent_classname} at 0x{_parent_id:08x},\n" " _base_uri={_base_uri!r},\n" " _oid_prop={_oid_prop!r},\n" " _uri_prop={_uri_prop!r},\n" " _name_prop={_name_prop!r},\n" " _query_props={_query_props},\n" " _list_has_name={_list_has_name!r},\n" " _name_uri_cache={_name_uri_cache!r}\n" ")".format( classname=self.__class__.__name__, id=id(self), _resource_class=self._resource_class, _class_name=self._class_name, _session_classname=self._session.__class__.__name__, _session_id=id(self._session), _parent_classname=self._parent.__class__.__name__, _parent_id=id(self._parent), _base_uri=self._base_uri, _oid_prop=self._oid_prop, _uri_prop=self._uri_prop, _name_prop=self._name_prop, _query_props=repr_list(self._query_props, indent=2), _list_has_name=self._list_has_name, _name_uri_cache=self._name_uri_cache, )) return ret def invalidate_cache(self): """ Invalidate the Name-URI cache of this manager. The zhmcclient maintains a Name-URI cache in each manager object, which caches the mappings between resource URIs and resource names, to speed up certain zhmcclient methods. The Name-URI cache is properly updated during changes on the resource name (e.g. via :meth:`~zhmcclient.Partition.update_properties`) or changes on the resource URI (e.g. via resource creation or deletion), if these changes are performed through the same Python manager object. However, changes performed through a different manager object (e.g. because a different session, client or parent resource object was used), or changes performed in a different Python process, or changes performed via other means than the zhmcclient library (e.g. directly on the HMC) will not automatically update the Name-URI cache of this manager. In cases where the resource name or resource URI are effected by such changes, the Name-URI cache can be manually invalidated by the user, using this method. Note that the Name-URI cache automatically invalidates itself after a certain time since the last invalidation. That auto invalidation time can be configured using the :attr:`~zhmcclient.RetryTimeoutConfig.name_uri_cache_timetolive` attribute of the :class:`~zhmcclient.RetryTimeoutConfig` class. """ self._name_uri_cache.invalidate() def _try_optimized_lookup(self, filter_args): """ Try to optimize the lookup by checking whether the filter arguments specify the property that is used as the last segment in the resource URI, with a plain string as match value (i.e. not using regular expression matching). If so, the lookup is performed by constructing the resource URI from the specified filter argument, and by issuing a Get Properties operation on that URI, returning a single resource object. Parameters: filter_args (dict): Filter arguments. For details, see :ref:`Filtering`. Returns: resource object, or `None` if the optimization was not possible. """ if filter_args is None or len(filter_args) != 1 or \ self._oid_prop not in filter_args: return None oid_match = filter_args[self._oid_prop] if not isinstance(oid_match, six.string_types) or \ not re.match(r'^[a-zA-Z0-9_\-]+$', oid_match): return None # The match string is a plain string (not a reg.expression) # Construct the resource URI from the filter property # and issue a Get Properties on that URI uri = self._base_uri + '/' + oid_match try: props = self.session.get(uri) except HTTPError as exc: if exc.http_status == 404 and exc.reason == 1: # No such resource return None raise resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) # pylint: disable=protected-access resource_obj._full_properties = True return resource_obj def _divide_filter_args(self, filter_args): """ Divide the filter arguments into filter query parameters for filtering on the server side, and the remaining client-side filters. Parameters: 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, i.e. all resources are returned. Returns: : tuple (query_parms_str, client_filter_args) """ query_parms = [] # query parameter strings client_filter_args = {} if filter_args is not None: for prop_name in filter_args: prop_match = filter_args[prop_name] if prop_name in self._query_props: append_query_parms(query_parms, prop_name, prop_match) else: client_filter_args[prop_name] = prop_match query_parms_str = '&'.join(query_parms) if query_parms_str: query_parms_str = '?{}'.format(query_parms_str) return query_parms_str, client_filter_args def _matches_filters(self, obj, filter_args): """ Return a boolean indicating whether a resource object matches a set of filter arguments. This is used for client-side filtering. Depending on the properties specified in the filter arguments, this method retrieves the resource properties from the HMC. Parameters: obj (BaseResource): Resource object. filter_args (dict): Filter arguments. For details, see :ref:`Filtering`. `None` causes the resource to always match. Returns: bool: Boolean indicating whether the resource object matches the filter arguments. """ if filter_args is not None: for prop_name in filter_args: prop_match = filter_args[prop_name] if not self._matches_prop(obj, prop_name, prop_match): return False return True def _matches_prop(self, obj, prop_name, prop_match): """ Return a boolean indicating whether a resource object matches with a single property against a property match value. This is used for client-side filtering. Depending on the specified property, this method retrieves the resource properties from the HMC. Parameters: obj (BaseResource): Resource object. prop_match: Property match value that is used to match the actual value of the specified property against, as follows: - If the match value is a list or tuple, this method is invoked recursively to find whether one or more match values inthe list match. - Else if the property is of string type, its value is matched by interpreting the match value as a regular expression. - Else the property value is matched by exact value comparison with the match value. Returns: bool: Boolean indicating whether the resource object matches w.r.t. the specified property and the match value. """ if isinstance(prop_match, (list, tuple)): # List items are logically ORed, so one matching item suffices. for pm in prop_match: if self._matches_prop(obj, prop_name, pm): return True else: # Some lists of resources do not have all properties, for example # Hipersocket adapters do not have a "card-location" property. # If a filter property does not exist on a resource, the resource # does not match. try: prop_value = obj.get_property(prop_name) except KeyError: return False if isinstance(prop_value, six.string_types): # HMC resource property is Enum String or (non-enum) String, # and is both matched by regexp matching. Ideally, regexp # matching should only be done for non-enum strings, but # distinguishing them is not possible given that the client # has no knowledge about the properties. # The regexp matching implemented in the HMC requires begin and # end of the string value to match, even if the '^' for begin # and '$' for end are not specified in the pattern. The code # here is consistent with that: We add end matching to the # pattern, and begin matching is done by re.match() # automatically. re_match = prop_match + '$' m = re.match(re_match, prop_value) if m: return True else: if prop_value == prop_match: return True return False @property def resource_class(self): """ The Python class of the parent resource of this manager. """ return self._resource_class @property def class_name(self): """ The resource class name """ return self._class_name @property def session(self): """ :class:`~zhmcclient.Session`: Session with the HMC. """ assert self._session is not None, \ "%s.session: No session set (in top-level resource manager " \ "class?)" % self.__class__.__name__ return self._session @property def parent(self): """ Subclass of :class:`~zhmcclient.BaseResource`: Parent resource defining the scope for this manager. `None`, if the manager has no parent, i.e. when it manages top-level resources. """ return self._parent def resource_object(self, uri_or_oid, props=None): """ Return a minimalistic Python resource object for this resource class, that is scoped to this manager. This method is an internal helper function and is not normally called by users. The returned resource object will have the following minimal set of properties set automatically: * `object-uri` * `object-id` * `parent` * `class` Additional properties for the Python resource object can be specified by the caller. Parameters: uri_or_oid (string): `object-uri` or `object-id` of the resource. props (dict): Property values in addition to the minimal list of properties that are set automatically (see above). Returns: Subclass of :class:`~zhmcclient.BaseResource`: A Python resource object for this resource class. """ if uri_or_oid.startswith('/api/'): assert uri_or_oid[-1] != '/' uri = uri_or_oid oid = uri.split('/')[-1] else: assert '/' not in uri_or_oid oid = uri_or_oid uri = '{}/{}'.format(self._base_uri, oid) res_props = { self._oid_prop: oid, 'parent': self.parent.uri if self.parent is not None else None, 'class': self.class_name, } name = None if props: res_props.update(props) try: name = props[self._name_prop] except KeyError: pass return self.resource_class(self, uri, name, res_props) @logged_api_call def findall(self, **filter_args): """ Find zero or more resources in scope of this manager, by matching resource properties against the specified filter arguments, and return a list of their Python resource objects (e.g. for CPCs, a list of :class:`~zhmcclient.Cpc` objects is returned). Any resource property may be specified in a filter argument. For details about filter arguments, see :ref:`Filtering`. The zhmcclient implementation handles the specified properties in an optimized way: Properties that can be filtered on the HMC are actually filtered there (this varies by resource type), and the remaining properties are filtered on the client side. If the "name" property is specified as the only filter argument, an optimized lookup is performed that uses a name-to-URI cache in this manager object. This this optimized lookup uses the specified match value for exact matching and is not interpreted as a regular expression. Authorization requirements: * see the `list()` method in the derived classes. Parameters: \\**filter_args: All keyword arguments are used as filter arguments. Specifying no keyword arguments causes no filtering to happen. See the examples for usage details. Returns: List of resource objects in scope of this manager object that match the filter arguments. These resource objects have a minimal set of properties. Raises: : Exceptions raised by the `list()` methods in derived resource manager classes (see :ref:`Resources`). Examples: * The following example finds partitions in a CPC by status. Because the 'status' resource property is also a valid Python variable name, there are two ways for the caller to specify the filter arguments for this method: As named parameters:: run_states = ['active', 'degraded'] run_parts = cpc.partitions.find(status=run_states) As a parameter dictionary:: run_parts = cpc.partitions.find(**{'status': run_states}) * The following example finds adapters of the OSA family in a CPC with an active status. Because the resource property for the adapter family is named 'adapter-family', it is not suitable as a Python variable name. Therefore, the caller can specify the filter argument only as a parameter dictionary:: filter_args = {'adapter-family': 'osa', 'status': 'active'} active_osa_adapters = cpc.adapters.findall(**filter_args) """ if len(filter_args) == 1 and self._name_prop in filter_args: try: obj = self.find_by_name(filter_args[self._name_prop]) except NotFound: return [] return [obj] obj_list = self.list(filter_args=filter_args) return obj_list @logged_api_call def find(self, **filter_args): """ Find exactly one resource in scope of this manager, by matching resource properties against the specified filter arguments, and return its Python resource object (e.g. for a CPC, a :class:`~zhmcclient.Cpc` object is returned). Any resource property may be specified in a filter argument. For details about filter arguments, see :ref:`Filtering`. The zhmcclient implementation handles the specified properties in an optimized way: Properties that can be filtered on the HMC are actually filtered there (this varies by resource type), and the remaining properties are filtered on the client side. If the "name" property is specified as the only filter argument, an optimized lookup is performed that uses a name-to-URI cache in this manager object. This this optimized lookup uses the specified match value for exact matching and is not interpreted as a regular expression. Authorization requirements: * see the `list()` method in the derived classes. Parameters: \\**filter_args: All keyword arguments are used as filter arguments. Specifying no keyword arguments causes no filtering to happen. See the examples for usage details. Returns: Resource object in scope of this manager object that matches the filter arguments. This resource object has a minimal set of properties. Raises: :exc:`~zhmcclient.NotFound`: No matching resource found. :exc:`~zhmcclient.NoUniqueMatch`: More than one matching resource found. : Exceptions raised by the `list()` methods in derived resource manager classes (see :ref:`Resources`). Examples: * The following example finds a CPC by its name. Because the 'name' resource property is also a valid Python variable name, there are two ways for the caller to specify the filter arguments for this method: As named parameters:: cpc = client.cpcs.find(name='CPC001') As a parameter dictionary:: filter_args = {'name': 'CPC0001'} cpc = client.cpcs.find(**filter_args) * The following example finds a CPC by its object ID. Because the 'object-id' resource property is not a valid Python variable name, the caller can specify the filter argument only as a parameter dictionary:: filter_args = {'object-id': '12345-abc...de-12345'} cpc = client.cpcs.find(**filter_args) """ obj_list = self.findall(**filter_args) num_objs = len(obj_list) if num_objs == 0: raise NotFound(filter_args, self) if num_objs > 1: raise NoUniqueMatch(filter_args, self, obj_list) return obj_list[0] def list(self, full_properties=False, filter_args=None): """ Find zero or more resources in scope of this manager, by matching resource properties against the specified filter arguments, and return a list of their Python resource objects (e.g. for CPCs, a list of :class:`~zhmcclient.Cpc` objects is returned). Any resource property may be specified in a filter argument. For details about filter arguments, see :ref:`Filtering`. The zhmcclient implementation handles the specified properties in an optimized way: Properties that can be filtered on the HMC are actually filtered there (this varies by resource type), and the remaining properties are filtered on the client side. At the level of the :class:`~zhmcclient.BaseManager` class, this method defines the interface for the `list()` methods implemented in the derived resource classes. Authorization requirements: * see the `list()` method in the derived classes. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only a minimal set as returned by the list operation. filter_args (dict): Filter arguments. `None` causes no filtering to happen. See the examples for usage details. Returns: List of resource objects in scope of this manager object that match the filter arguments. These resource objects have a set of properties according to the `full_properties` parameter. Raises: : Exceptions raised by the `list()` methods in derived resource manager classes (see :ref:`Resources`). Examples: * The following example finds those OSA adapters in cage '1234' of a given CPC, whose state is 'stand-by', 'reserved', or 'unknown':: filter_args = { 'adapter-family': 'osa', 'card-location': '1234-.*', 'state': ['stand-by', 'reserved', 'unknown'], } osa_adapters = cpc.adapters.list(full_properties=True, filter_args=filter_args) The returned resource objects will have the full set of properties. """ raise NotImplementedError @logged_api_call def find_by_name(self, name): """ Find a resource by name (i.e. value of its 'name' resource property) and return its Python resource object (e.g. for a CPC, a :class:`~zhmcclient.Cpc` object is returned). This method performs an optimized lookup that uses a name-to-URI mapping cached in this manager object. This method is automatically used by the :meth:`~zhmcclient.BaseManager.find` and :meth:`~zhmcclient.BaseManager.findall` methods, so it does not normally need to be used directly by users. Authorization requirements: * see the `list()` method in the derived classes. Parameters: name (string): Name of the resource (value of its 'name' resource property). Regular expression matching is not supported for the name for this optimized lookup. Returns: Resource object in scope of this manager object that matches the filter arguments. This resource object has a minimal set of properties. Raises: :exc:`~zhmcclient.NotFound`: No matching resource found. : Exceptions raised by the `list()` methods in derived resource manager classes (see :ref:`Resources`). Examples: * The following example finds a CPC by its name:: cpc = client.cpcs.find_by_name('CPC001') """ uri = self._name_uri_cache.get(name) obj = self.resource_class( manager=self, uri=uri, name=name, properties=None) return obj @logged_api_call def flush(self): """ Invalidate the Name-URI cache of this manager. **Deprecated:** This method is deprecated and using it will cause a :exc:`~py:exceptions.DeprecationWarning` to be issued. Use :meth:`~zhmcclient.BaseManager.invalidate_cache` instead. """ warnings.warn( "Use of flush() on zhmcclient manager objects is deprecated; " "use invalidate_cache() instead", DeprecationWarning) self.invalidate_cache() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_metrics.py0000644000076500000240000010457700000000000020652 0ustar00maierastaff00000000000000# Copyright 2017 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. """ The HMC supports the retrieval of metrics values for resources of a IBM Z or LinuxONE computer. This section describes the zhmcclient API for retrieving such metrics from the HMC. A resource termed :term:`Metrics Context` is associated with any metrics retrieval. These resources are user-created definitions of the kinds of metrics that are intended to be retrieved. A metrics context mostly defines the names of the metric groups to be retrieved. The available metric groups are described in section 'Metric Groups' in the :term:`HMC API` book. The zhmcclient API for metrics provides access to the metric values and to their definitions, so that clients using the metric values do not need to have intimate knowledge about the specific metric values when just displaying them. The basic usage of the metrics API is shown in this example: .. code-block:: python # Create a Metrics Context for the desired metric groups: metric_groups = ['dpm-system-usage-overview', 'partition-usage'] mc = client.metrics_contexts.create( {'anticipated-frequency-seconds': 15, 'metric-groups': metric_groups}) # Retrieve the current metric values: mr_str = mc.get_metrics() # Display the metric values: print("Current metric values:") mr = zhmcclient.MetricsResponse(mc, mr_str) for mg in mr.metric_groups: mg_name = mg.name mg_def = mc.metric_group_definitions[mg_name] print(" Metric group: {}".format(mg_name)) for ov in mg.object_values: print(" Resource: {}".format(ov.resource_uri)) print(" Timestamp: {}".format(ov.timestamp)) print(" Metric values:") for m_name in ov.metrics: m_value = ov.metrics[m_name] m_def = mg_def.metric_definitions[m_name] m_unit = m_def.unit m_type = m_def.type print(" {:30} {} {}". format(m_name, m_value, m_unit.encode('utf-8'))) # Delete the Metrics Context: mc.delete() """ from __future__ import absolute_import from collections import namedtuple import re from datetime import datetime import pytz import six from ._manager import BaseManager from ._resource import BaseResource from ._cpc import Cpc from ._lpar import Lpar from ._partition import Partition from ._adapter import Adapter from ._nic import Nic from ._logging import logged_api_call from ._exceptions import NotFound, MetricsResourceNotFound from ._utils import datetime_from_timestamp, repr_list __all__ = ['MetricsContextManager', 'MetricsContext', 'MetricGroupDefinition', 'MetricDefinition', 'MetricsResponse', 'MetricGroupValues', 'MetricObjectValues'] class MetricsContextManager(BaseManager): """ Manager providing access to the :term:`Metrics Context` resources that were created through this manager object. 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 :attr:`~zhmcclient.Client.metrics_contexts` attribute of the :class:`~zhmcclient.Client` object connected to the HMC. """ def __init__(self, client): # This function should not go into the docs. # Parameters: # client (:class:`~zhmcclient.Client`): # Client object for the HMC to be used. super(MetricsContextManager, self).__init__( resource_class=MetricsContext, class_name='', session=client.session, parent=None, base_uri='/api/services/metrics/context', oid_prop='', uri_prop='metrics-context-uri', name_prop='', query_props=[]) self._client = client self._metrics_contexts = [] def __repr__(self): """ Return a string with the state of this manager object, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " _resource_class={_resource_class!r},\n" " _class_name={_class_name!r},\n" " _session={_session_classname} at 0x{_session_id:08x},\n" " _parent={_parent_classname} at 0x{_parent_id:08x},\n" " _base_uri={_base_uri!r},\n" " _oid_prop={_oid_prop!r},\n" " _uri_prop={_uri_prop!r},\n" " _name_prop={_name_prop!r},\n" " _query_props={_query_props},\n" " _list_has_name={_list_has_name!r},\n" " _name_uri_cache={_name_uri_cache!r},\n" " _client={_client_classname} at 0x{_client_id:08x},\n" " _metrics_contexts={_metrics_contexts},\n" ")".format( classname=self.__class__.__name__, id=id(self), _resource_class=self._resource_class, _class_name=self._class_name, _session_classname=self._session.__class__.__name__, _session_id=id(self._session), _parent_classname=self._parent.__class__.__name__, _parent_id=id(self._parent), _base_uri=self._base_uri, _oid_prop=self._oid_prop, _uri_prop=self._uri_prop, _name_prop=self._name_prop, _query_props=repr_list(self._query_props, indent=2), _list_has_name=self._list_has_name, _name_uri_cache=self._name_uri_cache, _client_classname=self._client.__class__.__name__, _client_id=id(self._client), _metrics_contexts=repr_list(self._metrics_contexts, indent=2), )) return ret @property def client(self): """ :class:`~zhmcclient.Client`: The client defining the scope for this manager. """ return self._client @logged_api_call def list(self, full_properties=False): # pylint: disable=arguments-differ """ List the :term:`Metrics Context` resources that were created through this manager object. Note that the HMC does not provide a way to enumerate the existing :term:`Metrics Context` resources. Therefore, this method will only list the :term:`Metrics Context` resources that were created through this manager object. For example, :term:`Metrics Context` resources created through a second :class:`~zhmcclient.Client` object will not be listed. Parameters: full_properties (bool): This parameter exists for compatibility with other resource classes, but for this class, it has no effect on the result. Returns: : A list of :class:`~zhmcclient.MetricsContext` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ return self._metrics_contexts @logged_api_call def create(self, properties): """ Create a :term:`Metrics Context` resource in the HMC this client is connected to. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create Metrics Context' in the :term:`HMC API` book. Returns: :class:`~zhmcclient.MetricsContext`: The resource object for the new :term:`Metrics Context` resource. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.session.post('/api/services/metrics/context', body=properties) mc_properties = properties.copy() mc_properties.update(result) new_metrics_context = MetricsContext(self, result['metrics-context-uri'], None, mc_properties) self._metrics_contexts.append(new_metrics_context) return new_metrics_context class MetricsContext(BaseResource): """ Representation of a :term:`Metrics Context` resource. A :term:`Metrics Context` resource specifies a list of metrics groups for which the current metric values can be retrieved using the :meth:`~zhmcclient.MetricsContext.get_metrics` method. The properties of this resource are the response fields described for the 'Create Metrics Context' operation in the :term:`HMC API` book. This class is derived from :class:`~zhmcclient.BaseResource`; see there for common methods and attributes. Objects of this class can be created by the user with the :meth:`zhmcclient.MetricsContextManager.create` method. """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.MetricsContextManager`): # 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. if not isinstance(manager, MetricsContextManager): raise AssertionError( "MetricsContext init: Expected manager type %s, got %s" % (MetricsContextManager, type(manager))) super(MetricsContext, self).__init__(manager, uri, name, properties) self._metric_group_definitions = self._setup_metric_group_definitions() def _setup_metric_group_definitions(self): """ Return the dict of MetricGroupDefinition objects for this metrics context, by processing its 'metric-group-infos' property. """ # Dictionary of MetricGroupDefinition objects, by metric group name metric_group_definitions = dict() for mg_info in self._properties['metric-group-infos']: mg_name = mg_info['group-name'] mg_def = MetricGroupDefinition( name=mg_name, resource_class=_resource_class_from_group(mg_name), metric_definitions=dict()) for i, m_info in enumerate(mg_info['metric-infos']): m_name = m_info['metric-name'] m_def = MetricDefinition( index=i, name=m_name, type=_metric_type(m_info['metric-type']), unit=_metric_unit_from_name(m_name)) mg_def.metric_definitions[m_name] = m_def metric_group_definitions[mg_name] = mg_def return metric_group_definitions @property def metric_group_definitions(self): """ dict: The metric definitions for the metric groups of this :term:`Metrics Context` resource, as a dictionary of :class:`~zhmcclient.MetricGroupDefinition` objects, by metric group name. """ return self._metric_group_definitions @logged_api_call def get_metrics(self): """ Retrieve the current metric values for this :term:`Metrics Context` resource from the HMC. The metric values are returned by this method as a string in the `MetricsResponse` format described with the 'Get Metrics' operation in the :term:`HMC API` book. The :class:`~zhmcclient.MetricsResponse` class can be used to process the `MetricsResponse` string returned by this method, and provides structured access to the metrics values. Returns: :term:`string`: The current metric values, in the `MetricsResponse` string format. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ metrics_response = self.manager.session.get(self.uri) return metrics_response @logged_api_call def delete(self): """ Delete this :term:`Metrics Context` resource. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.delete(self.uri) self.manager._metrics_contexts.remove(self) _MetricGroupDefinitionTuple = namedtuple( '_MetricGroupDefinitionTuple', ['name', 'resource_class', 'metric_definitions'] ) class MetricGroupDefinition(_MetricGroupDefinitionTuple): """ A :func:`namedtuple ` representing definitional information for a metric group. """ def __new__(cls, name, resource_class, metric_definitions): """ Parameters: name (:term:`string`): Metric group name, as defined in section 'Metric groups' in the :term:`HMC API` book. resource_class (:term:`string`): A string identifying the resource class to which this metric group belongs, using the values from the 'class' property of resource objects. metric_definitions (dict): Metric definitions for the metrics in this metric group, as a dictionary where the key is the metric name and the value is the :class:`~zhmcclient.MetricDefinition` object for the metric. All these parameters are also available as same-named attributes. """ self = super(MetricGroupDefinition, cls).__new__( cls, name, resource_class, metric_definitions) return self __slots__ = () def __repr__(self): repr_str = "MetricGroupDefinition(" \ "name={s.name!r}, " \ "resource_class={s.resource_class!r}, " \ "metric_definitions={s.metric_definitions!r})". \ format(s=self) return repr_str _MetricDefinitionTuple = namedtuple( '_MetricDefinitionTuple', ['index', 'name', 'type', 'unit'] ) class MetricDefinition(_MetricDefinitionTuple): """ A :func:`namedtuple ` representing definitional information for a single metric. """ def __new__(cls, index, name, type, unit): # pylint: disable=redefined-builtin """ Parameters: index (:term:`integer`): 0-based index (=position) of the metric in a MetricsResponse value row. name (:term:`string`): Metric field name, as shown in the tables defining the metric groups in section 'Metric groups' in the :term:`HMC API` book. type (:term:`callable`): Python type for the metric value. The type must be a constructor (callable) that takes the metrics value from the `MetricsResponse` string as its only argument, using the following Python types for the metric group description types shown in the :term:`HMC API` book: ============================= ====================== Description type Python type ============================= ====================== Boolean :class:`py:bool` Byte :term:`integer` Short :term:`integer` Integer :term:`integer` Long :term:`integer` Double :class:`py:float` String, String Enum :term:`unicode string` ============================= ====================== unit (:term:`string`): Unit of the metric value. All these parameters are also available as same-named attributes. """ self = super(MetricDefinition, cls).__new__( cls, index, name, type, unit) return self __slots__ = () def __repr__(self): repr_str = "MetricDefinition(" \ "index={s.index!r}, " \ "name={s.name!r}, " \ "type={s.type!r}, " \ "unit={s.unit!r})". \ format(s=self) return repr_str def _metric_type(metric_type_name): """ Return a constructor callable for the given metric type name. The returned callable takes the metric value as a string as its only argument and returns a Python object representing that metric value using the correct Python type. """ return _METRIC_TYPES_BY_NAME[metric_type_name] _METRIC_TYPES_BY_NAME = { 'boolean-metric': bool, 'byte-metric': int, 'short-metric': int, 'integer-metric': int, 'long-metric': int, 'double-metric': float, 'string-metric': six.text_type, } def _metric_value(value_str, metric_type): """ Return a Python-typed metric value from a metric value string. """ if metric_type in (int, float): try: return metric_type(value_str) except ValueError: new_exc = ValueError( "Invalid {} metric value: {!r}". format(metric_type.__class__.__name__, value_str)) new_exc.__cause__ = None raise new_exc # ValueError elif metric_type is six.text_type: # In Python 3, decode('unicode_escape) requires bytes, so we need # to encode to bytes. This also works in Python 2. return value_str.strip('"').encode('utf-8').decode('unicode_escape') else: assert metric_type is bool lower_str = value_str.lower() if lower_str == 'true': return True if lower_str == 'false': return False raise ValueError("Invalid boolean metric value: {!r}".format(value_str)) def _metric_unit_from_name(metric_name): """ Return a metric unit string for human consumption, that is inferred from the metric name. If a unit cannot be inferred, `None` is returned. """ for item in _PATTERN_UNIT_LIST: pattern, unit = item if pattern.match(metric_name): return unit return None _USE_UNICODE = True if _USE_UNICODE: MICROSECONDS = u"\u00b5s" # U+00B5 = Micro Sign CELSIUS = u"\u00B0C" # U+00B0 = Degree Sign # Note: Use of U+2103 (Degree Celsius) is discouraged by Unicode standard else: MICROSECONDS = u"us" CELSIUS = u"degree Celsius" # Official SI unit when not using degree sign _PATTERN_UNIT_LIST = { # End patterns: (re.compile(r".+-usage$"), u"%"), (re.compile(r".+-time$"), MICROSECONDS), (re.compile(r".+-time-used$"), MICROSECONDS), (re.compile(r".+-celsius$"), CELSIUS), (re.compile(r".+-watts$"), u"W"), (re.compile(r".+-paging-rate$"), u"pages/s"), (re.compile(r".+-sampling-rate$"), u"samples/s"), # Begin patterns: (re.compile(r"^bytes-.+"), u"B"), (re.compile(r"^heat-load.+"), u"BTU/h"), # Note: No trailing hyphen (re.compile(r"^interval-bytes-.+"), u"B"), (re.compile(r"^bytes-per-second-.+"), u"B/s"), # Special cases: (re.compile(r"^storage-rate$"), u"kB/s"), (re.compile(r"^humidity$"), u"%"), (re.compile(r"^memory-used$"), u"MiB"), (re.compile(r"^policy-activation-time$"), u""), # timestamp (re.compile(r"^velocity-numerator$"), MICROSECONDS), (re.compile(r"^velocity-denominator$"), MICROSECONDS), (re.compile(r"^utilization$"), u"%"), } def _resource_class_from_group(metric_group_name): """ Return the resource class string from the metric group name. Metric groups for resources that are specific to ensemble mode are not supported. Returns an empty string if a metric group name is unknown. """ return _CLASS_FROM_GROUP.get(metric_group_name, '') _CLASS_FROM_GROUP = { # DPM mode only: 'dpm-system-usage-overview': 'cpc', 'partition-usage': 'partition', 'adapter-usage': 'adapter', 'network-physical-adapter-port': 'adapter', 'partition-attached-network-interface': 'nic', # Classic mode only: 'cpc-usage-overview': 'cpc', 'logical-partition-usage': 'logical-partition', 'channel-usage': 'cpc', 'crypto-usage': 'cpc', 'flash-memory-usage': 'cpc', # TODO: verify CPC mode dependency 'roce-usage': 'cpc', # TODO: verify CPC mode dependency # DPM mode or classic mode: 'zcpc-environmentals-and-power': 'cpc', 'zcpc-processor-usage': 'cpc', } class MetricsResponse(object): """ Represents the metric values returned by one call to the :meth:`~zhmcclient.MetricsContext.get_metrics` method, and provides structured access to the data. """ def __init__(self, metrics_context, metrics_response_str): """ Parameters: metrics_context (:class:`~zhmcclient.MetricsContext`): The :class:`~zhmcclient.MetricsContext` object that was used to retrieve the metrics response string. It defines the structure of the metric values in the metrics response string. metrics_response_str (:term:`string`): The metrics response string, as returned by the :meth:`~zhmcclient.MetricsContext.get_metrics` method. """ self._metrics_context = metrics_context self._metrics_response_str = metrics_response_str self._client = self._metrics_context.manager.client self._metric_group_values = self._setup_metric_group_values() def _setup_metric_group_values(self): """ Return the list of MetricGroupValues objects for this metrics response, by processing its metrics response string. The lines in the metrics response string are:: MetricsResponse: MetricsGroup{0,*} a third empty line at the end MetricsGroup: MetricsGroupName ObjectValues{0,*} a second empty line after each MG ObjectValues: ObjectURI Timestamp ValueRow{1,*} a first empty line after this blk """ mg_defs = self._metrics_context.metric_group_definitions metric_group_name = None resource_uri = None dt_timestamp = None object_values = None metric_group_values = list() state = 0 for mr_line in self._metrics_response_str.splitlines(): if state == 0: if object_values is not None: # Store the result from the previous metric group mgv = MetricGroupValues(metric_group_name, object_values) metric_group_values.append(mgv) object_values = None if mr_line == '': # Skip initial (or trailing) empty lines pass else: # Process the next metrics group metric_group_name = mr_line.strip('"') # No " or \ inside assert metric_group_name in mg_defs m_defs = mg_defs[metric_group_name].metric_definitions object_values = list() state = 1 elif state == 1: if mr_line == '': # There are no (or no more) ObjectValues items in this # metrics group state = 0 else: # There are ObjectValues items resource_uri = mr_line.strip('"') # No " or \ inside state = 2 elif state == 2: # Process the timestamp assert mr_line != '' try: dt_timestamp = datetime_from_timestamp(int(mr_line)) except ValueError: # Sometimes, the returned epoch timestamp values are way # too large, e.g. 3651584404810066 (which would translate # to the year 115791 A.D.). Python datetime supports # up to the year 9999. We circumvent this issue by # simply using the current date&time. # TODO: Remove the circumvention for too large timestamps. dt_timestamp = datetime.now(pytz.utc) state = 3 elif state == 3: if mr_line != '': # Process the metric values in the ValueRow line str_values = mr_line.split(',') metrics = dict() for m_name in m_defs: m_def = m_defs[m_name] m_type = m_def.type m_value_str = str_values[m_def.index] m_value = _metric_value(m_value_str, m_type) metrics[m_name] = m_value ov = MetricObjectValues( self._client, mg_defs[metric_group_name], resource_uri, dt_timestamp, metrics) object_values.append(ov) # stay in this state, for more ValueRow lines else: # On the empty line after the last ValueRow line state = 1 return metric_group_values @property def metrics_context(self): """ :class:`~zhmcclient.MetricsContext` object for this metric response. This can be used to access the metric definitions for this response. """ return self._metrics_context @property def metric_group_values(self): """ :class:`py:list`: The list of :class:`~zhmcclient.MetricGroupValues` objects representing the metric groups in this metric response. Each :class:`~zhmcclient.MetricGroupValues` object contains a list of :class:`~zhmcclient.MetricObjectValues` objects representing the metric values in this group (each for a single resource and point in time). """ return self._metric_group_values class MetricGroupValues(object): """ Represents the metric values for a metric group in a MetricsResponse string. """ def __init__(self, name, object_values): """ Parameters: name (:term:`string`): Metric group name. object_values (:class:`py:list`): The :class:`~zhmcclient.MetricObjectValues` objects in this metric group. Each of them represents the metric values for a single resource at a single point in time. """ self._name = name self._object_values = object_values @property def name(self): """ string: The metric group name. """ return self._name @property def object_values(self): """ :class:`py:list`: The :class:`~zhmcclient.MetricObjectValues` objects in this metric group. Each of them represents the metric values for a single resource at a single point in time. """ return self._object_values class MetricObjectValues(object): """ Represents the metric values for a single resource at a single point in time. """ def __init__(self, client, metric_group_definition, resource_uri, timestamp, metrics): """ Parameters: client (:class:`~zhmcclient.Client`): Client object, for retrieving the actual resource. metric_group_definition (:class:`~zhmcclient.MetricGroupDefinition`): Metric group definition for this set of metric values. resource_uri (:term:`string`): Resource URI of the resource these metric values apply to. timestamp (:class:`py:datetime.datetime`): Point in time when the HMC captured these metric values (as a timezone-aware datetime object). metrics (dict): The metric values, as a dictionary of the (Python typed) metric values, by metric name. """ self._client = client self._metric_group_definition = metric_group_definition self._resource_uri = resource_uri self._timestamp = timestamp self._metrics = metrics self._resource = None # Lazy initialization @property def client(self): """ :class:`~zhmcclient.Client`: Client object, for retrieving the actual resource. """ return self._client @property def metric_group_definition(self): """ :class:`~zhmcclient.MetricGroupDefinition`: Metric group definition for this set of metric values. """ return self._metric_group_definition @property def resource_uri(self): """ string: The canonical URI path of the resource these metric values apply to. Example: ``/api/cpcs/12345`` """ return self._resource_uri @property def timestamp(self): """ :class:`py:datetime.datetime`: Point in time when the HMC captured these metric values (as a timezone-aware datetime object). """ return self._timestamp @property def metrics(self): """ dict: The metric values, as a dictionary of the (Python typed) metric values, by metric name. """ return self._metrics @property def resource(self): """ :class:`~zhmcclient.BaseResource`: The Python resource object of the resource these metric values apply to. Raises: :exc:`~zhmcclient.MetricsResourceNotFound`: The resource for the resource URI of the resource these metric values apply to, was not found on the HMC. """ if self._resource is not None: return self._resource resource_class = self.metric_group_definition.resource_class resource_uri = self.resource_uri if resource_class == 'cpc': cpc_managers = [self.client] filter_args = {'object-uri': resource_uri} try: resource = self.client.cpcs.find(**filter_args) except NotFound: raise MetricsResourceNotFound( "{} with URI {} not found on HMC {}". format(resource_class, resource_uri, self.client.session.host), Cpc, cpc_managers) elif resource_class == 'logical-partition': lpar_managers = [] cpc_list = self.client.cpcs.list() for cpc in cpc_list: lpar_managers.append(cpc.lpars) try: filter_args = {'object-uri': resource_uri} resource = cpc.lpars.find(**filter_args) break except NotFound: pass # Try next CPC else: raise MetricsResourceNotFound( "{} with URI {} not found in CPCs {}". format(resource_class, resource_uri, ', '.join([cpc.name for cpc in cpc_list])), Lpar, lpar_managers) elif resource_class == 'partition': cpc_list = self.client.cpcs.list() partition_managers = [] for cpc in cpc_list: partition_managers.append(cpc.partitions) try: filter_args = {'object-uri': resource_uri} resource = cpc.partitions.find(**filter_args) break except NotFound: pass # Try next CPC else: raise MetricsResourceNotFound( "{} with URI {} not found in CPCs {}". format(resource_class, resource_uri, ', '.join([cpc.name for cpc in cpc_list])), Partition, partition_managers) elif resource_class == 'adapter': cpc_list = self.client.cpcs.list() adapter_managers = [] for cpc in cpc_list: adapter_managers.append(cpc.adapters) try: filter_args = {'object-uri': resource_uri} resource = cpc.adapters.find(**filter_args) break except NotFound: pass # Try next CPC else: raise MetricsResourceNotFound( "{} with URI {} not found in CPCs {}". format(resource_class, resource_uri, ', '.join([cpc.name for cpc in cpc_list])), Adapter, adapter_managers) elif resource_class == 'nic': cpc_list = self.client.cpcs.list() nic_managers = [] for cpc in cpc_list: found = False for partition in cpc.partitions.list(): nic_managers.append(partition.nics) try: filter_args = {'element-uri': resource_uri} resource = partition.nics.find(**filter_args) found = True break except NotFound: pass # Try next partition / next CPC if found: break else: raise MetricsResourceNotFound( "{} with URI {} not found in the partitions of CPCs {}". format(resource_class, resource_uri, ', '.join([cpc.name for cpc in cpc_list])), Nic, nic_managers) else: raise ValueError( "Invalid resource class: {!r}".format(resource_class)) self._resource = resource return self._resource ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_nic.py0000644000076500000240000002635300000000000017750 0ustar00maierastaff00000000000000# Copyright 2016-2017 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:`NIC` (Network Interface Card) is a logical entity that provides a :term:`Partition` with access to external communication networks through a :term:`Network Adapter`. More specifically, a NIC connects a Partition with a :term:`Network Port`, or with a :term:`Virtual Switch` which then connects to the Network Port. NIC resources are contained in Partition resources. NICs only exist in :term:`CPCs ` that are in DPM mode. """ from __future__ import absolute_import import copy from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['NicManager', 'Nic'] class NicManager(BaseManager): """ Manager providing access to the :term:`NICs ` in a particular :term:`Partition`. 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 of a :class:`~zhmcclient.Partition` object (in DPM mode): * :attr:`~zhmcclient.Partition.nics` """ def __init__(self, partition): # This function should not go into the docs. # Parameters: # partition (:class:`~zhmcclient.Partition`): # Partition defining the scope for this manager. super(NicManager, self).__init__( resource_class=Nic, class_name='nic', session=partition.manager.session, parent=partition, base_uri='{}/nics'.format(partition.uri), oid_prop='element-id', uri_prop='element-uri', name_prop='name', query_props=[], list_has_name=False) @property def partition(self): """ :class:`~zhmcclient.Partition`: :term:`Partition` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the NICs in this Partition. Authorization requirements: * Object-access permission to this Partition. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.Nic` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] uris = self.partition.get_property('nic-uris') if uris: for uri in uris: resource_obj = self.resource_class( manager=self, uri=uri, name=None, properties=None) if self._matches_filters(resource_obj, filter_args): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties): """ Create and configure a NIC in this Partition. The NIC must be backed by an adapter port (on an OSA, ROCE, or Hipersockets adapter). The way the backing adapter port is specified in the "properties" parameter of this method depends on the adapter type, as follows: * For OSA and Hipersockets adapters, the "virtual-switch-uri" property is used to specify the URI of the virtual switch that is associated with the backing adapter port. This virtual switch is a resource that automatically exists as soon as the adapter resource exists. Note that these virtual switches do not show up in the HMC GUI; but they do show up at the HMC REST API and thus also at the zhmcclient API as the :class:`~zhmcclient.VirtualSwitch` class. The value for the "virtual-switch-uri" property can be determined from a given adapter name and port index as shown in the following example code (omitting any error handling): .. code-block:: python partition = ... # Partition object for the new NIC adapter_name = 'OSA #1' # name of adapter with backing port adapter_port_index = 0 # port index of backing port adapter = partition.manager.cpc.adapters.find(name=adapter_name) vswitches = partition.manager.cpc.virtual_switches.findall( **{'backing-adapter-uri': adapter.uri}) vswitch = None for vs in vswitches: if vs.get_property('port') == adapter_port_index: vswitch = vs break properties['virtual-switch-uri'] = vswitch.uri * For RoCE adapters, the "network-adapter-port-uri" property is used to specify the URI of the backing adapter port, directly. The value for the "network-adapter-port-uri" property can be determined from a given adapter name and port index as shown in the following example code (omitting any error handling): .. code-block:: python partition = ... # Partition object for the new NIC adapter_name = 'ROCE #1' # name of adapter with backing port adapter_port_index = 0 # port index of backing port adapter = partition.manager.cpc.adapters.find(name=adapter_name) port = adapter.ports.find(index=adapter_port_index) properties['network-adapter-port-uri'] = port.uri Authorization requirements: * Object-access permission to this Partition. * Object-access permission to the backing Adapter for the new NIC. * Task permission to the "Partition Details" task. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create NIC' in the :term:`HMC API` book. Returns: Nic: The resource object for the new NIC. The object will have its 'element-uri' property set as returned by the HMC, and will also have the input properties set. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.session.post(self.partition.uri + '/nics', body=properties) # There should not be overlaps, but just in case there are, the # returned props should overwrite the input props: props = copy.deepcopy(properties) props.update(result) name = props.get(self._name_prop, None) uri = props[self._uri_prop] nic = Nic(self, uri, name, props) self._name_uri_cache.update(name, uri) return nic class Nic(BaseResource): """ Representation of a :term:`NIC`. Derived from :class:`~zhmcclient.BaseResource`; see there for common methods and attributes. For the properties of a NIC resource, see section 'Data model - NIC Element Object' in section 'Partition object' in the :term:`HMC API` book. 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.NicManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.NicManager`): # 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, NicManager), \ "Nic init: Expected manager type %s, got %s" % \ (NicManager, type(manager)) super(Nic, self).__init__(manager, uri, name, properties) @logged_api_call def delete(self): """ Delete this NIC. Authorization requirements: * Object-access permission to the Partition containing this HBA. * Task permission to the "Partition Details" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.delete(self._uri) self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this NIC. Authorization requirements: * Object-access permission to the Partition containing this NIC. * Object-access permission to the backing Adapter for this NIC. * Task permission to the "Partition Details" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model - NIC Element Object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) 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._properties.update(copy.deepcopy(properties)) if is_rename: # Add the new name to the cache self.manager._name_uri_cache.update(self.name, self.uri) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623169682.0 zhmcclient-0.31.0/zhmcclient/_notification.py0000755000076500000240000003652200000000000021667 0ustar00maierastaff00000000000000#!/usr/bin/env python # Copyright 2017 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. """ The HMC supports the publishing of notifications for specific topics. This includes for example asynchronous job completion, property or status changes, and operating system messages issued in an LPAR or DPM partition. The zhmcclient package supports receiving HMC notifications in an easy-to-use way, as shown in the following example that receives and displays OS messages for a DPM partition:: import zhmcclient hmc = ... userid = ... password = ... session = zhmcclient.Session(hmc, userid, password) client = zhmcclient.Client(session) cpc = client.cpcs.find(name=cpcname) partition = cpc.partitions.find(name=partname) topic = partition.open_os_message_channel(include_refresh_messages=True) print("Subscribing for OS messages for partition %s on CPC %s using " "notifications..." % (partition.name, cpc.name)) receiver = zhmcclient.NotificationReceiver(topic, hmc, userid, password) try: for headers, message in receiver.notifications(): print("HMC notification #%s:" % headers['session-sequence-nr']) os_msg_list = message['os-messages'] for os_msg in os_msg_list: msg_txt = os_msg['message-text'].strip('\\n') msg_id = os_msg['message-id'] print("OS message #%s:\\n%s" % (msg_id, msg_txt)) except zhmcclient.NotificationError as exc: print("Notification Error: {}".format(exc)) except KeyboardInterrupt: print("Keyboard Interrupt - Leaving notification receiver loop...") finally: print("Closing notification receiver...") receiver.close() When running this example code in one terminal, and stopping or starting the partition in another terminal, one can monitor the shutdown or boot messages issued by the operating system. The following commands use the ``zhmc`` CLI provided in the :term:`zhmccli project` to do that: .. code-block:: text $ zhmc partition stop {cpc-name} {partition-name} $ zhmc partition start {cpc-name} {partition-name} """ import threading import json from ._logging import logged_api_call from ._constants import DEFAULT_STOMP_PORT from ._exceptions import NotificationJMSError, NotificationParseError __all__ = ['NotificationReceiver'] class NotificationReceiver(object): """ A class for receiving HMC notifications that are published to particular HMC notification topics. **Experimental:** This class is considered experimental at this point, and its API may change incompatibly as long as it is experimental. Creating an object of this class establishes a JMS session with the HMC and subscribes for the specified HMC notification topic(s). Notification topic strings are created by the HMC in context of a particular client session (i.e. :class:`~zhmcclient.Session` object). However, these topic strings can be used by any JMS message listener that knows the topic string and that authenticates under some valid HMC userid. The HMC userid used by the JMS listener does not need to be the one that was used for the client session in which the notification topic was originally created. """ def __init__(self, topic_names, host, userid, password, port=DEFAULT_STOMP_PORT): """ Parameters: topic_names (:term:`string` or list/tuple thereof): Name(s) of the HMC notification topic(s). Must not be `None`. host (:term:`string`): HMC host. For valid formats, see the :attr:`~zhmcclient.Session.host` property. Must not be `None`. userid (:term:`string`): Userid of the HMC user to be used. Must not be `None`. password (:term:`string`): Password of the HMC user to be used. Must not be `None`. port (:term:`integer`): STOMP TCP port. Defaults to :attr:`~zhmcclient._constants.DEFAULT_STOMP_PORT`. """ if not isinstance(topic_names, (list, tuple)): topic_names = [topic_names] self._topic_names = topic_names self._host = host self._port = port self._userid = userid self._password = password # Wait timeout to honor keyboard interrupts after this time: self._wait_timeout = 10.0 # seconds # Subscription ID. We use some value that allows to identify on the # HMC that this is the zhmcclient, but otherwise we are not using # this value ourselves. self._sub_id = 'zhmcclient.%s' % id(self) # Sync variables for thread-safe handover between listener thread and # receiver thread: self._handover_dict = {} self._handover_cond = threading.Condition() # Lazy importing for stomp, because it is so slow (ca. 5 sec) if 'Stomp_Connection' not in globals(): # pylint: disable=import-outside-toplevel from stomp import Connection as Stomp_Connection self._conn = Stomp_Connection( [(self._host, self._port)], use_ssl="SSL") listener = _NotificationListener(self._handover_dict, self._handover_cond) self._conn.set_listener('', listener) self._conn.connect(self._userid, self._password, wait=True) for topic_name in self._topic_names: dest = "/topic/" + topic_name self._conn.subscribe(destination=dest, id=self._sub_id, ack='auto') @logged_api_call def notifications(self): """ Generator method that yields all HMC notifications (= JMS messages) received by this notification receiver. Example:: desired_topic_types = ('security-notification', 'audit-notification') topics = session.get_notification_topics() topic_names = [t['topic-name'] for t in topics if t['topic-type'] in desired_topic_types] receiver = zhmcclient.NotificationReceiver( topic_names, hmc, userid, password) for headers, message in receiver.notifications(): . . . # processing of topic-specific message format Yields: : A tuple (headers, message) representing one HMC notification, with: * headers (dict): The notification header fields. Some important header fields (dict items) are: * 'notification-type' (string): The HMC notification type (e.g. 'os-message', 'job-completion', or others). * 'session-sequence-nr' (string): The sequence number of this HMC notification within the session created by this notification receiver object. This number starts at 0 when this receiver object is created, and is incremented each time an HMC notification is published to this receiver. * 'class' (string): The class name of the HMC resource publishing the HMC notification (e.g. 'partition'). * 'object-id' (string) or 'element-id' (string): The ID of the HMC resource publishing the HMC notification. For a complete list of notification header fields, see section "Message format" in chapter 4. "Asynchronous notification" in the :term:`HMC API` book. * message (:term:`JSON object`): Body of the HMC notification, converted into a JSON object. The properties of the JSON object vary by notification type. For a description of the JSON properties, see the sub-sections for each notification type within section "Notification message formats" in chapter 4. "Asynchronous notification" in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.NotificationJMSError`: Received JMS error from the HMC. :exc:`~zhmcclient.NotificationParseError`: Cannot parse JMS message body as JSON. """ while True: with self._handover_cond: # serialize body via lock # Wait until MessageListener has a new notification while len(self._handover_dict) == 0: self._handover_cond.wait(self._wait_timeout) if self._handover_dict['headers'] is None: return # Process the notification headers = self._handover_dict['headers'] message = self._handover_dict['message'] msgtype = self._handover_dict['msgtype'] if msgtype == 'error': if 'message' in headers: # Not sure that is always the case, but it was the case # in issue #770. details = ": {}".format(headers['message'].strip()) else: details = "" raise NotificationJMSError( "Received JMS error from HMC{}".format(details), headers, message) try: msg_obj = json.loads(message) except Exception as exc: raise NotificationParseError( "Cannot convert JMS message body to JSON: {}: {}". format(exc.__class__.__name__, exc), message) yield headers, msg_obj del self._handover_dict['headers'] del self._handover_dict['message'] del self._handover_dict['msgtype'] # Indicate to MessageListener that we are ready for next # notification self._handover_cond.notifyAll() @logged_api_call def close(self): """ Disconnect and close the JMS session with the HMC. This implicitly unsubscribes from the notification topic this receiver was created for, and causes the :meth:`~zhmcclient.NotificationReceiver.notifications` method to stop its iterations. """ self._conn.disconnect() class _NotificationListener(object): """ A notification listener class for use by the Python `stomp` package. This is an internal class that does not need to be accessed or created by the user. An object of this class is automatically created by the :class:`~zhmcclient.NotificationReceiver` class, for its notification topic. Note: In the stomp examples, this class inherits from stomp.ConnectionListener. However, since that class defines only empty methods and since we want to import the stomp module in a lazy manner, we are not using that class, and stomp does not require us to. """ def __init__(self, handover_dict, handover_cond): """ Parameters: handover_dict (dict): Dictionary for handing over the notification header and message from this listener thread to the receiver thread. Must initially be an empty dictionary. handover_cond (threading.Condition): Condition object for handing over the notification from this listener thread to the receiver thread. Must initially be a new threading.Condition object. """ # Sync variables for thread-safe handover between listener thread and # receiver thread: self._handover_dict = handover_dict # keys: headers, message self._handover_cond = handover_cond # Wait timeout to honor keyboard interrupts after this time: self._wait_timeout = 10.0 # seconds def on_disconnected(self): """ Event method that gets called when the JMS session has been disconnected. It hands over a termination notification (headers and message are None). """ with self._handover_cond: # serialize body via lock # Wait until receiver has processed the previous notification while len(self._handover_dict) > 0: self._handover_cond.wait(self._wait_timeout) # Indicate to receiver that there is a termination notification self._handover_dict['headers'] = None # terminate receiver self._handover_dict['message'] = None self._handover_dict['msgtype'] = 'disconnected' self._handover_cond.notifyAll() def on_error(self, headers, message): """ Event method that gets called when this listener has received a JMS error. This happens for example when the client registers for a non-existing topic. Parameters: headers (dict): JMS message headers, as described for `headers` tuple item returned by the :meth:`~zhmcclient.NotificationReceiver.notifications` method. message (string): JMS message body as a string, which contains a serialized JSON object. The JSON object is described in the `message` tuple item returned by the :meth:`~zhmcclient.NotificationReceiver.notifications` method). """ with self._handover_cond: # serialize body via lock # Wait until receiver has processed the previous notification while len(self._handover_dict) > 0: self._handover_cond.wait(self._wait_timeout) # Indicate to receiver that there is a new notification self._handover_dict['headers'] = headers self._handover_dict['message'] = message self._handover_dict['msgtype'] = 'error' self._handover_cond.notifyAll() def on_message(self, headers, message): """ Event method that gets called when this listener has received a JMS message (representing an HMC notification). Parameters: headers (dict): JMS message headers, as described for `headers` tuple item returned by the :meth:`~zhmcclient.NotificationReceiver.notifications` method. message (string): JMS message body as a string, which contains a serialized JSON object. The JSON object is described in the `message` tuple item returned by the :meth:`~zhmcclient.NotificationReceiver.notifications` method). """ with self._handover_cond: # serialize body via lock # Wait until receiver has processed the previous notification while len(self._handover_dict) > 0: self._handover_cond.wait(self._wait_timeout) # Indicate to receiver that there is a new notification self._handover_dict['headers'] = headers self._handover_dict['message'] = message self._handover_dict['msgtype'] = 'message' self._handover_cond.notifyAll() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_partition.py0000644000076500000240000013611000000000000021201 0ustar00maierastaff00000000000000# Copyright 2016-2017 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` is a subset of the hardware resources of a :term:`CPC` in DPM mode, virtualized as a separate computer. Partitions can be created and deleted dynamically, and their resources such as CPU, memory or I/O devices can be configured dynamically. You can create as many partition definitions as you want, but only a specific number of partitions can be active at any given time. TODO: How can a user find out what the maximum is, before it is reached? Partition resources are contained in CPC resources. Partition resources only exist in CPCs that are in DPM mode. CPCs in classic mode (or ensemble mode) have :term:`LPAR` resources, instead. """ from __future__ import absolute_import import time import copy from requests.utils import quote from ._manager import BaseManager from ._resource import BaseResource from ._exceptions import StatusTimeout from ._nic import NicManager from ._hba import HbaManager from ._virtual_function import VirtualFunctionManager from ._logging import logged_api_call __all__ = ['PartitionManager', 'Partition'] class PartitionManager(BaseManager): """ Manager providing access to the :term:`Partitions ` in a particular :term:`CPC`. 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 of a :class:`~zhmcclient.Cpc` object (in DPM mode): * :attr:`~zhmcclient.Cpc.partitions` """ def __init__(self, cpc): # This function should not go into the docs. # Parameters: # cpc (:class:`~zhmcclient.Cpc`): # CPC 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 = [ 'name', 'status', ] super(PartitionManager, self).__init__( resource_class=Partition, class_name='partition', session=cpc.manager.session, parent=cpc, base_uri='/api/partitions', oid_prop='object-id', uri_prop='object-uri', name_prop='name', query_props=query_props) @property def cpc(self): """ :class:`~zhmcclient.Cpc`: :term:`CPC` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the Partitions in this CPC. Authorization requirements: * Object-access permission to this CPC. * Object-access permission to any Partition to be included in the result. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.Partition` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] resource_obj = self._try_optimized_lookup(filter_args) if resource_obj: resource_obj_list.append(resource_obj) # It already has full properties else: query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'partitions' uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties): """ Create and configure a Partition in this CPC. Authorization requirements: * Object-access permission to this CPC. * Task permission to the "New Partition" task. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create Partition' in the :term:`HMC API` book. Returns: Partition: The resource object for the new Partition. The object will have its 'object-uri' property set as returned by the HMC, and will also have the input properties set. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.session.post(self.cpc.uri + '/partitions', body=properties) # There should not be overlaps, but just in case there are, the # returned props should overwrite the input props: props = copy.deepcopy(properties) props.update(result) name = props.get(self._name_prop, None) uri = props[self._uri_prop] part = Partition(self, uri, name, props) self._name_uri_cache.update(name, uri) return part class Partition(BaseResource): """ Representation of a :term:`Partition`. 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.PartitionManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.PartitionManager`): # 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, PartitionManager), \ "Partition init: Expected manager type %s, got %s" % \ (PartitionManager, type(manager)) super(Partition, self).__init__(manager, uri, name, properties) # The manager objects for child resources (with lazy initialization): self._nics = None self._hbas = None self._virtual_functions = None @property def nics(self): """ :class:`~zhmcclient.NicManager`: Access to the :term:`NICs ` in this Partition. """ # We do here some lazy loading. if not self._nics: self._nics = NicManager(self) return self._nics @property def hbas(self): """ :class:`~zhmcclient.HbaManager`: Access to the :term:`HBAs ` in this Partition. If the "dpm-storage-management" feature is enabled, this property is `None`. """ # We do here some lazy loading. if not self._hbas: try: dpm_sm = self.feature_enabled('dpm-storage-management') except ValueError: dpm_sm = False if not dpm_sm: self._hbas = HbaManager(self) return self._hbas @property def virtual_functions(self): """ :class:`~zhmcclient.VirtualFunctionManager`: Access to the :term:`Virtual Functions ` in this Partition. """ # We do here some lazy loading. if not self._virtual_functions: self._virtual_functions = VirtualFunctionManager(self) return self._virtual_functions @logged_api_call def feature_enabled(self, feature_name): """ Indicates whether the specified feature is enabled for the CPC of this partition. The HMC must generally support features, and the specified feature must be available for the CPC. For a list of available features, see section "Features" in the :term:`HMC API`, or use the :meth:`feature_info` method. Authorization requirements: * Object-access permission to this partition. Parameters: feature_name (:term:`string`): The name of the feature. Returns: bool: `True` if the feature is enabled, or `False` if the feature is disabled (but available). Raises: :exc:`ValueError`: Features are not supported on the HMC. :exc:`ValueError`: The specified feature is not available for the CPC. :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ feature_list = self.prop('available-features-list', None) if feature_list is None: raise ValueError("Firmware features are not supported on CPC %s" % self.manager.cpc.name) for feature in feature_list: if feature['name'] == feature_name: break else: raise ValueError("Firmware feature %s is not available on CPC %s" % (feature_name, self.manager.cpc.name)) return feature['state'] # pylint: disable=undefined-loop-variable @logged_api_call def feature_info(self): """ Returns information about the features available for the CPC of this partition. Authorization requirements: * Object-access permission to this partition. Returns: :term:`iterable`: An iterable where each item represents one feature that is available for the CPC of this partition. Each item is a dictionary with the following items: * `name` (:term:`unicode string`): Name of the feature. * `description` (:term:`unicode string`): Short description of the feature. * `state` (bool): Enablement state of the feature (`True` if the enabled, `False` if disabled). Raises: :exc:`ValueError`: Features are not supported on the HMC. :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ feature_list = self.prop('available-features-list', None) if feature_list is None: raise ValueError("Firmware features are not supported on CPC %s" % self.manager.cpc.name) return feature_list @logged_api_call def start(self, wait_for_completion=True, operation_timeout=None, status_timeout=None): """ Start (activate) this Partition, using the HMC operation "Start Partition". This HMC operation has deferred status behavior: If the asynchronous job on the HMC is complete, it takes a few seconds until the partition status has reached the desired value (it still may show status "paused"). If `wait_for_completion=True`, this method repeatedly checks the status of the partition after the HMC operation has completed, and waits until the status is in one of the desired states "active" or "degraded". TODO: Describe what happens if the maximum number of active partitions is exceeded. Authorization requirements: * Object-access permission to this Partition. * Object-access permission to the CPC containing this Partition. * Task permission to the "Start Partition" task. Parameters: wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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. status_timeout (:term:`number`): Timeout in seconds, for waiting that the status of the partition has reached the desired status, after the HMC operation has completed. 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.StatusTimeout` is raised. Returns: :class:`py:dict` or :class:`~zhmcclient.Job`: If `wait_for_completion` is `True`, returns an empty :class:`py:dict` object. If `wait_for_completion` is `False`, returns a :class:`~zhmcclient.Job` object representing the asynchronously executing job on the HMC. 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.StatusTimeout`: The timeout expired while waiting for the desired partition status. """ result = self.manager.session.post( self.uri + '/operations/start', wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) if wait_for_completion: statuses = ["active", "degraded"] self.wait_for_status(statuses, status_timeout) return result @logged_api_call def stop(self, wait_for_completion=True, operation_timeout=None, status_timeout=None): """ Stop (deactivate) this Partition, using the HMC operation "Stop Partition". Authorization requirements: * Object-access permission to this Partition. * Task permission to the "Stop Partition" task. Parameters: wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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. status_timeout (:term:`number`): Timeout in seconds, for waiting that the status of the partition has reached the desired status, after the HMC operation has completed. 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.StatusTimeout` is raised. Returns: :class:`py:dict` or :class:`~zhmcclient.Job`: If `wait_for_completion` is `True`, returns an empty :class:`py:dict` object. If `wait_for_completion` is `False`, returns a :class:`~zhmcclient.Job` object representing the asynchronously executing job on the HMC. 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.StatusTimeout`: The timeout expired while waiting for the desired partition status. """ result = self.manager.session.post( self.uri + '/operations/stop', wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) if wait_for_completion: statuses = ["stopped"] self.wait_for_status(statuses, status_timeout) return result @logged_api_call def delete(self): """ Delete this Partition. Authorization requirements: * Object-access permission to this Partition. * Task permission to the "Delete Partition" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.delete(self.uri) self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this Partition. Authorization requirements: * Object-access permission to this Partition. * Task permission to the "Partition Details" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model' in section 'Partition object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) 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._properties.update(copy.deepcopy(properties)) if is_rename: # Add the new name to the cache self.manager._name_uri_cache.update(self.name, self.uri) @logged_api_call def dump_partition(self, parameters, wait_for_completion=True, operation_timeout=None): """ Dump this Partition, by loading a standalone dump program from a SCSI device and starting its execution, using the HMC operation 'Dump Partition'. This operation requires that the CPC does not have the storage management feature (i.e. is a z13 or earlier). Authorization requirements: * Object-access permission to this Partition. * Task permission to the "Dump Partition" task. Parameters: parameters (dict): Input parameters for the operation. Allowable input parameters are defined in section 'Request body contents' in section 'Dump Partition' in the :term:`HMC API` book. wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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: :class:`py:dict` or :class:`~zhmcclient.Job`: If `wait_for_completion` is `True`, returns an empty :class:`py:dict` object. If `wait_for_completion` is `False`, returns a :class:`~zhmcclient.Job` object representing the asynchronously executing job on the HMC. 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. """ result = self.manager.session.post( self.uri + '/operations/scsi-dump', wait_for_completion=wait_for_completion, operation_timeout=operation_timeout, body=parameters) return result @logged_api_call def start_dump_program(self, parameters, wait_for_completion=True, operation_timeout=None): """ Dump this Partition, by loading a standalone dump program from a storage volume and starting its execution, using the HMC operation 'Start Dump Program'. This operation requires that the CPC has the storage management feature (i.e. is a z14 or later). Authorization requirements: * Object-access permission to this Partition. * Task permission to the "Dump Partition" task. Parameters: parameters (dict): Input parameters for the operation. Allowable input parameters are defined in section 'Request body contents' in section 'Start Dump Program' in the :term:`HMC API` book. wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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: :class:`py:dict` or :class:`~zhmcclient.Job`: If `wait_for_completion` is `True`, returns an empty :class:`py:dict` object. If `wait_for_completion` is `False`, returns a :class:`~zhmcclient.Job` object representing the asynchronously executing job on the HMC. 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. """ result = self.manager.session.post( self.uri + '/operations/start-dump-program', wait_for_completion=wait_for_completion, operation_timeout=operation_timeout, body=parameters) return result @logged_api_call def psw_restart(self, wait_for_completion=True, operation_timeout=None): """ Initiates a PSW restart for this Partition, using the HMC operation 'Perform PSW Restart'. Authorization requirements: * Object-access permission to this Partition. * Task permission to the "PSW Restart" task. Parameters: wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation, as follows: * If `True`, this method will wait for completion of the asynchronous job performing the operation. * If `False`, this method will return immediately once the HMC has accepted the request to perform the operation. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the operation. 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: :class:`py:dict` or :class:`~zhmcclient.Job`: If `wait_for_completion` is `True`, returns an empty :class:`py:dict` object. If `wait_for_completion` is `False`, returns a :class:`~zhmcclient.Job` object representing the asynchronously executing job on the HMC. 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. """ result = self.manager.session.post( self.uri + '/operations/psw-restart', wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) return result @logged_api_call def mount_iso_image(self, image, image_name, ins_file_name): """ Upload an ISO image and associate it to this Partition using the HMC operation 'Mount ISO Image'. When the partition already has an ISO image associated, the newly uploaded image replaces the current one. Authorization requirements: * Object-access permission to this Partition. * Task permission to the "Partition Details" task. Parameters: image (:term:`byte string` or file-like object): The content of the ISO image. Images larger than 2GB cannot be specified as a Byte string; they must be specified as a file-like object. File-like objects must have opened the file in binary mode. image_name (:term:`string`): The displayable name of the image. This value must be a valid Linux file name without directories, must not contain blanks, and must end with '.iso' in lower case. This value will be shown in the 'boot-iso-image-name' property of this partition. ins_file_name (:term:`string`): The path name of the INS file within the file system of the ISO image. This value will be shown in the 'boot-iso-ins-file' property of this partition. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ query_parms_str = '?image-name={}&ins-file-name={}'. \ format(quote(image_name, safe=''), quote(ins_file_name, safe='')) self.manager.session.post( self.uri + '/operations/mount-iso-image' + query_parms_str, body=image) @logged_api_call def unmount_iso_image(self): """ Unmount the currently mounted ISO from this Partition using the HMC operation 'Unmount ISO Image'. This operation sets the partition's 'boot-iso-image-name' and 'boot-iso-ins-file' properties to null. Authorization requirements: * Object-access permission to this Partition. * Task permission to the "Partition Details" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ self.manager.session.post( self.uri + '/operations/unmount-iso-image') @logged_api_call def open_os_message_channel(self, include_refresh_messages=True): """ Open a JMS message channel to this partition's operating system, returning the string "topic" representing the message channel. Parameters: include_refresh_messages (bool): Boolean controlling whether refresh operating systems messages should be sent, as follows: * If `True`, refresh messages will be recieved when the user connects to the topic. The default. * If `False`, refresh messages will not be recieved when the user connects to the topic. Returns: :term:`string`: Returns a string representing the os-message-notification JMS topic. The user can connect to this topic to start the flow of operating system messages. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = {'include-refresh-messages': include_refresh_messages} result = self.manager.session.post( self.uri + '/operations/open-os-message-channel', body) return result['topic-name'] @logged_api_call def send_os_command(self, os_command_text, is_priority=False): """ Send a command to the operating system running in this partition. Parameters: os_command_text (string): The text of the operating system command. is_priority (bool): Boolean controlling whether this is a priority operating system command, as follows: * If `True`, this message is treated as a priority operating system command. * If `False`, this message is not treated as a priority operating system command. The default. Returns: None Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = {'is-priority': is_priority, 'operating-system-command-text': os_command_text} self.manager.session.post( self.uri + '/operations/send-os-cmd', body) @logged_api_call def wait_for_status(self, status, status_timeout=None): """ Wait until the status of this partition has a desired value. Parameters: status (:term:`string` or iterable of :term:`string`): Desired partition status or set of status values to reach; one or more of the values defined for the 'status' property in the data model for partitions in the :term:`HMC API` book. status_timeout (:term:`number`): Timeout in seconds, for waiting that the status of the partition has reached one of the desired status values. The special value 0 means that no timeout is set. `None` means that the default status timeout will be used. If the timeout expires, a :exc:`~zhmcclient.StatusTimeout` is raised. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` :exc:`~zhmcclient.StatusTimeout`: The status timeout expired while waiting for the desired partition status. """ if status_timeout is None: status_timeout = \ self.manager.session.retry_timeout_config.status_timeout if status_timeout > 0: end_time = time.time() + status_timeout if isinstance(status, (list, tuple)): statuses = status else: statuses = [status] while True: # Fastest way to get actual status value: parts = self.manager.cpc.partitions.list( filter_args={'name': self.name}) assert len(parts) == 1 this_part = parts[0] actual_status = this_part.get_property('status') if actual_status in statuses: return if status_timeout > 0 and time.time() > end_time: raise StatusTimeout( "Waiting for partition {} to reach status(es) '{}' timed " "out after {} s - current status is '{}'". format(self.name, statuses, status_timeout, actual_status), actual_status, statuses, status_timeout) time.sleep(1) # Avoid hot spin loop @logged_api_call def increase_crypto_config(self, crypto_adapters, crypto_domain_configurations): """ Add crypto adapters and/or crypto domains to the crypto configuration of this partition. The general principle for maintaining crypto configurations of partitions is as follows: Each adapter included in the crypto configuration of a partition has all crypto domains included in the crypto configuration. Each crypto domain included in the crypto configuration has the same access mode on all adapters included in the crypto configuration. Example: Assume that the current crypto configuration of a partition includes crypto adapter A and crypto domains 0 and 1. When this method is called to add adapter B and domain configurations for domains 1 and 2, the resulting crypto configuration of the partition will include domains 0, 1, and 2 on each of the adapters A and B. Authorization requirements: * Object-access permission to this Partition. * Task permission to the "Partition Details" task. Parameters: crypto_adapters (:term:`iterable` of :class:`~zhmcclient.Adapter`): Crypto adapters that should be added to the crypto configuration of this partition. crypto_domain_configurations (:term:`iterable` of `domain_config`): Crypto domain configurations that should be added to the crypto configuration of this partition. A crypto domain configuration (`domain_config`) is a dictionary with the following keys: * ``"domain-index"`` (:term:`integer`): Domain index of the crypto domain. The domain index is a number in the range of 0 to a maximum that depends on the model of the crypto adapter and the CPC model. For the Crypto Express 5S adapter in a z13, the maximum domain index is 84. * ``"access-mode"`` (:term:`string`): Access mode for the crypto domain. The access mode specifies the way the partition can use the crypto domain on the crypto adapter(s), using one of the following string values: * ``"control"`` - The partition can load cryptographic keys into the domain, but it may not use the domain to perform cryptographic operations. * ``"control-usage"`` - The partition can load cryptographic keys into the domain, and it can use the domain to perform cryptographic operations. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ crypto_adapter_uris = [a.uri for a in crypto_adapters] body = {'crypto-adapter-uris': crypto_adapter_uris, 'crypto-domain-configurations': crypto_domain_configurations} self.manager.session.post( self.uri + '/operations/increase-crypto-configuration', body) @logged_api_call def decrease_crypto_config(self, crypto_adapters, crypto_domain_indexes): """ Remove crypto adapters and/or crypto domains from the crypto configuration of this partition. For the general principle for maintaining crypto configurations of partitions, see :meth:`~zhmcclient.Partition.increase_crypto_config`. Example: Assume that the current crypto configuration of a partition includes crypto adapters A, B and C and crypto domains 0, 1, and 2 (on each of the adapters). When this method is called to remove adapter C and domain 2, the resulting crypto configuration of the partition will include domains 0 and 1 on each of the adapters A and B. Authorization requirements: * Object-access permission to this Partition. * Task permission to the "Partition Details" task. Parameters: crypto_adapters (:term:`iterable` of :class:`~zhmcclient.Adapter`): Crypto adapters that should be removed from the crypto configuration of this partition. crypto_domain_indexes (:term:`iterable` of :term:`integer`): Domain indexes of the crypto domains that should be removed from the crypto configuration of this partition. For values, see :meth:`~zhmcclient.Partition.increase_crypto_config`. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ crypto_adapter_uris = [a.uri for a in crypto_adapters] body = {'crypto-adapter-uris': crypto_adapter_uris, 'crypto-domain-indexes': crypto_domain_indexes} self.manager.session.post( self.uri + '/operations/decrease-crypto-configuration', body) @logged_api_call def change_crypto_domain_config(self, crypto_domain_index, access_mode): """ Change the access mode for a crypto domain that is currently included in the crypto configuration of this partition. The access mode will be changed for the specified crypto domain on all crypto adapters currently included in the crypto configuration of this partition. For the general principle for maintaining crypto configurations of partitions, see :meth:`~zhmcclient.Partition.increase_crypto_config`. Authorization requirements: * Object-access permission to this Partition. * Task permission to the "Partition Details" task. Parameters: crypto_domain_index (:term:`integer`): Domain index of the crypto domain to be changed. For values, see :meth:`~zhmcclient.Partition.increase_crypto_config`. access_mode (:term:`string`): The new access mode for the crypto domain. For values, see :meth:`~zhmcclient.Partition.increase_crypto_config`. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = {'domain-index': crypto_domain_index, 'access-mode': access_mode} self.manager.session.post( self.uri + '/operations/change-crypto-domain-configuration', body) @logged_api_call def zeroize_crypto_domain(self, crypto_adapter, crypto_domain_index): """ Zeroize a single crypto domain on a crypto adapter. Zeroizing a crypto domain clears the cryptographic keys and non-compliance mode settings in the crypto domain. The crypto domain must be attached to this partition in "control-usage" access mode. Supported CPC versions: z14 GA2 and above, and the corresponding LinuxOne systems. Authorization requirements: * Object-access permission to this Partition and to the Crypto Adapter. * Task permission to the "Zeroize Crypto Domain" task. Parameters: crypto_adapter (:class:`~zhmcclient.Adapter`): Crypto adapter with the crypto domain to be zeroized. crypto_domain_index (:term:`integer`): Domain index of the crypto domain to be zeroized. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = { 'crypto-adapter-uri': crypto_adapter.uri, 'domain-index': crypto_domain_index } self.manager.session.post( self.uri + '/operations/zeroize-crypto-domain', body) @logged_api_call def attach_storage_group(self, storage_group): """ Attach a :term:`storage group` to this partition. This will cause the :term:`storage volumes ` of the storage group to be attached to the partition, instantiating any necessary :term:`virtual storage resource` objects. A storage group can be attached to a partition regardless of its fulfillment state. The fulfillment state of its storage volumes and thus of the entire storage group changes as volumes are discovered by DPM, and will eventually reach "complete". The CPC must have the "dpm-storage-management" feature enabled. Authorization requirements: * Object-access permission to this partition. * Object-access permission to the specified storage group. * Task permission to the "Partition Details" task. Parameters: storage_group (:class:`~zhmcclient.StorageGroup`): Storage group to be attached. The storage group must not currently be attached to this partition. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = {'storage-group-uri': storage_group.uri} self.manager.session.post( self.uri + '/operations/attach-storage-group', body) @logged_api_call def detach_storage_group(self, storage_group): """ Detach a :term:`storage group` from this partition. This will cause the :term:`storage volumes ` of the storage group to be detached from the partition, removing any :term:`virtual storage resource` objects that had been created upon attachment. A storage group can be detached from a partition regardless of its fulfillment state. The fulfillment state of its storage volumes changes as volumes are discovered by DPM. The CPC must have the "dpm-storage-management" feature enabled. Authorization requirements: * Object-access permission to this partition. * Task permission to the "Partition Details" task. Parameters: storage_group (:class:`~zhmcclient.StorageGroup`): Storage group to be detached. The storage group must currently be attached to this partition. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = {'storage-group-uri': storage_group.uri} self.manager.session.post( self.uri + '/operations/detach-storage-group', body) @logged_api_call def list_attached_storage_groups(self, full_properties=False): """ Return the storage groups that are attached to this partition. The CPC must have the "dpm-storage-management" feature enabled. Authorization requirements: * Object-access permission to this partition. * Task permission to the "Partition Details" task. Parameters: full_properties (bool): Controls that the full set of resource properties for each returned storage group is being retrieved, vs. only the following short set: "object-uri", "object-id", "class", "parent". TODO: Verify short list of properties. Returns: List of :class:`~zhmcclient.StorageGroup` objects representing the storage groups that are attached to this partition. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ sg_list = [] sg_uris = self.get_property('storage-group-uris') if sg_uris: console = self.manager.cpc.manager.console for sg_uri in sg_uris: sg = console.storage_groups.resource_object(sg_uri) sg_list.append(sg) if full_properties: sg.pull_full_properties() return sg_list ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_password_rule.py0000644000076500000240000002241000000000000022056 0ustar00maierastaff00000000000000# Copyright 2017 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:`Password Rule` resource represents a rule which an HMC user must follow when creating a HMC logon password. Each HMC user using local authentication (i.e. not LDAP) is assigned a password rule. There are certain system-defined password rules available for use. """ from __future__ import absolute_import import copy from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['PasswordRuleManager', 'PasswordRule'] class PasswordRuleManager(BaseManager): """ Manager providing access to the :term:`Password Rule` resources of a 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 of a :class:`~zhmcclient.Console` object: * :attr:`zhmcclient.Console.password_rules` """ def __init__(self, console): # This function should not go into the docs. # Parameters: # console (:class:`~zhmcclient.Console`): # Console object representing the HMC. # 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 = [ 'name', 'type', ] super(PasswordRuleManager, self).__init__( resource_class=PasswordRule, class_name='password-rule', session=console.manager.session, parent=console, base_uri='/api/console/password-rules', oid_prop='element-id', uri_prop='element-uri', name_prop='name', query_props=query_props) @property def console(self): """ :class:`~zhmcclient.Console`: :term:`Console` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=True, filter_args=None): """ List the :term:`Password Rule` resources representing the password rules defined in this HMC. Authorization requirements: * User-related-access permission to the Password Rule objects included in the result, or task permission to the "Manage Password Rules" task. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.PasswordRule` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'password-rules' uri = '{}/{}{}'.format(self.console.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties): """ Create a new Password Rule in this HMC. Authorization requirements: * Task permission to the "Manage Password Rules" task. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create Password Rule' in the :term:`HMC API` book. Returns: PasswordRule: The resource object for the new Password Rule. The object will have its 'element-uri' property set as returned by the HMC, and will also have the input properties set. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.session.post(self.console.uri + '/password-rules', body=properties) # There should not be overlaps, but just in case there are, the # returned props should overwrite the input props: props = copy.deepcopy(properties) props.update(result) name = props.get(self._name_prop, None) uri = props[self._uri_prop] password_rule = PasswordRule(self, uri, name, props) self._name_uri_cache.update(name, uri) return password_rule class PasswordRule(BaseResource): """ Representation of a :term:`Password Rule`. 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.PasswordRuleManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.PasswordRuleManager`): # 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, PasswordRuleManager), \ "Console init: Expected manager type %s, got %s" % \ (PasswordRuleManager, type(manager)) super(PasswordRule, self).__init__(manager, uri, name, properties) @logged_api_call def delete(self): """ Delete this Password Rule. The Password Rule must be user-defined. System-defined Password Rules cannot be deleted. Authorization requirements: * Task permission to the "Manage Password Rules" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.delete(self.uri) self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this PasswordRule. The Password Rule must be user-defined. System-defined Password Rules cannot be updated. Authorization requirements: * Task permission to the "Manage Password Rules" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model' in section 'Password Rule object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) # The name of Password Rules cannot be updated. An attempt to do so # should cause HTTPError to be raised in the POST above, so we assert # that here, because we omit the extra code for handling name updates: assert self.manager._name_prop not in properties self._properties.update(copy.deepcopy(properties)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_port.py0000644000076500000240000001662500000000000020164 0ustar00maierastaff00000000000000# Copyright 2016-2017 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:`Port` is a physical connector port (jack) of an :term:`Adapter`. Port resources are contained in Adapter resources. Ports only exist in :term:`CPCs ` that are in DPM mode. """ from __future__ import absolute_import import copy from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['PortManager', 'Port'] class PortManager(BaseManager): """ Manager providing access to the :term:`Ports ` of a particular :term:`Adapter`. 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 as properties in higher level resources (in this case, the :class:`~zhmcclient.Adapter` object). """ def __init__(self, adapter, port_type): # This function should not go into the docs. # Parameters: # adapter (:class:`~zhmcclient.Adapter`): # Adapter defining the scope for this manager. # port_type (string): # Type of Ports managed by this manager: # * `network`: Ports of a network adapter # * `storage`: Ports of a storage adapter # * None: Adapter family without ports super(PortManager, self).__init__( resource_class=Port, session=adapter.manager.session, class_name='{}-port'.format(port_type) if port_type else '', parent=adapter, base_uri='', # TODO: Re-enable the following when unit/test_hba.py has been # converted to using the zhmcclient mock support: # base_uri='{}/{}'.format(adapter.uri, adapter.port_uri_segment), oid_prop='element-id', uri_prop='element-uri', name_prop='name', query_props=[], list_has_name=False) self._port_type = port_type @property def adapter(self): """ :class:`~zhmcclient.Adapter`: :term:`Adapter` defining the scope for this manager. """ return self._parent @property def port_type(self): """ :term:`string`: Type of the Ports managed by this object: * ``'network'`` - Ports of a network adapter * ``'storage'`` - Ports of a storage adapter * ``None`` - Adapter family without ports """ return self._port_type @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the Ports of this Adapter. If the adapter does not have any ports, an empty list is returned. Authorization requirements: * Object-access permission to this Adapter. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.Port` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ uris_prop = self.adapter.port_uris_prop if not uris_prop: # Adapter does not have any ports return [] uris = self.adapter.get_property(uris_prop) assert uris is not None # TODO: Remove the following circumvention once fixed. # The following line circumvents a bug for FCP adapters that sometimes # causes duplicate URIs to show up in this property: uris = list(set(uris)) resource_obj_list = [] for uri in uris: resource_obj = self.resource_class( manager=self, uri=uri, name=None, properties=None) if self._matches_filters(resource_obj, filter_args): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list class Port(BaseResource): """ Representation of a :term:`Port`. Derived from :class:`~zhmcclient.BaseResource`; see there for common methods and attributes. For the properties of a Port, see section 'Data model - Port Element Object' in section 'Adapter object' in the :term:`HMC API` book. 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.PortManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.PortManager`): # 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, PortManager), \ "Port init: Expected manager type %s, got %s" % \ (PortManager, type(manager)) super(Port, self).__init__(manager, uri, name, properties) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this Port. Authorization requirements: * Object-access permission to the Adapter of this Port. * Task permission to the "Adapter Details" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model - Port Element Object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) # Attempts to change the 'name' property will be rejected by the HMC, # so we don't need to update the name-to-URI cache. assert self.manager._name_prop not in properties self._properties.update(copy.deepcopy(properties)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_resource.py0000644000076500000240000002756000000000000021027 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ Base definitions for resource classes. Resource objects represent the real manageable resources in the systems managed by the HMC. """ from __future__ import absolute_import import time from immutable_views import DictView from ._logging import logged_api_call from ._utils import repr_dict, repr_timestamp __all__ = ['BaseResource'] class BaseResource(object): """ Abstract base class for resource classes (e.g. :class:`~zhmcclient.Cpc`) representing manageable resources. It defines the interface for the derived resource classes, and implements methods that have a common implementation for the derived resource classes. Objects of derived resource classes are representations of the actual manageable resources in the HMC or in systems managed by the HMC. Objects of derived resource classes should not be created by users of this package by simply instantiating the derived resource classes. Instead, such objects are created by this package and are returned to the user as a result of methods such as :meth:`~zhmcclient.BaseManager.find` or :meth:`~zhmcclient.BaseManager.list`. For this reason, the `__init__()` method of this class and of its derived resource classes are considered internal interfaces and their parameters are not documented and may change incompatibly. """ def __init__(self, manager, uri, name, properties): # This method intentionally has no docstring, because it is internal. # # Parameters: # manager (subclass of :class:`~zhmcclient.BaseManager`): # Manager object for this resource object (and for all resource # objects of the same type in the scope of that manager). # Must not be `None`. # uri (string): # Canonical URI path of the resource. # Must not be `None`. # name (string): # Name of the resource. # May be `None`. # properties (dict): # Properties for this resource object. May be `None` or empty. # * Key: Name of the property. # * Value: Value of the property. # We want to surface precondition violations as early as possible, # so we test those that are not surfaced through the init code: assert manager is not None assert uri is not None self._manager = manager self._uri = uri self._properties = dict(properties) if properties else {} if name is not None: name_prop = self._manager._name_prop if name_prop in self._properties: assert self._properties[name_prop] == name else: self._properties[name_prop] = name uri_prop = self._manager._uri_prop if uri_prop in self._properties: assert self._properties[uri_prop] == uri else: self._properties[uri_prop] = uri self._properties_timestamp = int(time.time()) self._full_properties = False @property def properties(self): """ :class:`iv:immutable_views.DictView`: The properties of this resource that are currently present in this Python object, as a dictionary. * Key: Name of the property. * Value: Value of the property. The returned :class:`iv:immutable_views.DictView` object is an immutable dictionary view that behaves like a standard Python :class:`dict` except that it prevents any modifications to the dictionary. See the respective 'Data model' sections in the :term:`HMC API` book for a description of the resources along with their properties. The dictionary contains either the full set of resource properties, or a subset thereof, or can be empty in some cases. Because the presence of properties in this dictionary depends on the situation, the purpose of this dictionary is only for iterating through the resource properties that are currently present. Specific resource properties should be accessed via: * The resource name, via the :attr:`~zhmcclient.BaseResource.name` attribute. * The resource URI, via the :attr:`~zhmcclient.BaseResource.uri` attribute. * Any resource property, via the :meth:`~zhmcclient.BaseResource.get_property` or :meth:`~zhmcclient.BaseResource.prop` methods. Updates to property values can be done via the ``update_properties()`` method of the resource class. Which properties can be updated is indicated with the 'w' qualifier in the data model of the resource in the :term:`HMC API` book. """ return DictView(self._properties) @property def uri(self): """ string: The canonical URI path of the resource. Will not be `None`. Example: ``/api/cpcs/12345`` """ return self._uri @property def name(self): """ string: The name of the resource. Will not be `None`. The resource name is unique across its sibling resources of the same type and with the same parent resource. Accessing this property will cause the properties of this resource object to be updated from the HMC, if it does not yet contain the property for the resource name. """ # pylint: disable=protected-access # We avoid storing the name in an instance variable, because it can # be modified via update_properties(). return self.get_property(self.manager._name_prop) @property def manager(self): """ Subclass of :class:`~zhmcclient.BaseManager`: Manager object for this resource (and for all resources of the same type in the scope of that manager). Will not be `None`. """ return self._manager @property def full_properties(self): """ A boolean indicating whether or not the resource properties in this object are the full set of resource properties. Note that listing resources and creating new resources produces objects that have less than the full set of properties. """ return self._full_properties @property def properties_timestamp(self): """ The point in time of the last update of the resource properties cached in this object, as Unix time (an integer that is the number of seconds since the Unix epoch). """ return self._properties_timestamp @logged_api_call def pull_full_properties(self): """ Retrieve the full set of resource properties and cache them in this object. Authorization requirements: * Object-access permission to this resource. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ full_properties = self.manager.session.get(self._uri) self._properties = dict(full_properties) self._properties_timestamp = int(time.time()) self._full_properties = True @logged_api_call def get_property(self, name): """ Return the value of a resource property. If the resource property is not cached in this object yet, the full set of resource properties is retrieved and cached in this object, and the resource property is again attempted to be returned. Authorization requirements: * Object-access permission to this resource. Parameters: name (:term:`string`): Name of the resource property, using the names defined in the respective 'Data model' sections in the :term:`HMC API` book. Returns: The value of the resource property. Raises: KeyError: The resource property could not be found (also not in the full set of resource properties). :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ try: return self._properties[name] except KeyError: if self._full_properties: raise self.pull_full_properties() return self._properties[name] @logged_api_call def prop(self, name, default=None): """ Return the value of a resource property, applying a default if it does not exist. If the resource property is not cached in this object yet, the full set of resource properties is retrieved and cached in this object, and the resource property is again attempted to be returned. Authorization requirements: * Object-access permission to this resource. Parameters: name (:term:`string`): Name of the resource property, using the names defined in the respective 'Data model' sections in the :term:`HMC API` book. default: Default value to be used, if the resource property does not exist. Returns: The value of the resource property. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ try: return self.get_property(name) except KeyError: return default def __str__(self): """ Return a human readable string representation of this resource. Example result: .. code-block:: text Cpc(name=P0000S12, object-uri=/api/cpcs/f1bc49af-f71a-3467-8def-3c186b5d9352, status=service-required) """ properties_keys = self._properties.keys() search_keys = ['status', 'object-uri', 'element-uri', 'name', 'type', 'class'] sorted_keys = sorted([k for k in properties_keys if k in search_keys]) info = ", ".join("%s=%r" % (k, self._properties[k]) for k in sorted_keys) return "%s(%s)" % (self.__class__.__name__, info) def __repr__(self): """ Return a string with the state of this resource, for debug purposes. Note that the derived resource classes that have child resources have their own ``__repr__()`` methods, because only they know which child resources they have. """ ret = ( "{classname} at 0x{id:08x} (\n" " _manager={_manager_classname} at 0x{_manager_id:08x},\n" " _uri={_uri!r},\n" " _full_properties={_full_properties!r},\n" " _properties_timestamp={_properties_timestamp},\n" " _properties={_properties}\n" ")".format( classname=self.__class__.__name__, id=id(self), _manager_classname=self._manager.__class__.__name__, _manager_id=id(self._manager), _uri=self._uri, _full_properties=self._full_properties, _properties_timestamp=repr_timestamp( self._properties_timestamp), _properties=repr_dict(self._properties, indent=4), )) return ret ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324143.0 zhmcclient-0.31.0/zhmcclient/_session.py0000644000076500000240000016544500000000000020670 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ Session class: A session to the HMC, optionally in context of an HMC user. """ from __future__ import absolute_import import sys import json import time import re import collections from copy import copy try: from collections import OrderedDict except ImportError: from ordereddict import OrderedDict import six import requests from requests.packages import urllib3 from ._exceptions import HTTPError, ServerAuthError, ClientAuthError, \ ParseError, ConnectTimeout, ReadTimeout, RetriesExceeded, OperationTimeout from ._exceptions import ConnectionError # pylint: disable=redefined-builtin from ._timestats import TimeStatsKeeper from ._logging import get_logger, logged_api_call from ._constants import DEFAULT_CONNECT_TIMEOUT, DEFAULT_CONNECT_RETRIES, \ DEFAULT_READ_TIMEOUT, DEFAULT_READ_RETRIES, DEFAULT_MAX_REDIRECTS, \ DEFAULT_OPERATION_TIMEOUT, DEFAULT_STATUS_TIMEOUT, \ DEFAULT_NAME_URI_CACHE_TIMETOLIVE, HMC_LOGGER_NAME, \ HTML_REASON_WEB_SERVICES_DISABLED, HTML_REASON_OTHER, \ DEFAULT_HMC_PORT __all__ = ['Session', 'Job', 'RetryTimeoutConfig', 'get_password_interface'] HMC_LOGGER = get_logger(HMC_LOGGER_NAME) _HMC_SCHEME = "https" _STD_HEADERS = { 'Content-type': 'application/json', 'Accept': '*/*' } BLANKED_OUT = '********' # Replacement for blanked out sensitive values def _handle_request_exc(exc, retry_timeout_config): """ Handle a :exc:`request.exceptions.RequestException` exception that was raised. """ if isinstance(exc, requests.exceptions.ConnectTimeout): raise ConnectTimeout(_request_exc_message(exc), exc, retry_timeout_config.connect_timeout, retry_timeout_config.connect_retries) if isinstance(exc, requests.exceptions.ReadTimeout): raise ReadTimeout(_request_exc_message(exc), exc, retry_timeout_config.read_timeout, retry_timeout_config.read_retries) if isinstance(exc, requests.exceptions.RetryError): raise RetriesExceeded(_request_exc_message(exc), exc, retry_timeout_config.connect_retries) raise ConnectionError(_request_exc_message(exc), exc) def _request_exc_message(exc): """ Return a reasonable exception message from a :exc:`request.exceptions.RequestException` exception. The approach is to dig deep to the original reason, if the original exception is present, skipping irrelevant exceptions such as `urllib3.exceptions.MaxRetryError`, and eliminating useless object representations such as the connection pool object in `urllib3.exceptions.NewConnectionError`. Parameters: exc (:exc:`~request.exceptions.RequestException`): Exception Returns: string: A reasonable exception message from the specified exception. """ if exc.args: if isinstance(exc.args[0], Exception): org_exc = exc.args[0] if isinstance(org_exc, urllib3.exceptions.MaxRetryError): reason_exc = org_exc.reason message = str(reason_exc) else: message = str(org_exc.args[0]) else: message = str(exc.args[0]) # Eliminate useless object repr at begin of the message m = re.match(r'^(\(<[^>]+>, \'(.*)\'\)|<[^>]+>: (.*))$', message) if m: message = m.group(2) or m.group(3) else: message = "" return message class RetryTimeoutConfig(object): # pylint: disable=too-few-public-methods """ A configuration setting that specifies verious retry counts and timeout durations. """ def __init__(self, connect_timeout=None, connect_retries=None, read_timeout=None, read_retries=None, max_redirects=None, operation_timeout=None, status_timeout=None, name_uri_cache_timetolive=None): """ For all parameters, `None` means that this object does not specify a value for the parameter, and that a default value should be used (see :ref:`Constants`). All parameters are available as instance attributes. Parameters: connect_timeout (:term:`number`): Connect timeout in seconds. This timeout applies to making a connection at the socket level. The same socket connection is used for sending an HTTP request to the HMC and for receiving its HTTP response. The special value 0 means that no timeout is set. connect_retries (:term:`integer`): Number of retries (after the initial attempt) for connection-related issues. These retries are performed for failed DNS lookups, failed socket connections, and socket connection timeouts. read_timeout (:term:`number`): Read timeout in seconds. This timeout applies to reading at the socket level, when receiving an HTTP response. The special value 0 means that no timeout is set. read_retries (:term:`integer`): Number of retries (after the initial attempt) for read-related issues. These retries are performed for failed socket reads and socket read timeouts. A retry consists of resending the original HTTP request. The zhmcclient restricts these retries to just the HTTP GET method. For other HTTP methods, no retry will be performed. max_redirects (:term:`integer`): Maximum number of HTTP redirects. operation_timeout (:term:`number`): Asynchronous operation timeout in seconds. This timeout applies when waiting for the completion of asynchronous HMC operations. The special value 0 means that no timeout is set. status_timeout (:term:`number`): Resource status timeout in seconds. This timeout applies when waiting for the transition of the status of a resource to a desired status. The special value 0 means that no timeout is set. name_uri_cache_timetolive (:term:`number`): Time to the next automatic invalidation of the Name-URI cache of manager objects, in seconds since the last invalidation. The special value 0 means that no Name-URI cache is maintained (i.e. the caching is disabled). """ self.connect_timeout = connect_timeout self.connect_retries = connect_retries self.read_timeout = read_timeout self.read_retries = read_retries self.max_redirects = max_redirects self.operation_timeout = operation_timeout self.status_timeout = status_timeout self.name_uri_cache_timetolive = name_uri_cache_timetolive # Read retries only for these HTTP methods: self.method_whitelist = {'GET'} _attrs = ('connect_timeout', 'connect_retries', 'read_timeout', 'read_retries', 'max_redirects', 'operation_timeout', 'status_timeout', 'name_uri_cache_timetolive', 'method_whitelist') def override_with(self, override_config): """ Return a new configuration object that represents the configuration from this configuration object acting as a default, and the specified configuration object overriding that default. Parameters: override_config (:class:`~zhmcclient.RetryTimeoutConfig`): The configuration object overriding the defaults defined in this configuration object. Returns: :class:`~zhmcclient.RetryTimeoutConfig`: A new configuration object representing this configuration object, overridden by the specified configuration object. """ ret = RetryTimeoutConfig() for attr in RetryTimeoutConfig._attrs: value = getattr(self, attr) if override_config and getattr(override_config, attr) is not None: value = getattr(override_config, attr) setattr(ret, attr, value) return ret def get_password_interface(host, userid): """ Interface to the password retrieval function that is invoked by :class:`~zhmcclient.Session` if no password is provided. Parameters: host (string): Hostname or IP address of the HMC userid (string): Userid on the HMC Returns: string: Password of the userid on the HMC """ raise NotImplementedError def _headers_for_logging(headers): """ Return the input headers dict with blanked out values for any headers that carry sensitive information, so that it can be logged or displayed. The headers argument is not modified; if it needs to be changed, a copy is made that is changed. """ if headers and 'X-API-Session' in headers: headers = headers.copy() headers['X-API-Session'] = BLANKED_OUT return headers class Session(object): """ A session to the HMC, optionally in context of an HMC user. The session supports operations that require to be authenticated, as well as operations that don't (e.g. obtaining the API version). The session can keep statistics about the elapsed time for issuing HTTP requests against the HMC API. Instance variable :attr:`~zhmcclient.Session.time_stats_keeper` is used to enable/disable the measurements, and to print the statistics. """ default_rt_config = RetryTimeoutConfig( connect_timeout=DEFAULT_CONNECT_TIMEOUT, connect_retries=DEFAULT_CONNECT_RETRIES, read_timeout=DEFAULT_READ_TIMEOUT, read_retries=DEFAULT_READ_RETRIES, max_redirects=DEFAULT_MAX_REDIRECTS, operation_timeout=DEFAULT_OPERATION_TIMEOUT, status_timeout=DEFAULT_STATUS_TIMEOUT, name_uri_cache_timetolive=DEFAULT_NAME_URI_CACHE_TIMETOLIVE, ) def __init__(self, host, userid=None, password=None, session_id=None, get_password=None, retry_timeout_config=None, port=DEFAULT_HMC_PORT, verify_cert=True): # pylint: disable=line-too-long """ Creating a session object will not immediately cause a logon to be attempted; the logon is deferred until needed. There are several alternatives for specifying the authentication related parameters: * `userid`/`password` only: The session is initially in a logged-off state and subsequent operations that require logon will use the specified userid and password to automatically log on. The returned session-id will be stored in this session object. Subsequent operations that require logon will use that session-id. Once the HMC expires that session-id, subsequent operations that require logon will cause a re-logon with the specified userid and password. * `userid`/`password` and `session_id`: The specified session-id will be stored in this session object, so that the session is initially in a logged-on state. Subsequent operations that require logon will use that session-id. Once the HMC expires that session-id, subsequent operations that require logon will cause a re-logon with the specified userid/password. * `session_id` only: The specified session-id will be stored in this session object, so that the session is initially in a logged-on state. Subsequent operations that require logon will use the stored session-id. Once the HMC expires the session-id, subsequent operations that require logon will cause an :exc:`~zhmcclient.ServerAuthError` to be raised (because userid/password have not been specified, so an automatic re-logon is not possible). * Neither `userid`/`password` nor `session_id`: Only operations that do not require logon, are possible. Parameters: host (:term:`string`): HMC host. For valid formats, see the :attr:`~zhmcclient.Session.host` property. Must not be `None`. userid (:term:`string`): Userid of the HMC user to be used, or `None`. password (:term:`string`): Password of the HMC user to be used, if `userid` was specified. session_id (:term:`string`): Session-id to be used for this session, or `None`. get_password (:term:`callable`): A password retrieval function, or `None`. If provided, this function will be called if a password is needed but not provided. This mechanism can be used for example by command line interfaces for prompting for the password. The password retrieval function must follow the interface defined in :func:`~zhmcclient.get_password_interface`. retry_timeout_config (:class:`~zhmcclient.RetryTimeoutConfig`): The retry/timeout configuration for this session for use by any of its HMC operations, overriding any defaults. `None` for an attribute in that configuration object means that the default value will be used for that attribute. `None` for the entire `retry_timeout_config` parameter means that a default configuration will be used with the default values for all of its attributes. See :ref:`Constants` for the default values. port (:term:`integer`): HMC TCP port. Defaults to :attr:`~zhmcclient._constants.DEFAULT_HMC_PORT`. For details, see the :attr:`~zhmcclient.Session.port` property. verify_cert (bool or :term:`string`): Controls whether and how the client verifies the server certificate presented by the HMC during SSL/TLS handshake: * `False`: Do not verify the HMC certificate. Not verifying the HMC certificate means the zhmcclient will not detect hostname mismatches, expired certificates, revoked certificates, or otherwise invalid certificates. Since this mode makes the connection vulnerable to man-in-the-middle attacks, it is insecure and should not be used in production environments. * `True`: Verify the HMC certificate using the CA certificates from the first of these locations: - The file or directory in the REQUESTS_CA_BUNDLE env.var, if set - The file or directory in the CURL_CA_BUNDLE env.var, if set - The Python 'certifi' package (which contains the `Mozilla Included CA Certificate List `_). * :term:`string`: Path name of a certificate file or directory. Verify the HMC certificate using the CA certificates in that file or directory. For details, see the :ref:`HMC certificate` section. *Added in version 0.31* """ # noqa: E501 # pylint: enable=line-too-long self._host = host self._port = port self._userid = userid self._password = password self._verify_cert = verify_cert self._get_password = get_password self._retry_timeout_config = self.default_rt_config.override_with( retry_timeout_config) self._base_url = "{scheme}://{host}:{port}".format( scheme=_HMC_SCHEME, host=self._host, port=self._port) self._headers = copy(_STD_HEADERS) # dict with standard HTTP headers if session_id is not None: # Create a logged-on state (same state as in _do_logon()) self._session_id = session_id self._session = self._new_session(self.retry_timeout_config) self._headers['X-API-Session'] = session_id else: # Create a logged-off state (same state as in _do_logoff()) self._session_id = None self._session = None self._time_stats_keeper = TimeStatsKeeper() def __repr__(self): """ Return a string with the state of this session, for debug purposes. """ headers = _headers_for_logging(self.headers) ret = ( "{classname} at 0x{id:08x} (\n" " _host={s._host!r},\n" " _userid={s._userid!r},\n" " _password='...',\n" " _verify_cert={s._verify_cert!r},\n" " _get_password={s._get_password!r},\n" " _retry_timeout_config={s._retry_timeout_config!r},\n" " _base_url={s._base_url!r},\n" " _headers={headers!r},\n" " _session_id={blanked_out!r},\n" " _session={s._session!r}\n" ")". format(classname=self.__class__.__name__, id=id(self), s=self, headers=headers, blanked_out=BLANKED_OUT)) return ret @property def host(self): """ :term:`string`: HMC host, in one of the following formats: * a short or fully qualified DNS hostname * a literal (= dotted) IPv4 address * a literal IPv6 address, formatted as defined in :term:`RFC3986` with the extensions for zone identifiers as defined in :term:`RFC6874`, supporting ``-`` (minus) for the delimiter before the zone ID string, as an additional choice to ``%25`` """ return self._host @property def port(self): """ :term:`integer`: HMC TCP port to be used. """ return self._port @property def userid(self): """ :term:`string`: Userid of the HMC user to be used. If `None`, only operations that do not require authentication, can be performed. """ return self._userid @property def verify_cert(self): """ bool or :term:`string`: Controls whether and how the client verifies server certificate presented by the HMC during SSL/TLS handshake. For details, see the same-named init parameter. """ return self._verify_cert @property def get_password(self): """ The password retrieval function, or `None`. The password retrieval function must follow the interface defined in :func:`~zhmcclient.get_password_interface`. """ return self._get_password @property def retry_timeout_config(self): """ :class:`~zhmcclient.RetryTimeoutConfig`: The effective retry/timeout configuration for this session for use by any of its HMC operations, taking into account the defaults and the session-specific overrides. """ return self._retry_timeout_config @property def base_url(self): """ :term:`string`: Base URL of the HMC in this session. Example: .. code-block:: text https://myhmc.acme.com:6794 """ return self._base_url @property def headers(self): """ :term:`header dict`: HTTP headers to be used in each request. Initially, this is the following set of headers: .. code-block:: text Content-type: application/json Accept: */* When the session is logged on to the HMC, the session token is added to these headers: .. code-block:: text X-API-Session: ... """ return self._headers @property def time_stats_keeper(self): """ The time statistics keeper (for a usage example, see section :ref:`Time Statistics`). """ return self._time_stats_keeper @property def session_id(self): """ :term:`string`: Session ID for this session, returned by the HMC. """ return self._session_id @property def session(self): """ :term:`string`: :class:`requests.Session` object for this session. """ return self._session @logged_api_call def logon(self, verify=False): """ Make sure the session is logged on to the HMC. By default, this method checks whether there is a session-id set and considers that sufficient for determining that the session is logged on. The `verify` parameter can be used to verify the validity of a session-id that is already set, by issuing a dummy operation ("Get Console Properties") to the HMC. After successful logon to the HMC, the following is stored in this session object for reuse in subsequent operations: * the HMC session-id, in order to avoid extra userid authentications, * a :class:`requests.Session` object, in order to enable connection pooling. Connection pooling avoids repetitive SSL/TLS handshakes. Parameters: verify (bool): If a session-id is already set, verify its validity. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.ClientAuthError` :exc:`~zhmcclient.ServerAuthError` :exc:`~zhmcclient.ConnectionError` """ if not self.is_logon(verify): self._do_logon() @logged_api_call def logoff(self, verify=False): """ Make sure the session is logged off from the HMC. After successful logoff, the HMC session-id and :class:`requests.Session` object stored in this object are reset. Parameters: verify (bool): If a session-id is already set, verify its validity. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.ServerAuthError` :exc:`~zhmcclient.ConnectionError` """ if self.is_logon(verify): self._do_logoff() @logged_api_call def is_logon(self, verify=False): """ Return a boolean indicating whether the session is currently logged on to the HMC. By default, this method checks whether there is a session-id set and considers that sufficient for determining that the session is logged on. The `verify` parameter can be used to verify the validity of a session-id that is already set, by issuing a dummy operation ("Get Console Properties") to the HMC. Parameters: verify (bool): If a session-id is already set, verify its validity. """ if self._session_id is None: return False if verify: try: self.get('/api/console', logon_required=True) except ServerAuthError: return False return True def _do_logon(self): """ Log on, unconditionally. This can be used to re-logon. This requires credentials to be provided. Raises: :exc:`~zhmcclient.ClientAuthError` :exc:`~zhmcclient.ServerAuthError` :exc:`~zhmcclient.ConnectionError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.HTTPError` """ if self._userid is None: raise ClientAuthError("Userid is not provided.") if self._password is None: if self._get_password: self._password = self._get_password(self._host, self._userid) else: raise ClientAuthError("Password is not provided.") logon_uri = '/api/sessions' logon_body = { 'userid': self._userid, 'password': self._password } self._headers.pop('X-API-Session', None) # Just in case self._session = self._new_session(self.retry_timeout_config) logon_res = self.post(logon_uri, logon_body, logon_required=False) self._session_id = logon_res['api-session'] self._headers['X-API-Session'] = self._session_id @staticmethod def _new_session(retry_timeout_config): """ Return a new `requests.Session` object. """ retry = urllib3.Retry( total=retry_timeout_config.connect_retries, connect=retry_timeout_config.connect_retries, read=retry_timeout_config.read_retries, method_whitelist=retry_timeout_config.method_whitelist, redirect=retry_timeout_config.max_redirects) session = requests.Session() session.mount('https://', requests.adapters.HTTPAdapter(max_retries=retry)) session.mount('http://', requests.adapters.HTTPAdapter(max_retries=retry)) return session def _do_logoff(self): """ Log off, unconditionally. Raises: :exc:`~zhmcclient.ServerAuthError` :exc:`~zhmcclient.ConnectionError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.HTTPError` """ session_uri = '/api/sessions/this-session' self.delete(session_uri, logon_required=False) self._session_id = None self._session = None self._headers.pop('X-API-Session', None) @staticmethod def _log_http_request(method, url, headers=None, content=None): """ Log the HTTP request of an HMC REST API call, at the debug level. Parameters: method (:term:`string`): HTTP method name in upper case, e.g. 'GET' url (:term:`string`): HTTP URL (base URL and operation URI) headers (iterable): HTTP headers used for the request content (:term:`string`): HTTP body (aka content) used for the request (byte string or unicode string) """ content_msg = None if content is not None: if isinstance(content, six.binary_type): content = content.decode('utf-8') content_len = len(content) # may change after JSON conversion try: content_dict = json2dict(content) except ValueError: # If the content is not JSON, we assume it does not contain # structured data such as a password or session IDs. pass else: if 'password' in content_dict: content_dict['password'] = BLANKED_OUT content = dict2json(content_dict) trunc = 30000 if content_len > trunc: content_label = 'content(first {} B of {} B)'. \ format(trunc, content_len) content_msg = content[0:trunc] + '...(truncated)' else: content_label = 'content({} B)'.format(content_len) content_msg = content else: content_label = 'content' content_msg = content HMC_LOGGER.debug("Request: %s %s, headers: %r, %s: %r", method, url, _headers_for_logging(headers), content_label, content_msg) @staticmethod def _log_http_response(method, url, status, headers=None, content=None): """ Log the HTTP response of an HMC REST API call, at the debug level. Parameters: method (:term:`string`): HTTP method name in upper case, e.g. 'GET' url (:term:`string`): HTTP URL (base URL and operation URI) status (integer): HTTP status code headers (iterable): HTTP headers returned in the response content (:term:`string`): HTTP body (aka content) returned in the response (byte string or unicode string) """ if content is not None: if isinstance(content, six.binary_type): content = content.decode('utf-8') content_len = len(content) # may change after JSON conversion try: content_dict = json2dict(content) except ValueError: # If the content is not JSON (e.g. response from metrics # context retrieval), we assume it does not contain structured # data such as a password or session IDs. pass else: if 'request-headers' in content_dict: headers_dict = content_dict['request-headers'] if 'x-api-session' in headers_dict: headers_dict['x-api-session'] = BLANKED_OUT if 'api-session' in content_dict: content_dict['api-session'] = BLANKED_OUT if 'session-credential' in content_dict: content_dict['session-credential'] = BLANKED_OUT content = dict2json(content_dict) if status >= 400: content_label = 'content' content_msg = content else: trunc = 30000 if content_len > trunc: content_label = 'content(first {} B of {} B)'. \ format(trunc, content_len) content_msg = content[0:trunc] + '...(truncated)' else: content_label = 'content({} B)'.format(len(content)) content_msg = content else: content_label = 'content' content_msg = content HMC_LOGGER.debug("Respons: %s %s, status: %s, headers: %r, %s: %r", method, url, status, _headers_for_logging(headers), content_label, content_msg) @logged_api_call def get(self, uri, logon_required=True): """ Perform the HTTP GET method against the resource identified by a URI. A set of standard HTTP headers is automatically part of the request. If the HMC session token is expired, this method re-logs on and retries the operation. Parameters: uri (:term:`string`): Relative URI path of the resource, e.g. "/api/session". This URI is relative to the base URL of the session (see the :attr:`~zhmcclient.Session.base_url` property). Must not be `None`. logon_required (bool): Boolean indicating whether the operation requires that the session is logged on to the HMC. For example, the API version retrieval operation does not require that. Returns: :term:`json object` with the operation result. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.ClientAuthError` :exc:`~zhmcclient.ServerAuthError` :exc:`~zhmcclient.ConnectionError` """ if logon_required: self.logon() url = self.base_url + uri self._log_http_request('GET', url, headers=self.headers) stats = self.time_stats_keeper.get_stats('get ' + uri) stats.begin() req = self._session or requests req_timeout = (self.retry_timeout_config.connect_timeout, self.retry_timeout_config.read_timeout) try: result = req.get(url, headers=self.headers, verify=self.verify_cert, timeout=req_timeout) # Note: The requests method may raise OSError/IOError in case of # HMC certificate validation issues (e.g. incorrect cert path) except (requests.exceptions.RequestException, IOError, OSError) as exc: _handle_request_exc(exc, self.retry_timeout_config) finally: stats.end() self._log_http_response('GET', url, status=result.status_code, headers=result.headers, content=result.content) if result.status_code == 200: return _result_object(result) if result.status_code == 403: result_object = _result_object(result) reason = result_object.get('reason', None) if reason == 5: # API session token expired: re-logon and retry self._do_logon() return self.get(uri, logon_required) if reason == 1: # Login user's authentication is fine; this is an authorization # issue, so we don't raise ServerAuthError. raise HTTPError(result_object) msg = result_object.get('message', None) raise ServerAuthError("HTTP authentication failed: {}". format(msg), HTTPError(result_object)) result_object = _result_object(result) raise HTTPError(result_object) @logged_api_call def post(self, uri, body=None, logon_required=True, wait_for_completion=False, operation_timeout=None): """ Perform the HTTP POST method against the resource identified by a URI, using a provided request body. A set of standard HTTP headers is automatically part of the request. HMC operations using HTTP POST are either synchronous or asynchronous. Asynchronous operations return the URI of an asynchronously executing job that can be queried for status and result. Examples for synchronous operations: * With no result: "Logon", "Update CPC Properties" * With a result: "Create Partition" Examples for asynchronous operations: * With no result: "Start Partition" The `wait_for_completion` parameter of this method can be used to deal with asynchronous HMC operations in a synchronous way. If executing the operation reveals that the HMC session token is expired, this method re-logs on and retries the operation. The timeout and retry Parameters: uri (:term:`string`): Relative URI path of the resource, e.g. "/api/session". This URI is relative to the base URL of the session (see the :attr:`~zhmcclient.Session.base_url` property). Must not be `None`. body (:term:`json object`): JSON object to be used as the HTTP request body (payload). `None` means the same as an empty dictionary, namely that no HTTP body is included in the request. logon_required (bool): Boolean indicating whether the operation requires that the session is logged on to the HMC. For example, the "Logon" operation does not require that. wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested asynchronous HMC operation. A value of `True` will cause an additional entry in the time statistics to be created that represents the entire asynchronous operation including the waiting for its completion. That time statistics entry will have a URI that is the targeted URI, appended with "+completion". For synchronous HMC operations, this parameter has no effect on the operation execution or on the return value of this method, but it should still be set (or defaulted) to `False` in order to avoid the additional entry in the time statistics. operation_timeout (:term:`number`): Timeout in seconds, when waiting for completion of an asynchronous operation. The special value 0 means that no timeout is set. `None` means that the default async operation timeout of the session is used. For `wait_for_completion=True`, a :exc:`~zhmcclient.OperationTimeout` is raised when the timeout expires. For `wait_for_completion=False`, this parameter has no effect. Returns: : A :term:`json object` or `None` or a :class:`~zhmcclient.Job` object, as follows: * For synchronous HMC operations, and for asynchronous HMC operations with `wait_for_completion=True`: If this method returns, the HMC operation has completed successfully (otherwise, an exception is raised). For asynchronous HMC operations, the associated job has been deleted. The return value is the result of the HMC operation as a :term:`json object`, or `None` if the operation has no result. See the section in the :term:`HMC API` book about the specific HMC operation for a description of the members of the returned JSON object. * For asynchronous HMC operations with `wait_for_completion=False`: If this method returns, the asynchronous execution of the HMC operation has been started successfully as a job on the HMC (if the operation could not be started, an exception is raised). The return value is a :class:`~zhmcclient.Job` object representing the job on the HMC. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.ClientAuthError` :exc:`~zhmcclient.ServerAuthError` :exc:`~zhmcclient.ConnectionError` :exc:`~zhmcclient.OperationTimeout`: The timeout expired while waiting for completion of the asynchronous operation. :exc:`TypeError`: Body has invalid type. """ if logon_required: self.logon() url = self.base_url + uri headers = self.headers.copy() # Standard headers if body is None: data = None elif isinstance(body, dict): data = json.dumps(body) # Content-type is already set in standard headers. elif isinstance(body, six.text_type): data = body.encode('utf-8') headers['Content-type'] = 'application/octet-stream' elif isinstance(body, six.binary_type): data = body headers['Content-type'] = 'application/octet-stream' elif isinstance(body, collections.Iterable): # For example, open files: open(), io.open() data = body headers['Content-type'] = 'application/octet-stream' else: raise TypeError("Body has invalid type: {}".format(type(body))) self._log_http_request('POST', url, headers=headers, content=data) req = self._session or requests req_timeout = (self.retry_timeout_config.connect_timeout, self.retry_timeout_config.read_timeout) if wait_for_completion: stats_total = self.time_stats_keeper.get_stats( 'post ' + uri + '+completion') stats_total.begin() try: stats = self.time_stats_keeper.get_stats('post ' + uri) stats.begin() try: if data is None: result = req.post(url, headers=headers, verify=self.verify_cert, timeout=req_timeout) else: result = req.post(url, data=data, headers=headers, verify=self.verify_cert, timeout=req_timeout) # Note: The requests method may raise OSError/IOError in case of # HMC certificate validation issues (e.g. incorrect cert path) except (requests.exceptions.RequestException, IOError, OSError) \ as exc: _handle_request_exc(exc, self.retry_timeout_config) finally: stats.end() self._log_http_response('POST', url, status=result.status_code, headers=result.headers, content=result.content) if result.status_code in (200, 201): return _result_object(result) if result.status_code == 204: # No content return None if result.status_code == 202: if result.content == '': # Some operations (e.g. "Restart Console", # "Shutdown Console" or "Cancel Job") return 202 # with no response content. return None # This is the most common case to return 202: An # asynchronous job has been started. result_object = _result_object(result) job_uri = result_object['job-uri'] job = Job(self, job_uri, 'POST', uri) if wait_for_completion: return job.wait_for_completion(operation_timeout) return job if result.status_code == 403: result_object = _result_object(result) reason = result_object.get('reason', None) if reason == 5: # API session token expired: re-logon and retry self._do_logon() return self.post(uri, body, logon_required) if reason == 1: # Login user's authentication is fine; this is an # authorization issue, so we don't raise ServerAuthError. raise HTTPError(result_object) msg = result_object.get('message', None) raise ServerAuthError("HTTP authentication failed: {}". format(msg), HTTPError(result_object)) result_object = _result_object(result) raise HTTPError(result_object) finally: if wait_for_completion: stats_total.end() @logged_api_call def delete(self, uri, logon_required=True): """ Perform the HTTP DELETE method against the resource identified by a URI. A set of standard HTTP headers is automatically part of the request. If the HMC session token is expired, this method re-logs on and retries the operation. Parameters: uri (:term:`string`): Relative URI path of the resource, e.g. "/api/session/{session-id}". This URI is relative to the base URL of the session (see the :attr:`~zhmcclient.Session.base_url` property). Must not be `None`. logon_required (bool): Boolean indicating whether the operation requires that the session is logged on to the HMC. For example, for the logoff operation, it does not make sense to first log on. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.ClientAuthError` :exc:`~zhmcclient.ServerAuthError` :exc:`~zhmcclient.ConnectionError` """ if logon_required: self.logon() url = self.base_url + uri self._log_http_request('DELETE', url, headers=self.headers) stats = self.time_stats_keeper.get_stats('delete ' + uri) stats.begin() req = self._session or requests req_timeout = (self.retry_timeout_config.connect_timeout, self.retry_timeout_config.read_timeout) try: result = req.delete(url, headers=self.headers, verify=self.verify_cert, timeout=req_timeout) # Note: The requests method may raise OSError/IOError in case of # HMC certificate validation issues (e.g. incorrect cert path) except (requests.exceptions.RequestException, IOError, OSError) as exc: _handle_request_exc(exc, self.retry_timeout_config) finally: stats.end() self._log_http_response('DELETE', url, status=result.status_code, headers=result.headers, content=result.content) if result.status_code in (200, 204): return if result.status_code == 403: result_object = _result_object(result) reason = result_object.get('reason', None) if reason == 5: # API session token expired: re-logon and retry self._do_logon() self.delete(uri, logon_required) return if reason == 1: # Login user's authentication is fine; this is an authorization # issue, so we don't raise ServerAuthError. raise HTTPError(result_object) msg = result_object.get('message', None) raise ServerAuthError("HTTP authentication failed: {}". format(msg), HTTPError(result_object)) result_object = _result_object(result) raise HTTPError(result_object) @logged_api_call def get_notification_topics(self): """ The 'Get Notification Topics' operation returns a structure that describes the JMS notification topics associated with the API session. Returns: : A list with one item for each notification topic. Each item is a dictionary with the following keys: * ``"topic-type"`` (string): Topic type, e.g. "job-notification". * ``"topic-name"`` (string): Topic name; can be used for subscriptions. * ``"object-uri"`` (string): When topic-type is "os-message-notification", this item is the canonical URI path of the Partition for which this topic exists. This field does not exist for the other topic types. * ``"include-refresh-messages"`` (bool): When the topic-type is "os-message-notification", this item indicates whether refresh operating system messages will be sent on this topic. """ topics_uri = '/api/sessions/operations/get-notification-topics' response = self.get(topics_uri) return response['topics'] class Job(object): """ A job on the HMC that performs an asynchronous HMC operation. This class supports checking the job for completion, and waiting for job completion. """ def __init__(self, session, uri, op_method, op_uri): """ Parameters: session (:class:`~zhmcclient.Session`): Session with the HMC. Must not be `None`. uri (:term:`string`): Canonical URI of the job on the HMC. Must not be `None`. Example: ``"/api/jobs/{job-id}"`` op_method (:term:`string`): Name of the HTTP method of the operation that is executing asynchronously on the HMC. Must not be `None`. Example: ``"POST"`` op_uri (:term:`string`): Canonical URI of the operation that is executing asynchronously on the HMC. Must not be `None`. Example: ``"/api/partitions/{partition-id}/stop"`` """ self._session = session self._uri = uri self._op_method = op_method self._op_uri = op_uri @property def session(self): """ :class:`~zhmcclient.Session`: Session with the HMC. """ return self._session @property def uri(self): """ :term:`string`: Canonical URI of the job on the HMC. Example: ``"/api/jobs/{job-id}"`` """ return self._uri @property def op_method(self): """ :term:`string`: Name of the HTTP method of the operation that is executing asynchronously on the HMC. Example: ``"POST"`` """ return self._op_method @property def op_uri(self): """ :term:`string`: Canonical URI of the operation that is executing asynchronously on the HMC. Example: ``"/api/partitions/{partition-id}/stop"`` """ return self._op_uri @logged_api_call def check_for_completion(self): """ Check once for completion of the job and return completion status and result if it has completed. If the job completed in error, an :exc:`~zhmcclient.HTTPError` exception is raised. Returns: : A tuple (status, result) with: * status (:term:`string`): Completion status of the job, as returned in the ``status`` field of the response body of the "Query Job Status" HMC operation, as follows: * ``"complete"``: Job completed (successfully). * any other value: Job is not yet complete. * result (:term:`json object` or `None`): `None` for incomplete jobs. For completed jobs, the result of the original asynchronous operation that was performed by the job, from the ``job-results`` field of the response body of the "Query Job Status" HMC operation. That result is a :term:`json object` as described for the asynchronous operation, or `None` if the operation has no result. Raises: :exc:`~zhmcclient.HTTPError`: The job completed in error, or the job status cannot be retrieved, or the job cannot be deleted. :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.ClientAuthError` :exc:`~zhmcclient.ServerAuthError` :exc:`~zhmcclient.ConnectionError` """ job_result_obj = self.session.get(self.uri) job_status = job_result_obj['status'] if job_status == 'complete': self.session.delete(self.uri) op_status_code = job_result_obj['job-status-code'] if op_status_code in (200, 201): op_result_obj = job_result_obj.get('job-results', None) elif op_status_code == 204: # No content op_result_obj = None else: error_result_obj = job_result_obj.get('job-results', None) if not error_result_obj: message = None elif 'message' in error_result_obj: message = error_result_obj['message'] elif 'error' in error_result_obj: message = error_result_obj['error'] else: message = None error_obj = { 'http-status': op_status_code, 'reason': job_result_obj['job-reason-code'], 'message': message, 'request-method': self.op_method, 'request-uri': self.op_uri, } raise HTTPError(error_obj) else: op_result_obj = None return job_status, op_result_obj @logged_api_call def wait_for_completion(self, operation_timeout=None): """ Wait for completion of the job, then delete the job on the HMC and return the result of the original asynchronous HMC operation, if it completed successfully. If the job completed in error, an :exc:`~zhmcclient.HTTPError` exception is raised. Parameters: operation_timeout (:term:`number`): Timeout in seconds, when waiting for completion of the job. 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, a :exc:`~zhmcclient.OperationTimeout` is raised. This method gives completion of the job priority over strictly achieving the timeout. This may cause a slightly longer duration of the method than prescribed by the timeout. Returns: :term:`json object` or `None`: The result of the original asynchronous operation that was performed by the job, from the ``job-results`` field of the response body of the "Query Job Status" HMC operation. That result is a :term:`json object` as described for the asynchronous operation, or `None` if the operation has no result. Raises: :exc:`~zhmcclient.HTTPError`: The job completed in error, or the job status cannot be retrieved, or the job cannot be deleted. :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.ClientAuthError` :exc:`~zhmcclient.ServerAuthError` :exc:`~zhmcclient.ConnectionError` :exc:`~zhmcclient.OperationTimeout`: The timeout expired while waiting for job completion. """ if operation_timeout is None: operation_timeout = \ self.session.retry_timeout_config.operation_timeout if operation_timeout > 0: start_time = time.time() while True: job_status, op_result_obj = self.check_for_completion() # We give completion of status priority over strictly achieving # the timeout, so we check status first. This may cause a longer # duration of the method than prescribed by the timeout. if job_status == 'complete': return op_result_obj if operation_timeout > 0: current_time = time.time() if current_time > start_time + operation_timeout: raise OperationTimeout( "Waiting for completion of job {} timed out " "(operation timeout: {} s)". format(self.uri, operation_timeout), operation_timeout) time.sleep(1) # Avoid hot spin loop def _text_repr(text, max_len=1000): """ Return the input text as a Python string representation (i.e. using repr()) that is limited to a maximum length. """ if text is None: text_repr = 'None' elif len(text) > max_len: text_repr = repr(text[0:max_len]) + '...' else: text_repr = repr(text) return text_repr def _result_object(result): """ Return the JSON payload in the HTTP response as a Python dict. Parameters: result (requests.Response): HTTP response object. Raises: zhmcclient.ParseError: Error parsing the returned JSON. """ content_type = result.headers.get('content-type', None) if content_type is None or content_type.startswith('application/json'): # This function is only called when there is content expected. # Therefore, a response without content will result in a ParseError. try: return result.json(object_pairs_hook=OrderedDict) except ValueError as exc: new_exc = ParseError( "JSON parse error in HTTP response: {}. " "HTTP request: {} {}. " "Response status {}. " "Response content-type: {!r}. " "Content (max.1000, decoded using {}): {}". format(exc.args[0], result.request.method, result.request.url, result.status_code, content_type, result.encoding, _text_repr(result.text, 1000))) new_exc.__cause__ = None raise new_exc # zhmcclient.ParseError if content_type.startswith('text/html'): # We are in some error situation. The HMC returns HTML content # for some 5xx status codes. We try to deal with it somehow, # but we are not going as far as real HTML parsing. m = re.search(r'charset=([^;,]+)', content_type) if m: encoding = m.group(1) # e.g. RFC "ISO-8859-1" else: encoding = 'utf-8' try: html_uni = result.content.decode(encoding) except LookupError: html_uni = result.content.decode() # We convert to one line to be regexp-friendly. html_oneline = html_uni.replace('\r\n', '\\n').replace('\r', '\\n').\ replace('\n', '\\n') # Check for some well-known errors: if re.search(r'javax\.servlet\.ServletException: ' r'Web Services are not enabled\.', html_oneline): html_title = "Console Configuration Error" html_details = "Web Services API is not enabled on the HMC." html_reason = HTML_REASON_WEB_SERVICES_DISABLED else: m = re.search( r'([^<]*).*' r'

Details:

(.*)(
)?', html_oneline) if m: html_title = m.group(1) # Spend a reasonable effort to make the HTML readable: html_details = m.group(2).replace('

', '\\n').\ replace('
', '\\n').replace('\\n\\n', '\\n').strip() else: html_title = "Console Internal Error" html_details = "Response body: {!r}".format(html_uni) html_reason = HTML_REASON_OTHER message = "{}: {}".format(html_title, html_details) # We create a minimal JSON error object (to the extent we use it # when processing it): result_obj = { 'http-status': result.status_code, 'reason': html_reason, 'message': message, 'request-uri': result.request.url, 'request-method': result.request.method, } return result_obj if content_type.startswith('application/vnd.ibm-z-zmanager-metrics'): content_bytes = result.content assert isinstance(content_bytes, six.binary_type) return content_bytes.decode('utf-8') # as a unicode object raise ParseError( "Unknown content type in HTTP response: {}. " "HTTP request: {} {}. " "Response status {}. " "Content (max.1000, decoded using {}): {}". format(content_type, result.request.method, result.request.url, result.status_code, result.encoding, _text_repr(result.text, 1000))) def json2dict(json_str): """ Convert a JSON string into a dict. Parameters: json_str (string): Unicode or binary string in JSON format. Returns: dict: JSON string converted to a dict. Raises: ValueError: Cannot parse JSON string """ # In Python 3 up to 3.5, json.loads() requires unicode strings. if sys.version_info[0] == 3 and sys.version_info[1] in (4, 5) and \ isinstance(json_str, six.binary_type): json_str = json_str.decode('utf-8') json_dict = json.loads(json_str) # May raise ValueError return json_dict def dict2json(json_dict): """ Convert a dict into a JSON string. Parameters: json_dict (dict): The dict. Returns: unicode string (py3) or byte string (py2): Dict converted to a JSON string. """ json_str = json.dumps(json_dict) return json_str ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_storage_group.py0000644000076500000240000007743300000000000022064 0ustar00maierastaff00000000000000# Copyright 2018 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. """ Starting with the z14-ZR1 and LinuxONE Rockhopper II machine generations, the "dpm-storage-management" firmware feature has been introduced to support a simpler management of FCP and FICON (=ECKD) storage for DPM mode. If machines of these generations are in DPM mode, the feature is always enabled and cannot be disabled. When the "dpm-storage-management" feature is enabled, :term:`storage group` and :term:`storage volume` resources can be defined on the HMC, and can cause fulfillment requests to be sent via email to storage administrators. Once these requests are satisfied on the actual storage subsystem and possibly SAN switches, the changes are automatically discovered by DPM and reflected in the state of these resources. Thus, the allocation of actual storage volumes on the storage subsystem is not performed by DPM. The sending of email is optional, and if the changes are done by some automation tool, they will also be discovered automatically. That way, the whole process of allocating and attaching volumes can be fully automated, if so desired. The top level resource objects are :term:`storage groups `. Storage groups are defined globally at the HMC level, and are associated with a CPC. They can only be associated with one CPC at a time. In the zhmcclient, the :class:`~zhmcclient.StorageGroup` objects are accessible via the :attr:`~zhmcclient.ConsoleManager.storage_groups` property. Storage groups are a grouping mechanism for :term:`storage volumes `. An FCP-type storage group can contain only FCP type storage volumes, and a FICON-type storage group can contain only FICON/ECKD type storage volumes. Attachment and detachment of volumes to a partition happens at the level of storage groups, and always applies to all volumes defined in the storage group. The storage-related z/Architecture devices that are visible to a partition are different for the two storage architectures: For FCP, the virtualized HBA is visible as a device, and the storage volumes (LUNs) are not represented as devices. For FICON, each ECKD volume is visible as a device, but the virtualized FICON adapter port is not represented as a device. When the "dpm-storage-management" feature is enabled, each storage-related z/Architecture device that is visible to a partition is represented as a :term:`virtual storage resource` object. The virtual storage resource objects are instantiated automatically when a storage group is attached to a partition. The :term:`HBA` resource objects known from DPM mode before the introduction of the "dpm-storage-management" feature are not exposed anymore (their equivalent are now the :term:`virtual storage resource` objects). Single storage volumes cannot be attached to partitions, only entire storage groups can be. In fact, storage volume objects do not even exist outside the scope of storage groups. A particular storage volume can be contained in only one storage group. Storage groups can be listed, created, deleted, and updated, and their storage volumes can also be listed, created, deleted, and updated. Storage groups can be attached to zero or more partitions. Attachment to multiple partitions at the same time is possible for storage groups that are defined to be shareable. In case of multiple attachments of a storage group, it contains the storage volume objects only once for all attachments, but the virtual storage resource objects are instantiated separately for each attachment. Storage groups can only be associated with CPCs that have the "dpm-storage-management" feature enabled. """ from __future__ import absolute_import import copy import re from ._manager import BaseManager from ._resource import BaseResource from ._storage_volume import StorageVolumeManager from ._virtual_storage_resource import VirtualStorageResourceManager from ._logging import logged_api_call from ._utils import append_query_parms __all__ = ['StorageGroupManager', 'StorageGroup'] class StorageGroupManager(BaseManager): """ Manager providing access to the :term:`storage groups ` 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.storage_groups` of a :class:`~zhmcclient.Console` object. """ 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', 'type', 'fulfillment-state', ] super(StorageGroupManager, self).__init__( resource_class=StorageGroup, class_name='storage-group', session=console.manager.session, parent=console, base_uri='/api/storage-groups', 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 storage groups defined in the HMC. Storage groups for which the authenticated user does not have object-access permission are not included. Authorization requirements: * Object-access permission to any storage groups to be included in the result. Parameters: full_properties (bool): Controls that the full set of resource properties for each returned storage volume is being retrieved, vs. only the following short set: "object-uri", "cpc-uri", "name", "fulfillment-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.StorageGroup` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] if filter_args is None: filter_args = {} resource_obj = self._try_optimized_lookup(filter_args) if resource_obj: resource_obj_list.append(resource_obj) # It already has full properties else: query_parms, client_filters = self._divide_filter_args(filter_args) uri = '{}{}'.format(self._base_uri, query_parms) result = self.session.get(uri) if result: props_list = result['storage-groups'] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties=None, template=None): """ Create and configure a storage group either from input properties or from a :term:`storage group template`. The input properties may specify initial storage volumes for the new storage group via the `storage-volumes` property. Additional storage volumes can be added with :meth:`~zhmcclient.StorageGroup.update_properties`. A storage group template can also specify initial storage volumes for the new storage group. For details, see :class:`~zhmcclient.StorageGroupTemplate`. The new storage group will be associated with the CPC identified by the `cpc-uri` input property. Authorization requirements: * Object-access permission to the CPC that will be associated with the new storage group. * Task permission to the "Configure Storage - System Programmer" task. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create Storage Group' in the :term:`HMC API` book. The 'cpc-uri' property identifies the CPC to which the new storage group will be associated, and is required to be specified in this parameter. The 'template-uri' property is not allowed. If you want to create a storage group from a :term:`storage group template`, specify the `template` parameter. The `properties` and `template` parameters are mutually exclusive. template (:class:`~zhmcclient.StorageGroupTemplate`): :term:`storage group template` defining the initial property values for the storage group, including its initial storage volumes. Returns: :class:`~zhmcclient.StorageGroup`: The resource object for the new storage group. The object will have its 'object-uri' property set as returned by the HMC, and will also have the input properties set. Raises: :exc:`ValueError` - Property 'template-uri' is not permitted :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ if properties is None: properties = {} if 'template-uri' in properties: raise ValueError( "Property 'template-uri' is not permitted - use the " "'template' parameter to create storage groups from " "templates.") if template is not None: properties['template-uri'] = template.uri result = self.session.post(self._base_uri, body=properties) # There should not be overlaps, but just in case there are, the # returned props should overwrite the input props: props = copy.deepcopy(properties) props.update(result) name = props.get(self._name_prop, None) uri = props[self._uri_prop] storage_group = StorageGroup(self, uri, name, props) self._name_uri_cache.update(name, uri) return storage_group class StorageGroup(BaseResource): """ Representation of a :term:`storage group`. 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.StorageGroupManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.StorageGroupManager`): # 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, StorageGroupManager), \ "StorageGroup init: Expected manager type %s, got %s" % \ (StorageGroupManager, type(manager)) super(StorageGroup, self).__init__(manager, uri, name, properties) # The manager objects for child resources (with lazy initialization): self._storage_volumes = None self._virtual_storage_resources = None self._cpc = None @property def storage_volumes(self): """ :class:`~zhmcclient.StorageVolumeManager`: Access to the :term:`storage volumes ` in this storage group. """ # We do here some lazy loading. if not self._storage_volumes: self._storage_volumes = StorageVolumeManager(self) return self._storage_volumes @property def virtual_storage_resources(self): """ :class:`~zhmcclient.VirtualStorageResourceManager`: Access to the :term:`virtual storage resources ` in this storage group. """ # We do here some lazy loading. if not self._virtual_storage_resources: self._virtual_storage_resources = \ VirtualStorageResourceManager(self) return self._virtual_storage_resources @property def cpc(self): """ :class:`~zhmcclient.Cpc`: The :term:`CPC` to which this storage group 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 storage group is currently attached, optionally filtered by partition name and status. Authorization requirements: * Object-access permission to this storage group. * Task permission to the "Configure Storage - System Programmer" task. 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 whivch this storage group is currently attached, with a minimal set of properties ('object-id', 'name', 'status'). Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ query_parms = [] if name is not None: append_query_parms(query_parms, 'name', name) if status is not None: append_query_parms(query_parms, 'status', status) query_parms_str = '&'.join(query_parms) if query_parms_str: query_parms_str = '?{}'.format(query_parms_str) uri = '{}/operations/get-partitions{}'.format( self.uri, query_parms_str) sg_cpc = self.cpc part_mgr = sg_cpc.partitions result = self.manager.session.get(uri) props_list = result['partitions'] part_list = [] for props in props_list: part = part_mgr.resource_object(props['object-uri'], props) part_list.append(part) return part_list @logged_api_call def delete(self, email_to_addresses=None, email_cc_addresses=None, email_insert=None): """ Delete this storage group and its storage volume resources on the HMC, and optionally send emails to storage administrators requesting deletion of the storage volumes on the storage subsystem and cleanup of any resources related to the storage group (e.g. zones on a SAN switch, or host objects on a storage subsystem). Authorization requirements: * Object-access permission to this storage group. * Task permission to the "Configure Storage - System Programmer" task. Parameters: email_to_addresses (:term:`iterable` of :term:`string`): Email addresses of one or more storage administrator to be notified. If `None` or empty, no email will be sent. email_cc_addresses (:term:`iterable` of :term:`string`): Email addresses of one or more storage administrator to be copied on the notification email. If `None` or empty, nobody will be copied on the email. Must be `None` or empty if `email_to_addresses` is `None` or empty. email_insert (:term:`string`): Additional text to be inserted in the notification email. The text can include HTML formatting tags. If `None`, no additional text will be inserted. Must be `None` or empty if `email_to_addresses` is `None` or empty. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` :exc:`ValueError`: Incorrect input arguments """ body = {} if email_to_addresses: body['email-to-addresses'] = email_to_addresses if email_cc_addresses: body['email-cc-addresses'] = email_cc_addresses if email_insert: body['email-insert'] = email_insert else: if email_cc_addresses: raise ValueError("email_cc_addresses must not be specified if " "there is no email_to_addresses: %r" % email_cc_addresses) if email_insert: raise ValueError("email_insert must not be specified if " "there is no email_to_addresses: %r" % email_insert) self.manager.session.post( uri=self.uri + '/operations/delete', body=body) # pylint: disable=protected-access self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this storage group. This includes the `storage-volumes` property which contains requests for creations, deletions and updates of :class:`~zhmcclient.StorageVolume` resources of this storage group. As an alternative to this bulk approach for managing storage volumes, each :class:`~zhmcclient.StorageVolume` resource can individually be created, deleted and updated using the respective methods on :attr:`~zhmcclient.StorageGroup.storage_volumes`. Authorization requirements: * Object-access permission to this storage group. * Task permission to the "Configure Storage - System Programmer" task. 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 Storage Group Properties' in section 'Storage Group object' in the :term:`HMC API` book. This includes the email addresses of the storage administrators. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access uri = '{}/operations/modify'.format(self.uri) self.manager.session.post(uri, body=properties) 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._properties.update(copy.deepcopy(properties)) if is_rename: # Add the new name to the cache self.manager._name_uri_cache.update(self.name, self.uri) @logged_api_call def add_candidate_adapter_ports(self, ports): """ Add a list of storage adapter ports to this storage group's candidate adapter ports list. This operation only applies to storage groups of type "fcp". These adapter ports become candidates for use as backing adapters when creating virtual storage resources when the storage group is attached to a partition. The adapter ports should have connectivity to the storage area network (SAN). Candidate adapter ports may only be added before the CPC discovers a working communications path, indicated by a "verified" status on at least one of this storage group's WWPNs. After that point, all adapter ports in the storage group are automatically detected and manually adding them is no longer possible. Because the CPC discovers working communications paths automatically, candidate adapter ports do not need to be added by the user. Any ports that are added, are validated by the CPC during discovery, and may or may not actually be used. Authorization requirements: * Object-access permission to this storage group. * Object-access permission to the adapter of each specified port. * Task permission to the "Configure Storage - System Programmer" task. Parameters: ports (:class:`py:list`): List of :class:`~zhmcclient.Port` objects representing the ports to be added. All specified ports must not already be members of this storage group's candidate adapter ports list. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = { 'adapter-port-uris': [p.uri for p in ports], } self.manager.session.post( self.uri + '/operations/add-candidate-adapter-ports', body=body) @logged_api_call def remove_candidate_adapter_ports(self, ports): """ Remove a list of storage adapter ports from this storage group's candidate adapter ports list. This operation only applies to storage groups of type "fcp". Because the CPC discovers working communications paths automatically, candidate adapter ports do not need to be managed by the user. Any ports that are removed using this function, might actually be added again by the CPC dependent on the results of discovery. Authorization requirements: * Object-access permission to this storage group. * Object-access permission to the adapter of each specified port. * Task permission to the "Configure Storage - System Programmer" task. Parameters: ports (:class:`py:list`): List of :class:`~zhmcclient.Port` objects representing the ports to be removed. All specified ports must currently be members of this storage group's candidate adapter ports list and must not be referenced by any of the group's virtual storage resources. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = { 'adapter-port-uris': [p.uri for p in ports], } self.manager.session.post( self.uri + '/operations/remove-candidate-adapter-ports', body=body) @logged_api_call def list_candidate_adapter_ports(self, full_properties=False): """ Return the current candidate storage adapter port list of this storage group. The result reflects the actual list of ports used by the CPC, including any changes that have been made during discovery. The source for this information is the 'candidate-adapter-port-uris' property of the storage group object. Parameters: full_properties (bool): Controls that the full set of resource properties for each returned candidate storage adapter port is being retrieved, vs. only the following short set: "element-uri", "element-id", "class", "parent". TODO: Verify short list of properties. Returns: List of :class:`~zhmcclient.Port` objects representing the current candidate storage adapter ports of this storage group. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ sg_cpc = self.cpc adapter_mgr = sg_cpc.adapters port_list = [] port_uris = self.get_property('candidate-adapter-port-uris') if port_uris: for port_uri in port_uris: m = re.match(r'^(/api/adapters/[^/]*)/.*', port_uri) adapter_uri = m.group(1) adapter = adapter_mgr.resource_object(adapter_uri) port_mgr = adapter.ports port = port_mgr.resource_object(port_uri) port_list.append(port) if full_properties: port.pull_full_properties() return port_list @logged_api_call def discover_fcp( self, force_restart=False, wait_for_completion=True, operation_timeout=None): """ Perform Logical Unit Number (LUN) discovery for this FCP storage group. The corresponding HMC operation is "Start FCP Storage Discovery". This operation only applies to storage groups of type "fcp". The HMC performs the LUN discovery in an asynchronous job. This method supports waiting for completion of the job and thus the LUN discovery, or returning immediately after the HMC has started the asynchronous job. When the LUN discovery is completed, a connection report will have been created that reflects the results of the discovery. The connection report can be retrieved with :meth:`~zhmcclient.StorageGroup.get_connection_report`. It is recommended to retrieve the connection report and verify that it reports correct volumes. Authorization requirements: * Object-access permission to this storage group. * Task permission to the "Configure Storage - System Programmer" task or to the "Configure Storage - Storage Administrator" task. Parameters: force_restart (bool): Indicates if there is an in-progress discovery operation for the specified storage group, it should be terminated and started again. If `False` or there is no in-progress discovery operation for the specified storage group, a new one is started. wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the asynchronous job performing the LUN discovery. operation_timeout (:term:`number`): Timeout in seconds, for waiting for completion of the asynchronous job performing the LUN discovery. 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: `None` or :class:`~zhmcclient.Job`: If `wait_for_completion` is `True`, returns nothing. If `wait_for_completion` is `False`, returns a :class:`~zhmcclient.Job` object representing the asynchronously executing job on the HMC. 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 = { 'force-restart': force_restart, } result = self.manager.session.post( self.uri + '/operations/start-fcp-storage-discovery', body=body, wait_for_completion=wait_for_completion, operation_timeout=operation_timeout) return result @logged_api_call def get_connection_report(self): # pylint: disable=line-too-long """ Get the latest connection report for this storage group. The corresponding HMC operation is "Get Connection Report". The connection report is updated when LUN discovery is performed. LUN discovery for FCP storage groups is performed automatically based on certain triggers, and regularly every 10 minutes, and can additionally be triggered with :meth:`~zhmcclient.StorageGroup.discover_fcp`. To verify that the correct volumes are reported for an FCP storage group, verify that the property `volumes-configuration-status` has a value of "correct-volumes" for all WWPNs in all FCP subsystems:: report = storage_group.get_connection_report() for subsys in report['fcp-storage-subsystems']: for config in subsys['storage-configurations']: if config['volumes-configuration-status'] != "correct-volumes": # report incorrect volumes Authorization requirements: * Object-access permission to this storage group. * Task permission to the "Configure Storage - System Programmer" task or to the "Configure Storage - Storage Administrator" task. Returns: :term:`json object`: A JSON object with the connection report. For details about the items in the JSON object, see section 'Response body contents' in section 'Get Connection Report' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # noqa: E501 result = self.manager.session.get( self.uri + '/operations/get-connection-report') return result # TODO: Add support for "Fulfill FICON Storage Volumes" operation # TODO: Add support for "Accept Mismatched FCP Storage Volumes" operation # TODO: Add support for "Reject Mismatched FCP Storage Volumes" operation # TODO: Add support for "Get Storage Group Histories" operation ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_storage_group_template.py0000644000076500000240000003061300000000000023744 0ustar00maierastaff00000000000000# Copyright 2019 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 :class:`~zhmcclient.StorageGroupTemplate` object represents a :term:`storage group template` object and can be used to create :term:`storage group` objects with the properties of the template, including an initial set of storage volumes, using the :meth:`zhmcclient.StorageGroupTemplateManager.create` method. Storage group template objects can be created, updated and deleted. Storage group template objects can only be defined in CPCs that are in DPM mode and that have the "dpm-storage-management" feature enabled. """ from __future__ import absolute_import import copy from ._manager import BaseManager from ._resource import BaseResource from ._storage_volume_template import StorageVolumeTemplateManager from ._logging import logged_api_call __all__ = ['StorageGroupTemplateManager', 'StorageGroupTemplate'] class StorageGroupTemplateManager(BaseManager): """ Manager providing access to the :term:`storage group templates ` 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.storage_group_templates` of a :class:`~zhmcclient.Console` object. """ 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', 'type', ] super(StorageGroupTemplateManager, self).__init__( resource_class=StorageGroupTemplate, class_name='storage-template', session=console.manager.session, parent=console, base_uri='/api/storage-templates', 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 storage group templates defined in the HMC. Storage group templates for which the authenticated user does not have object-access permission are not included. Authorization requirements: * Object-access permission to any storage group templates to be included in the result. Parameters: full_properties (bool): Controls that the full set of resource properties for each returned storage group template is being retrieved, vs. only the following short set: "object-uri", "cpc-uri", "name", 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.StorageGroupTemplate` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] if filter_args is None: filter_args = {} resource_obj = self._try_optimized_lookup(filter_args) if resource_obj: resource_obj_list.append(resource_obj) # It already has full properties else: query_parms, client_filters = self._divide_filter_args(filter_args) uri = '{}{}'.format(self._base_uri, query_parms) result = self.session.get(uri) if result: props_list = result['storage-templates'] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties): """ Create a storage group template. The new storage group will be associated with the CPC identified by the `cpc-uri` input property. Authorization requirements: * Object-access permission to the CPC that will be associated with the new storage group template. * Task permission to the "Configure Storage - System Programmer" task. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create Storage Template' in the :term:`HMC API` book. The 'cpc-uri' property identifies the CPC to which the new storage group template will be associated, and is required to be specified in this parameter. Returns: :class:`~zhmcclient.StorageGroupTemplate`: The resource object for the new storage group template. The object will have its 'object-uri' property set as returned by the HMC, and will also have the input properties set. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ if properties is None: properties = {} result = self.session.post(self._base_uri, body=properties) # There should not be overlaps, but just in case there are, the # returned props should overwrite the input props: props = copy.deepcopy(properties) props.update(result) name = props.get(self._name_prop, None) uri = props[self._uri_prop] storage_group_template = StorageGroupTemplate(self, uri, name, props) self._name_uri_cache.update(name, uri) return storage_group_template class StorageGroupTemplate(BaseResource): """ Representation of a :term:`storage group template`. 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.StorageGroupTemplateManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.StorageGroupTemplateManager`): # 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, StorageGroupTemplateManager), \ "StorageGroupTemplate init: Expected manager type %s, got %s" % \ (StorageGroupTemplateManager, type(manager)) super(StorageGroupTemplate, self).__init__( manager, uri, name, properties) # The manager objects for child resources (with lazy initialization): self._storage_volume_templates = None self._cpc = None @property def storage_volume_templates(self): """ :class:`~zhmcclient.StorageVolumeManager`: Access to the :term:`storage volumes ` in this storage group. """ # We do here some lazy loading. if not self._storage_volume_templates: self._storage_volume_templates = StorageVolumeTemplateManager(self) return self._storage_volume_templates @property def cpc(self): """ :class:`~zhmcclient.Cpc`: The :term:`CPC` to which this storage group template 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 delete(self): """ Delete this storage group template and its storage volume template resources on the HMC. Storage groups and their volumes that have been created from the template that is deleted, are not affected. Authorization requirements: * Object-access permission to this storage group template. * Task permission to the "Configure Storage - System Programmer" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.delete(uri=self.uri) self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this storage group template. This includes the `storage-template-volumes` property which contains requests for creations, deletions and updates of :class:`~zhmcclient.StorageVolumeTemplate` resources of this storage group template. As an alternative to this bulk approach for managing storage volume templates, each :class:`~zhmcclient.StorageVolumeTemplate` resource can individually be created, deleted and updated using the respective methods on :attr:`~zhmcclient.StorageGroupTemplate.storage_volume_templates`. Authorization requirements: * Object-access permission to this storage group template. * Task permission to the "Configure Storage - System Programmer" task. 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 Storage Template Properties' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access uri = '{}/operations/modify'.format(self.uri) self.manager.session.post(uri, body=properties) 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._properties.update(copy.deepcopy(properties)) if is_rename: # Add the new name to the cache self.manager._name_uri_cache.update(self.name, self.uri) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_storage_volume.py0000644000076500000240000006025400000000000022230 0ustar00maierastaff00000000000000# Copyright 2018 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 :class:`~zhmcclient.StorageVolume` object represents an FCP or FICON (ECKD) :term:`storage volume` that is defined in a :term:`storage group`. Storage volume objects can be created, but what is created is the definition of a storage volume in the HMC and CPC. This does not include the act of actually allocating the volume on a storage subsystem. That is performed by a storage administrator who :term:`fulfills ` the volumes. In order to represent that, storage volume objects have a fulfillment state that is available in their 'fulfillment-state' property. When a storage group is attached to a :term:`partition`, the group's storage volumes are attached to the partition and any :term:`virtual storage resource` objects are instantiated. Storage volumes are contained in :term:`storage groups `. You can create as many storage volumes as you want in a storage group. Storage groups and storage volumes only can be defined in CPCs that are in DPM mode and that have the "dpm-storage-management" feature enabled. """ from __future__ import absolute_import import re import copy # from requests.utils import quote from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['StorageVolumeManager', 'StorageVolume'] class StorageVolumeManager(BaseManager): """ Manager providing access to the :term:`storage volumes ` in a particular :term:`storage group`. 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 of a :class:`~zhmcclient.StorageGroup` object: * :attr:`~zhmcclient.StorageGroup.storage_volumes` """ def __init__(self, storage_group): # This function should not go into the docs. # Parameters: # storage_group (:class:`~zhmcclient.StorageGroup`): # Storage group 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 = [ 'name', 'fulfillment-state', 'maximum-size', 'minimum-size', 'usage', ] super(StorageVolumeManager, self).__init__( resource_class=StorageVolume, class_name='storage_volume', session=storage_group.manager.session, parent=storage_group, base_uri='{}/storage-volumes'.format(storage_group.uri), oid_prop='element-id', uri_prop='element-uri', name_prop='name', query_props=query_props) @property def storage_group(self): """ :class:`~zhmcclient.StorageGroup`: :term:`Storage group` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the storage volumes in this storage group. Authorization requirements: * Object-access permission to this storage group. Parameters: full_properties (bool): Controls that the full set of resource properties for each returned storage volume is being retrieved, vs. only the following short set: "element-uri", "name", "fulfillment-state", "size", and "usage". 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.StorageVolume` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] resource_obj = self._try_optimized_lookup(filter_args) if resource_obj: resource_obj_list.append(resource_obj) # It already has full properties else: query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'storage-volumes' uri = '{}/{}{}'.format(self.storage_group.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties, email_to_addresses=None, email_cc_addresses=None, email_insert=None): """ Create a :term:`storage volume` in this storage group on the HMC, and optionally send emails to storage administrators requesting creation of the storage volume on the storage subsystem and setup of any resources related to the storage volume (e.g. LUN mask definition on the storage subsystem). This method performs the "Modify Storage Group Properties" operation, requesting creation of the volume. Authorization requirements: * Object-access permission to this storage group. * Task permission to the "Configure Storage - System Programmer" task. Parameters: properties (dict): Initial property values for the new volume. Allowable properties are the fields defined in the "storage-volume-request-info" nested object described for operation "Modify Storage Group Properties" in the :term:`HMC API` book. The valid fields are those for the "create" operation. The `operation` field must not be provided; it is set automatically to the value "create". The properties provided in this parameter will be copied and then amended with the `operation="create"` field, and then used as a single array item for the `storage-volumes` field in the request body of the "Modify Storage Group Properties" operation. Note that for storage volumes, the HMC does auto-generate a value for the "name" property, but that auto-generated name is not unique within the parent storage group. If you depend on a unique name, you need to specify a "name" property accordingly. email_to_addresses (:term:`iterable` of :term:`string`): Email addresses of one or more storage administrator to be notified. If `None` or empty, no email will be sent. email_cc_addresses (:term:`iterable` of :term:`string`): Email addresses of one or more storage administrator to be copied on the notification email. If `None` or empty, nobody will be copied on the email. Must be `None` or empty if `email_to_addresses` is `None` or empty. email_insert (:term:`string`): Additional text to be inserted in the notification email. The text can include HTML formatting tags. If `None`, no additional text will be inserted. Must be `None` or empty if `email_to_addresses` is `None` or empty. Returns: StorageVolume: The resource object for the new storage volume. The object will have the following properties set: - 'element-uri' as returned by the HMC - 'element-id' as determined from the 'element-uri' property - 'class' and 'parent' - additional properties as specified in the input properties Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` Example:: stovol1 = fcp_stogrp.storage_volumes.create( properties=dict( name='vol1', size=30, # GiB )) """ volreq_obj = copy.deepcopy(properties) volreq_obj['operation'] = 'create' body = { 'storage-volumes': [volreq_obj], } if email_to_addresses: body['email-to-addresses'] = email_to_addresses if email_cc_addresses: body['email-cc-addresses'] = email_cc_addresses if email_insert: body['email-insert'] = email_insert else: if email_cc_addresses: raise ValueError("email_cc_addresses must not be specified if " "there is no email_to_addresses: %r" % email_cc_addresses) if email_insert: raise ValueError("email_insert must not be specified if " "there is no email_to_addresses: %r" % email_insert) result = self.session.post( self.storage_group.uri + '/operations/modify', body=body) uri = result['element-uris'][0] storage_volume = self.resource_object(uri, properties) # The name is not guaranteed to be unique, so we don't maintain # a name-to-uri cache for storage volumes. return storage_volume class StorageVolume(BaseResource): """ Representation of a :term:`storage volume`. 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.StorageVolumeManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.StorageVolumeManager`): # 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, StorageVolumeManager), \ "StorageVolume init: Expected manager type %s, got %s" % \ (StorageVolumeManager, type(manager)) super(StorageVolume, self).__init__(manager, uri, name, properties) @property def oid(self): """ :term `unicode string`: The object ID of this storage volume. The object ID is unique within the parent storage group. Note that for storage volumes, the 'name' property is not unique and therefore cannot be used to identify a storage volume. Therefore, storage volumes provide easy access to the object ID, as a means to identify the storage volume in CLIs and other string-based tooling. """ m = re.match(r'^/api/storage-groups/[^/]*/storage-volumes/([^/]*)$', self.uri) oid = m.group(1) return oid @logged_api_call def delete(self, email_to_addresses=None, email_cc_addresses=None, email_insert=None): """ Delete this storage volume on the HMC, and optionally send emails to storage administrators requesting deletion of the storage volume on the storage subsystem and cleanup of any resources related to the storage volume (e.g. LUN mask definitions on a storage subsystem). This method performs the "Modify Storage Group Properties" operation, requesting deletion of the volume. Authorization requirements: * Object-access permission to the storage group owning this storage volume. * Task permission to the "Configure Storage - System Programmer" task. Parameters: email_to_addresses (:term:`iterable` of :term:`string`): Email addresses of one or more storage administrator to be notified. If `None` or empty, no email will be sent. email_cc_addresses (:term:`iterable` of :term:`string`): Email addresses of one or more storage administrator to be copied on the notification email. If `None` or empty, nobody will be copied on the email. Must be `None` or empty if `email_to_addresses` is `None` or empty. email_insert (:term:`string`): Additional text to be inserted in the notification email. The text can include HTML formatting tags. If `None`, no additional text will be inserted. Must be `None` or empty if `email_to_addresses` is `None` or empty. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ volreq_obj = { 'operation': 'delete', 'element-uri': self.uri, } body = { 'storage-volumes': [ volreq_obj ], } if email_to_addresses: body['email-to-addresses'] = email_to_addresses if email_cc_addresses: body['email-cc-addresses'] = email_cc_addresses if email_insert: body['email-insert'] = email_insert else: if email_cc_addresses: raise ValueError("email_cc_addresses must not be specified if " "there is no email_to_addresses: %r" % email_cc_addresses) if email_insert: raise ValueError("email_insert must not be specified if " "there is no email_to_addresses: %r" % email_insert) self.manager.session.post( self.manager.storage_group.uri + '/operations/modify', body=body) # pylint: disable=protected-access self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties, email_to_addresses=None, email_cc_addresses=None, email_insert=None): """ Update writeable properties of this storage volume on the HMC, and optionally send emails to storage administrators requesting modification of the storage volume on the storage subsystem and of any resources related to the storage volume. This method performs the "Modify Storage Group Properties" operation, requesting modification of the volume. Authorization requirements: * Object-access permission to the storage group owning this storage volume. * Task permission to the "Configure Storage - System Programmer" task. Parameters: properties (dict): New property values for the volume. Allowable properties are the fields defined in the "storage-volume-request-info" nested object for the "modify" operation. That nested object is described in section "Request body contents" for operation "Modify Storage Group Properties" in the :term:`HMC API` book. The properties provided in this parameter will be copied and then amended with the `operation="modify"` and `element-uri` properties, and then used as a single array item for the `storage-volumes` field in the request body of the "Modify Storage Group Properties" operation. email_to_addresses (:term:`iterable` of :term:`string`): Email addresses of one or more storage administrator to be notified. If `None` or empty, no email will be sent. email_cc_addresses (:term:`iterable` of :term:`string`): Email addresses of one or more storage administrator to be copied on the notification email. If `None` or empty, nobody will be copied on the email. Must be `None` or empty if `email_to_addresses` is `None` or empty. email_insert (:term:`string`): Additional text to be inserted in the notification email. The text can include HTML formatting tags. If `None`, no additional text will be inserted. Must be `None` or empty if `email_to_addresses` is `None` or empty. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ volreq_obj = copy.deepcopy(properties) volreq_obj['operation'] = 'modify' volreq_obj['element-uri'] = self.uri body = { 'storage-volumes': [volreq_obj], } if email_to_addresses: body['email-to-addresses'] = email_to_addresses if email_cc_addresses: body['email-cc-addresses'] = email_cc_addresses if email_insert: body['email-insert'] = email_insert else: if email_cc_addresses: raise ValueError("email_cc_addresses must not be specified if " "there is no email_to_addresses: %r" % email_cc_addresses) if email_insert: raise ValueError("email_insert must not be specified if " "there is no email_to_addresses: %r" % email_insert) self.manager.session.post( self.manager.storage_group.uri + '/operations/modify', body=body) self._properties.update(copy.deepcopy(properties)) @logged_api_call def indicate_fulfillment_ficon(self, control_unit, unit_address): """ TODO: Add ControlUnit objects etc for FICON support. Indicate completion of :term:`fulfillment` for this ECKD (=FICON) storage volume and provide identifying information (control unit and unit address) about the actual storage volume on the storage subsystem. Manually indicating fulfillment is required for all ECKD volumes, because they are not auto-discovered by the CPC. This method performs the "Fulfill FICON Storage Volume" HMC operation. Upon successful completion of this operation, the "fulfillment-state" property of this storage volume object will have been set to "complete". That is necessary for the CPC to be able to address and connect to the volume. If the "fulfillment-state" properties of all storage volumes in the owning storage group are "complete", the owning storage group's "fulfillment-state" property will also be set to "complete". Parameters: control_unit (:class:`~zhmcclient.ControlUnit`): Logical control unit (LCU) in which the backing ECKD volume is defined. unit_address (:term:`string`): Unit address of the backing ECKD volume within its logical control unit, as a hexadecimal number of up to 2 characters in any lexical case. Authorization requirements: * Object-access permission to the storage group owning this storage volume. * Task permission to the "Configure Storage - Storage Administrator" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # The operation requires exactly 2 characters in lower case unit_address_2 = format(int(unit_address, 16), '02x') body = { 'control-unit-uri': control_unit.uri, 'unit-address': unit_address_2, } self.manager.session.post( self.uri + '/operations/fulfill-ficon-storage-volume', body=body) @logged_api_call def indicate_fulfillment_fcp(self, wwpn, lun, host_port): """ Indicate completion of :term:`fulfillment` for this FCP storage volume and provide identifying information (WWPN and LUN) about the actual storage volume on the storage subsystem. Manually indicating fulfillment is required for storage volumes that will be used as boot devices for a partition. The specified host port will be used to access the storage volume during boot of the partition. Because the CPC discovers storage volumes automatically, the fulfillment of non-boot volumes does not need to be manually indicated using this function (it may be indicated though before the CPC discovers a working communications path to the volume, but the role of the specified host port is not clear in this case). This method performs the "Fulfill FCP Storage Volume" HMC operation. Upon successful completion of this operation, the "fulfillment-state" property of this storage volume object will have been set to "complete". That is necessary for the CPC to be able to address and connect to the volume. If the "fulfillment-state" properties of all storage volumes in the owning storage group are "complete", the owning storage group's "fulfillment-state" property will also be set to "complete". Parameters: wwpn (:term:`string`): World wide port name (WWPN) of the FCP storage subsystem containing the storage volume, as a hexadecimal number of up to 16 characters in any lexical case. lun (:term:`string`): Logical Unit Number (LUN) of the storage volume within its FCP storage subsystem, as a hexadecimal number of up to 16 characters in any lexical case. host_port (:class:`~zhmcclient.Port`): Storage port on the CPC that will be used to boot from. Authorization requirements: * Object-access permission to the storage group owning this storage volume. * Task permission to the "Configure Storage - Storage Administrator" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # The operation requires exactly 16 characters in lower case wwpn_16 = format(int(wwpn, 16), '016x') lun_16 = format(int(lun, 16), '016x') body = { 'world-wide-port-name': wwpn_16, 'logical-unit-number': lun_16, 'adapter-port-uri': host_port.uri, } self.manager.session.post( self.uri + '/operations/fulfill-fcp-storage-volume', body=body) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_storage_volume_template.py0000644000076500000240000003150500000000000024120 0ustar00maierastaff00000000000000# Copyright 2019 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 :class:`~zhmcclient.StorageVolumeTemplate` object represents a :term:`storage volume template` for FCP or FICON (ECKD) that is defined in a :term:`storage group template`. Storage volume template objects can be created, updated and deleted. Storage volume templates are contained in :term:`storage group templates `. You can create as many storage volume templates as you want in a storage group template. When creating a storage group from the storage group template, each storage volume template will cause a storage volume to be created in the new storage group. Storage group templates and storage volume templates only can be defined in CPCs that are in DPM mode and that have the "dpm-storage-management" feature enabled. """ from __future__ import absolute_import import copy # from requests.utils import quote from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['StorageVolumeTemplateManager', 'StorageVolumeTemplate'] class StorageVolumeTemplateManager(BaseManager): """ Manager providing access to the :term:`storage volume templates ` in a particular :term:`storage group template`. 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 of a :class:`~zhmcclient.StorageGroupTemplate` object: * :attr:`~zhmcclient.StorageGroupTemplate.storage_volume_templates` """ def __init__(self, storage_group_template): # This function should not go into the docs. # Parameters: # storage_group_template (:class:`~zhmcclient.StorageGroupTemplate`): # Storage group template 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 = [ 'name', 'maximum-size', 'minimum-size', 'usage', ] super(StorageVolumeTemplateManager, self).__init__( resource_class=StorageVolumeTemplate, class_name='storage-template-volume', session=storage_group_template.manager.session, parent=storage_group_template, base_uri='{}/storage-template-volumes'.format( storage_group_template.uri), oid_prop='element-id', uri_prop='element-uri', name_prop='name', query_props=query_props) @property def storage_group_template(self): """ :class:`~zhmcclient.StorageGroupTemplate`: :term:`Storage group template` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the storage volume templates in this storage group template. Authorization requirements: * Object-access permission to this storage group template. Parameters: full_properties (bool): Controls that the full set of resource properties for each returned storage volume template is being retrieved, vs. only the following short set: "element-uri", "name", "size", and "usage". 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.StorageVolumeTemplate` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] resource_obj = self._try_optimized_lookup(filter_args) if resource_obj: resource_obj_list.append(resource_obj) # It already has full properties else: query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'storage-template-volumes' uri = '{}/{}{}'.format(self.storage_group_template.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties): """ Create a :term:`storage volume template` in this storage group template on the HMC. This method performs the "Modify Storage Template Properties" operation, requesting creation of the volume. Authorization requirements: * Object-access permission to this storage group template. * Task permission to the "Configure Storage - System Programmer" task. Parameters: properties (dict): Initial property values for the new volume template. Allowable properties are the fields defined in the "storage-template-volume-request-info" nested object described for operation "Modify Storage Template Properties" in the :term:`HMC API` book. The valid fields are those for the "create" operation. The `operation` field must not be provided; it is set automatically to the value "create". The properties provided in this parameter will be copied and then amended with the `operation="create"` field, and then used as a single array item for the `storage-template-volumes` field in the request body of the "Modify Storage Template Properties" operation. Returns: StorageVolumeTemplate: The resource object for the new storage volume template. The object will have the following properties set: - 'element-uri' as returned by the HMC - 'element-id' as determined from the 'element-uri' property - 'class' and 'parent' - additional properties as specified in the input properties Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` Example:: stovol1 = fcp_stogrp.storage_volume_templates.create( properties=dict( name='vol1', size=30, # GiB )) """ volreq_obj = copy.deepcopy(properties) volreq_obj['operation'] = 'create' body = { 'storage-template-volumes': [volreq_obj], } result = self.session.post( self.storage_group_template.uri + '/operations/modify', body=body) uri = result['element-uris'][0] storage_volume_template = self.resource_object(uri, properties) # The 'name' property is unique within the parent object. However, it # is not returned by this operation, so we don't set the name-to-uri # cache. It will be set lazily, upon first use. return storage_volume_template class StorageVolumeTemplate(BaseResource): """ Representation of a :term:`storage volume template`. 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.StorageVolumeTemplateManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.StorageVolumeTemplateManager`): # 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, StorageVolumeTemplateManager), \ "StorageVolumeTemplate init: Expected manager type %s, got %s" % \ (StorageVolumeTemplateManager, type(manager)) super(StorageVolumeTemplate, self).__init__( manager, uri, name, properties) @logged_api_call def delete(self): """ Delete this storage volume template on the HMC. This method performs the "Modify Storage Template Properties" operation, requesting deletion of the volume template. Authorization requirements: * Object-access permission to the storage group template owning this storage volume template. * Task permission to the "Configure Storage - System Programmer" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ volreq_obj = { 'operation': 'delete', 'element-uri': self.uri, } body = { 'storage-volumes': [ volreq_obj ], } self.manager.session.post( self.manager.storage_group_template.uri + '/operations/modify', body=body) # pylint: disable=protected-access self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this storage volume template on the HMC. This method performs the "Modify Storage Template Properties" operation, requesting modification of the volume. Authorization requirements: * Object-access permission to the storage group template owning this storage volume template. * Task permission to the "Configure Storage - System Programmer" task. Parameters: properties (dict): New property values for the volume. Allowable properties are the fields defined in the "storage-template-volume-request-info" nested object for the "modify" operation. That nested object is described in operation "Modify Storage Template Properties" in the :term:`HMC API` book. The properties provided in this parameter will be copied and then amended with the `operation="modify"` and `element-uri` properties, and then used as a single array item for the `storage-template-volumes` field in the request body of the "Modify Storage Template Properties" operation. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ volreq_obj = copy.deepcopy(properties) volreq_obj['operation'] = 'modify' volreq_obj['element-uri'] = self.uri body = { 'storage-template-volumes': [volreq_obj], } self.manager.session.post( self.manager.storage_group_template.uri + '/operations/modify', body=body) self._properties.update(copy.deepcopy(properties)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/_task.py0000644000076500000240000001307000000000000020131 0ustar00maierastaff00000000000000# Copyright 2017 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:`Task` resource represents an action that an HMC user with appropriate authority can perform. These actions could be available via the HMC's graphical user interface, the Web Services APIs or both. Tasks are predefined by the HMC and cannot be created, modified or deleted. """ from __future__ import absolute_import from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['TaskManager', 'Task'] class TaskManager(BaseManager): """ Manager providing access to the :term:`Task` resources of a 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 of a :class:`~zhmcclient.Console` object: * :attr:`zhmcclient.Console.tasks` """ def __init__(self, console): # This function should not go into the docs. # Parameters: # console (:class:`~zhmcclient.Console`): # Console object representing the HMC. # 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 = [ 'name', ] super(TaskManager, self).__init__( resource_class=Task, class_name='task', session=console.manager.session, parent=console, base_uri='/api/console/tasks', oid_prop='element-id', uri_prop='element-uri', name_prop='name', query_props=query_props) @property def console(self): """ :class:`~zhmcclient.Console`: :term:`Console` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=True, filter_args=None): """ List the :term:`Task` resources representing the tasks defined in this HMC. Authorization requirements: * None Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.Task` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'tasks' uri = '{}/{}{}'.format(self.console.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list class Task(BaseResource): """ Representation of a :term:`Task`. 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.TaskManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.TaskManager`): # 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, TaskManager), \ "Console init: Expected manager type %s, got %s" % \ (TaskManager, type(manager)) super(Task, self).__init__(manager, uri, name, properties) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/_timestats.py0000644000076500000240000002406100000000000021206 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ The :class:`~zhmcclient.TimeStatsKeeper` class allows measuring the elapsed time of accordingly instrumented code and keeps a statistics of these times. The :class:`~zhmcclient.Session` class uses this class for keeping statistics about the time to issue HTTP requests against the HMC API (see its :attr:`~zhmcclient.Session.time_stats_keeper` property). The :class:`~zhmcclient.TimeStats` class is a helper class that contains the actual measurement data for all invocations of a particular HTTP request. Its objects are under control of the :class:`~zhmcclient.TimeStatsKeeper` class. Example:: import zhmcclient session = zhmcclient.Session(hmc, userid, password) session.time_stats_keeper.enable() # Some operations that are being measured client = zhmcclient.Client(session) cpcs = client.cpcs.list() print(session.time_stats_keeper) """ from __future__ import absolute_import import time import copy from ._logging import logged_api_call __all__ = ['TimeStatsKeeper', 'TimeStats'] class TimeStats(object): """ Elapsed time statistics for all invocations of a particular named operation. All invocations of the operation will be accumulated into the statistics data kept by an object of this class. Objects of this class don't need to (and in fact, are not supposed to) be created by the user. Instead, the :meth:`zhmcclient.TimeStatsKeeper.get_stats` method should be used to create objects of this class. """ def __init__(self, keeper, name): """ Parameters: keeper (TimeStatsKeeper): The statistics keeper that holds this time statistics. name (string): Name of the operation. """ self._keeper = keeper self._name = name self._count = 0 self._sum = float(0) self._min = float('inf') self._max = float(0) self._begin_time = None @property def name(self): """ :term:`string`: Name of the operation this time statistics has data for. This name is used by the :class:`~zhmcclient.TimeStatsKeeper` object holding this time statistics as a key. """ return self._name @property def keeper(self): """ :class:`~zhmcclient.TimeStatsKeeper`: The time statistics keeper holding this time statistics. """ return self._keeper @property def count(self): """ :term:`integer`: The number of invocations of the operation. """ return self._count @property def avg_time(self): """ float: The average elapsed time for invoking the operation, in seconds. """ try: return self._sum / self._count except ZeroDivisionError: return 0 @property def min_time(self): """ float: The minimum elapsed time for invoking the operation, in seconds. """ return self._min @property def max_time(self): """ float: The maximum elapsed time for invoking the operation, in seconds. """ return self._max @logged_api_call def reset(self): """ Reset the time statistics data for the operation. """ self._count = 0 self._sum = float(0) self._min = float('inf') self._max = float(0) @logged_api_call def begin(self): """ This method must be called before invoking the operation. Note that this method is not to be invoked by the user; it is invoked by the implementation of the :class:`~zhmcclient.Session` class. If the statistics keeper holding this time statistics is enabled, this method takes the current time, so that :meth:`~zhmcclient.TimeStats.end` can calculate the elapsed time between the two method calls. If the statistics keeper holding this time statistics is disabled, this method does nothing, in order to save resources. """ if self.keeper.enabled: self._begin_time = time.time() @logged_api_call def end(self): """ This method must be called after the operation returns. Note that this method is not to be invoked by the user; it is invoked by the implementation of the :class:`~zhmcclient.Session` class. If the statistics keeper holding this time statistics is enabled, this method takes the current time, calculates the duration of the operation since the last call to :meth:`~zhmcclient.TimeStats.begin`, and updates the time statistics to reflect the new operation. If the statistics keeper holding this time statistics is disabled, this method does nothing, in order to save resources. If this method is called without a preceding call to :meth:`~zhmcclient.TimeStats.begin`, a :exc:`py:RuntimeError` is raised. Raises: RuntimeError """ if self.keeper.enabled: if self._begin_time is None: raise RuntimeError("end() called without preceding begin()") dt = time.time() - self._begin_time self._begin_time = None self._count += 1 self._sum += dt if dt > self._max: self._max = dt if dt < self._min: self._min = dt def __str__(self): """ Return a human readable string with the time statistics for this operation. Example result: .. code-block:: text TimeStats: count=1 avg=1.000s min=1.000s max=1.000s get /api/cpcs """ return "TimeStats: count={:d} avg={:.3f}s min={:.3f}s "\ "max={:.3f}s {}".format( self.count, self.avg_time, self.min_time, self.max_time, self.name) class TimeStatsKeeper(object): """ Statistics keeper for elapsed times. The statistics keeper can hold multiple time statistics (see :class:`~zhmcclient.TimeStats`), that are identified by a name. The statistics keeper can be in a state of enabled or disabled. If enabled, it accumulates the elapsed times between subsequent calls to the :meth:`~zhmcclient.TimeStats.begin` and :meth:`~zhmcclient.TimeStats.end` methods of class :class:`~zhmcclient.TimeStats`. If disabled, calls to these methods do not accumulate any time. Initially, the statistics keeper is disabled. """ def __init__(self): self._enabled = False self._time_stats = {} # TimeStats objects self._disabled_stats = TimeStats(self, "disabled") @property def enabled(self): """ Indicates whether the statistics keeper is enabled. """ return self._enabled @logged_api_call def enable(self): """ Enable the statistics keeper. """ self._enabled = True @logged_api_call def disable(self): """ Disable the statistics keeper. """ self._enabled = False @logged_api_call def get_stats(self, name): """ Get the time statistics for a name. If a time statistics for that name does not exist yet, create one. Parameters: name (string): Name of the time statistics. Returns: TimeStats: The time statistics for the specified name. If the statistics keeper is disabled, a dummy time statistics object is returned, in order to save resources. """ if not self.enabled: return self._disabled_stats if name not in self._time_stats: self._time_stats[name] = TimeStats(self, name) return self._time_stats[name] @logged_api_call def snapshot(self): """ Return a snapshot of the time statistics of this keeper. The snapshot represents the statistics data at the time this method is called, and remains unchanged even if the statistics of this keeper continues to be updated. Returns: dict: A dictionary of the time statistics by operation, where: - key (:term:`string`): Name of the operation - value (:class:`~zhmcclient.TimeStats`): Time statistics for the operation """ return copy.deepcopy(self._time_stats) def __str__(self): """ Return a human readable string with the time statistics for this keeper. The operations are sorted by decreasing average time. Example result, if keeper is enabled: .. code-block:: text Time statistics (times in seconds): Count Average Minimum Maximum Operation name 1 0.024 0.024 0.024 get /api/cpcs 1 0.009 0.009 0.009 get /api/version """ ret = "Time statistics (times in seconds):\n" if self.enabled: ret += "Count Average Minimum Maximum Operation name\n" stats_dict = self.snapshot() snapshot_by_avg = sorted(stats_dict.items(), key=lambda item: item[1].avg_time, reverse=True) for name, stats in snapshot_by_avg: ret += "{:5d} {:7.3f} {:7.3f} {:7.3f} {}\n".format( stats.count, stats.avg_time, stats.min_time, stats.max_time, name) else: ret += "Disabled.\n" return ret.strip() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/_unmanaged_cpc.py0000644000076500000240000001400700000000000021754 0ustar00maierastaff00000000000000# Copyright 2017 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:`CPC` (Central Processor Complex) is a physical IBM Z or LinuxONE computer. A particular HMC can manage multiple CPCs and can discover other CPCs that are not managed by that HMC. Such other CPCs are called "unmanaged CPCs" and they may or may not be managed by another HMC. This section describes the interface for *unmanaged* CPCs using resource class :class:`~zhmcclient.UnmanagedCpc` and the corresponding manager class :class:`~zhmcclient.UnmanagedCpcManager`. """ from __future__ import absolute_import from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['UnmanagedCpcManager', 'UnmanagedCpc'] class UnmanagedCpcManager(BaseManager): """ Manager providing access to the :term:`CPCs ` that have been discovered by the HMC this client is connected to, but are not managed by it. They may or may not be managed by another 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 of a :class:`~zhmcclient.Console` object: * :attr:`~zhmcclient.Console.unmanaged_cpcs` """ def __init__(self, console): # This function should not go into the docs. # Parameters: # console (:class:`~zhmcclient.Console`): # Console object for the HMC to be used. # Resource properties that are supported as filter query parameters # (for server-side filtering). query_props = [ 'name', ] super(UnmanagedCpcManager, self).__init__( resource_class=UnmanagedCpc, class_name='cpc', session=console.manager.session, parent=console, base_uri='/api/console', oid_prop='object-id', uri_prop='object-uri', name_prop='name', query_props=query_props) @property def console(self): """ :class:`~zhmcclient.Console`: :term:`Console` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the unmanaged CPCs exposed by the HMC this client is connected to. Because the CPCs are unmanaged, the returned :class:`~zhmcclient.UnmanagedCpc` objects cannot perform any operations and will have only the following properties: * ``object-uri`` * ``name`` Authorization requirements: * None Parameters: full_properties (bool): Ignored (exists for consistency with other list() methods). 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.UnmanagedCpc` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] resource_obj = self._try_optimized_lookup(filter_args) if resource_obj: resource_obj_list.append(resource_obj) else: query_parms, client_filters = self._divide_filter_args(filter_args) uri = self.parent.uri + '/operations/list-unmanaged-cpcs' + \ query_parms result = self.session.get(uri) if result: props_list = result['cpcs'] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list class UnmanagedCpc(BaseResource): """ Representation of an unmanaged :term:`CPC`. 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.UnmanagedCpcManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.UnmanagedCpcManager`): # 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, UnmanagedCpcManager), \ "UnmanagedCpc init: Expected manager type %s, got %s" % \ (UnmanagedCpcManager, type(manager)) super(UnmanagedCpc, self).__init__(manager, uri, name, properties) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_user.py0000644000076500000240000002561300000000000020153 0ustar00maierastaff00000000000000# Copyright 2017 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:`User` resource represents a user configured in the HMC. """ from __future__ import absolute_import import copy from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['UserManager', 'User'] class UserManager(BaseManager): """ Manager providing access to the :term:`User` resources of a 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 of a :class:`~zhmcclient.Console` object: * :attr:`zhmcclient.Console.users` """ def __init__(self, console): # This function should not go into the docs. # Parameters: # console (:class:`~zhmcclient.Console`): # Console object representing the HMC. # 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 = [ 'name', 'type', ] super(UserManager, self).__init__( resource_class=User, class_name='user', session=console.manager.session, parent=console, base_uri='/api/users', oid_prop='object-id', uri_prop='object-uri', name_prop='name', query_props=query_props) @property def console(self): """ :class:`~zhmcclient.Console`: :term:`Console` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=True, filter_args=None): """ List the :term:`User` resources representing the users defined in this HMC. Authorization requirements: * User-related-access permission to the User object included in the result, or, depending on the type of User object, task permission to the "Manage Users" task or the "Manage User Templates" task. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.User` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'users' uri = '{}/{}{}'.format(self.console.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties): """ Create a new User in this HMC. Authorization requirements: * Task permission to the "Manage Users" task to create a standard user or the "Manage User Templates" task to create a template user. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create User' in the :term:`HMC API` book. Returns: User: The resource object for the new User. The object will have its 'object-uri' property set as returned by the HMC, and will also have the input properties set. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.session.post(self.console.uri + '/users', body=properties) # There should not be overlaps, but just in case there are, the # returned props should overwrite the input props: props = copy.deepcopy(properties) props.update(result) name = props.get(self._name_prop, None) uri = props[self._uri_prop] user = User(self, uri, name, props) self._name_uri_cache.update(name, uri) return user class User(BaseResource): """ Representation of a :term:`User`. 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.UserManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.UserManager`): # 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, UserManager), \ "Console init: Expected manager type %s, got %s" % \ (UserManager, type(manager)) super(User, self).__init__(manager, uri, name, properties) @logged_api_call def delete(self): """ Delete this User. Authorization requirements: * Task permission to the "Manage Users" task to delete a non-template user, or the "Manage User Templates" task to delete a template user. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.delete(self.uri) self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this User. Authorization requirements: * Task permission to the "Manage Users" task to update a non-template user, or the "Manage User Templates" task to update a template user. * For a user to update their own password or default-group-uri property, user-related-access permission to the user represented by this User object, or task permission to the "Manage Users" task is required. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model' in section 'User object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) # The name of Users cannot be updated. An attempt to do so should cause # HTTPError to be raised in the POST above, so we assert that here, # because we omit the extra code for handling name updates: assert self.manager._name_prop not in properties self._properties.update(copy.deepcopy(properties)) @logged_api_call def add_user_role(self, user_role): """ Add the specified User Role to this User. This User must not be a system-defined or pattern-based user. Authorization requirements: * Task permission to the "Manage Users" task to modify a standard user or the "Manage User Templates" task to modify a template user. Parameters: user_role (:class:`~zhmcclient.UserRole`): User Role to be added. Must not be `None`. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = { 'user-role-uri': user_role.uri } self.manager.session.post( self.uri + '/operations/add-user-role', body=body) @logged_api_call def remove_user_role(self, user_role): """ Remove the specified User Role from this User. This User must not be a system-defined or pattern-based user. Authorization requirements: * Task permission to the "Manage Users" task to modify a standard user or the "Manage User Templates" task to modify a template user. Parameters: user_role (:class:`~zhmcclient.UserRole`): User Role to be removed. Must not be `None`. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ body = { 'user-role-uri': user_role.uri } self.manager.session.post( self.uri + '/operations/remove-user-role', body=body) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_user_pattern.py0000644000076500000240000002474700000000000021717 0ustar00maierastaff00000000000000# Copyright 2017 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:`User Pattern` resource represents a pattern for HMC user IDs that are not defined on the HMC but can be verified by an LDAP server for user authentication. User Patterns and user templates allow a system administrator to define a group of HMC users at once whose user IDs all match a certain pattern (for example, a regular expression) and who have a certain set of attributes. Each pattern identifies a template User object which defines many characteristics of such users. A successful logon with a user ID that matches a User Pattern results in the creation of a pattern-based user, with many of its attributes coming from the associated template. User Patterns are searched in a defined order during logon processing. That order can be customized through the :meth:`~zhmcclient.UserPatternManager.reorder` method. """ from __future__ import absolute_import import copy from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['UserPatternManager', 'UserPattern'] class UserPatternManager(BaseManager): """ Manager providing access to the :term:`User Pattern` resources of a 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 of a :class:`~zhmcclient.Console` object: * :attr:`zhmcclient.Console.user_patterns` """ def __init__(self, console): # This function should not go into the docs. # Parameters: # console (:class:`~zhmcclient.Console`): # Console object representing the HMC. # 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 = [ 'name', 'type', ] super(UserPatternManager, self).__init__( resource_class=UserPattern, class_name='user-pattern', session=console.manager.session, parent=console, base_uri='/api/console/user-patterns', oid_prop='element-id', uri_prop='element-uri', name_prop='name', query_props=query_props) @property def console(self): """ :class:`~zhmcclient.Console`: :term:`Console` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=True, filter_args=None): """ List the :term:`User Pattern` resources representing the user patterns defined in this HMC. Authorization requirements: * User-related-access permission to the User Pattern objects included in the result, or task permission to the "Manage User Patterns" task. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.UserPattern` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'user-patterns' uri = '{}/{}{}'.format(self.console.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties): """ Create a new User Pattern in this HMC. Authorization requirements: * Task permission to the "Manage User Patterns" task. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create User Pattern' in the :term:`HMC API` book. Returns: UserPattern: The resource object for the new User Pattern. The object will have its 'element-uri' property set as returned by the HMC, and will also have the input properties set. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.session.post(self.console.uri + '/user-patterns', body=properties) # There should not be overlaps, but just in case there are, the # returned props should overwrite the input props: props = copy.deepcopy(properties) props.update(result) name = props.get(self._name_prop, None) uri = props[self._uri_prop] user_pattern = UserPattern(self, uri, name, props) self._name_uri_cache.update(name, uri) return user_pattern @logged_api_call def reorder(self, user_patterns): """ Reorder the User Patterns of the HMC as specified. The order of User Patterns determines the search order during logon processing. Authorization requirements: * Task permission to the "Manage User Patterns" task. Parameters: user_patterns (list of :class:`~zhmcclient.UserPattern`): The User Patterns in the desired order. Must not be `None`. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # noqa: E501 body = { 'user-pattern-uris': [up.uri for up in user_patterns] } self.session.post( '/api/console/operations/reorder-user-patterns', body=body) class UserPattern(BaseResource): """ Representation of a :term:`User Pattern`. 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.UserPatternManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.UserPatternManager`): # 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, UserPatternManager), \ "Console init: Expected manager type %s, got %s" % \ (UserPatternManager, type(manager)) super(UserPattern, self).__init__(manager, uri, name, properties) @logged_api_call def delete(self): """ Delete this User Pattern. Authorization requirements: * Task permission to the "Manage User Patterns" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.delete(self.uri) self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this UserPattern. Authorization requirements: * Task permission to the "Manage User Patterns" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model' in section 'User Pattern object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) 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._properties.update(copy.deepcopy(properties)) if is_rename: # Add the new name to the cache self.manager._name_uri_cache.update(self.name, self.uri) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_user_role.py0000644000076500000240000003606600000000000021200 0ustar00maierastaff00000000000000# Copyright 2017 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:`User Role` resource represents an authority role which can be assigned to one or more HMC users. A User Role may allow access to specific managed objects, classes of managed objects, groups and/or tasks. There are two types of User Roles: user-defined and system-defined. User-defined User Roles are created by an HMC user, whereas the system-defined User Roles are pre-defined, standard User Roles supplied with the HMC. """ from __future__ import absolute_import import copy import six from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['UserRoleManager', 'UserRole'] class UserRoleManager(BaseManager): """ Manager providing access to the :term:`User Role` resources of a 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 of a :class:`~zhmcclient.Console` object: * :attr:`zhmcclient.Console.user_roles` """ def __init__(self, console): # This function should not go into the docs. # Parameters: # console (:class:`~zhmcclient.Console`): # Console object representing the HMC. # 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 = [ 'name', 'type', ] super(UserRoleManager, self).__init__( resource_class=UserRole, class_name='user-role', session=console.manager.session, parent=console, base_uri='/api/user-roles', oid_prop='object-id', uri_prop='object-uri', name_prop='name', query_props=query_props) @property def console(self): """ :class:`~zhmcclient.Console`: :term:`Console` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=True, filter_args=None): """ List the :term:`User Role` resources representing the user roles defined in this HMC. Authorization requirements: * User-related-access permission to the User Role objects included in the result, or task permission to the "Manage User Roles" task. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.UserRole` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'user-roles' uri = '{}/{}{}'.format(self.console.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties): """ Create a new (user-defined) User Role in this HMC. Authorization requirements: * Task permission to the "Manage User Roles" task. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create User Role' in the :term:`HMC API` book. Returns: UserRole: The resource object for the new User Role. The object will have its 'object-uri' property set as returned by the HMC, and will also have the input properties set. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.session.post(self.console.uri + '/user-roles', body=properties) # There should not be overlaps, but just in case there are, the # returned props should overwrite the input props: props = copy.deepcopy(properties) props.update(result) name = props.get(self._name_prop, None) uri = props[self._uri_prop] user_role = UserRole(self, uri, name, props) self._name_uri_cache.update(name, uri) return user_role class UserRole(BaseResource): """ Representation of a :term:`User Role`. 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.UserRoleManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.UserRoleManager`): # 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, UserRoleManager), \ "Console init: Expected manager type %s, got %s" % \ (UserRoleManager, type(manager)) super(UserRole, self).__init__(manager, uri, name, properties) @logged_api_call def delete(self): """ Delete this User Role. The User Role must be user-defined. System-defined User Roles cannot be deleted. Authorization requirements: * Task permission to the "Manage User Roles" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.delete(self.uri) self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this User Role. The User Role must be user-defined. System-defined User Roles cannot be updated. Authorization requirements: * Task permission to the "Manage User Roles" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model' in section 'User Role object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) # The name of User Roles cannot be updated. An attempt to do so should # cause HTTPError to be raised in the POST above, so we assert that # here, because we omit the extra code for handling name updates: assert self.manager._name_prop not in properties self._properties.update(copy.deepcopy(properties)) @logged_api_call def add_permission(self, permitted_object, include_members=False, view_only=True): # pylint: disable=line-too-long """ Add permission for the specified permitted object(s) to this User Role, thereby granting that permission to all users that have this User Role. The granted permission depends on the resource class of the permitted object(s): * For Task resources, the granted permission is task permission for that task. * For Group resources, the granted permission is object access permission for the group resource, and optionally also for the group members. * For any other resources, the granted permission is object access permission for these resources. The User Role must be user-defined. Authorization requirements: * Task permission to the "Manage User Roles" task. Parameters: permitted_object (:class:`~zhmcclient.BaseResource` or :term:`string`): Permitted object(s), either as a Python resource object (e.g. :class:`~zhmcclient.Partition`), or as a resource class string (e.g. 'partition'). Must not be `None`. include_members (bool): Controls whether for Group resources, the operation applies additionally to its group member resources. This parameter will be ignored when the permitted object does not specify Group resources. view_only (bool): Controls whether for Task resources, the operation aplies to the view-only version of the task (if `True`), or to the full version of the task (if `False`). Only certain tasks support a view-only version. This parameter will be ignored when the permitted object does not specify Task resources. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # noqa: E501 # pylint: enable=line-too-long if isinstance(permitted_object, BaseResource): perm_obj = permitted_object.uri perm_type = 'object' elif isinstance(permitted_object, six.string_types): perm_obj = permitted_object perm_type = 'object-class' else: raise TypeError( "permitted_object must be a string or BaseResource, but is: " "{}".format(type(permitted_object))) body = { 'permitted-object': perm_obj, 'permitted-object-type': perm_type, 'include-members': include_members, 'view-only-mode': view_only, } self.manager.session.post( self.uri + '/operations/add-permission', body=body) @logged_api_call def remove_permission(self, permitted_object, include_members=False, view_only=True): # pylint: disable=line-too-long """ Remove permission for the specified permitted object(s) from this User Role, thereby no longer granting that permission to all users that have this User Role. The granted permission depends on the resource class of the permitted object(s): * For Task resources, the granted permission is task permission for that task. * For Group resources, the granted permission is object access permission for the group resource, and optionally also for the group members. * For any other resources, the granted permission is object access permission for these resources. The User Role must be user-defined. Authorization requirements: * Task permission to the "Manage User Roles" task. Parameters: permitted_object (:class:`~zhmcclient.BaseResource` or :term:`string`): Permitted object(s), either as a Python resource object (e.g. :class:`~zhmcclient.Partition`), or as a resource class string (e.g. 'partition'). Must not be `None`. include_members (bool): Controls whether for Group resources, the operation applies additionally to its group member resources. This parameter will be ignored when the permitted object does not specify Group resources. view_only (bool): Controls whether for Task resources, the operation aplies to the view-only version of the task (if `True`), or to the full version of the task (if `False`). Only certain tasks support a view-only version. This parameter will be ignored when the permitted object does not specify Task resources. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # noqa: E501 # pylint: enable=line-too-long if isinstance(permitted_object, BaseResource): perm_obj = permitted_object.uri perm_type = 'object' elif isinstance(permitted_object, six.string_types): perm_obj = permitted_object perm_type = 'object-class' else: raise TypeError( "permitted_object must be a string or BaseResource, but is: " "{}".format(type(permitted_object))) body = { 'permitted-object': perm_obj, 'permitted-object-type': perm_type, 'include-members': include_members, 'view-only-mode': view_only, } self.manager.session.post( self.uri + '/operations/remove-permission', body=body) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/_utils.py0000644000076500000240000002056500000000000020336 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ Utility functions. """ from __future__ import absolute_import from collections import OrderedDict try: from collections.abc import Mapping, MutableSequence, Iterable except ImportError: from collections import Mapping, MutableSequence, Iterable from datetime import datetime import six import pytz from requests.utils import quote __all__ = ['datetime_from_timestamp', 'timestamp_from_datetime'] _EPOCH_DT = datetime(1970, 1, 1, 0, 0, 0, 0, pytz.utc) def _indent(text, amount, ch=' '): """Return the indent text, where each line is indented by `amount` characters `ch`.""" padding = amount * ch return ''.join(padding + line for line in text.splitlines(True)) def repr_text(text, indent): """Return a debug representation of a multi-line text (e.g. the result of another repr...() function).""" if text is None: return 'None' ret = _indent(text, amount=indent) return ret.lstrip(' ') def repr_list(lst, indent): """Return a debug representation of a list or tuple.""" # pprint represents lists and tuples in one row if possible. We want one # per row, so we iterate ourselves. if lst is None: return 'None' if isinstance(lst, MutableSequence): bm = '[' em = ']' elif isinstance(lst, Iterable): bm = '(' em = ')' else: raise TypeError("Object must be an iterable, but is a %s" % type(lst)) ret = bm + '\n' for value in lst: ret += _indent('%r,\n' % value, 2) ret += em ret = repr_text(ret, indent=indent) return ret.lstrip(' ') def repr_dict(dct, indent): """Return a debug representation of a dict or OrderedDict.""" # pprint represents OrderedDict objects using the tuple init syntax, # which is not very readable. Therefore, dictionaries are iterated over. if dct is None: return 'None' if not isinstance(dct, Mapping): raise TypeError("Object must be a mapping, but is a %s" % type(dct)) if isinstance(dct, OrderedDict): kind = 'ordered' ret = '%s {\n' % kind # non standard syntax for the kind indicator for key in six.iterkeys(dct): value = dct[key] ret += _indent('%r: %r,\n' % (key, value), 2) else: # dict kind = 'sorted' ret = '%s {\n' % kind # non standard syntax for the kind indicator for key in sorted(six.iterkeys(dct)): value = dct[key] ret += _indent('%r: %r,\n' % (key, value), 2) ret += '}' ret = repr_text(ret, indent=indent) return ret.lstrip(' ') def repr_timestamp(timestamp): """Return a debug representation of an HMC timestamp number.""" if timestamp is None: return 'None' dt = datetime_from_timestamp(timestamp) ret = "%d (%s)" % (timestamp, dt.strftime('%Y-%m-%d %H:%M:%S.%f %Z')) return ret def repr_manager(manager, indent): """Return a debug representation of a manager object.""" return repr_text(repr(manager), indent=indent) def datetime_from_timestamp(ts, tzinfo=pytz.utc): """ Convert an :term:`HMC timestamp number ` into a :class:`~py:datetime.datetime` object. The resulting object will be timezone-aware and will be represented in the specified timezone, defaulting to UTC. The HMC timestamp number must be non-negative. This means the special timestamp value -1 cannot be represented as datetime and will cause ``ValueError`` to be raised. The date and time range supported by this function has the following bounds: * The upper bounds is determined by :attr:`py:datetime.datetime.max` and additional limitations, as follows: * 9999-12-31 23:59:59 UTC, for 32-bit and 64-bit CPython on Linux and OS-X. * 3001-01-01 07:59:59 UTC, for 32-bit and 64-bit CPython on Windows, due to a limitation in `gmtime() in Visual C++ `_. * 2038-01-19 03:14:07 UTC, for some 32-bit Python implementations, due to the `Year 2038 problem `_. * The lower bounds is the UNIX epoch: 1970-01-01 00:00:00 UTC. Parameters: ts (:term:`timestamp`): Point in time as an HMC timestamp number. Must not be `None`. tzinfo (:class:`py:datetime.tzinfo`): Timezone in which the returned object will be represented. This may be any object derived from :class:`py:datetime.tzinfo`, including but not limited to objects returned by :func:`pytz.timezone`. Note that this parameter does not affect how the HMC timestamp value is interpreted; i.e. the effective point in time represented by the returned object is not affected. What is affected by this parameter is for example the timezone in which the point in time is shown when printing the returned object. Must not be `None`. Returns: :class:`~py:datetime.datetime`: Point in time as a timezone-aware Python datetime object for the specified timezone. Raises: ValueError """ # Note that in Python 2, "None < 0" is allowed and will return True, # therefore we do an extra check for None. if ts is None: raise ValueError("HMC timestamp value must not be None.") if ts < 0: raise ValueError( "Negative HMC timestamp value {} cannot be represented as " "datetime.".format(ts)) epoch_seconds = ts // 1000 delta_microseconds = ts % 1000 * 1000 if tzinfo is None: raise ValueError("Timezone must not be None.") try: dt = datetime.fromtimestamp(epoch_seconds, tzinfo) except (ValueError, OSError) as exc: new_exc = ValueError(str(exc)) new_exc.__cause__ = None raise new_exc # ValueError dt = dt.replace(microsecond=delta_microseconds) return dt def timestamp_from_datetime(dt): """ Convert a :class:`~py:datetime.datetime` object into an :term:`HMC timestamp number `. The date and time range supported by this function has the following bounds: * The upper bounds is :attr:`py:datetime.datetime.max`, as follows: * 9999-12-31 23:59:59 UTC, for 32-bit and 64-bit CPython on Linux and OS-X. * 2038-01-19 03:14:07 UTC, for some 32-bit Python implementations, due to the `Year 2038 problem `_. * The lower bounds is the UNIX epoch: 1970-01-01 00:00:00 UTC. Parameters: dt (:class:`~py:datetime.datetime`): Point in time as a Python datetime object. The datetime object may be timezone-aware or timezone-naive. If timezone-naive, the UTC timezone is assumed. Must not be `None`. Returns: :term:`timestamp`: Point in time as an HMC timestamp number. Raises: ValueError """ if dt is None: raise ValueError("datetime value must not be None.") if dt.tzinfo is None: # Apply default timezone to the timezone-naive input dt = pytz.utc.localize(dt) epoch_seconds = (dt - _EPOCH_DT).total_seconds() ts = int(epoch_seconds * 1000) return ts def append_query_parms(query_parms, prop_name, prop_match): """ Append prop_name=prop_match to query_parms. prop_match may be a list. """ if isinstance(prop_match, (list, tuple)): for pm in prop_match: append_query_parms(query_parms, prop_name, pm) else: # Just in case, we also escape the property name parm_name = quote(prop_name, safe='') parm_value = quote(str(prop_match), safe='') qp = '{}={}'.format(parm_name, parm_value) query_parms.append(qp) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623327196.0 zhmcclient-0.31.0/zhmcclient/_version.py0000644000076500000240000000265700000000000020665 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ Definition of the package version, and check for supported Python versions. """ import sys __all__ = ['__version__'] #: The full version of this package including any development levels, as a #: :term:`string`. #: #: Possible formats for this version string are: #: #: * "M.N.P.dev1": A not yet released version M.N.P #: * "M.N.P": A released version M.N.P __version__ = '0.31.0' # Check supported Python versions # Keep these Python versions in sync with: # - python_requires and classifiers in setup.py # - Section "Supported environments" in docs/intro.rst _PYTHON_M = sys.version_info[0] _PYTHON_N = sys.version_info[1] if _PYTHON_M == 2 and _PYTHON_N < 7: raise RuntimeError('On Python 2, zhcmclient requires Python 2.7') if _PYTHON_M == 3 and _PYTHON_N < 4: raise RuntimeError('On Python 3, zhmcclient requires Python 3.4 or higher') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_virtual_function.py0000644000076500000240000002205200000000000022562 0ustar00maierastaff00000000000000# Copyright 2016-2017 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:`Virtual Function` is a logical entity that provides a :term:`Partition` with access to :term:`Accelerator Adapters `. Virtual Function resources are contained in Partition resources. Virtual Functions only exist in :term:`CPCs ` that are in DPM mode. """ from __future__ import absolute_import import copy from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['VirtualFunctionManager', 'VirtualFunction'] class VirtualFunctionManager(BaseManager): """ Manager providing access to the :term:`Virtual Functions ` in a particular :term:`Partition`. 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 of a :class:`~zhmcclient.Partition` object (in DPM mode): * :attr:`~zhmcclient.Partition.virtual_functions` """ def __init__(self, partition): # This function should not go into the docs. # Parameters: # partition (:class:`~zhmcclient.Partition`): # Partition defining the scope for this manager. super(VirtualFunctionManager, self).__init__( resource_class=VirtualFunction, class_name='virtual-function', session=partition.manager.session, parent=partition, base_uri='{}/virtual-functions'.format(partition.uri), oid_prop='element-id', uri_prop='element-uri', name_prop='name', query_props=[], list_has_name=False) @property def partition(self): """ :class:`~zhmcclient.Partition`: :term:`Partition` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the Virtual Functions of this Partition. Authorization requirements: * Object-access permission to this Partition. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.VirtualFunction` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] uris = self.partition.get_property('virtual-function-uris') if uris: for uri in uris: resource_obj = self.resource_class( manager=self, uri=uri, name=None, properties=None) if self._matches_filters(resource_obj, filter_args): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list @logged_api_call def create(self, properties): """ Create a Virtual Function in this Partition. Authorization requirements: * Object-access permission to this Partition. * Object-access permission to the backing accelerator Adapter. * Task permission for the "Partition Details" task. Parameters: properties (dict): Initial property values. Allowable properties are defined in section 'Request body contents' in section 'Create Virtual Function' in the :term:`HMC API` book. Returns: VirtualFunction: The resource object for the new Virtual Function. The object will have its 'element-uri' property set as returned by the HMC, and will also have the input properties set. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.session.post(self.partition.uri + '/virtual-functions', body=properties) # There should not be overlaps, but just in case there are, the # returned props should overwrite the input props: props = copy.deepcopy(properties) props.update(result) name = props.get(self._name_prop, None) uri = props[self._uri_prop] vf = VirtualFunction(self, uri, name, props) self._name_uri_cache.update(name, uri) return vf class VirtualFunction(BaseResource): """ Representation of a :term:`Virtual Function`. Derived from :class:`~zhmcclient.BaseResource`; see there for common methods and attributes. For the properties of a Virtual Function, see section 'Data model - Virtual Function Element Object' in section 'Partition object' in the :term:`HMC API` book. 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.VirtualFunctionManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.VirtualFunctionManager`): # 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, VirtualFunctionManager), \ "VirtualFunction init: Expected manager type %s, got %s" % \ (VirtualFunctionManager, type(manager)) super(VirtualFunction, self).__init__(manager, uri, name, properties) @logged_api_call def delete(self): """ Delete this Virtual Function. Authorization requirements: * Object-access permission to the Partition of this Virtual Function. * Task permission for the "Partition Details" task. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.delete(self._uri) self.manager._name_uri_cache.delete( self._properties.get(self.manager._name_prop, None)) @logged_api_call def update_properties(self, properties): """ Update writeable properties of this Virtual Function. Authorization requirements: * Object-access permission to the Partition of this Virtual Function. * When updating the "adapter-uri" property, object-access permission to the Adapter identified in that URI. * Task permission for the "Partition Details" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model - Virtual Function element object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) 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._properties.update(copy.deepcopy(properties)) if is_rename: # Add the new name to the cache self.manager._name_uri_cache.update(self.name, self.uri) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_virtual_storage_resource.py0000644000076500000240000002747000000000000024321 0ustar00maierastaff00000000000000# Copyright 2018 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:`virtual storage resource` object represents a storage-related z/Architecture device that is visible to a partition and that provides access for that partition to a :term:`storage volume`. The storage-related z/Architecture devices that are visible to a partition are different for the two storage architectures: For FCP, the virtualized HBA is visible as a device, and the storage volumes (LUNs) are not represented as devices. For FICON, each ECKD volume is visible as a device, but the virtualized FICON adapter port is not represented as a device. What the virtual storage resource objects represent, therefore depends on the storage architecture of the storage volume they are used for: * For FCP, a virtual storage resource object represents the virtualized HBA in the partition that is used to access the LUN. However, each usage of the virtual HBA in context of a storage group has its own virtual storage resource object. * For FICON, a virtual storage resource object represents the ECKD volume. Virtual storage resource objects are instantiated automatically when a storage group is attached to a partition, and are removed automatically upon detachment. The :term:`HBA` resource objects known from DPM mode before introduction of the "dpm-storage-management" feature are no longer exposed. Virtual storage resource objects are contained in :term:`storage group` objects. Storage groups and storage volumes only can be defined in CPCs that are in DPM mode and that have the "dpm-storage-management" feature enabled. """ from __future__ import absolute_import import re import copy # from requests.utils import quote from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['VirtualStorageResourceManager', 'VirtualStorageResource'] class VirtualStorageResourceManager(BaseManager): """ Manager providing access to the :term:`virtual storage resources ` in a particular :term:`storage group`. 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 of a :class:`~zhmcclient.StorageGroup` object: * :attr:`~zhmcclient.StorageGroup.virtual_storage_resources` """ def __init__(self, storage_group): # This function should not go into the docs. # Parameters: # storage_group (:class:`~zhmcclient.StorageGroup`): # Storage group 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 = [ 'name', 'device-number', 'adapter-port-uri', 'partition-uri', ] super(VirtualStorageResourceManager, self).__init__( resource_class=VirtualStorageResource, class_name='virtual-storage-resource', session=storage_group.manager.session, parent=storage_group, base_uri='{}/virtual-storage-resources'.format(storage_group.uri), oid_prop='element-id', uri_prop='element-uri', name_prop='name', query_props=query_props) @property def storage_group(self): """ :class:`~zhmcclient.StorageGroup`: :term:`Storage group` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the virtual storage resources in this storage group. Authorization requirements: * Object-access permission to this storage group. Parameters: full_properties (bool): Controls that the full set of resource properties for each returned storage volume is being retrieved, vs. only the following short set: "element-uri", "name", "device-number", "adapter-port-uri", and "partition-uri". 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.VirtualStorageResource` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] resource_obj = self._try_optimized_lookup(filter_args) if resource_obj: resource_obj_list.append(resource_obj) # It already has full properties else: query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'virtual-storage-resources' uri = '{}/{}{}'.format(self.storage_group.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list class VirtualStorageResource(BaseResource): """ Representation of a :term:`virtual storage resource`. 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.VirtualStorageResourceManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.VirtualStorageResourceManager`): # 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, VirtualStorageResourceManager), \ "VirtualStorageResource init: Expected manager type %s, got %s" % \ (VirtualStorageResourceManager, type(manager)) super(VirtualStorageResource, self).__init__( manager, uri, name, properties) self._attached_partition = None self._adapter_port = None self._storage_volume = None @property def attached_partition(self): """ :class:`~zhmcclient.Partition`: The partition to which this virtual storage resource is attached. The returned partition object has only a minimal set of properties set ('object-id', 'object-uri', 'class', 'parent'). Note that a virtual storage resource is always attached to a partition, as long as it exists. Authorization requirements: * Object-access permission to the storage group owning this virtual storage resource. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ if self._attached_partition is None: part_mgr = self.manager.storage_group.manager.cpc.partitions part = part_mgr.resource_object(self.get_property('partition-uri')) self._attached_partition = part return self._attached_partition @property def adapter_port(self): """ :class:`~zhmcclient.Port`: The storage adapter port associated with this virtual storage resource, once discovery has determined which port to use for this virtual storage resource. This applies to both, FCP and FICON/ECKD typed storage groups. The returned adapter port object has only a minimal set of properties set ('object-id', 'object-uri', 'class', 'parent'). Authorization requirements: * Object-access permission to the storage group owning this virtual storage resource. * Object-access permission to the CPC of the storage adapter. * Object-access permission to the storage adapter. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ if self._adapter_port is None: port_uri = self.get_property('adapter-port-uri') assert port_uri is not None m = re.match(r'^(/api/adapters/[^/]+)/.*', port_uri) adapter_uri = m.group(1) adapter_mgr = self.manager.storage_group.manager.cpc.adapters filter_args = {'object-uri': adapter_uri} adapter = adapter_mgr.find(**filter_args) port_mgr = adapter.ports port = port_mgr.resource_object(port_uri) self._adapter_port = port return self._adapter_port @logged_api_call def update_properties(self, properties): """ Update writeable properties of this virtual storage resource. Authorization requirements: * Object-access permission to the storage group owning this virtual storage resource. * Task permission to the "Configure Storage - System Programmer" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model' in section 'Virtual Storage Resource object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) 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._properties.update(copy.deepcopy(properties)) if is_rename: # Add the new name to the cache self.manager._name_uri_cache.update(self.name, self.uri) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient/_virtual_switch.py0000644000076500000240000002252600000000000022244 0ustar00maierastaff00000000000000# Copyright 2016-2017 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:`Virtual Switch` is a virtualized networking switch connecting :term:`NICs ` with a :term:`Network Port`. Virtual Switches are generated automatically every time a new :term:`Network Adapter` is detected and configured. Virtual Switch resources are contained in :term:`CPC` resources. Virtual Switches only exist in CPCs that are in DPM mode. """ from __future__ import absolute_import import re import copy from ._manager import BaseManager from ._resource import BaseResource from ._logging import logged_api_call __all__ = ['VirtualSwitchManager', 'VirtualSwitch'] class VirtualSwitchManager(BaseManager): """ Manager providing access to the :term:`Virtual Switches ` in a particular :term:`CPC`. 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 of a :class:`~zhmcclient.Cpc` object (in DPM mode): * :attr:`~zhmcclient.Cpc.virtual_switches` """ def __init__(self, cpc): # This function should not go into the docs. # Parameters: # cpc (:class:`~zhmcclient.Cpc`): # CPC 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 = [ 'name', 'type', ] super(VirtualSwitchManager, self).__init__( resource_class=VirtualSwitch, class_name='virtual-switch', session=cpc.manager.session, parent=cpc, base_uri='/api/virtual-switches', oid_prop='object-id', uri_prop='object-uri', name_prop='name', query_props=query_props) @property def cpc(self): """ :class:`~zhmcclient.Cpc`: :term:`CPC` defining the scope for this manager. """ return self._parent @logged_api_call def list(self, full_properties=False, filter_args=None): """ List the Virtual Switches in this CPC. Authorization requirements: * Object-access permission to this CPC. * Object-access permission to the backing Adapters of any Virtual Switches to be included in the result. Parameters: full_properties (bool): Controls whether the full set of resource properties should be retrieved, vs. only the short set as returned by the list operation. 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, i.e. all resources are returned. Returns: : A list of :class:`~zhmcclient.VirtualSwitch` objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ resource_obj_list = [] resource_obj = self._try_optimized_lookup(filter_args) if resource_obj: resource_obj_list.append(resource_obj) # It already has full properties else: query_parms, client_filters = self._divide_filter_args(filter_args) resources_name = 'virtual-switches' uri = '{}/{}{}'.format(self.cpc.uri, resources_name, query_parms) result = self.session.get(uri) if result: props_list = result[resources_name] for props in props_list: resource_obj = self.resource_class( manager=self, uri=props[self._uri_prop], name=props.get(self._name_prop, None), properties=props) if self._matches_filters(resource_obj, client_filters): resource_obj_list.append(resource_obj) if full_properties: resource_obj.pull_full_properties() self._name_uri_cache.update_from(resource_obj_list) return resource_obj_list class VirtualSwitch(BaseResource): """ Representation of a :term:`Virtual Switch`. Derived from :class:`~zhmcclient.BaseResource`; see there for common methods and attributes. For the properties of a Virtual Switch, see section 'Data model' in section 'Virtual Switch object' in the :term:`HMC API` book. 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.VirtualSwitchManager`). """ def __init__(self, manager, uri, name=None, properties=None): # This function should not go into the docs. # manager (:class:`~zhmcclient.VirtualSwitchManager`): # 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, VirtualSwitchManager), \ "VirtualSwitch init: Expected manager type %s, got %s" % \ (VirtualSwitchManager, type(manager)) super(VirtualSwitch, self).__init__(manager, uri, name, properties) @logged_api_call def get_connected_nics(self): """ List the :term:`NICs ` connected to this Virtual Switch. This method performs the "Get Connected VNICs of a Virtual Switch" HMC operation. Authorization requirements: * Object-access permission to this CPC. * Object-access permission to the backing Adapter of this Virtual Switch. Returns: : A list of :term:`Nic` objects. These objects will be connected in the resource tree (i.e. have a parent :term:`Partition` object, etc.) and will have the following properties set: * `element-uri` * `element-id` * `parent` * `class` Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ result = self.manager.session.get( self.uri + '/operations/get-connected-vnics') nic_uris = result['connected-vnic-uris'] nic_list = [] parts = {} # Key: Partition ID; Value: Partition object for nic_uri in nic_uris: m = re.match(r"^/api/partitions/([^/]+)/nics/([^/]+)/?$", nic_uri) part_id = m.group(1) nic_id = m.group(2) # We remember created Partition objects and reuse them. try: part = parts[part_id] except KeyError: part = self.manager.cpc.partitions.resource_object(part_id) parts[part_id] = part nic = part.nics.resource_object(nic_id) nic_list.append(nic) return nic_list @logged_api_call def update_properties(self, properties): """ Update writeable properties of this Virtual Switch. Authorization requirements: * Object-access permission to the backing Adapter of this Virtual Switch. * Task permission for the "Manage Adapters" task. Parameters: properties (dict): New values for the properties to be updated. Properties not to be updated are omitted. Allowable properties are the properties with qualifier (w) in section 'Data model' in section 'Virtual Switch object' in the :term:`HMC API` book. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` :exc:`~zhmcclient.AuthError` :exc:`~zhmcclient.ConnectionError` """ # pylint: disable=protected-access self.manager.session.post(self.uri, body=properties) 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._properties.update(copy.deepcopy(properties)) if is_rename: # Add the new name to the cache self.manager._name_uri_cache.update(self.name, self.uri) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/debuginfo.py0000644000076500000240000000652200000000000020776 0ustar00maierastaff00000000000000# Copyright 2020 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. """ Script for printing debug information. The debug information can be used when reporting bugs to the zhmcclient issue tracker. Command line usage: python -m zhmcclient.debuginfo Programmatic usage: from zhmclient import debuginfo info = debuginfo.as_dict() """ from __future__ import print_function, absolute_import import sys import platform import ctypes import zhmcclient __all__ = ['as_dict'] def version_string(version_info): """ Return the 5-tuple version_info as a version string, as follows: "1.2.3" # if version_info[3] == 'final' "1.2.3.alpha.42" # if version_info[3] != 'final' """ major, minor, micro, releaselevel, serial = version_info version_str = '{}.{}.{}'.format(major, minor, micro) if releaselevel != 'final': version_str = '{}.{}.{}'.format(version_str, releaselevel, serial) return version_str def as_dict(): """ Return debug information as a dictionary. """ debug_info = dict() debug_info['os_name'] = platform.system() debug_info['os_version'] = platform.release() debug_info['cpu_arch'] = platform.machine() debug_info['bit_size'] = ctypes.sizeof(ctypes.c_void_p) * 8 char_len = len(b'\\U00010142'.decode('unicode-escape')) if char_len == 1: debug_info['unicode_size'] = 'wide' elif char_len == 2: debug_info['unicode_size'] = 'narrow' else: # Should not happen debug_info['unicode_size'] = 'unknown' impl = platform.python_implementation() debug_info['impl'] = impl if impl == 'CPython': impl_version = version_string(sys.version_info) elif impl == 'PyPy': # pylint: disable=no-member impl_version = version_string(sys.pypy_version_info) elif impl == 'Jython': impl_version = version_string(sys.version_info) # TODO: Verify elif impl == 'IronPython': impl_version = version_string(sys.version_info) # TODO: Verify else: impl_version = 'unknown' debug_info['impl_version'] = impl_version debug_info['python_version'] = platform.python_version() # pylint: disable=no-member debug_info['zhmcclient_version'] = zhmcclient.__version__ return debug_info def main(): """ Print debug information. """ d = as_dict() # pylint: disable=invalid-format-index print("Operating system: {d[os_name]} {d[os_version]} " "on {d[cpu_arch]}".format(d=d)) print("Python implementation: {d[impl]} {d[impl_version]} " "({d[bit_size]} bit, {d[unicode_size]} unicode)".format(d=d)) print("Python version: {d[python_version]}".format(d=d)) print("zhmcclient version: {d[zhmcclient_version]}".format(d=d)) # pylint: enable=invalid-format-index if __name__ == '__main__': main() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1623327804.7507374 zhmcclient-0.31.0/zhmcclient/testutils/0000755000076500000240000000000000000000000020515 5ustar00maierastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/testutils/__init__.py0000644000076500000240000000000000000000000022614 0ustar00maierastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/testutils/cpc_fixtures.py0000644000076500000240000000401700000000000023567 0ustar00maierastaff00000000000000# Copyright 2019 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. """ Pytest fixtures for CPCs. """ from __future__ import absolute_import import pytest import zhmcclient # pylint: disable=unused-import from .hmc_definition_fixtures import hmc_session # noqa: F401, E501 def fixtureid_cpc(fixture_value): """ Return a fixture ID to be used by pytest, for fixtures `one_cpc()` and `all_cpcs()`. Parameters: * fixture_value (zhmcclient.Cpc): The Cpc object the test runs against. """ cpc = fixture_value assert isinstance(cpc, zhmcclient.Cpc) return "CPC={}".format(cpc.name) @pytest.fixture( scope='module', ids=fixtureid_cpc ) def one_cpc(request, hmc_session): # noqa: F811 # pylint: disable=redefined-outer-name,unused-argument """ Fixture representing a single, arbitrary CPC managed by the HMC. Returns a `zhmcclient.Cpc` object, with full properties. """ client = zhmcclient.Client(hmc_session) cpcs = client.cpcs.list() assert len(cpcs) >= 1 cpc = cpcs[0] cpc.pull_full_properties() return cpc @pytest.fixture( scope='module', ids=fixtureid_cpc ) def all_cpcs(request, hmc_session): # noqa: F811 # pylint: disable=redefined-outer-name,unused-argument """ Fixture representing the set of all CPCs managed by the HMC. Returns a list of `zhmcclient.Cpc` objects, with full properties. """ client = zhmcclient.Client(hmc_session) cpcs = client.cpcs.list(full_properties=True) return cpcs ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/testutils/hmc_definition_fixtures.py0000644000076500000240000001266000000000000026004 0ustar00maierastaff00000000000000# Copyright 2019 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. """ Pytest fixtures for mocked HMCs. """ from __future__ import absolute_import import os import errno import logging import pytest import yaml import yamlloader import zhmcclient import zhmcclient_mock from .hmc_definitions import HMCDefinitionFile, HMCDefinition # Nicknames in HMC definition file TESTHMCDIR = os.getenv('TESTHMCDIR', 'tests') TESTHMCFILE = os.path.join(TESTHMCDIR, 'hmc_definitions.yaml') TESTHMC = os.getenv('TESTHMC', 'default') HMC_DEF_LIST = HMCDefinitionFile(filepath=TESTHMCFILE).list_hmcs(TESTHMC) # Log file TESTLOGFILE = os.getenv('TESTLOGFILE', None) if TESTLOGFILE: LOG_HANDLER = logging.FileHandler(TESTLOGFILE, encoding='utf-8') LOG_FORMAT_STRING = '%(asctime)s %(name)s %(levelname)s %(message)s' LOG_HANDLER.setFormatter(logging.Formatter(LOG_FORMAT_STRING)) else: LOG_HANDLER = None LOG_FORMAT_STRING = None class FakedHMCFileError(Exception): """ Exception indicating an issue with the faked HMC file. """ pass def fixtureid_hmc_definition(fixture_value): """ Return a fixture ID to be used by pytest, for fixture `hmc_definition()`. Parameters: * fixture_value (HMCDefinition): The HMC definition of the HMC the test runs against. """ hd = fixture_value assert isinstance(hd, HMCDefinition) return "hmc_definition={}".format(hd.nickname) @pytest.fixture( params=HMC_DEF_LIST, scope='module', ids=fixtureid_hmc_definition ) def hmc_definition(request): """ Fixture representing the set of HMC definitions to use for the end2end tests. Returns the `HMCDefinition` object of each HMC to test against. """ return request.param @pytest.fixture( scope='module' ) def hmc_session(request, hmc_definition): # pylint: disable=redefined-outer-name,unused-argument """ Pytest fixture representing the set of `zhmcclient.Session` objects to use for the end2end tests. Because the `hmc_definition` parameter of this fixture is again a fixture, `hmc_definition` needs to be imported as well when this fixture is used. Returns a `zhmcclient.Session` object that is logged on to the HMC. Upon teardown, the `zhmcclient.Session` object is logged off. """ hd = hmc_definition # We use the cached skip reason from previous attempts skip_msg = getattr(hd, 'skip_msg', None) if skip_msg: pytest.skip("Skip reason from earlier attempt: {0}".format(skip_msg)) if hd.faked_hmc_file: # A faked HMC # Read the faked HMC file filepath = os.path.join( os.path.dirname(hd.hmc_filepath), hd.faked_hmc_file) try: with open(filepath) as fp: try: data = yaml.load(fp, Loader=yamlloader.ordereddict.Loader) except (yaml.parser.ParserError, yaml.scanner.ScannerError) as exc: new_exc = FakedHMCFileError( "Invalid YAML syntax in faked HMC file {0!r}: {1} {2}". format(filepath, exc.__class__.__name__, exc)) new_exc.__cause__ = None raise new_exc # FakedHMCFileError except IOError as exc: if exc.errno == errno.ENOENT: new_exc = FakedHMCFileError( "The faked HMC file {0!r} was not found". format(filepath)) new_exc.__cause__ = None raise new_exc # FakedHMCFileError raise client = data['faked_client'] session = zhmcclient_mock.FakedSession( client['hmc_host'], client['hmc_name'], client['hmc_version'], client['api_version']) for cpc in client['cpcs']: session.hmc.cpcs.add(cpc['properties']) else: # A real HMC # Enable debug logging if specified if LOG_HANDLER: logger = logging.getLogger('zhmcclient.hmc') if LOG_HANDLER not in logger.handlers: logger.addHandler(LOG_HANDLER) logger.setLevel(logging.DEBUG) logger = logging.getLogger('zhmcclient.api') if LOG_HANDLER not in logger.handlers: logger.addHandler(LOG_HANDLER) logger.setLevel(logging.DEBUG) # Creating a session does not interact with the HMC (logon is deferred) session = zhmcclient.Session( hd.hmc_host, hd.hmc_userid, hd.hmc_password) # Check access to the HMC try: session.logon() except zhmcclient.Error as exc: msg = "Cannot log on to HMC {0} at {1} due to {2}: {3}". \ format(hd.nickname, hd.hmc_host, exc.__class__.__name__, exc) hd.skip_msg = msg pytest.skip(msg) hd.skip_msg = None session.hmc_definition = hd yield session session.logoff() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient/testutils/hmc_definitions.py0000644000076500000240000002702200000000000024234 0ustar00maierastaff00000000000000# Copyright 2019 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. """ Encapsulation of HMC definition file defining HMCs for zhmcclient end2end tests. """ from __future__ import absolute_import import os try: from collections import OrderedDict except ImportError: from ordereddict import OrderedDict import errno import yaml import yamlloader THIS_DIR = os.path.dirname(__file__) DEFAULT_HMC_FILE = os.path.join('tests', 'hmc_definitions.yaml') EXAMPLE_HMC_FILE = os.path.join('tests', 'example_hmc_definitions.yaml') class HMCDefinitionFileError(Exception): """ An error in the HMC definition file. """ pass class HMCDefinitionFile(object): """ Encapsulation of the definitions in the HMC definition file. """ def __init__(self, filepath=DEFAULT_HMC_FILE): self._filepath = filepath self._hmcs = OrderedDict() self._hmc_groups = OrderedDict() self._load_file() def _load_file(self): """ Load the HMC definition file. """ try: with open(self._filepath) as fp: try: data = yaml.load(fp, Loader=yamlloader.ordereddict.Loader) except (yaml.parser.ParserError, yaml.scanner.ScannerError) as exc: new_exc = HMCDefinitionFileError( "Invalid YAML syntax in HMC definition file " "{0!r}: {1} {2}". format(self._filepath, exc.__class__.__name__, exc)) new_exc.__cause__ = None raise new_exc # HMCDefinitionFileError except IOError as exc: if exc.errno == errno.ENOENT: new_exc = HMCDefinitionFileError( "The HMC definition file {0!r} was not found; " "copy it from {1!r}". format(self._filepath, EXAMPLE_HMC_FILE)) new_exc.__cause__ = None raise new_exc # HMCDefinitionFileError raise if data is None: raise HMCDefinitionFileError( "The HMC definition file {0!r} is empty". format(self._filepath)) if not isinstance(data, OrderedDict): raise HMCDefinitionFileError( "The HMC definition file {0!r} must contain a " "dictionary at the top level, but contains {1}". format(self._filepath, type(data))) if 'hmcs' not in data: raise HMCDefinitionFileError( "The HMC definition file {0!r} does not define a " "'hmcs' item, but items: {1}". format(self._filepath, data.keys())) hmcs = data.get('hmcs') if not isinstance(hmcs, OrderedDict): raise HMCDefinitionFileError( "'hmcs' in HMC definition file {0!r} " "must be a dictionary, but is a {1}". format(self._filepath, type(hmcs))) self._hmcs.update(hmcs) hmc_groups = data.get('hmc_groups', OrderedDict()) if not isinstance(hmc_groups, OrderedDict): raise HMCDefinitionFileError( "'hmc_groups' in HMC definition file {0!r} " "must be a dictionary, but is a {1}". format(self._filepath, type(hmc_groups))) for hmc_nick in hmc_groups: visited_hmc_nicks = list() self._check_hmc_group(hmc_nick, hmc_groups, hmcs, visited_hmc_nicks) self._hmc_groups.update(hmc_groups) def _check_hmc_group(self, hmc_nick, hmc_groups, hmcs, visited_hmc_nicks): """ Check the HMC group specified in hmc_nick. """ visited_hmc_nicks.append(hmc_nick) hmc_group = hmc_groups[hmc_nick] if not isinstance(hmc_group, list): raise HMCDefinitionFileError( "HMC group {0!r} in HMC definition file {1!r} " "must be a list, but is a {2}". format(hmc_nick, self._filepath, type(hmc_group))) for nick in hmc_group: if nick in visited_hmc_nicks: raise HMCDefinitionFileError( "Circular reference: HMC group {0!r} in HMC " "definition file {1!r} contains HMC group {2!r}, which " "directly or indirectly contains HMC group {0!r}". format(hmc_nick, self._filepath, nick)) if not isinstance(nick, str): raise HMCDefinitionFileError( "Item {0!r} in HMC group {1!r} in HMC definition file " "{2!r} must be a string, but is a {3}". format(nick, hmc_nick, self._filepath, type(nick))) if nick in hmc_groups: self._check_hmc_group( nick, hmc_groups, hmcs, visited_hmc_nicks) elif nick not in hmcs: raise HMCDefinitionFileError( "Item {0!r} in HMC group {1!r} in HMC definition file " "{2!r} is not a known HMC or HMC group". format(nick, hmc_nick, self._filepath)) @property def filepath(self): """ Path name of the HMC definition file. """ return self._filepath def get_hmc(self, nickname): """ Return a `HMCDefinition` object for the HMC with the specified nickname. """ try: hmc_dict = self._hmcs[nickname] except KeyError: new_exc = ValueError( "HMC with nickname {0!r} not found in HMC definition file " "{1!r}".format(nickname, self._filepath)) new_exc.__cause__ = None raise new_exc # ValueError return HMCDefinition(nickname, hmc_dict, self._filepath) def list_hmcs(self, nickname): """ Return a list of `HMCDefinition` objects for the hmcs in the HMC group with the specified nickname, or the single HMC with the specified nickname. """ if nickname in self._hmcs: return [self.get_hmc(nickname)] if nickname in self._hmc_groups: hmc_list = list() # of HMCDefinition objects hmc_nick_list = list() # of HMC nicknames for item_nick in self._hmc_groups[nickname]: for hd in self.list_hmcs(item_nick): if hd.nickname not in hmc_nick_list: hmc_list.append(hd) hmc_nick_list.append(hd.nickname) return hmc_list raise ValueError( "HMC group or HMC with nickname {0!r} not found in " "HMC definition file {1!r}". format(nickname, self._filepath)) def list_all_hmcs(self): """ Return a list of all HMCs in the HMC definition file. """ return [self.get_hmc(nickname) for nickname in self._hmcs] def _required_attr(hmc_dict, attr_name, nickname): """ Return a required attribute. """ try: return hmc_dict[attr_name] except KeyError: new_exc = HMCDefinitionFileError( "Required HMC attribute is missing in definition of HMC " "{0}: {1}".format(nickname, attr_name)) new_exc.__cause__ = None raise new_exc # HMCDefinitionFileError class HMCDefinition(object): """ Encapsulation of a single HMC definition (e.g. from an HMC definition file). An HMC definition contains information needed to use the WS API of the HMC (such as its IP address and credentials, and any networking preconditions for reaching the IP address), some organizational information (such as a description and a technical contact), and some information about the CPCs managed by the HMC (such as whether they are in DPM mode). """ def __init__(self, nickname, hmc_dict, hmc_filepath): self._nickname = nickname self._hmc_filepath = hmc_filepath self._description = hmc_dict.get('description', '') self._contact = hmc_dict.get('contact', '') self._access_via = hmc_dict.get('access_via', '') self._faked_hmc_file = hmc_dict.get('faked_hmc_file', None) if self._faked_hmc_file: self._hmc_host = None self._hmc_userid = None self._hmc_password = None else: self._hmc_host = _required_attr(hmc_dict, 'hmc_host', nickname) self._hmc_userid = _required_attr(hmc_dict, 'hmc_userid', nickname) self._hmc_password = _required_attr(hmc_dict, 'hmc_password', nickname) self._cpcs = hmc_dict.get('cpcs', dict()) def __repr__(self): return "HMCDefinition(" \ "nickname={s.nickname!r}, " \ "hmc_filepath={s.hmc_filepath!r}, " \ "description={s.description!r}, " \ "contact={s.contact!r}, " \ "access_via={s.access_via!r}, " \ "faked_hmc_file={s.faked_hmc_file!r}, " \ "hmc_host={s.hmc_host!r}, " \ "hmc_userid={s.hmc_userid!r}, " \ "hmc_password=..., " \ "cpcs={s.cpcs!r})". \ format(s=self) @property def nickname(self): """ Nickname of the HMC. """ return self._nickname @property def hmc_filepath(self): """ Path name of the HMC definition file defining this HMC. """ return self._hmc_filepath @property def description(self): """ Short description of the HMC. """ return self._description @property def contact(self): """ Name of the technical contact of the HMC. """ return self._contact @property def access_via(self): """ Networking preconditions for reaching the IP address of the HMC (e.g. Boundary firewall, VPN connection). """ return self._access_via @property def faked_hmc_file(self): """ Path name of faked HMC file, defining a faked HMC with CPCs to be used for setting up the zhmcclient mock support. This property is `None` for real HMCs. """ return self._faked_hmc_file @property def hmc_host(self): """ IP address or hostname of the HMC. This property is `None` for faked HMCs. """ return self._hmc_host @property def hmc_userid(self): """ Userid for logging on to the HMC. This property is `None` for faked HMCs. """ return self._hmc_userid @property def hmc_password(self): """ Password for logging on to the HMC. This property is `None` for faked HMCs. """ return self._hmc_password @property def cpcs(self): """ List of CPCs managed by the HMC. Each list item represents one CPC and is a dict with these keys: * 'name' (string): CPC name. * 'dpm' (bool): CPC is in DPM mode. None if not known. """ return self._cpcs ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1623327804.7495196 zhmcclient-0.31.0/zhmcclient.egg-info/0000755000076500000240000000000000000000000020147 5ustar00maierastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623327804.0 zhmcclient-0.31.0/zhmcclient.egg-info/PKG-INFO0000644000076500000240000001725000000000000021251 0ustar00maierastaff00000000000000Metadata-Version: 2.1 Name: zhmcclient Version: 0.31.0 Summary: A pure Python client library for the IBM Z HMC Web Services API. Home-page: https://github.com/zhmcclient/python-zhmcclient Author: Juergen Leopold, Andreas Maier Author-email: leopoldj@de.ibm.com, maiera@de.ibm.com Maintainer: Andreas Maier, Kathir Velusamy Maintainer-email: maiera@de.ibm.com, kathir.velu@in.ibm.com License: Apache License, Version 2.0 Project-URL: Bug Tracker, https://github.com/zhmcclient/python-zhmcclient/issues Project-URL: Documentation, https://python-zhmcclient.readthedocs.io/en/stable/ Project-URL: Change Log, https://python-zhmcclient.readthedocs.io/en/stable/changes.html Platform: any Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* Description-Content-Type: text/x-rst License-File: LICENSE .. Copyright 2016-2017 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. .. zhmcclient - A pure Python client library for the IBM Z HMC Web Services API ============================================================================ .. PyPI download statistics are broken, but the new PyPI warehouse makes PyPI .. download statistics available through Google BigQuery .. (https://bigquery.cloud.google.com). .. Query to list package downloads by version: .. SELECT file.project, file.version, COUNT(*) as total_downloads, SUM(CASE WHEN REGEXP_EXTRACT(details.python, r"^([^\.]+\.[^\.]+)") = "2.6" THEN 1 ELSE 0 END) as py26_downloads, SUM(CASE WHEN REGEXP_EXTRACT(details.python, r"^([^\.]+\.[^\.]+)") = "2.7" THEN 1 ELSE 0 END) as py27_downloads, SUM(CASE WHEN REGEXP_EXTRACT(details.python, r"^([^\.]+)\.[^\.]+") = "3" THEN 1 ELSE 0 END) as py3_downloads, FROM TABLE_DATE_RANGE( [the-psf:pypi.downloads], TIMESTAMP("19700101"), CURRENT_TIMESTAMP() ) WHERE file.project = 'zhmcclient' GROUP BY file.project, file.version ORDER BY file.version DESC .. image:: https://img.shields.io/pypi/v/zhmcclient.svg :target: https://pypi.python.org/pypi/zhmcclient/ :alt: Version on Pypi .. # .. image:: https://img.shields.io/pypi/dm/zhmcclient.svg .. # :target: https://pypi.python.org/pypi/zhmcclient/ .. # :alt: Pypi downloads .. image:: https://github.com/zhmcclient/python-zhmcclient/workflows/test/badge.svg?branch=master :target: https://github.com/zhmcclient/python-zhmcclient/actions/ :alt: Actions status .. image:: https://readthedocs.org/projects/python-zhmcclient/badge/?version=latest :target: https://readthedocs.org/projects/python-zhmcclient/builds/ :alt: ReadTheDocs status .. image:: https://coveralls.io/repos/github/zhmcclient/python-zhmcclient/badge.svg?branch=master :target: https://coveralls.io/github/zhmcclient/python-zhmcclient?branch=master :alt: Coveralls status .. image:: https://codeclimate.com/github/zhmcclient/python-zhmcclient/badges/gpa.svg :target: https://codeclimate.com/github/zhmcclient/python-zhmcclient :alt: CodeClimate status .. contents:: Contents: :local: Overview ======== The zhmcclient package is a client library written in pure Python that interacts with the Web Services API of the Hardware Management Console (HMC) of `IBM Z`_ or `LinuxONE`_ machines. The goal of this package is to make the HMC Web Services API easily consumable for Python programmers. .. _IBM Z: http://www.ibm.com/systems/z/ .. _LinuxONE: http://www.ibm.com/systems/linuxone/ The HMC Web Services API is the access point for any external tools to manage the IBM Z or LinuxONE platform. It supports management of the lifecycle and configuration of various platform resources, such as partitions, CPU, memory, virtual switches, I/O adapters, and more. The zhmcclient package encapsulates both protocols supported by the HMC Web Services API: * REST over HTTPS for request/response-style operations driven by the client. Most of these operations complete synchronously, but some long-running tasks complete asynchronously. * JMS (Java Messaging Services) for notifications from the HMC to the client. This can be used to be notified about changes in the system, or about completion of asynchronous tasks started using REST. Installation ============ The quick way: .. code-block:: bash $ pip install zhmcclient For more details, see the `Installation section`_ in the documentation. .. _Installation section: http://python-zhmcclient.readthedocs.io/en/latest/intro.html#installation Quickstart =========== The following example code lists the machines (CPCs) managed by an HMC: .. code-block:: python #!/usr/bin/env python import zhmcclient import requests.packages.urllib3 requests.packages.urllib3.disable_warnings() # Set these variables for your environment: hmc_host = "" hmc_userid = "" hmc_password = "" session = zhmcclient.Session(hmc_host, hmc_userid, hmc_password) client = zhmcclient.Client(session) cpcs = client.cpcs.list() for cpc in cpcs: print(cpc) Possible output when running the script: .. code-block:: text Cpc(name=P000S67B, object-uri=/api/cpcs/fa1f2466-12df-311a-804c-4ed2cc1d6564, status=service-required) Documentation and Change Log ============================ For the latest released version on PyPI: * `Documentation`_ * `Change log`_ .. _Documentation: http://python-zhmcclient.readthedocs.io/en/latest/ .. _Change log: http://python-zhmcclient.readthedocs.io/en/latest/changes.html zhmc CLI ======== Before version 0.18.0 of the zhmcclient package, it contained the zhmc CLI. Starting with zhmcclient version 0.18.0, the zhmc CLI has been moved from this project into the new `zhmccli project`_. If your project uses the zhmc CLI, and you are upgrading the zhmcclient package from before 0.18.0 to 0.18.0 or later, your project will need to add the `zhmccli package`_ to its dependencies. .. _zhmccli project: https://github.com/zhmcclient/zhmccli .. _zhmccli package: https://pypi.python.org/pypi/zhmccli Contributing ============ For information on how to contribute to this project, see the `Development section`_ in the documentation. .. _Development section: http://python-zhmcclient.readthedocs.io/en/latest/development.html License ======= The zhmcclient package is licensed under the `Apache 2.0 License`_. .. _Apache 2.0 License: https://github.com/zhmcclient/python-zhmcclient/tree/master/LICENSE ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623327804.0 zhmcclient-0.31.0/zhmcclient.egg-info/SOURCES.txt0000644000076500000240000000300600000000000022032 0ustar00maierastaff00000000000000LICENSE MANIFEST.in README.rst requirements.txt setup.py zhmcclient/__init__.py zhmcclient/_activation_profile.py zhmcclient/_adapter.py zhmcclient/_capacity_group.py zhmcclient/_client.py zhmcclient/_console.py zhmcclient/_constants.py zhmcclient/_cpc.py zhmcclient/_exceptions.py zhmcclient/_hba.py zhmcclient/_ldap_server_definition.py zhmcclient/_logging.py zhmcclient/_lpar.py zhmcclient/_manager.py zhmcclient/_metrics.py zhmcclient/_nic.py zhmcclient/_notification.py zhmcclient/_partition.py zhmcclient/_password_rule.py zhmcclient/_port.py zhmcclient/_resource.py zhmcclient/_session.py zhmcclient/_storage_group.py zhmcclient/_storage_group_template.py zhmcclient/_storage_volume.py zhmcclient/_storage_volume_template.py zhmcclient/_task.py zhmcclient/_timestats.py zhmcclient/_unmanaged_cpc.py zhmcclient/_user.py zhmcclient/_user_pattern.py zhmcclient/_user_role.py zhmcclient/_utils.py zhmcclient/_version.py zhmcclient/_virtual_function.py zhmcclient/_virtual_storage_resource.py zhmcclient/_virtual_switch.py zhmcclient/debuginfo.py zhmcclient.egg-info/PKG-INFO zhmcclient.egg-info/SOURCES.txt zhmcclient.egg-info/dependency_links.txt zhmcclient.egg-info/requires.txt zhmcclient.egg-info/top_level.txt zhmcclient.egg-info/zip-safe zhmcclient/testutils/__init__.py zhmcclient/testutils/cpc_fixtures.py zhmcclient/testutils/hmc_definition_fixtures.py zhmcclient/testutils/hmc_definitions.py zhmcclient_mock/__init__.py zhmcclient_mock/_hmc.py zhmcclient_mock/_idpool.py zhmcclient_mock/_session.py zhmcclient_mock/_urihandler.py././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623327804.0 zhmcclient-0.31.0/zhmcclient.egg-info/dependency_links.txt0000644000076500000240000000000100000000000024215 0ustar00maierastaff00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623327804.0 zhmcclient-0.31.0/zhmcclient.egg-info/requires.txt0000644000076500000240000000057500000000000022556 0ustar00maierastaff00000000000000pytz>=2016.10 six>=1.14.0 immutable-views>=0.6.0 [:python_version <= "3.4"] decorator<5.0,>=4.0.11 [:python_version <= "3.5"] stomp.py<5.0.0,>=4.1.23 [:python_version == "2.7"] requests>=2.20.1 [:python_version == "3.4"] requests<2.22.0,>=2.20.1 [:python_version >= "3.5"] decorator>=4.0.11 requests>=2.20.1 [:python_version >= "3.6"] stomp.py!=6.1.0,!=6.1.1,<7.0.0,>=4.1.23 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623327804.0 zhmcclient-0.31.0/zhmcclient.egg-info/top_level.txt0000644000076500000240000000003300000000000022675 0ustar00maierastaff00000000000000zhmcclient zhmcclient_mock ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1618550725.0 zhmcclient-0.31.0/zhmcclient.egg-info/zip-safe0000644000076500000240000000000100000000000021577 0ustar00maierastaff00000000000000 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1623327804.7527256 zhmcclient-0.31.0/zhmcclient_mock/0000755000076500000240000000000000000000000017466 5ustar00maierastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient_mock/__init__.py0000644000076500000240000000163700000000000021606 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ zhmcclient_mock - Unit test support for users of the zhmcclient package. """ from __future__ import absolute_import from ._session import * # noqa: F401 pylint: disable=redefined-builtin from ._urihandler import * # noqa: F401 from ._hmc import * # noqa: F401 from ._idpool import * # noqa: F401 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1623324983.0 zhmcclient-0.31.0/zhmcclient_mock/_hmc.py0000644000076500000240000034653700000000000020770 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ The `zhmcclient_mock` package provides a faked HMC with all resources that are relevant for the `zhmcclient` package. The faked HMC is implemented as a local Python object and maintains its resource state across operations. """ from __future__ import absolute_import try: from collections import OrderedDict except ImportError: from ordereddict import OrderedDict import re import copy import six from zhmcclient._utils import repr_dict, repr_manager, repr_list, \ timestamp_from_datetime from ._idpool import IdPool __all__ = ['InputError', 'FakedBaseResource', 'FakedBaseManager', 'FakedHmc', 'FakedConsoleManager', 'FakedConsole', 'FakedUserManager', 'FakedUser', 'FakedUserRoleManager', 'FakedUserRole', 'FakedUserPatternManager', 'FakedUserPattern', 'FakedPasswordRuleManager', 'FakedPasswordRule', 'FakedTaskManager', 'FakedTask', 'FakedLdapServerDefinitionManager', 'FakedLdapServerDefinition', 'FakedActivationProfileManager', 'FakedActivationProfile', 'FakedAdapterManager', 'FakedAdapter', 'FakedCpcManager', 'FakedCpc', 'FakedUnmanagedCpcManager', 'FakedUnmanagedCpc', 'FakedHbaManager', 'FakedHba', 'FakedLparManager', 'FakedLpar', 'FakedNicManager', 'FakedNic', 'FakedPartitionManager', 'FakedPartition', 'FakedPortManager', 'FakedPort', 'FakedVirtualFunctionManager', 'FakedVirtualFunction', 'FakedVirtualSwitchManager', 'FakedVirtualSwitch', 'FakedStorageGroupManager', 'FakedStorageGroup', 'FakedMetricsContextManager', 'FakedMetricsContext', 'FakedMetricGroupDefinition', 'FakedMetricObjectValues', 'FakedCapacityGroupManager', 'FakedCapacityGroup', ] class InputError(Exception): """ An error that is raised by the faked resource classes and indicates that the input is invalid in some way. ``args[0]`` will be set to a message detailing the issue. """ def __init__(self, message): # pylint: disable=useless-super-delegation super(InputError, self).__init__(message) class FakedBaseResource(object): """ A base class for faked resource classes in the faked HMC. """ def __init__(self, manager, properties): self._manager = manager # May be None if properties is not None: self._properties = copy.deepcopy(properties) else: self._properties = {} if self.manager: if self.manager.oid_prop is None: self._oid = None else: if self.manager.oid_prop not in self._properties: new_oid = self.manager._new_oid() self._properties[self.manager.oid_prop] = new_oid self._oid = self._properties[self.manager.oid_prop] if self.manager.uri_prop not in self._properties: new_uri = self.manager.base_uri if self.oid is not None: new_uri += '/' + self.oid self._properties[self.manager.uri_prop] = new_uri self._uri = self._properties[self.manager.uri_prop] if self.manager.class_value: if 'class' not in self._properties: self._properties['class'] = self.manager.class_value if self.manager.parent: if 'parent' not in self._properties: self._properties['parent'] = self.manager.parent.uri else: self._oid = None self._uri = None def __repr__(self): """ Return a string with the state of this faked resource, for debug purposes. Note that the derived faked resource classes that have child resources have their own __repr__() methods, because only they know which child resources they have. """ ret = ( "{classname} at 0x{id:08x} (\n" " _manager = {_manager_classname} at 0x{_manager_id:08x}\n" " _oid = {_oid!r}\n" " _uri = {_uri!r}\n" " _properties = {_properties}\n" ")".format( classname=self.__class__.__name__, id=id(self), _manager_classname=self._manager.__class__.__name__, _manager_id=id(self._manager), _oid=self._oid, _uri=self._uri, _properties=repr_dict(self._properties, indent=2), )) return ret @property def manager(self): """ The manager for this resource (a derived class of :class:`~zhmcclient_mock.FakedBaseManager`). """ return self._manager @property def properties(self): """ The properties of this resource (a dictionary). """ return self._properties @property def oid(self): """ The object ID (property 'object-id' or 'element-id') of this resource. """ return self._oid @property def uri(self): """ The object URI (property 'object-uri' or 'element-uri') of this resource. """ return self._uri @property def name(self): """ The name (property 'name') of this resource. Raises: :exc:`KeyError`: Resource does not have a 'name' property. """ return self._properties['name'] def update(self, properties): """ update the properties of this resource. Parameters: properties (dict): Resource properties to be updated. Any other properties remain unchanged. """ self._properties.update(properties) def add_resources(self, resources): """ Add faked child resources to this resource, from the provided resource definitions. Duplicate resource names in the same scope are not permitted. Although this method is typically used to initially load the faked HMC with resource state just once, it can be invoked multiple times and can also be invoked on faked resources (e.g. on a faked CPC). Parameters: resources (dict): resource dictionary with definitions of faked child resources to be added. For an explanation of how the resource dictionary is set up, see the examples below. For requirements on and auto-generation of certain resource properties, see the ``add()`` methods of the various faked resource managers (e.g. :meth:`zhmcclient_mock.FakedCpcManager.add`). For example, the object-id or element-id properties and the corresponding uri properties are always auto-generated. The resource dictionary specifies a tree of resource managers and resources, in an alternating manner. It starts with the resource managers of child resources of the target resource, which contains a list of those child resources. For an HMC, the CPCs managed by the HMC would be its child resources. Each resource specifies its own properties (``properties`` key) and the resource managers for its child resources. For example, the CPC resource specifies its adapter child resources using the ``adapters`` key. The keys for the child resource managers are the attribute names of these resource managers in the parent resource. For example, the ``adapters`` key is named after the :attr:`zhmcclient.Cpc.adapters` attribute (which has the same name as in its corresponding faked CPC resource: :attr:`zhmcclient_mock.FakedCpc.adapters`). Raises: :exc:`zhmcclient_mock.InputError`: Some issue with the input resources. Examples: Example for targeting a faked HMC for adding a CPC with one adapter:: resources = { 'cpcs': [ # name of manager attribute for this resource { 'properties': { 'name': 'cpc_1', }, 'adapters': [ # name of manager attribute for this # resource { 'properties': { 'object-id': '12', 'name': 'ad_1', }, 'ports': [ { 'properties': { 'name': 'port_1', } }, ], }, ], }, ], } Example for targeting a faked CPC for adding an LPAR and a load activation profile:: resources = { 'lpars': [ # name of manager attribute for this resource { 'properties': { # object-id is not provided -> auto-generated # object-uri is not provided -> auto-generated 'name': 'lpar_1', }, }, ], 'load_activation_profiles': [ # name of manager attribute { 'properties': { # object-id is not provided -> auto-generated # object-uri is not provided -> auto-generated 'name': 'lpar_1', }, }, ], } """ for child_attr in resources: child_list = resources[child_attr] self._process_child_list(self, child_attr, child_list) def _process_child_list(self, parent_resource, child_attr, child_list): """ Add properties of child resources. """ child_manager = getattr(parent_resource, child_attr, None) if child_manager is None: raise InputError("Invalid child resource type specified in " "resource dictionary: {}".format(child_attr)) for child_dict in child_list: # child_dict is a dict of 'properties' and grand child resources properties = child_dict.get('properties', None) if properties is None: raise InputError("A resource for resource type {} has no" "properties specified.".format(child_attr)) child_resource = child_manager.add(properties) for grandchild_attr in child_dict: if grandchild_attr == 'properties': continue grandchild_list = child_dict[grandchild_attr] self._process_child_list(child_resource, grandchild_attr, grandchild_list) class FakedBaseManager(object): """ A base class for manager classes for faked resources in the faked HMC. """ api_root = '/api' # root of all resource URIs next_oid = 1 # next object ID, for auto-generating them def __init__(self, hmc, parent, resource_class, base_uri, oid_prop, uri_prop, class_value): self._hmc = hmc self._parent = parent self._resource_class = resource_class self._base_uri = base_uri # Base URI for resources of this type self._oid_prop = oid_prop self._uri_prop = uri_prop self._class_value = class_value # List of Faked{Resource} objects in this faked manager, by object ID self._resources = OrderedDict() def __repr__(self): """ Return a string with the state of this faked manager, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " _hmc = {_hmc_classname} at 0x{_hmc_id:08x}\n" " _parent = {_parent_classname} at 0x{_parent_id:08x}\n" " _resource_class = {_resource_class!r}\n" " _base_uri = {_base_uri!r}\n" " _oid_prop = {_oid_prop!r}\n" " _uri_prop = {_uri_prop!r}\n" " _class_value = {_class_value!r}\n" " _resources = {_resources}\n" ")".format( classname=self.__class__.__name__, id=id(self), _hmc_classname=self._hmc.__class__.__name__, _hmc_id=id(self._hmc), _parent_classname=self._parent.__class__.__name__, _parent_id=id(self._parent), _resource_class=self._resource_class, _base_uri=self._base_uri, _oid_prop=self._oid_prop, _uri_prop=self._uri_prop, _class_value=self._class_value, _resources=repr_dict(self._resources, indent=2), )) return ret def _matches_filters(self, obj, filter_args): """ Return a boolean indicating whether a faked resource object matches a set of filter arguments. This is used for implementing filtering in the faked resource managers. Parameters: obj (FakedBaseResource): Resource object. filter_args (dict): Filter arguments. For details, see :ref:`Filtering`. `None` causes the resource to always match. Returns: bool: Boolean indicating whether the resource object matches the filter arguments. """ if filter_args is not None: for prop_name in filter_args: prop_match = filter_args[prop_name] if not self._matches_prop(obj, prop_name, prop_match): return False return True def _matches_prop(self, obj, prop_name, prop_match): """ Return a boolean indicating whether a faked resource object matches with a single property against a property match value. This is used for implementing filtering in the faked resource managers. Parameters: obj (FakedBaseResource): Resource object. prop_match: Property match value that is used to match the actual value of the specified property against, as follows: - If the match value is a list or tuple, this method is invoked recursively to find whether one or more match values in the list match. - Else if the property is of string type, its value is matched by interpreting the match value as a regular expression. - Else the property value is matched by exact value comparison with the match value. Returns: bool: Boolean indicating whether the resource object matches w.r.t. the specified property and the match value. """ if isinstance(prop_match, (list, tuple)): # List items are logically ORed, so one matching item suffices. for pm in prop_match: if self._matches_prop(obj, prop_name, pm): return True else: # Some lists of resources do not have all properties, for example # Hipersocket adapters do not have a "card-location" property. # If a filter property does not exist on a resource, the resource # does not match. if prop_name not in obj.properties: return False prop_value = obj.properties[prop_name] if isinstance(prop_value, six.string_types): # HMC resource property is Enum String or (non-enum) String, # and is both matched by regexp matching. Ideally, regexp # matching should only be done for non-enum strings, but # distinguishing them is not possible given that the client # has no knowledge about the properties. # The regexp matching implemented in the HMC requires begin and # end of the string value to match, even if the '^' for begin # and '$' for end are not specified in the pattern. The code # here is consistent with that: We add end matching to the # pattern, and begin matching is done by re.match() # automatically. re_match = prop_match + '$' m = re.match(re_match, prop_value) if m: return True else: if prop_value == prop_match: return True return False @property def hmc(self): """ The faked HMC this manager is part of (an object of :class:`~zhmcclient_mock.FakedHmc`). """ return self._hmc @property def parent(self): """ The parent (scoping resource) for this manager (an object of a derived class of :class:`~zhmcclient_mock.FakedBaseResource`). """ return self._parent @property def resource_class(self): """ The resource class managed by this manager (a derived class of :class:`~zhmcclient_mock.FakedBaseResource`). """ return self._resource_class @property def base_uri(self): """ The base URI for URIs of resources managed by this manager. """ return self._base_uri @property def oid_prop(self): """ The name of the resource property for the object ID ('object-id' or 'element-id' or 'name'). """ return self._oid_prop @property def uri_prop(self): """ The name of the resource property for the object URI ('object-uri' or 'element-uri'). """ return self._uri_prop @property def class_value(self): """ The value for the "class" property of resources managed by this manager, as defined in the data model for the resource. For example, for LPAR resources this is set to 'logical-partition'. """ return self._class_value def _new_oid(self): """ Return a new OID. """ new_oid = self.next_oid self.next_oid += 1 return str(new_oid) def add(self, properties): """ Add a faked resource to this manager. For URI-based lookup, the resource is also added to the faked HMC. Parameters: properties (dict): Resource properties. If the URI property (e.g. 'object-uri') or the object ID property (e.g. 'object-id') are not specified, they will be auto-generated. Returns: FakedBaseResource: The faked resource object. """ resource = self.resource_class(self, properties) self._resources[resource.oid] = resource self._hmc.all_resources[resource.uri] = resource return resource def remove(self, oid): """ Remove a faked resource from this manager. Parameters: oid (string): The object ID of the resource (e.g. value of the 'object-uri' property). """ uri = self._resources[oid].uri del self._resources[oid] del self._hmc.all_resources[uri] def list(self, filter_args=None): """ List the faked resources of this manager. Parameters: filter_args (dict): Filter arguments. `None` causes no filtering to happen. See :meth:`~zhmcclient.BaseManager.list()` for details. Returns: list of FakedBaseResource: The faked resource objects of this manager. """ res = list() for oid in self._resources: resource = self._resources[oid] if self._matches_filters(resource, filter_args): res.append(resource) return res def lookup_by_oid(self, oid): """ Look up a faked resource by its object ID, in the scope of this manager. Parameters: oid (string): The object ID of the faked resource (e.g. value of the 'object-id' property). Returns: FakedBaseResource: The faked resource object. Raises: KeyError: No resource found for this object ID. """ return self._resources[oid] class FakedHmc(FakedBaseResource): """ A faked HMC. Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. An object of this class represents a faked HMC that can have all faked resources that are relevant for the zhmcclient package. The Python API to this class and its child resource classes is not compatible with the zhmcclient API. Instead, these classes serve as an in-memory backend for a faked session class (see :class:`zhmcclient_mock.FakedSession`) that replaces the normal :class:`zhmcclient.Session` class. Objects of this class should not be created by the user. Instead, access the :attr:`zhmcclient_mock.FakedSession.hmc` attribute. """ def __init__(self, hmc_name, hmc_version, api_version): super(FakedHmc, self).__init__(manager=None, properties=None) self.hmc_name = hmc_name self.hmc_version = hmc_version self.api_version = api_version self.cpcs = FakedCpcManager(hmc=self, client=self) self.metrics_contexts = FakedMetricsContextManager( hmc=self, client=self) self.consoles = FakedConsoleManager(hmc=self, client=self) # Flat list of all Faked{Resource} objs in this faked HMC, by URI: self.all_resources = {} self._enabled = True def __repr__(self): """ Return a string with the state of this faked HMC, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " hmc_name = {hmc_name!r}\n" " hmc_version = {hmc_version!r}\n" " api_version = {api_version!r}\n" " enabled = {enabled!r}\n" " cpcs = {cpcs}\n" " metrics_contexts = {metrics_contexts}\n" " consoles = {consoles}\n" " all_resources (keys only) = {all_resource_keys}\n" ")".format( classname=self.__class__.__name__, id=id(self), hmc_name=self.hmc_name, hmc_version=self.hmc_version, api_version=self.api_version, enabled=self.enabled, cpcs=repr_manager(self.cpcs, indent=2), metrics_contexts=repr_manager(self.metrics_contexts, indent=2), consoles=repr_manager(self.consoles, indent=2), all_resource_keys=repr_list(self.all_resources.keys(), indent=2), )) return ret @property def enabled(self): """ Return whether the faked HMC is enabled. """ return self._enabled def enable(self): """ Enable the faked HMC. """ self._enabled = True def disable(self): """ Disable the faked HMC. This will cause an error to be raised when a faked session attempts to communicate with the disabled HMC. """ self._enabled = False def lookup_by_uri(self, uri): """ Look up a faked resource by its object URI, within this faked HMC. Parameters: uri (string): The object URI of the faked resource (e.g. value of the 'object-uri' property). Returns: :class:`~zhmcclient_mock.FakedBaseResource`: The faked resource. Raises: KeyError: No resource found for this object ID. """ return self.all_resources[uri] class FakedConsoleManager(FakedBaseManager): """ A manager for faked Console resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, client): super(FakedConsoleManager, self).__init__( hmc=hmc, parent=client, resource_class=FakedConsole, base_uri=self.api_root + '/console', oid_prop=None, # Console does not have an object ID property uri_prop='object-uri', class_value='console') self._console = None @property def console(self): """ The faked Console representing the faked HMC (an object of :class:`~zhmcclient_mock.FakedConsole`). The object is cached. """ if self._console is None: self._console = self.list()[0] return self._console def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked Console resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'object-uri' will be auto-generated to '/api/console', if not specified. * 'class' will be auto-generated to 'console', if not specified. Returns: :class:`~zhmcclient_mock.FakedConsole`: The faked Console resource. """ return super(FakedConsoleManager, self).add(properties) class FakedConsole(FakedBaseResource): """ A faked Console resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedConsole, self).__init__( manager=manager, properties=properties) self._storage_groups = FakedStorageGroupManager( hmc=manager.hmc, console=self) self._users = FakedUserManager(hmc=manager.hmc, console=self) self._user_roles = FakedUserRoleManager(hmc=manager.hmc, console=self) self._user_patterns = FakedUserPatternManager( hmc=manager.hmc, console=self) self._password_rules = FakedPasswordRuleManager( hmc=manager.hmc, console=self) self._tasks = FakedTaskManager(hmc=manager.hmc, console=self) self._ldap_server_definitions = FakedLdapServerDefinitionManager( hmc=manager.hmc, console=self) self._unmanaged_cpcs = FakedUnmanagedCpcManager( hmc=manager.hmc, console=self) def __repr__(self): """ Return a string with the state of this faked Console resource, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " _manager = {manager_classname} at 0x{manager_id:08x}\n" " _manager._parent._uri = {parent_uri!r}\n" " _uri = {_uri!r}\n" " _properties = {_properties}\n" " _storage_groups = {_storage_groups}\n" " _users = {_users}\n" " _user_roles = {_user_roles}\n" " _user_patterns = {_user_patterns}\n" " _password_rules = {_password_rules}\n" " _tasks = {_tasks}\n" " _ldap_server_definitions = {_ldap_server_definitions}\n" " _unmanaged_cpcs = {_unmanaged_cpcs}\n" ")".format( classname=self.__class__.__name__, id=id(self), manager_classname=self._manager.__class__.__name__, manager_id=id(self._manager), parent_uri=self._manager.parent.uri, _uri=self._uri, _properties=repr_dict(self._properties, indent=2), _storage_groups=repr_manager(self.storage_groups, indent=2), _users=repr_manager(self.users, indent=2), _user_roles=repr_manager(self.user_roles, indent=2), _user_patterns=repr_manager(self.user_patterns, indent=2), _password_rules=repr_manager(self.password_rules, indent=2), _tasks=repr_manager(self.tasks, indent=2), _ldap_server_definitions=repr_manager( self.ldap_server_definitions, indent=2), _unmanaged_cpcs=repr_manager(self.unmanaged_cpcs, indent=2), )) return ret @property def storage_groups(self): """ :class:`~zhmcclient_mock.FakedStorageGroupManager`: Access to the faked Storage Group resources of this Console. """ return self._storage_groups @property def users(self): """ :class:`~zhmcclient_mock.FakedUserManager`: Access to the faked User resources of this Console. """ return self._users @property def user_roles(self): """ :class:`~zhmcclient_mock.FakedUserRoleManager`: Access to the faked User Role resources of this Console. """ return self._user_roles @property def user_patterns(self): """ :class:`~zhmcclient_mock.FakedUserPatternManager`: Access to the faked User Pattern resources of this Console. """ return self._user_patterns @property def password_rules(self): """ :class:`~zhmcclient_mock.FakedPasswordRulesManager`: Access to the faked Password Rule resources of this Console. """ return self._password_rules @property def tasks(self): """ :class:`~zhmcclient_mock.FakedTaskManager`: Access to the faked Task resources of this Console. """ return self._tasks @property def ldap_server_definitions(self): """ :class:`~zhmcclient_mock.FakedLdapServerDefinitionManager`: Access to the faked LDAP Server Definition resources of this Console. """ return self._ldap_server_definitions @property def unmanaged_cpcs(self): """ :class:`~zhmcclient_mock.FakedUnmanagedCpcManager`: Access to the faked unmanaged CPC resources of this Console. """ return self._unmanaged_cpcs class FakedUserManager(FakedBaseManager): """ A manager for faked User resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, console): super(FakedUserManager, self).__init__( hmc=hmc, parent=console, resource_class=FakedUser, base_uri=self.api_root + '/users', oid_prop='object-id', uri_prop='object-uri', class_value='user') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked User resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'object-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'object-uri' will be auto-generated based upon the object ID, if not specified. * 'class' will be auto-generated to 'user', if not specified. Returns: :class:`~zhmcclient_mock.FakedUser`: The faked User resource. """ return super(FakedUserManager, self).add(properties) class FakedUser(FakedBaseResource): """ A faked User resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedUser, self).__init__( manager=manager, properties=properties) class FakedUserRoleManager(FakedBaseManager): """ A manager for faked User Role resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, console): super(FakedUserRoleManager, self).__init__( hmc=hmc, parent=console, resource_class=FakedUserRole, base_uri=self.api_root + '/user-roles', oid_prop='object-id', uri_prop='object-uri', class_value='user-role') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked User Role resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'object-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'object-uri' will be auto-generated based upon the object ID, if not specified. * 'class' will be auto-generated to 'user-role', if not specified. Returns: :class:`~zhmcclient_mock.FakedUserRole`: The faked User Role resource. """ return super(FakedUserRoleManager, self).add(properties) class FakedUserRole(FakedBaseResource): """ A faked User Role resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedUserRole, self).__init__( manager=manager, properties=properties) class FakedUserPatternManager(FakedBaseManager): """ A manager for faked User Pattern resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, console): super(FakedUserPatternManager, self).__init__( hmc=hmc, parent=console, resource_class=FakedUserPattern, base_uri=self.api_root + '/console/user-patterns', oid_prop='element-id', uri_prop='element-uri', class_value='user-pattern') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked User Pattern resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'element-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'element-uri' will be auto-generated based upon the element ID, if not specified. * 'class' will be auto-generated to 'user-pattern', if not specified. Returns: :class:`~zhmcclient_mock.FakedUserPattern`: The faked User Pattern resource. """ return super(FakedUserPatternManager, self).add(properties) class FakedUserPattern(FakedBaseResource): """ A faked User Pattern resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedUserPattern, self).__init__( manager=manager, properties=properties) class FakedPasswordRuleManager(FakedBaseManager): """ A manager for faked Password Rule resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, console): super(FakedPasswordRuleManager, self).__init__( hmc=hmc, parent=console, resource_class=FakedPasswordRule, base_uri=self.api_root + '/console/password-rules', oid_prop='element-id', uri_prop='element-uri', class_value='password-rule') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked Password Rule resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'element-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'element-uri' will be auto-generated based upon the element ID, if not specified. * 'class' will be auto-generated to 'password-rule', if not specified. Returns: :class:`~zhmcclient_mock.FakedPasswordRule`: The faked Password Rule resource. """ return super(FakedPasswordRuleManager, self).add(properties) class FakedPasswordRule(FakedBaseResource): """ A faked Password Rule resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedPasswordRule, self).__init__( manager=manager, properties=properties) class FakedTaskManager(FakedBaseManager): """ A manager for faked Task resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, console): super(FakedTaskManager, self).__init__( hmc=hmc, parent=console, resource_class=FakedTask, base_uri=self.api_root + '/console/tasks', oid_prop='element-id', uri_prop='element-uri', class_value='task') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked Task resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'element-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'element-uri' will be auto-generated based upon the element ID, if not specified. * 'class' will be auto-generated to 'task', if not specified. Returns: :class:`~zhmcclient_mock.FakedTask`: The faked Task resource. """ return super(FakedTaskManager, self).add(properties) class FakedTask(FakedBaseResource): """ A faked Task resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedTask, self).__init__( manager=manager, properties=properties) class FakedLdapServerDefinitionManager(FakedBaseManager): """ A manager for faked LDAP Server Definition resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, console): super(FakedLdapServerDefinitionManager, self).__init__( hmc=hmc, parent=console, resource_class=FakedLdapServerDefinition, base_uri=self.api_root + '/console/ldap-server-definitions', oid_prop='element-id', uri_prop='element-uri', class_value='ldap-server-definition') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked LDAP Server Definition resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'element-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'element-uri' will be auto-generated based upon the element ID, if not specified. * 'class' will be auto-generated to 'ldap-server-definition', if not specified. Returns: :class:`~zhmcclient_mock.FakedLdapServerDefinition`: The faked LdapServerDefinition resource. """ return super(FakedLdapServerDefinitionManager, self).add(properties) class FakedLdapServerDefinition(FakedBaseResource): """ A faked LDAP Server Definition resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedLdapServerDefinition, self).__init__( manager=manager, properties=properties) class FakedActivationProfileManager(FakedBaseManager): """ A manager for faked Activation Profile resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, cpc, profile_type): ap_uri_segment = profile_type + '-activation-profiles' ap_class_value = profile_type + '-activation-profile' super(FakedActivationProfileManager, self).__init__( hmc=hmc, parent=cpc, resource_class=FakedActivationProfile, base_uri=cpc.uri + '/' + ap_uri_segment, oid_prop='name', # This is an exception! uri_prop='element-uri', class_value=ap_class_value) self._profile_type = profile_type def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked Activation Profile resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'name' (the OID property for this resource type!) will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'element-uri' will be auto-generated based upon the OID ('name') property, if not specified. * 'class' will be auto-generated to '{profile_type}'-activation-profile', if not specified. Returns: :class:`~zhmcclient_mock.FakedActivationProfile`: The faked Activation Profile resource. """ return super(FakedActivationProfileManager, self).add(properties) @property def profile_type(self): """ Type of the activation profile ('reset', 'image', 'load'). """ return self._profile_type class FakedActivationProfile(FakedBaseResource): """ A faked Activation Profile resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedActivationProfile, self).__init__( manager=manager, properties=properties) class FakedAdapterManager(FakedBaseManager): """ A manager for faked Adapter resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, cpc): super(FakedAdapterManager, self).__init__( hmc=hmc, parent=cpc, resource_class=FakedAdapter, base_uri=self.api_root + '/adapters', oid_prop='object-id', uri_prop='object-uri', class_value='adapter') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked Adapter resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'object-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'object-uri' will be auto-generated based upon the object ID, if not specified. * 'class' will be auto-generated to 'adapter', if not specified. * 'status' is auto-set to 'active', if not specified. * 'adapter-family' or 'type' is required to be specified, in order to determine whether the adapter is a network or storage adapter. * 'adapter-family' is auto-set based upon 'type', if not specified. * For network adapters, 'network-port-uris' is auto-set to an empty list, if not specified. * For storage adapters, 'storage-port-uris' is auto-set to an empty list, if not specified. Returns: :class:`~zhmcclient_mock.FakedAdapter`: The faked Adapter resource. Raises: :exc:`zhmcclient_mock.InputError`: Some issue with the input properties. """ return super(FakedAdapterManager, self).add(properties) class FakedAdapter(FakedBaseResource): """ A faked Adapter resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedAdapter, self).__init__( manager=manager, properties=properties) # TODO: Maybe move this stuff into AdapterManager.add()? if 'adapter-family' in self._properties: family = self._properties['adapter-family'] if family in ('osa', 'roce', 'hipersockets'): self._adapter_kind = 'network' elif family in ('ficon',): self._adapter_kind = 'storage' else: self._adapter_kind = 'other' elif 'type' in self._properties: # because 'type' is more specific than 'adapter-family', we can # auto-set 'adapter-family' from 'type'. type_ = self._properties['type'] if type_ in ('osd', 'osm'): self._properties['adapter-family'] = 'osa' self._adapter_kind = 'network' elif type_ == 'roce': self._properties['adapter-family'] = 'roce' self._adapter_kind = 'network' elif type_ == 'hipersockets': self._properties['adapter-family'] = 'hipersockets' self._adapter_kind = 'network' elif type_ in ('fcp', 'fc'): self._properties['adapter-family'] = 'ficon' self._adapter_kind = 'storage' elif type_ == 'crypto': self._properties['adapter-family'] = 'crypto' self._adapter_kind = 'other' elif type_ == 'zedc': self._properties['adapter-family'] = 'accelerator' self._adapter_kind = 'other' else: raise InputError("FakedAdapter with object-id=%s has an " "unknown value in its 'type' property: %s." % (self.oid, type_)) else: raise InputError("FakedAdapter with object-id=%s must have " "'adapter-family' or 'type' property specified." % self.oid) if self.adapter_kind == 'network': if 'network-port-uris' not in self._properties: self._properties['network-port-uris'] = [] self._ports = FakedPortManager(hmc=manager.hmc, adapter=self) elif self.adapter_kind == 'storage': if 'storage-port-uris' not in self._properties: self._properties['storage-port-uris'] = [] self._ports = FakedPortManager(hmc=manager.hmc, adapter=self) else: self._ports = None if 'status' not in self._properties: self._properties['status'] = 'active' def __repr__(self): """ Return a string with the state of this faked Adapter resource, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " _manager = {manager_classname} at 0x{manager_id:08x}\n" " _manager._parent._uri = {parent_uri!r}\n" " _uri = {_uri!r}\n" " _properties = {_properties}\n" " _ports = {_ports}\n" ")".format( classname=self.__class__.__name__, id=id(self), manager_classname=self._manager.__class__.__name__, manager_id=id(self._manager), parent_uri=self._manager.parent.uri, _uri=self._uri, _properties=repr_dict(self._properties, indent=2), _ports=repr_manager(self.ports, indent=2), )) return ret @property def ports(self): """ :class:`~zhmcclient_mock.FakedPort`: The Port resources of this Adapter. If the kind of adapter does not have ports, this is `None`. """ return self._ports @property def adapter_kind(self): """ string: The kind of adapter, determined from the 'adapter-family' or 'type' properties. This is currently used to distinguish storage and network adapters. Possible values are: * 'network' - A network adapter (OSA, ROCE, Hipersockets) * 'storage' - A storage adapter (FICON, FCP) * 'other' - Another adapter (zEDC, Crypto) """ return self._adapter_kind class FakedCpcManager(FakedBaseManager): """ A manager for faked managed CPC resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, client): super(FakedCpcManager, self).__init__( hmc=hmc, parent=client, resource_class=FakedCpc, base_uri=self.api_root + '/cpcs', oid_prop='object-id', uri_prop='object-uri', class_value='cpc') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked CPC resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'object-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'object-uri' will be auto-generated based upon the object ID, if not specified. * 'class' will be auto-generated to 'cpc', if not specified. * 'dpm-enabled' is auto-set to `False`, if not specified. * 'is-ensemble-member' is auto-set to `False`, if not specified. * 'status' is auto-set, if not specified, as follows: If the 'dpm-enabled' property is `True`, it is set to 'active'; otherwise it is set to 'operating'. Returns: :class:`~zhmcclient_mock.FakedCpc`: The faked CPC resource. """ return super(FakedCpcManager, self).add(properties) class FakedCpc(FakedBaseResource): """ A faked managed CPC resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedCpc, self).__init__( manager=manager, properties=properties) self._lpars = FakedLparManager(hmc=manager.hmc, cpc=self) self._partitions = FakedPartitionManager(hmc=manager.hmc, cpc=self) self._adapters = FakedAdapterManager(hmc=manager.hmc, cpc=self) self._virtual_switches = FakedVirtualSwitchManager( hmc=manager.hmc, cpc=self) self._capacity_groups = FakedCapacityGroupManager( hmc=manager.hmc, cpc=self) self._reset_activation_profiles = FakedActivationProfileManager( hmc=manager.hmc, cpc=self, profile_type='reset') self._image_activation_profiles = FakedActivationProfileManager( hmc=manager.hmc, cpc=self, profile_type='image') self._load_activation_profiles = FakedActivationProfileManager( hmc=manager.hmc, cpc=self, profile_type='load') if 'dpm-enabled' not in self._properties: self._properties['dpm-enabled'] = False if 'is-ensemble-member' not in self._properties: self._properties['is-ensemble-member'] = False if 'status' not in self._properties: if self.dpm_enabled: self._properties['status'] = 'active' else: self._properties['status'] = 'operating' def __repr__(self): """ Return a string with the state of this faked Cpc resource, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " _manager = {manager_classname} at 0x{manager_id:08x}\n" " _manager._parent._uri = {parent_uri!r}\n" " _uri = {_uri!r}\n" " _properties = {_properties}\n" " _lpars = {_lpars}\n" " _partitions = {_partitions}\n" " _adapters = {_adapters}\n" " _virtual_switches = {_virtual_switches}\n" " _capacity_groups = {_capacity_groups}\n" " _reset_activation_profiles = {_reset_activation_profiles}\n" " _image_activation_profiles = {_image_activation_profiles}\n" " _load_activation_profiles = {_load_activation_profiles}\n" ")".format( classname=self.__class__.__name__, id=id(self), manager_classname=self._manager.__class__.__name__, manager_id=id(self._manager), parent_uri=self._manager.parent.uri, _uri=self._uri, _properties=repr_dict(self._properties, indent=2), _lpars=repr_manager(self.lpars, indent=2), _partitions=repr_manager(self.partitions, indent=2), _adapters=repr_manager(self.adapters, indent=2), _virtual_switches=repr_manager( self.virtual_switches, indent=2), _capacity_groups=repr_manager( self.capacity_groups, indent=2), _reset_activation_profiles=repr_manager( self.reset_activation_profiles, indent=2), _image_activation_profiles=repr_manager( self.image_activation_profiles, indent=2), _load_activation_profiles=repr_manager( self.load_activation_profiles, indent=2), )) return ret @property def dpm_enabled(self): """ bool: Indicates whether this CPC is in DPM mode. This returns the value of the 'dpm-enabled' property. """ return self._properties['dpm-enabled'] @property def lpars(self): """ :class:`~zhmcclient_mock.FakedLparManager`: Access to the faked LPAR resources of this CPC. """ return self._lpars @property def partitions(self): """ :class:`~zhmcclient_mock.FakedPartitionManager`: Access to the faked Partition resources of this CPC. """ return self._partitions @property def adapters(self): """ :class:`~zhmcclient_mock.FakedAdapterManager`: Access to the faked Adapter resources of this CPC. """ return self._adapters @property def virtual_switches(self): """ :class:`~zhmcclient_mock.FakedVirtualSwitchManager`: Access to the faked Virtual Switch resources of this CPC. """ return self._virtual_switches @property def capacity_groups(self): """ :class:`~zhmcclient_mock.FakedCapacityGroupManager`: Access to the faked Capacity Group resources of this CPC. """ return self._capacity_groups @property def reset_activation_profiles(self): """ :class:`~zhmcclient_mock.FakedActivationProfileManager`: Access to the faked Reset Activation Profile resources of this CPC. """ return self._reset_activation_profiles @property def image_activation_profiles(self): """ :class:`~zhmcclient_mock.FakedActivationProfileManager`: Access to the faked Image Activation Profile resources of this CPC. """ return self._image_activation_profiles @property def load_activation_profiles(self): """ :class:`~zhmcclient_mock.FakedActivationProfileManager`: Access to the faked Load Activation Profile resources of this CPC. """ return self._load_activation_profiles class FakedUnmanagedCpcManager(FakedBaseManager): """ A manager for faked unmanaged CPC resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, console): super(FakedUnmanagedCpcManager, self).__init__( hmc=hmc, parent=console, resource_class=FakedUnmanagedCpc, base_uri=self.api_root + '/cpcs', oid_prop='object-id', uri_prop='object-uri', class_value=None) def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked unmanaged CPC resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'object-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'object-uri' will be auto-generated based upon the object ID, if not specified. Returns: :class:`~zhmcclient_mock.FakedUnmanagedCpc`: The faked unmanaged CPC resource. """ return super(FakedUnmanagedCpcManager, self).add(properties) class FakedUnmanagedCpc(FakedBaseResource): """ A faked unmanaged CPC resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedUnmanagedCpc, self).__init__( manager=manager, properties=properties) def __repr__(self): """ Return a string with the state of this faked unmanaged Cpc resource, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " _manager = {manager_classname} at 0x{manager_id:08x}\n" " _manager._parent._uri = {parent_uri!r}\n" " _uri = {_uri!r}\n" " _properties = {_properties}\n" ")".format( classname=self.__class__.__name__, id=id(self), manager_classname=self._manager.__class__.__name__, manager_id=id(self._manager), parent_uri=self._manager.parent.uri, _uri=self._uri, _properties=repr_dict(self._properties, indent=2), )) return ret class FakedHbaManager(FakedBaseManager): """ A manager for faked HBA resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, partition): super(FakedHbaManager, self).__init__( hmc=hmc, parent=partition, resource_class=FakedHba, base_uri=partition.uri + '/hbas', oid_prop='element-id', uri_prop='element-uri', class_value='hba') def add(self, properties): """ Add a faked HBA resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'element-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'element-uri' will be auto-generated based upon the element ID, if not specified. * 'class' will be auto-generated to 'hba', if not specified. * 'adapter-port-uri' identifies the backing FCP port for this HBA and is required to be specified. * 'device-number' will be auto-generated with a unique value within the partition in the range 0x8000 to 0xFFFF, if not specified. This method also updates the 'hba-uris' property in the parent faked Partition resource, by adding the URI for the faked HBA resource. Returns: :class:`~zhmcclient_mock.FakedHba`: The faked HBA resource. Raises: :exc:`zhmcclient_mock.InputError`: Some issue with the input properties. """ new_hba = super(FakedHbaManager, self).add(properties) partition = self.parent # Reflect the new NIC in the partition assert 'hba-uris' in partition.properties partition.properties['hba-uris'].append(new_hba.uri) # Create a default device-number if not specified if 'device-number' not in new_hba.properties: devno = partition.devno_alloc() new_hba.properties['device-number'] = devno # Create a default wwpn if not specified if 'wwpn' not in new_hba.properties: wwpn = partition.wwpn_alloc() new_hba.properties['wwpn'] = wwpn return new_hba def remove(self, oid): """ Remove a faked HBA resource. This method also updates the 'hba-uris' property in the parent Partition resource, by removing the URI for the faked HBA resource. Parameters: oid (string): The object ID of the faked HBA resource. """ hba = self.lookup_by_oid(oid) partition = self.parent devno = hba.properties.get('device-number', None) if devno: partition.devno_free_if_allocated(devno) wwpn = hba.properties.get('wwpn', None) if wwpn: partition.wwpn_free_if_allocated(wwpn) assert 'hba-uris' in partition.properties hba_uris = partition.properties['hba-uris'] hba_uris.remove(hba.uri) super(FakedHbaManager, self).remove(oid) # deletes the resource class FakedHba(FakedBaseResource): """ A faked HBA resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedHba, self).__init__( manager=manager, properties=properties) class FakedLparManager(FakedBaseManager): """ A manager for faked LPAR resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, cpc): super(FakedLparManager, self).__init__( hmc=hmc, parent=cpc, resource_class=FakedLpar, base_uri=self.api_root + '/logical-partitions', oid_prop='object-id', uri_prop='object-uri', class_value='logical-partition') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked LPAR resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'object-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'object-uri' will be auto-generated based upon the object ID, if not specified. * 'class' will be auto-generated to 'logical-partition', if not specified. * 'status' is auto-set to 'not-activated', if not specified. Returns: :class:`~zhmcclient_mock.FakedLpar`: The faked LPAR resource. """ return super(FakedLparManager, self).add(properties) class FakedLpar(FakedBaseResource): """ A faked LPAR resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedLpar, self).__init__( manager=manager, properties=properties) if 'status' not in self._properties: self._properties['status'] = 'not-activated' class FakedNicManager(FakedBaseManager): """ A manager for faked NIC resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, partition): super(FakedNicManager, self).__init__( hmc=hmc, parent=partition, resource_class=FakedNic, base_uri=partition.uri + '/nics', oid_prop='element-id', uri_prop='element-uri', class_value='nic') def add(self, properties): """ Add a faked NIC resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'element-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'element-uri' will be auto-generated based upon the element ID, if not specified. * 'class' will be auto-generated to 'nic', if not specified. * Either 'network-adapter-port-uri' (for backing ROCE adapters) or 'virtual-switch-uri'(for backing OSA or Hipersockets adapters) is required to be specified. * 'device-number' will be auto-generated with a unique value within the partition in the range 0x8000 to 0xFFFF, if not specified. This method also updates the 'nic-uris' property in the parent faked Partition resource, by adding the URI for the faked NIC resource. This method also updates the 'connected-vnic-uris' property in the virtual switch referenced by 'virtual-switch-uri' property, and sets it to the URI of the faked NIC resource. Returns: :class:`zhmcclient_mock.FakedNic`: The faked NIC resource. Raises: :exc:`zhmcclient_mock.InputError`: Some issue with the input properties. """ new_nic = super(FakedNicManager, self).add(properties) partition = self.parent # For OSA-backed NICs, reflect the new NIC in the virtual switch if 'virtual-switch-uri' in new_nic.properties: vswitch_uri = new_nic.properties['virtual-switch-uri'] # Even though the URI handler when calling this method ensures that # the vswitch exists, this method can be called by the user as # well, so we have to handle the possibility that it does not # exist: try: vswitch = self.hmc.lookup_by_uri(vswitch_uri) except KeyError: new_exc = InputError("The virtual switch specified in the " "'virtual-switch-uri' property does not " "exist: {!r}".format(vswitch_uri)) new_exc.__cause__ = None raise new_exc # InputError connected_uris = vswitch.properties['connected-vnic-uris'] if new_nic.uri not in connected_uris: connected_uris.append(new_nic.uri) # Create a default device-number if not specified if 'device-number' not in new_nic.properties: devno = partition.devno_alloc() new_nic.properties['device-number'] = devno # Reflect the new NIC in the partition assert 'nic-uris' in partition.properties partition.properties['nic-uris'].append(new_nic.uri) return new_nic def remove(self, oid): """ Remove a faked NIC resource. This method also updates the 'nic-uris' property in the parent Partition resource, by removing the URI for the faked NIC resource. Parameters: oid (string): The object ID of the faked NIC resource. """ nic = self.lookup_by_oid(oid) partition = self.parent devno = nic.properties.get('device-number', None) if devno: partition.devno_free_if_allocated(devno) assert 'nic-uris' in partition.properties nic_uris = partition.properties['nic-uris'] nic_uris.remove(nic.uri) super(FakedNicManager, self).remove(oid) # deletes the resource class FakedNic(FakedBaseResource): """ A faked NIC resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedNic, self).__init__( manager=manager, properties=properties) class FakedPartitionManager(FakedBaseManager): """ A manager for faked Partition resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, cpc): super(FakedPartitionManager, self).__init__( hmc=hmc, parent=cpc, resource_class=FakedPartition, base_uri=self.api_root + '/partitions', oid_prop='object-id', uri_prop='object-uri', class_value='partition') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked Partition resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'object-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'object-uri' will be auto-generated based upon the object ID, if not specified. * 'class' will be auto-generated to 'partition', if not specified. * 'hba-uris' will be auto-generated as an empty array, if not specified. * 'nic-uris' will be auto-generated as an empty array, if not specified. * 'virtual-function-uris' will be auto-generated as an empty array, if not specified. * 'status' is auto-set to 'stopped', if not specified. Returns: :class:`~zhmcclient_mock.FakedPartition`: The faked Partition resource. """ return super(FakedPartitionManager, self).add(properties) class FakedPartition(FakedBaseResource): """ A faked Partition resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. Each partition uses the device number range of 0x8000 to 0xFFFF for automatically assigned device numbers of HBAs, NICs and virtual functions. Users of the mock support should not use device numbers in that range (unless all of them are user-assigned for a particular partition). """ def __init__(self, manager, properties): super(FakedPartition, self).__init__( manager=manager, properties=properties) if 'hba-uris' not in self._properties: self._properties['hba-uris'] = [] if 'nic-uris' not in self._properties: self._properties['nic-uris'] = [] if 'virtual-function-uris' not in self._properties: self._properties['virtual-function-uris'] = [] if 'status' not in self._properties: self._properties['status'] = 'stopped' self._nics = FakedNicManager(hmc=manager.hmc, partition=self) self._hbas = FakedHbaManager(hmc=manager.hmc, partition=self) self._virtual_functions = FakedVirtualFunctionManager( hmc=manager.hmc, partition=self) self._devno_pool = IdPool(0x8000, 0xFFFF) self._wwpn_pool = IdPool(0x8000, 0xFFFF) def __repr__(self): """ Return a string with the state of this faked Partition resource, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " _manager = {manager_classname} at 0x{manager_id:08x}\n" " _manager._parent._uri = {parent_uri!r}\n" " _uri = {_uri!r}\n" " _properties = {_properties}\n" " _nics = {_nics}\n" " _hbas = {_hbas}\n" " _virtual_functions = {_virtual_functions}\n" ")".format( classname=self.__class__.__name__, id=id(self), manager_classname=self._manager.__class__.__name__, manager_id=id(self._manager), parent_uri=self._manager.parent.uri, _uri=self._uri, _properties=repr_dict(self._properties, indent=2), _nics=repr_manager(self.nics, indent=2), _hbas=repr_manager(self.hbas, indent=2), _virtual_functions=repr_manager( self.virtual_functions, indent=2), )) return ret @property def nics(self): """ :class:`~zhmcclient_mock.FakedNicManager`: Access to the faked NIC resources of this Partition. """ return self._nics @property def hbas(self): """ :class:`~zhmcclient_mock.FakedHbaManager`: Access to the faked HBA resources of this Partition. """ return self._hbas @property def virtual_functions(self): """ :class:`~zhmcclient_mock.FakedVirtualFunctionManager`: Access to the faked Virtual Function resources of this Partition. """ return self._virtual_functions def devno_alloc(self): """ Allocates a device number unique to this partition, in the range of 0x8000 to 0xFFFF. Returns: string: The device number as four hexadecimal digits in upper case. Raises: ValueError: No more device numbers available in that range. """ devno_int = self._devno_pool.alloc() devno = "{:04X}".format(devno_int) return devno def devno_free(self, devno): """ Free a device number allocated with :meth:`devno_alloc`. The device number must be allocated. Parameters: devno (string): The device number as four hexadecimal digits. Raises: ValueError: Device number not in pool range or not currently allocated. """ devno_int = int(devno, 16) self._devno_pool.free(devno_int) def devno_free_if_allocated(self, devno): """ Free a device number allocated with :meth:`devno_alloc`. If the device number is not currently allocated or not in the pool range, nothing happens. Parameters: devno (string): The device number as four hexadecimal digits. """ devno_int = int(devno, 16) self._devno_pool.free_if_allocated(devno_int) def wwpn_alloc(self): """ Allocates a WWPN unique to this partition, in the range of 0xAFFEAFFE00008000 to 0xAFFEAFFE0000FFFF. Returns: string: The WWPN as 16 hexadecimal digits in upper case. Raises: ValueError: No more WWPNs available in that range. """ wwpn_int = self._wwpn_pool.alloc() wwpn = "AFFEAFFE0000" + "{:04X}".format(wwpn_int) return wwpn def wwpn_free(self, wwpn): """ Free a WWPN allocated with :meth:`wwpn_alloc`. The WWPN must be allocated. Parameters: WWPN (string): The WWPN as 16 hexadecimal digits. Raises: ValueError: WWPN not in pool range or not currently allocated. """ wwpn_int = int(wwpn[-4:], 16) self._wwpn_pool.free(wwpn_int) def wwpn_free_if_allocated(self, wwpn): """ Free a WWPN allocated with :meth:`wwpn_alloc`. If the WWPN is not currently allocated or not in the pool range, nothing happens. Parameters: WWPN (string): The WWPN as 16 hexadecimal digits. """ wwpn_int = int(wwpn[-4:], 16) self._wwpn_pool.free_if_allocated(wwpn_int) class FakedPortManager(FakedBaseManager): """ A manager for faked Adapter Port resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, adapter): if adapter.adapter_kind == 'network': port_uri_segment = 'network-ports' port_class_value = 'network-port' elif adapter.adapter_kind == 'storage': port_uri_segment = 'storage-ports' port_class_value = 'storage-port' else: raise AssertionError("FakedAdapter with object-id=%s must be a " "storage or network adapter to have ports." % adapter.oid) super(FakedPortManager, self).__init__( hmc=hmc, parent=adapter, resource_class=FakedPort, base_uri=adapter.uri + '/' + port_uri_segment, oid_prop='element-id', uri_prop='element-uri', class_value=port_class_value) def add(self, properties): """ Add a faked Port resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'element-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'element-uri' will be auto-generated based upon the element ID, if not specified. * 'class' will be auto-generated to 'network-port' or 'storage-port', if not specified. This method also updates the 'network-port-uris' or 'storage-port-uris' property in the parent Adapter resource, by adding the URI for the faked Port resource. Returns: :class:`zhmcclient_mock.FakedPort`: The faked Port resource. """ new_port = super(FakedPortManager, self).add(properties) adapter = self.parent if 'network-port-uris' in adapter.properties: adapter.properties['network-port-uris'].append(new_port.uri) if 'storage-port-uris' in adapter.properties: adapter.properties['storage-port-uris'].append(new_port.uri) return new_port def remove(self, oid): """ Remove a faked Port resource. This method also updates the 'network-port-uris' or 'storage-port-uris' property in the parent Adapter resource, by removing the URI for the faked Port resource. Parameters: oid (string): The object ID of the faked Port resource. """ port = self.lookup_by_oid(oid) adapter = self.parent if 'network-port-uris' in adapter.properties: port_uris = adapter.properties['network-port-uris'] port_uris.remove(port.uri) if 'storage-port-uris' in adapter.properties: port_uris = adapter.properties['storage-port-uris'] port_uris.remove(port.uri) super(FakedPortManager, self).remove(oid) # deletes the resource class FakedPort(FakedBaseResource): """ A faked Adapter Port resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedPort, self).__init__( manager=manager, properties=properties) class FakedVirtualFunctionManager(FakedBaseManager): """ A manager for faked Virtual Function resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, partition): super(FakedVirtualFunctionManager, self).__init__( hmc=hmc, parent=partition, resource_class=FakedVirtualFunction, base_uri=partition.uri + '/virtual-functions', oid_prop='element-id', uri_prop='element-uri', class_value='virtual-function') def add(self, properties): """ Add a faked Virtual Function resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'element-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'element-uri' will be auto-generated based upon the element ID, if not specified. * 'class' will be auto-generated to 'virtual-function', if not specified. * 'device-number' will be auto-generated with a unique value within the partition in the range 0x8000 to 0xFFFF, if not specified. This method also updates the 'virtual-function-uris' property in the parent Partition resource, by adding the URI for the faked Virtual Function resource. Returns: :class:`zhmcclient_mock.FakedVirtualFunction`: The faked Virtual Function resource. """ new_vf = super(FakedVirtualFunctionManager, self).add(properties) partition = self.parent assert 'virtual-function-uris' in partition.properties partition.properties['virtual-function-uris'].append(new_vf.uri) if 'device-number' not in new_vf.properties: devno = partition.devno_alloc() new_vf.properties['device-number'] = devno return new_vf def remove(self, oid): """ Remove a faked Virtual Function resource. This method also updates the 'virtual-function-uris' property in the parent Partition resource, by removing the URI for the faked Virtual Function resource. Parameters: oid (string): The object ID of the faked Virtual Function resource. """ virtual_function = self.lookup_by_oid(oid) partition = self.parent devno = virtual_function.properties.get('device-number', None) if devno: partition.devno_free_if_allocated(devno) assert 'virtual-function-uris' in partition.properties vf_uris = partition.properties['virtual-function-uris'] vf_uris.remove(virtual_function.uri) super(FakedVirtualFunctionManager, self).remove(oid) # deletes res. class FakedVirtualFunction(FakedBaseResource): """ A faked Virtual Function resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedVirtualFunction, self).__init__( manager=manager, properties=properties) class FakedVirtualSwitchManager(FakedBaseManager): """ A manager for faked Virtual Switch resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, cpc): super(FakedVirtualSwitchManager, self).__init__( hmc=hmc, parent=cpc, resource_class=FakedVirtualSwitch, base_uri=self.api_root + '/virtual-switches', oid_prop='object-id', uri_prop='object-uri', class_value='virtual-switch') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked Virtual Switch resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'object-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'object-uri' will be auto-generated based upon the object ID, if not specified. * 'class' will be auto-generated to 'virtual-switch', if not specified. * 'connected-vnic-uris' will be auto-generated as an empty array, if not specified. Returns: :class:`~zhmcclient_mock.FakedVirtualSwitch`: The faked Virtual Switch resource. """ return super(FakedVirtualSwitchManager, self).add(properties) class FakedVirtualSwitch(FakedBaseResource): """ A faked Virtual Switch resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedVirtualSwitch, self).__init__( manager=manager, properties=properties) if 'connected-vnic-uris' not in self._properties: self._properties['connected-vnic-uris'] = [] class FakedStorageGroupManager(FakedBaseManager): """ A manager for faked StorageGroup resources within a faked Console (see :class:`zhmcclient_mock.FakedConsole`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, console): super(FakedStorageGroupManager, self).__init__( hmc=hmc, parent=console, resource_class=FakedStorageGroup, base_uri=self.api_root + '/storage-groups', oid_prop='object-id', uri_prop='object-uri', class_value='storage-group') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked StorageGroup resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'object-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'object-uri' will be auto-generated based upon the object ID, if not specified. * 'class' will be auto-generated to 'storage-group', if not specified. * 'storage-volume-uris' will be auto-generated as an empty array, if not specified. * 'shared' is auto-set to False, if not specified. Returns: :class:`~zhmcclient_mock.FakedStorageGroup`: The faked StorageGroup resource. """ return super(FakedStorageGroupManager, self).add(properties) class FakedStorageGroup(FakedBaseResource): """ A faked StorageGroup resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedStorageGroup, self).__init__( manager=manager, properties=properties) if 'storage-volume-uris' not in self._properties: self._properties['storage-volume-uris'] = [] if 'shared' not in self._properties: self._properties['shared'] = False self._storage_volumes = FakedStorageVolumeManager( hmc=manager.hmc, storage_group=self) def __repr__(self): """ Return a string with the state of this faked StorageGroup resource, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " _manager = {manager_classname} at 0x{manager_id:08x}\n" " _manager._parent._uri = {parent_uri!r}\n" " _uri = {_uri!r}\n" " _properties = {_properties}\n" " _storage_volumes = {_storage_volumes}\n" ")".format( classname=self.__class__.__name__, id=id(self), manager_classname=self._manager.__class__.__name__, manager_id=id(self._manager), parent_uri=self._manager.parent.uri, _uri=self._uri, _properties=repr_dict(self._properties, indent=2), _storage_volumes=repr_manager(self.storage_volumes, indent=2), )) return ret @property def storage_volumes(self): """ :class:`~zhmcclient_mock.FakedStorageVolumeManager`: Access to the faked StorageVolume resources of this StorageGroup. """ return self._storage_volumes class FakedStorageVolumeManager(FakedBaseManager): """ A manager for faked StorageVolume resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, storage_group): super(FakedStorageVolumeManager, self).__init__( hmc=hmc, parent=storage_group, resource_class=FakedStorageVolume, base_uri=self.api_root + '/storage-groups', oid_prop='element-id', uri_prop='element-uri', class_value='storage-volume') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked StorageVolume resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'object-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'object-uri' will be auto-generated based upon the object ID, if not specified. * 'class' will be auto-generated to 'storage-group', if not specified. Returns: :class:`~zhmcclient_mock.FakedStorageVolume`: The faked StorageVolume resource. """ return super(FakedStorageVolumeManager, self).add(properties) class FakedStorageVolume(FakedBaseResource): """ A faked StorageVolume resource within a faked StorageGroup (see :class:`zhmcclient_mock.FakedStorageGroup`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedStorageVolume, self).__init__( manager=manager, properties=properties) def __repr__(self): """ Return a string with the state of this faked StorageVolume resource, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " _manager = {manager_classname} at 0x{manager_id:08x}\n" " _manager._parent._uri = {parent_uri!r}\n" " _uri = {_uri!r}\n" " _properties = {_properties}\n" ")".format( classname=self.__class__.__name__, id=id(self), manager_classname=self._manager.__class__.__name__, manager_id=id(self._manager), parent_uri=self._manager.parent.uri, _uri=self._uri, _properties=repr_dict(self._properties, indent=2), )) return ret class FakedCapacityGroupManager(FakedBaseManager): """ A manager for faked CapacityGroup resources within a faked Cpc (see :class:`zhmcclient_mock.FakedCpc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. """ def __init__(self, hmc, cpc): super(FakedCapacityGroupManager, self).__init__( hmc=hmc, parent=cpc, resource_class=FakedCapacityGroup, base_uri=cpc.uri + '/capacity-groups', oid_prop='element-id', uri_prop='element-uri', class_value='capacity-group') def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked CapacityGroup resource. Parameters: properties (dict): Resource properties. Special handling and requirements for certain properties: * 'element-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'element-uri' will be auto-generated based upon the object ID, if not specified. * 'class' will be auto-generated to 'capacity-group', if not specified. * 'capping-enabled' will be set to False. Returns: :class:`~zhmcclient_mock.FakedCapacityGroup`: The faked CapacityGroup resource. """ return super(FakedCapacityGroupManager, self).add(properties) class FakedCapacityGroup(FakedBaseResource): """ A faked CapacityGroup resource within a faked Cpc (see :class:`zhmcclient_mock.FakedCpc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. """ def __init__(self, manager, properties): super(FakedCapacityGroup, self).__init__( manager=manager, properties=properties) if 'capping-enabled' not in self._properties: self._properties['capping-enabled'] = False if 'partition-uris' not in self._properties: self._properties['partition-uris'] = [] def __repr__(self): """ Return a string with the state of this faked CapacityGroup resource, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " _manager = {manager_classname} at 0x{manager_id:08x}\n" " _manager._parent._uri = {parent_uri!r}\n" " _uri = {_uri!r}\n" " _properties = {_properties}\n" ")".format( classname=self.__class__.__name__, id=id(self), manager_classname=self._manager.__class__.__name__, manager_id=id(self._manager), parent_uri=self._manager.parent.uri, _uri=self._uri, _properties=repr_dict(self._properties, indent=2), )) return ret class FakedMetricsContextManager(FakedBaseManager): """ A manager for faked Metrics Context resources within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseManager`, see there for common methods and attributes. Example: * The following code sets up the faked data for metrics retrieval for partition usage metrics, and then retrieves the metrics: .. code-block:: python session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') client = Client(session) # URIs of (faked or real) Partitions the metric apply to: part1_uri = ... part2_uri = ... # Add a faked metric group definition for group 'partition-usage': session.hmc.metric_contexts.add_metric_group_definition( FakedMetricGroupDefinition( name='partition-usage', types=[ ('processor-usage', 'integer-metric'), ('network-usage', 'integer-metric'), ('storage-usage', 'integer-metric'), ('accelerator-usage', 'integer-metric'), ('crypto-usage', 'integer-metric'), ])) # Prepare the faked metric response for that metric group, with # data for two partitions: session.hmc.metric_contexts.add_metric_values( FakedMetricObjectValues( group_name='partition-usage', resource_uri=part1_uri, timestamp=datetime.now(), values=[ ('processor-usage', 15), ('network-usage', 0), ('storage-usage', 1), ('accelerator-usage', 0), ('crypto-usage', 0), ])) session.hmc.metric_contexts.add_metric_values( FakedMetricObjectValues( group_name='partition-usage', resource_uri=part2_uri, timestamp=datetime.now(), values=[ ('processor-usage', 17), ('network-usage', 5), ('storage-usage', 2), ('accelerator-usage', 0), ('crypto-usage', 0), ])) # Create a Metrics Context resource for one metric group: mc = client.metrics_contexts.create({ 'anticipated-frequency-seconds': 15, 'metric-groups' ['partition-usage'], }) # Retrieve the metrics for that metric context: metrics_response = mc.get_metrics() """ def __init__(self, hmc, client): super(FakedMetricsContextManager, self).__init__( hmc=hmc, parent=client, resource_class=FakedMetricsContext, base_uri=self.api_root + '/services/metrics/context', oid_prop='fake-id', uri_prop='fake-uri', class_value=None) self._metric_group_def_names = [] self._metric_group_defs = {} # by group name self._metric_value_names = [] self._metric_values = {} # by group name def add(self, properties): # pylint: disable=useless-super-delegation """ Add a faked Metrics Context resource. Parameters: properties (dict): Resource properties, as defined in the description of the :class:`~zhmcclient_mock.FakedMetricsContext` class. Special handling and requirements for certain properties: * 'fake-id' will be auto-generated with a unique value across all instances of this resource type, if not specified. * 'fake-uri' will be auto-generated based upon the 'fake-id' property, if not specified. Returns: :class:`~zhmcclient_mock.FakedMetricsContext`: The faked Metrics Context resource. """ return super(FakedMetricsContextManager, self).add(properties) def add_metric_group_definition(self, definition): """ Add a faked metric group definition. The definition will be used: * For later addition of faked metrics responses. * For returning the metric-group-info objects in the response of the Create Metrics Context operations. For defined metric groups, see chapter "Metric groups" in the :term:`HMC API` book. Parameters: definition (:class:~zhmcclient.FakedMetricGroupDefinition`): Definition of the metric group. Raises: ValueError: A metric group definition with this name already exists. """ assert isinstance(definition, FakedMetricGroupDefinition) group_name = definition.name if group_name in self._metric_group_defs: raise ValueError("A metric group definition with this name " "already exists: {}".format(group_name)) self._metric_group_defs[group_name] = definition self._metric_group_def_names.append(group_name) def get_metric_group_definition(self, group_name): """ Get a faked metric group definition by its group name. Parameters: group_name (:term:`string`): Name of the metric group. Returns: :class:~zhmcclient.FakedMetricGroupDefinition`: Definition of the metric group. Raises: ValueError: A metric group definition with this name does not exist. """ if group_name not in self._metric_group_defs: raise ValueError("A metric group definition with this name does " "not exist: {}".format(group_name)) return self._metric_group_defs[group_name] def get_metric_group_definition_names(self): """ Get the group names of all faked metric group definitions. Returns: iterable of string: The group names, in the order their metric group definitions had been added. """ return self._metric_group_def_names def add_metric_values(self, values): """ Add one set of faked metric values for a particular resource to the metrics response for a particular metric group, for later retrieval. For defined metric groups, see chapter "Metric groups" in the :term:`HMC API` book. Parameters: values (:class:`~zhmclient.FakedMetricObjectValues`): The set of metric values to be added. It specifies the resource URI and the targeted metric group name. """ assert isinstance(values, FakedMetricObjectValues) group_name = values.group_name if group_name not in self._metric_values: self._metric_values[group_name] = [] self._metric_values[group_name].append(values) if group_name not in self._metric_value_names: self._metric_value_names.append(group_name) def get_metric_values(self, group_name): """ Get the faked metric values for a metric group, by its metric group name. The result includes all metric object values added earlier for that metric group name, using :meth:`~zhmcclient.FakedMetricsContextManager.add_metric_object_values` i.e. the metric values for all resources and all points in time that were added. Parameters: group_name (:term:`string`): Name of the metric group. Returns: iterable of :class:`~zhmclient.FakedMetricObjectValues`: The metric values for that metric group, in the order they had been added. Raises: ValueError: Metric values for this group name do not exist. """ if group_name not in self._metric_values: raise ValueError("Metric values for this group name do not " "exist: {}".format(group_name)) return self._metric_values[group_name] def get_metric_values_group_names(self): """ Get the group names of metric groups for which there are faked metric values. Returns: iterable of string: The group names, in the order their metric values had been added. """ return self._metric_value_names class FakedMetricsContext(FakedBaseResource): """ A faked Metrics Context resource within a faked HMC (see :class:`zhmcclient_mock.FakedHmc`). Derived from :class:`zhmcclient_mock.FakedBaseResource`, see there for common methods and attributes. The Metrics Context "resource" is really a service and therefore does not have a data model defined in the :term:`HMC API` book. In order to fit into the zhmcclient mock framework, the faked Metrics Context in the zhmcclient mock framework is treated like all other faked resources and does have a data model. Data Model: 'fake-id' (:term:`string`): Object ID of the resource. Initialization: Optional. If omitted, it will be auto-generated. 'fake-uri' (:term:`string`): Resource URI of the resource (used for Get Metrics operation). Initialization: Optional. If omitted, it will be auto-generated from the Object ID. 'anticipated-frequency-seconds' (:term:`integer`): The number of seconds the client anticipates will elapse between metrics retrievals using this context. The minimum accepted value is 15. Initialization: Required. 'metric-groups' (list of :term:`string`): The metric group names to be returned by a metric retrieval using this context. Initialization: Optional. If omitted or the empty list, all metric groups that are valid for the operational mode of each CPC will be returned. """ def __init__(self, manager, properties): super(FakedMetricsContext, self).__init__( manager=manager, properties=properties) assert 'anticipated-frequency-seconds' in properties def get_metric_group_definitions(self): """ Get the faked metric group definitions for this context object that are to be returned from its create operation. If a 'metric-groups' property had been specified for this context, only those faked metric group definitions of its manager object that are in that list, are included in the result. Otherwise, all metric group definitions of its manager are included in the result. Returns: iterable of :class:~zhmcclient.FakedMetricGroupDefinition`: The faked metric group definitions, in the order they had been added. """ group_names = self._properties.get('metric-groups', None) if not group_names: group_names = self.manager.get_metric_group_definition_names() mg_defs = [] for group_name in group_names: try: mg_def = self.manager.get_metric_group_definition(group_name) mg_defs.append(mg_def) except ValueError: pass # ignore metric groups without metric group defs return mg_defs def get_metric_group_infos(self): """ Get the faked metric group definitions for this context object that are to be returned from its create operation, in the format needed for the "Create Metrics Context" operation response. Returns: "metric-group-infos" JSON object as described for the "Create Metrics Context "operation response. """ mg_defs = self.get_metric_group_definitions() mg_infos = [] for mg_def in mg_defs: metric_infos = [] for metric_name, metric_type in mg_def.types: metric_infos.append({ 'metric-name': metric_name, 'metric-type': metric_type, }) mg_info = { 'group-name': mg_def.name, 'metric-infos': metric_infos, } mg_infos.append(mg_info) return mg_infos def get_metric_values(self): """ Get the faked metrics, for all metric groups and all resources that have been prepared on the manager object of this context object. Returns: iterable of tuple (group_name, iterable of values): The faked metrics, in the order they had been added, where: group_name (string): Metric group name. values (:class:~zhmcclient.FakedMetricObjectValues`): The metric values for one resource at one point in time. """ group_names = self._properties.get('metric-groups', None) if not group_names: group_names = self.manager.get_metric_values_group_names() ret = [] for group_name in group_names: try: mo_val = self.manager.get_metric_values(group_name) ret_item = (group_name, mo_val) ret.append(ret_item) except ValueError: pass # ignore metric groups without metric values return ret def get_metric_values_response(self): """ Get the faked metrics, for all metric groups and all resources that have been prepared on the manager object of this context object, as a string in the format needed for the "Get Metrics" operation response. Returns: "MetricsResponse" string as described for the "Get Metrics" operation response. """ mv_list = self.get_metric_values() resp_lines = [] for mv in mv_list: group_name = mv[0] resp_lines.append('"{}"'.format(group_name)) mo_vals = mv[1] for mo_val in mo_vals: resp_lines.append('"{}"'.format(mo_val.resource_uri)) resp_lines.append( str(timestamp_from_datetime(mo_val.timestamp))) v_list = [] for _, v in mo_val.values: if isinstance(v, six.string_types): v_str = '"{}"'.format(v) else: v_str = str(v) v_list.append(v_str) v_line = ','.join(v_list) resp_lines.append(v_line) resp_lines.append('') resp_lines.append('') resp_lines.append('') return '\n'.join(resp_lines) + '\n' class FakedMetricGroupDefinition(object): # pylint: disable=too-few-public-methods """ A faked metric group definition (of one metric group). An object of this class contains the information (in a differently structured way) of a "metric-group-info" object described for the "Create Metrics Context" operation in the :term:`HMC API` book. The following table lists for each type mentioned in the metric group descriptions in chapter "Metric groups" in the :term:`HMC API` book, the Python types that are used for representing metric values of that type, and the metric type strings used in the metric group definitions for that type: ============================= ====================== ================== Metric group description type Python type Metric type string ============================= ====================== ================== Boolean :class:`py:bool` ``boolean-metric`` Byte :term:`integer` ``byte-metric`` Short :term:`integer` ``short-metric`` Integer :term:`integer` ``integer-metric`` Long :term:`integer` ``long-metric`` Double :class:`py:float` ``double-metric`` String, String Enum :term:`unicode string` ``string-metric`` ============================= ====================== ================== """ def __init__(self, name, types): """ Parameters: name (:term:`string`): Name of the metric group. types (list of tuple(name, type)): Definition of the metric names and their types, as follows: * name (string): The metric name. * type (string): The metric type string (see table above). """ self.name = name self.types = copy.deepcopy(types) def __repr__(self): """ Return a string with the state of this object, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " name = {s.name!r}\n" " types = {s.types!r}\n" ")".format(classname=self.__class__.__name__, id=id(self), s=self)) return ret class FakedMetricObjectValues(object): # pylint: disable=too-few-public-methods """ Faked metric values for one resource and one metric group. An object of this class contains the information (in a structured way) of an "ObjectValues" item described for the data format of the response body of the "Get Metrics" operation in the :term:`HMC API` book. """ def __init__(self, group_name, resource_uri, timestamp, values): """ Parameters: group_name (:term:`string`): Name of the metric group to which these metric values apply. resource_uri (:term:`string`): URI of the resource to which these metric values apply. timestamp (datetime): Point in time to which these metric values apply. values (list of tuple(name, value)): The metric values, as follows: * name (string): The metric name. * value: The metric value as an object of the Python type listed in the table in the description of :class:`~zhmcclient.FakedMetricGroupDefinition`). """ self.group_name = group_name self.resource_uri = resource_uri self.timestamp = timestamp self.values = copy.deepcopy(values) def __repr__(self): """ Return a string with the state of this object, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " group_name = {s.group_name!r}\n" " resource_uri = {s.resource_uri!r}\n" " timestamp = {s.timestamp!r}\n" " values = {s.values!r}\n" ")".format(classname=self.__class__.__name__, id=id(self), s=self)) return ret ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient_mock/_idpool.py0000644000076500000240000000776300000000000021502 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. """ The :class:`~zhmcclient_mock.IdPool` class provides a pool of integer ID values from a defined value range. This is used for example to manage automatically allocated device numbers. """ from __future__ import absolute_import __all__ = ['IdPool'] class IdPool(object): """ A pool of integer ID values from a defined value range. The IDs can be allocated from and returned to the pool. The pool is optimized for memory consumption, by only materializing ID values as needed. """ def __init__(self, lowest, highest): """ Parameters: lowest (integer): Lowest value of the ID value range. highest (integer): Highest value of the ID value range. """ if lowest > highest: raise ValueError("Lowest value %d is higher than highest %d" % (lowest, highest)) # ID value range, using slice semantics (end points past the highest) self._range_start = lowest self._range_end = highest + 1 # The ID values in use. self._used = set() # Free pool: The ID values that are free and materialized. self._free = set() # Start of new free ID values to be materialized when the free pool is # expanded. self._expand_start = lowest # Expansion chunk size: Number of new free ID values to be materialized # when the free pool is expanded. self._expand_len = 10 def _expand(self): """ Expand the free pool, if possible. If out of capacity w.r.t. the defined ID value range, ValueError is raised. """ assert not self._free # free pool is empty expand_end = self._expand_start + self._expand_len if expand_end > self._range_end: # This happens if the size of the value range is not a multiple # of the expansion chunk size. expand_end = self._range_end if self._expand_start == expand_end: raise ValueError("Out of capacity in ID pool") self._free = set(range(self._expand_start, expand_end)) self._expand_start = expand_end def alloc(self): """ Allocate an ID value and return it. Raises: ValueError: Out of capacity in ID pool. """ if not self._free: self._expand() _id = self._free.pop() self._used.add(_id) return _id def free(self, id): # pylint: disable=redefined-builtin """ Free an ID value. The ID value must be allocated. Raises: ValueError: ID value to be freed is not currently allocated. """ self._free_impl(id, fail_if_not_allocated=True) def free_if_allocated(self, id): # pylint: disable=redefined-builtin """ Free an ID value, if it is currently allocated. If the specified ID value is not currently allocated, nothing happens. """ self._free_impl(id, fail_if_not_allocated=False) def _free_impl(self, id, fail_if_not_allocated): # pylint: disable=redefined-builtin """ Implementation of free. """ if id in self._used: self._used.remove(id) self._free.add(id) elif fail_if_not_allocated: raise ValueError("ID value to be freed is not currently " "allocated: %d" % id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616675934.0 zhmcclient-0.31.0/zhmcclient_mock/_session.py0000644000076500000240000003034500000000000021667 0ustar00maierastaff00000000000000# Copyright 2016-2017 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 faked Session class for the zhmcclient package. """ from __future__ import absolute_import import zhmcclient from ._hmc import FakedHmc from ._urihandler import UriHandler, HTTPError, URIS from ._urihandler import ConnectionError # pylint: disable=redefined-builtin __all__ = ['FakedSession'] class FakedSession(zhmcclient.Session): """ A faked Session class for the zhmcclient package, that can be used as a replacement for the :class:`zhmcclient.Session` class. This class is derived from :class:`zhmcclient.Session`. This class can be used by projects using the zhmcclient package for their unit testing. It can also be used by unit tests of the zhmcclient package itself. This class provides a faked HMC with all of its resources that are relevant for the zhmcclient. The faked HMC provided by this class maintains its resource state in memory as Python objects, and no communication happens to any real HMC. The faked HMC implements all HMC operations that are relevant for the zhmcclient package in a successful manner. It is possible to populate the faked HMC with an initial resource state (see :meth:`~zhmcclient_mock.FakedHmc.add_resources`). """ def __init__(self, host, hmc_name, hmc_version, api_version): """ Parameters: host (:term:`string`): HMC host. hmc_name (:term:`string`): HMC name. Used for result of Query Version Info operation. hmc_version (:term:`string`): HMC version string (e.g. '2.13.1'). Used for result of Query Version Info operation. api_version (:term:`string`): HMC API version string (e.g. '1.8'). Used for result of Query Version Info operation. """ super(FakedSession, self).__init__(host) self._hmc = FakedHmc(hmc_name, hmc_version, api_version) self._urihandler = UriHandler(URIS) def __repr__(self): """ Return a string with the state of this faked session, for debug purposes. """ ret = ( "{classname} at 0x{id:08x} (\n" " _host = {s._host!r}\n" " _userid = {s._userid!r}\n" " _password = '...'\n" " _get_password = {s._get_password!r}\n" " _retry_timeout_config = {s._retry_timeout_config!r}\n" " _base_url = {s._base_url!r}\n" " _headers = {s._headers!r}\n" " _session_id = {s._session_id!r}\n" " _session = {s._session!r}\n" " _hmc = {hmc_classname} at 0x{hmc_id:08x}\n" " _urihandler = {s._urihandler!r}\n" ")".format( classname=self.__class__.__name__, id=id(self), hmc_classname=self._hmc.__class__.__name__, hmc_id=id(self._hmc), s=self)) return ret @property def hmc(self): """ :class:`~zhmcclient_mock.FakedHmc`: The faked HMC provided by this faked session. The faked HMC supports being populated with initial resource state, for example using its :meth:`zhmcclient_mock.FakedHmc.add_resources` method. As an alternative to providing an entire resource tree, the resources can also be added one by one, from top to bottom, using the :meth:`zhmcclient_mock.FakedBaseManager.add` methods of the respective managers (the top-level manager for CPCs can be accessed via ``hmc.cpcs``). """ return self._hmc def get(self, uri, logon_required=True): """ Perform the HTTP GET method against the resource identified by a URI, on the faked HMC. Parameters: uri (:term:`string`): Relative URI path of the resource, e.g. "/api/session". This URI is relative to the base URL of the session (see the :attr:`~zhmcclient.Session.base_url` property). Must not be `None`. logon_required (bool): Boolean indicating whether the operation requires that the session is logged on to the HMC. Because this is a faked HMC, this does not perform a real logon, but it is still used to update the state in the faked HMC. Returns: :term:`json object` with the operation result. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` (not implemented) :exc:`~zhmcclient.AuthError` (not implemented) :exc:`~zhmcclient.ConnectionError` """ try: return self._urihandler.get(self._hmc, uri, logon_required) except HTTPError as exc: new_exc = zhmcclient.HTTPError(exc.response()) new_exc.__cause__ = None raise new_exc # zhmcclient.HTTPError except ConnectionError as exc: new_exc = zhmcclient.ConnectionError(exc.message, None) new_exc.__cause__ = None raise new_exc # zhmcclient.ConnectionError def post(self, uri, body=None, logon_required=True, wait_for_completion=True, operation_timeout=None): """ Perform the HTTP POST method against the resource identified by a URI, using a provided request body, on the faked HMC. HMC operations using HTTP POST are either synchronous or asynchronous. Asynchronous operations return the URI of an asynchronously executing job that can be queried for status and result. Examples for synchronous operations: * With no response body: "Logon", "Update CPC Properties" * With a response body: "Create Partition" Examples for asynchronous operations: * With no ``job-results`` field in the completed job status response: "Start Partition" * With a ``job-results`` field in the completed job status response (under certain conditions): "Activate a Blade", or "Set CPC Power Save" The `wait_for_completion` parameter of this method can be used to deal with asynchronous HMC operations in a synchronous way. Parameters: uri (:term:`string`): Relative URI path of the resource, e.g. "/api/session". This URI is relative to the base URL of the session (see the :attr:`~zhmcclient.Session.base_url` property). Must not be `None`. body (:term:`json object`): JSON object to be used as the HTTP request body (payload). `None` means the same as an empty dictionary, namely that no HTTP body is included in the request. logon_required (bool): Boolean indicating whether the operation requires that the session is logged on to the HMC. For example, the "Logon" operation does not require that. Because this is a faked HMC, this does not perform a real logon, but it is still used to update the state in the faked HMC. wait_for_completion (bool): Boolean controlling whether this method should wait for completion of the requested HMC operation, as follows: * If `True`, this method will wait for completion of the requested operation, regardless of whether the operation is synchronous or asynchronous. This will cause an additional entry in the time statistics to be created for the asynchronous operation and waiting for its completion. This entry will have a URI that is the targeted URI, appended with "+completion". * If `False`, this method will immediately return the result of the HTTP POST method, regardless of whether the operation is synchronous or asynchronous. operation_timeout (:term:`number`): Timeout in seconds, when waiting for completion of an asynchronous operation. The special value 0 means that no timeout is set. `None` means that the default async operation timeout of the session is used. For `wait_for_completion=True`, a :exc:`~zhmcclient.OperationTimeout` is raised when the timeout expires. For `wait_for_completion=False`, this parameter has no effect. Returns: :term:`json object`: If `wait_for_completion` is `True`, returns a JSON object representing the response body of the synchronous operation, or the response body of the completed job that performed the asynchronous operation. If a synchronous operation has no response body, `None` is returned. If `wait_for_completion` is `False`, returns a JSON object representing the response body of the synchronous or asynchronous operation. In case of an asynchronous operation, the JSON object will have a member named ``job-uri``, whose value can be used with the :meth:`~zhmcclient.Session.query_job_status` method to determine the status of the job and the result of the original operation, once the job has completed. See the section in the :term:`HMC API` book about the specific HMC operation and about the 'Query Job Status' operation, for a description of the members of the returned JSON objects. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` (not implemented) :exc:`~zhmcclient.AuthError` (not implemented) :exc:`~zhmcclient.ConnectionError` """ try: return self._urihandler.post(self._hmc, uri, body, logon_required, wait_for_completion) except HTTPError as exc: new_exc = zhmcclient.HTTPError(exc.response()) new_exc.__cause__ = None raise new_exc # zhmcclient.HTTPError except ConnectionError as exc: new_exc = zhmcclient.ConnectionError(exc.message, None) new_exc.__cause__ = None raise new_exc # zhmcclient.ConnectionError def delete(self, uri, logon_required=True): """ Perform the HTTP DELETE method against the resource identified by a URI, on the faked HMC. Parameters: uri (:term:`string`): Relative URI path of the resource, e.g. "/api/session/{session-id}". This URI is relative to the base URL of the session (see the :attr:`~zhmcclient.Session.base_url` property). Must not be `None`. logon_required (bool): Boolean indicating whether the operation requires that the session is logged on to the HMC. For example, for the logoff operation, it does not make sense to first log on. Because this is a faked HMC, this does not perform a real logon, but it is still used to update the state in the faked HMC. Raises: :exc:`~zhmcclient.HTTPError` :exc:`~zhmcclient.ParseError` (not implemented) :exc:`~zhmcclient.AuthError` (not implemented) :exc:`~zhmcclient.ConnectionError` """ try: self._urihandler.delete(self._hmc, uri, logon_required) except HTTPError as exc: new_exc = zhmcclient.HTTPError(exc.response()) new_exc.__cause__ = None raise new_exc # zhmcclient.HTTPError except ConnectionError as exc: new_exc = zhmcclient.ConnectionError(exc.message, None) new_exc.__cause__ = None raise new_exc # zhmcclient.ConnectionError ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1617707270.0 zhmcclient-0.31.0/zhmcclient_mock/_urihandler.py0000644000076500000240000043374700000000000022356 0ustar00maierastaff00000000000000# Copyright 2016-2017 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. # pylint: disable=too-few-public-methods """ A module with various handler classes for the HTTP methods against HMC URIs, based on the faked HMC. Most handler classes do not need to be documented, but some of them have methods that can be mocked in order to provoke non-standard behavior in the handling of the HTTP methods. """ from __future__ import absolute_import import re import time import copy from requests.utils import unquote from ._hmc import InputError __all__ = ['UriHandler', 'LparActivateHandler', 'LparDeactivateHandler', 'LparLoadHandler', 'HTTPError', 'URIS'] class HTTPError(Exception): """ Exception that will be turned into an HTTP error response message. """ def __init__(self, method, uri, http_status, reason, message): super(HTTPError, self).__init__() self.method = method self.uri = uri self.http_status = http_status self.reason = reason self.message = message def response(self): """ Return the JSON object for the HTTP error response message. """ return { 'request-method': self.method, 'request-uri': self.uri, 'http-status': self.http_status, 'reason': self.reason, 'message': self.message, } class ConnectionError(Exception): # pylint: disable=redefined-builtin """ Indicates a connection error to the faked HMC. This mimics the requests.exception.ConnectionError. """ def __init__(self, message): super(ConnectionError, self).__init__() self.message = message class InvalidResourceError(HTTPError): """ HTTP error indicating an invalid resource. """ def __init__(self, method, uri, handler_class=None, reason=1, resource_uri=None): if handler_class is not None: handler_txt = " (handler class %s)" % handler_class.__name__ else: handler_txt = "" if not resource_uri: resource_uri = uri super(InvalidResourceError, self).__init__( method, uri, http_status=404, reason=reason, message="Unknown resource with URI: %s%s" % (resource_uri, handler_txt)) class InvalidMethodError(HTTPError): """ HTTP error indicating an invalid HTTP method. """ def __init__(self, method, uri, handler_class=None): if handler_class is not None: handler_txt = "handler class %s" % handler_class.__name__ else: handler_txt = "no handler class" super(InvalidMethodError, self).__init__( method, uri, http_status=404, reason=1, message="Invalid HTTP method %s on URI: %s %s" % (method, uri, handler_txt)) class BadRequestError(HTTPError): """ HTTP error indicating an invalid client request (status 400). """ def __init__(self, method, uri, reason, message): super(BadRequestError, self).__init__( method, uri, http_status=400, reason=reason, message=message) class ConflictError(HTTPError): """ HTTP error indicating a conflict in the client request (status 409). """ def __init__(self, method, uri, reason, message): super(ConflictError, self).__init__( method, uri, http_status=409, reason=reason, message=message) class CpcNotInDpmError(ConflictError): """ Indicates that the operation requires DPM mode but the CPC is not in DPM mode. Out of the set of operations that only work in DPM mode, this error is used only for the following subset: - Create Partition - Create Hipersocket - Start CPC - Stop CPC - Set Auto-Start List """ def __init__(self, method, uri, cpc): super(CpcNotInDpmError, self).__init__( method, uri, reason=5, message="CPC is not in DPM mode: %s" % cpc.uri) class CpcInDpmError(ConflictError): """ Indicates that the operation requires to be not in DPM mode, but the CPC is in DPM mode. Out of the set of operations that do not work in DPM mode, this error is used only for the following subset: - Activate CPC (not yet implemented in zhmcclient) - Deactivate CPC (not yet implemented in zhmcclient) - Import Profiles (not yet implemented in this URI handler) - Export Profiles (not yet implemented in this URI handler) """ def __init__(self, method, uri, cpc): super(CpcInDpmError, self).__init__( method, uri, reason=4, message="CPC is in DPM mode: %s" % cpc.uri) class ServerError(HTTPError): """ HTTP error indicating a server error (status 500). """ def __init__(self, method, uri, reason, message): super(ServerError, self).__init__( method, uri, http_status=500, reason=reason, message=message) def parse_query_parms(method, uri, query_str): """ Parse the specified query parms string and return a dictionary of query parameters. The key of each dict item is the query parameter name, and the value of each dict item is the query parameter value. If a query parameter shows up more than once, the resulting dict item value is a list of all those values. query_str is the query string from the URL, everything after the '?'. If it is empty or None, None is returned. If a query parameter is not of the format "name=value", an HTTPError 400,1 is raised. """ if not query_str: return None query_parms = {} for query_item in query_str.split('&'): # Example for these items: 'name=a%20b' if query_item == '': continue items = query_item.split('=') if len(items) != 2: raise BadRequestError( method, uri, reason=1, message="Invalid format for URI query parameter: {!r} " "(valid format is: 'name=value').". format(query_item)) name = unquote(items[0]) value = unquote(items[1]) if name in query_parms: existing_value = query_parms[name] if not isinstance(existing_value, list): query_parms[name] = list() query_parms[name].append(existing_value) query_parms[name].append(value) else: query_parms[name] = value return query_parms def check_required_fields(method, uri, body, field_names): """ Check required fields in the request body. Raises: BadRequestError with reason 3: Missing request body BadRequestError with reason 5: Missing required field in request body """ # Check presence of request body if body is None: raise BadRequestError(method, uri, reason=3, message="Missing request body") # Check required input fields for field_name in field_names: if field_name not in body: raise BadRequestError(method, uri, reason=5, message="Missing required field in request " "body: {}".format(field_name)) def check_valid_cpc_status(method, uri, cpc): """ Check that the CPC is in a valid status, as indicated by its 'status' property. If the Cpc object does not have a 'status' property set, this function does nothing (in order to make the mock support easy to use). Raises: ConflictError with reason 1: The CPC itself has been targeted by the operation. ConflictError with reason 6: The CPC is hosting the resource targeted by the operation. """ status = cpc.properties.get('status', None) if status is None: # Do nothing if no status is set on the faked CPC return valid_statuses = ['active', 'service-required', 'degraded', 'exceptions'] if status not in valid_statuses: if uri.startswith(cpc.uri): # The uri targets the CPC (either is the CPC uri or some # multiplicity under the CPC uri) raise ConflictError(method, uri, reason=1, message="The operation cannot be performed " "because the targeted CPC {} has a status " "that is not valid for the operation: {}". format(cpc.name, status)) # The uri targets a resource hosted by the CPC raise ConflictError(method, uri, reason=6, message="The operation cannot be performed " "because CPC {} hosting the targeted resource " "has a status that is not valid for the " "operation: {}". format(cpc.name, status)) def check_partition_status(method, uri, partition, valid_statuses=None, invalid_statuses=None): """ Check that the partition is in one of the valid statuses (if specified) and not in one of the invalid statuses (if specified), as indicated by its 'status' property. If the Partition object does not have a 'status' property set, this function does nothing (in order to make the mock support easy to use). Raises: ConflictError with reason 1 (reason 6 is not used for partitions). """ status = partition.properties.get('status', None) if status is None: # Do nothing if no status is set on the faked partition return if valid_statuses and status not in valid_statuses or \ invalid_statuses and status in invalid_statuses: if uri.startswith(partition.uri): # The uri targets the partition (either is the partition uri or # some multiplicity under the partition uri) raise ConflictError(method, uri, reason=1, message="The operation cannot be performed " "because the targeted partition {} has a " "status that is not valid for the operation: " "{}". format(partition.name, status)) # The uri targets a resource hosted by the partition raise ConflictError(method, uri, reason=1, # Note: 6 not used for partitions message="The operation cannot be performed " "because partition {} hosting the targeted " "resource has a status that is not valid for " "the operation: {}". format(partition.name, status)) class UriHandler(object): """ Handle HTTP methods against a set of known URIs and invoke respective handlers. """ def __init__(self, uris): self._uri_handlers = [] # tuple of (regexp-pattern, handler-name) for uri, handler_class in uris: uri_pattern = re.compile('^' + uri + '$') tup = (uri_pattern, handler_class) self._uri_handlers.append(tup) def handler(self, uri, method): """ Return the handler function for an URI and HTTP method. """ for uri_pattern, handler_class in self._uri_handlers: m = uri_pattern.match(uri) if m: uri_parms = m.groups() return handler_class, uri_parms new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError def get(self, hmc, uri, logon_required): """ Process a HTTP GET method on a URI. """ if not hmc.enabled: raise ConnectionError("HMC is not enabled.") handler_class, uri_parms = self.handler(uri, 'GET') if not getattr(handler_class, 'get', None): raise InvalidMethodError('GET', uri, handler_class) return handler_class.get('GET', hmc, uri, uri_parms, logon_required) def post(self, hmc, uri, body, logon_required, wait_for_completion): """ Process a HTTP POST method on a URI. """ if not hmc.enabled: raise ConnectionError("HMC is not enabled.") handler_class, uri_parms = self.handler(uri, 'POST') if not getattr(handler_class, 'post', None): raise InvalidMethodError('POST', uri, handler_class) return handler_class.post('POST', hmc, uri, uri_parms, body, logon_required, wait_for_completion) def delete(self, hmc, uri, logon_required): """ Process a HTTP DELETE method on a URI. """ if not hmc.enabled: raise ConnectionError("HMC is not enabled.") handler_class, uri_parms = self.handler(uri, 'DELETE') if not getattr(handler_class, 'delete', None): raise InvalidMethodError('DELETE', uri, handler_class) handler_class.delete('DELETE', hmc, uri, uri_parms, logon_required) class GenericGetPropertiesHandler(object): """ Handler class for generic get of resource properties. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Get Properties.""" try: resource = hmc.lookup_by_uri(uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError return resource.properties class GenericUpdatePropertiesHandler(object): """ Handler class for generic update of resource properties. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Update Properties.""" assert wait_for_completion is True # async not supported yet try: resource = hmc.lookup_by_uri(uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError resource.update(body) class GenericDeleteHandler(object): """ Handler class for generic delete of a resource. """ @staticmethod def delete(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Delete .""" try: resource = hmc.lookup_by_uri(uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError resource.manager.remove(resource.oid) class VersionHandler(object): """ Handler class for operation: Get version. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Get version.""" api_major, api_minor = hmc.api_version.split('.') return { 'hmc-name': hmc.hmc_name, 'hmc-version': hmc.hmc_version, 'api-major-version': int(api_major), 'api-minor-version': int(api_minor), } class ConsoleHandler(GenericGetPropertiesHandler): """ Handler class for HTTP methods on Console resource. """ pass class ConsoleRestartHandler(object): """ Handler class for Console operation: Restart Console. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Restart Console.""" assert wait_for_completion is True # synchronous operation console_uri = '/api/console' try: hmc.lookup_by_uri(console_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError hmc.disable() time.sleep(5) hmc.enable() # Note: The HTTP status 202 that the real HMC operation returns, is # not visible for the caller of FakedSession (or Session). class ConsoleShutdownHandler(object): """ Handler class for Console operation: Shutdown Console. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Shutdown Console.""" assert wait_for_completion is True # synchronous operation console_uri = '/api/console' try: hmc.lookup_by_uri(console_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError hmc.disable() # Note: The HTTP status 202 that the real HMC operation returns, is # not visible for the caller of FakedSession (or Session). class ConsoleMakePrimaryHandler(object): """ Handler class for Console operation: Make Console Primary. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Make Console Primary.""" assert wait_for_completion is True # synchronous operation console_uri = '/api/console' try: hmc.lookup_by_uri(console_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError # Nothing to do, as long as the faked HMC does not need to know whether # it is primary or alternate. class ConsoleReorderUserPatternsHandler(object): """ Handler class for Console operation: Reorder User Patterns. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Reorder User Patterns.""" assert wait_for_completion is True # synchronous operation console_uri = '/api/console' try: console = hmc.lookup_by_uri(console_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['user-pattern-uris']) new_order_uris = body['user-pattern-uris'] objs = console.user_patterns.list() obj_by_uri = {} for obj in objs: obj_by_uri[obj.uri] = obj # Perform the reordering in the faked HMC: for _uri in new_order_uris: obj = obj_by_uri[_uri] console.user_patterns.remove(obj.oid) # remove from old position console.user_patterns.add(obj.properties) # append to end class ConsoleGetAuditLogHandler(object): """ Handler class for Console operation: Get Console Audit Log. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Get Console Audit Log.""" console_uri = '/api/console' try: hmc.lookup_by_uri(console_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError resp = [] # TODO: Add the ability to return audit log entries in mock support. return resp class ConsoleGetSecurityLogHandler(object): """ Handler class for Console operation: Get Console Security Log. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Get Console Security Log.""" console_uri = '/api/console' try: hmc.lookup_by_uri(console_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError resp = [] # TODO: Add the ability to return security log entries in mock support. return resp class ConsoleListUnmanagedCpcsHandler(object): """ Handler class for Console operation: List Unmanaged CPCs. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List Unmanaged CPCs.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError result_ucpcs = [] filter_args = parse_query_parms(method, uri, query_str) for ucpc in console.unmanaged_cpcs.list(filter_args): result_ucpc = {} for prop in ucpc.properties: if prop in ('object-uri', 'name'): result_ucpc[prop] = ucpc.properties[prop] result_ucpcs.append(result_ucpc) return {'cpcs': result_ucpcs} class UsersHandler(object): """ Handler class for HTTP methods on set of User resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List Users.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError result_users = [] filter_args = parse_query_parms(method, uri, query_str) for user in console.users.list(filter_args): result_user = {} for prop in user.properties: if prop in ('object-uri', 'name', 'type'): result_user[prop] = user.properties[prop] result_users.append(result_user) return {'users': result_users} @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Create User.""" assert wait_for_completion is True # synchronous operation try: console = hmc.consoles.lookup_by_oid(None) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['name', 'type', 'authentication-type']) # TODO: There are some more input properties that are required under # certain conditions. new_user = console.users.add(body) return {'object-uri': new_user.uri} class UserHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single User resource. """ # TODO: Add post() for Update User that rejects name update @staticmethod def delete(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Delete User.""" try: user = hmc.lookup_by_uri(uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError # Check user type type_ = user.properties['type'] if type_ == 'pattern-based': raise BadRequestError( method, uri, reason=312, message="Cannot delete pattern-based user {!r}". format(user.name)) # Delete the mocked resource user.manager.remove(user.oid) class UserAddUserRoleHandler(object): """ Handler class for operation: Add User Role to User. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Add User Role to User.""" assert wait_for_completion is True # synchronous operation user_oid = uri_parms[0] user_uri = '/api/users/' + user_oid try: user = hmc.lookup_by_uri(user_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['user-role-uri']) user_type = user.properties['type'] if user_type in ('pattern-based', 'system-defined'): raise BadRequestError( method, uri, reason=314, message="Cannot add user role to user of type {}: {}". format(user_type, user_uri)) user_role_uri = body['user-role-uri'] try: hmc.lookup_by_uri(user_role_uri) except KeyError: new_exc = InvalidResourceError(method, user_role_uri, reason=2) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError if user.properties.get('user-roles', None) is None: user.properties['user-roles'] = [] user.properties['user-roles'].append(user_role_uri) class UserRemoveUserRoleHandler(object): """ Handler class for operation: Remove User Role from User. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Remove User Role from User.""" assert wait_for_completion is True # synchronous operation user_oid = uri_parms[0] user_uri = '/api/users/' + user_oid try: user = hmc.lookup_by_uri(user_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['user-role-uri']) user_type = user.properties['type'] if user_type in ('pattern-based', 'system-defined'): raise BadRequestError( method, uri, reason=314, message="Cannot remove user role from user of type {}: {}". format(user_type, user_uri)) user_role_uri = body['user-role-uri'] try: user_role = hmc.lookup_by_uri(user_role_uri) except KeyError: new_exc = InvalidResourceError(method, user_role_uri, reason=2) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError if user.properties.get('user-roles', None) is None \ or user_role_uri not in user.properties['user-roles']: raise ConflictError( method, uri, reason=316, message="User {!r} does not have User Role {!r}". format(user.name, user_role.name)) user.properties['user-roles'].remove(user_role_uri) class UserRolesHandler(object): """ Handler class for HTTP methods on set of UserRole resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List User Roles.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError result_user_roles = [] filter_args = parse_query_parms(method, uri, query_str) for user_role in console.user_roles.list(filter_args): result_user_role = {} for prop in user_role.properties: if prop in ('object-uri', 'name', 'type'): result_user_role[prop] = user_role.properties[prop] result_user_roles.append(result_user_role) return {'user-roles': result_user_roles} @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Create User Role.""" assert wait_for_completion is True # synchronous operation try: console = hmc.consoles.lookup_by_oid(None) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['name']) properties = copy.deepcopy(body) if 'type' in properties: raise BadRequestError( method, uri, reason=6, message="Type specified when creating a user role: {!r}". format(properties['type'])) properties['type'] = 'user-defined' new_user_role = console.user_roles.add(properties) return {'object-uri': new_user_role.uri} class UserRoleHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler, GenericDeleteHandler): """ Handler class for HTTP methods on single UserRole resource. """ pass # TODO: Add post() for Update UserRole that rejects name update # TODO: Add delete() for Delete UserRole that rejects system-defined type class UserRoleAddPermissionHandler(object): """ Handler class for operation: Add Permission to User Role. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Add Permission to User Role.""" assert wait_for_completion is True # synchronous operation user_role_oid = uri_parms[0] user_role_uri = '/api/user-roles/' + user_role_oid try: user_role = hmc.lookup_by_uri(user_role_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['permitted-object', 'permitted-object-type']) # Reject if User Role is system-defined: if user_role.properties['type'] == 'system-defined': raise BadRequestError( method, uri, reason=314, message="Cannot add permission to " "system-defined user role: {}".format(user_role_uri)) # Apply defaults, so our internally stored copy has all fields: permission = copy.deepcopy(body) if 'include-members' not in permission: permission['include-members'] = False if 'view-only-mode' not in permission: permission['view-only-mode'] = True # Add the permission to its store (the faked User Role object): if user_role.properties.get('permissions', None) is None: user_role.properties['permissions'] = [] user_role.properties['permissions'].append(permission) class UserRoleRemovePermissionHandler(object): """ Handler class for operation: Remove Permission from User Role. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Remove Permission from User Role.""" assert wait_for_completion is True # synchronous operation user_role_oid = uri_parms[0] user_role_uri = '/api/user-roles/' + user_role_oid try: user_role = hmc.lookup_by_uri(user_role_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['permitted-object', 'permitted-object-type']) # Reject if User Role is system-defined: if user_role.properties['type'] == 'system-defined': raise BadRequestError( method, uri, reason=314, message="Cannot remove permission " "from system-defined user role: {}".format(user_role_uri)) # Apply defaults, so we can locate it based upon all fields: permission = copy.deepcopy(body) if 'include-members' not in permission: permission['include-members'] = False if 'view-only-mode' not in permission: permission['view-only-mode'] = True # Remove the permission from its store (the faked User Role object): if user_role.properties.get('permissions', None) is not None: user_role.properties['permissions'].remove(permission) class TasksHandler(object): """ Handler class for HTTP methods on set of Task resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List Tasks.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError result_tasks = [] filter_args = parse_query_parms(method, uri, query_str) for task in console.tasks.list(filter_args): result_task = {} for prop in task.properties: if prop in ('element-uri', 'name'): result_task[prop] = task.properties[prop] result_tasks.append(result_task) return {'tasks': result_tasks} class TaskHandler(GenericGetPropertiesHandler): """ Handler class for HTTP methods on single Task resource. """ pass class UserPatternsHandler(object): """ Handler class for HTTP methods on set of UserPattern resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List User Patterns.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError result_user_patterns = [] filter_args = parse_query_parms(method, uri, query_str) for user_pattern in console.user_patterns.list(filter_args): result_user_pattern = {} for prop in user_pattern.properties: if prop in ('element-uri', 'name', 'type'): result_user_pattern[prop] = user_pattern.properties[prop] result_user_patterns.append(result_user_pattern) return {'user-patterns': result_user_patterns} @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Create User Pattern.""" assert wait_for_completion is True # synchronous operation try: console = hmc.consoles.lookup_by_oid(None) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['name', 'pattern', 'type', 'retention-time', 'user-template-uri']) new_user_pattern = console.user_patterns.add(body) return {'element-uri': new_user_pattern.uri} class UserPatternHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler, GenericDeleteHandler): """ Handler class for HTTP methods on single UserPattern resource. """ pass class PasswordRulesHandler(object): """ Handler class for HTTP methods on set of PasswordRule resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List Password Rules.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError result_password_rules = [] filter_args = parse_query_parms(method, uri, query_str) for password_rule in console.password_rules.list(filter_args): result_password_rule = {} for prop in password_rule.properties: if prop in ('element-uri', 'name', 'type'): result_password_rule[prop] = password_rule.properties[prop] result_password_rules.append(result_password_rule) return {'password-rules': result_password_rules} @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Create Password Rule.""" assert wait_for_completion is True # synchronous operation try: console = hmc.consoles.lookup_by_oid(None) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['name']) new_password_rule = console.password_rules.add(body) return {'element-uri': new_password_rule.uri} class PasswordRuleHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler, GenericDeleteHandler): """ Handler class for HTTP methods on single PasswordRule resource. """ pass # TODO: Add post() for Update PasswordRule that rejects name update class LdapServerDefinitionsHandler(object): """ Handler class for HTTP methods on set of LdapServerDefinition resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List LDAP Server Definitions.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError result_ldap_srv_defs = [] filter_args = parse_query_parms(method, uri, query_str) for ldap_srv_def in console.ldap_server_definitions.list(filter_args): result_ldap_srv_def = {} for prop in ldap_srv_def.properties: if prop in ('element-uri', 'name', 'type'): result_ldap_srv_def[prop] = ldap_srv_def.properties[prop] result_ldap_srv_defs.append(result_ldap_srv_def) return {'ldap-server-definitions': result_ldap_srv_defs} @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Create LDAP Server Definition.""" assert wait_for_completion is True # synchronous operation try: console = hmc.consoles.lookup_by_oid(None) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['name']) new_ldap_srv_def = console.ldap_server_definitions.add(body) return {'element-uri': new_ldap_srv_def.uri} class LdapServerDefinitionHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler, GenericDeleteHandler): """ Handler class for HTTP methods on single LdapServerDefinition resource. """ pass # TODO: Add post() for Update LdapServerDefinition that rejects name update class CpcsHandler(object): """ Handler class for HTTP methods on set of Cpc resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List CPCs.""" query_str = uri_parms[0] result_cpcs = [] filter_args = parse_query_parms(method, uri, query_str) for cpc in hmc.cpcs.list(filter_args): result_cpc = {} for prop in cpc.properties: if prop in ('object-uri', 'name', 'status'): result_cpc[prop] = cpc.properties[prop] result_cpcs.append(result_cpc) return {'cpcs': result_cpcs} class CpcHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single Cpc resource. """ pass class CpcSetPowerSaveHandler(object): """ Handler class for operation: Set CPC Power Save. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Set CPC Power Save (any CPC mode).""" assert wait_for_completion is True # async not supported yet cpc_oid = uri_parms[0] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['power-saving']) power_saving = body['power-saving'] if power_saving not in ['high-performance', 'low-power', 'custom']: raise BadRequestError(method, uri, reason=7, message="Invalid power-saving value: %r" % power_saving) cpc.properties['cpc-power-saving'] = power_saving cpc.properties['cpc-power-saving-state'] = power_saving cpc.properties['zcpc-power-saving'] = power_saving cpc.properties['zcpc-power-saving-state'] = power_saving class CpcSetPowerCappingHandler(object): """ Handler class for operation: Set CPC Power Capping. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Set CPC Power Capping (any CPC mode).""" assert wait_for_completion is True # async not supported yet cpc_oid = uri_parms[0] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['power-capping-state']) power_capping_state = body['power-capping-state'] power_cap_current = body.get('power-cap-current', None) if power_capping_state not in ['disabled', 'enabled', 'custom']: raise BadRequestError(method, uri, reason=7, message="Invalid power-capping-state value: " "%r" % power_capping_state) if power_capping_state == 'enabled' and power_cap_current is None: raise BadRequestError(method, uri, reason=7, message="Power-cap-current must be provided " "when enabling power capping") cpc.properties['cpc-power-capping-state'] = power_capping_state cpc.properties['cpc-power-cap-current'] = power_cap_current cpc.properties['zcpc-power-capping-state'] = power_capping_state cpc.properties['zcpc-power-cap-current'] = power_cap_current class CpcGetEnergyManagementDataHandler(object): """ Handler class for operation: Get CPC Energy Management Data. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Get CPC Energy Management Data (any CPC mode).""" cpc_oid = uri_parms[0] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError energy_props = { 'cpc-power-cap-allowed': cpc.properties.get('cpc-power-cap-allowed'), 'cpc-power-cap-current': cpc.properties.get('cpc-power-cap-current'), 'cpc-power-cap-maximum': cpc.properties.get('cpc-power-cap-maximum'), 'cpc-power-cap-minimum': cpc.properties.get('cpc-power-cap-minimum'), 'cpc-power-capping-state': cpc.properties.get('cpc-power-capping-state'), 'cpc-power-consumption': cpc.properties.get('cpc-power-consumption'), 'cpc-power-rating': cpc.properties.get('cpc-power-rating'), 'cpc-power-save-allowed': cpc.properties.get('cpc-power-save-allowed'), 'cpc-power-saving': cpc.properties.get('cpc-power-saving'), 'cpc-power-saving-state': cpc.properties.get('cpc-power-saving-state'), 'zcpc-ambient-temperature': cpc.properties.get('zcpc-ambient-temperature'), 'zcpc-dew-point': cpc.properties.get('zcpc-dew-point'), 'zcpc-exhaust-temperature': cpc.properties.get('zcpc-exhaust-temperature'), 'zcpc-heat-load': cpc.properties.get('zcpc-heat-load'), 'zcpc-heat-load-forced-air': cpc.properties.get('zcpc-heat-load-forced-air'), 'zcpc-heat-load-water': cpc.properties.get('zcpc-heat-load-water'), 'zcpc-humidity': cpc.properties.get('zcpc-humidity'), 'zcpc-maximum-potential-heat-load': cpc.properties.get('zcpc-maximum-potential-heat-load'), 'zcpc-maximum-potential-power': cpc.properties.get('zcpc-maximum-potential-power'), 'zcpc-power-cap-allowed': cpc.properties.get('zcpc-power-cap-allowed'), 'zcpc-power-cap-current': cpc.properties.get('zcpc-power-cap-current'), 'zcpc-power-cap-maximum': cpc.properties.get('zcpc-power-cap-maximum'), 'zcpc-power-cap-minimum': cpc.properties.get('zcpc-power-cap-minimum'), 'zcpc-power-capping-state': cpc.properties.get('zcpc-power-capping-state'), 'zcpc-power-consumption': cpc.properties.get('zcpc-power-consumption'), 'zcpc-power-rating': cpc.properties.get('zcpc-power-rating'), 'zcpc-power-save-allowed': cpc.properties.get('zcpc-power-save-allowed'), 'zcpc-power-saving': cpc.properties.get('zcpc-power-saving'), 'zcpc-power-saving-state': cpc.properties.get('zcpc-power-saving-state'), } cpc_data = { 'error-occurred': False, 'object-uri': cpc.uri, 'object-id': cpc.oid, 'class': 'cpcs', 'properties': energy_props, } result = {'objects': [cpc_data]} return result class CpcStartHandler(object): """ Handler class for operation: Start CPC. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Start CPC (requires DPM mode).""" assert wait_for_completion is True # async not supported yet cpc_oid = uri_parms[0] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError if not cpc.dpm_enabled: raise CpcNotInDpmError(method, uri, cpc) cpc.properties['status'] = 'active' class CpcStopHandler(object): """ Handler class for operation: Stop CPC. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Stop CPC (requires DPM mode).""" assert wait_for_completion is True # async not supported yet cpc_oid = uri_parms[0] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError if not cpc.dpm_enabled: raise CpcNotInDpmError(method, uri, cpc) cpc.properties['status'] = 'not-operating' class CpcImportProfilesHandler(object): """ Handler class for operation: Import Profiles. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Import Profiles (requires classic mode).""" assert wait_for_completion is True # no async cpc_oid = uri_parms[0] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError if cpc.dpm_enabled: raise CpcInDpmError(method, uri, cpc) check_required_fields(method, uri, body, ['profile-area']) # TODO: Import the CPC profiles from a simulated profile area class CpcExportProfilesHandler(object): """ Handler class for operation: Export Profiles. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Export Profiles (requires classic mode).""" assert wait_for_completion is True # no async cpc_oid = uri_parms[0] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError if cpc.dpm_enabled: raise CpcInDpmError(method, uri, cpc) check_required_fields(method, uri, body, ['profile-area']) # TODO: Export the CPC profiles to a simulated profile area class CpcExportPortNamesListHandler(object): """ Handler class for operation: Export WWPN List. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Export WWPN List (requires DPM mode).""" assert wait_for_completion is True # this operation is always synchr. cpc_oid = uri_parms[0] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError if not cpc.dpm_enabled: raise CpcNotInDpmError(method, uri, cpc) check_required_fields(method, uri, body, ['partitions']) partition_uris = body['partitions'] if len(partition_uris) == 0: raise BadRequestError( method, uri, reason=149, message="'partitions' field in request body is empty.") wwpn_list = [] for partition_uri in partition_uris: partition = hmc.lookup_by_uri(partition_uri) partition_cpc = partition.manager.parent if partition_cpc.oid != cpc_oid: raise BadRequestError( method, uri, reason=149, message="Partition %r specified in 'partitions' field " "is not in the targeted CPC with ID %r (but in the CPC " "with ID %r)." % (partition.uri, cpc_oid, partition_cpc.oid)) partition_name = partition.properties.get('name', '') for hba in partition.hbas.list(): port_uri = hba.properties['adapter-port-uri'] port = hmc.lookup_by_uri(port_uri) adapter = port.manager.parent adapter_id = adapter.properties.get('adapter-id', '') devno = hba.properties.get('device-number', '') wwpn = hba.properties.get('wwpn', '') wwpn_str = '%s,%s,%s,%s' % (partition_name, adapter_id, devno, wwpn) wwpn_list.append(wwpn_str) return { 'wwpn-list': wwpn_list } CPC_PROPNAME_FROM_PROCTYPE = { 'sap': 'processor-count-service-assist', 'aap': 'processor-count-aap', 'ifl': 'processor-count-ifl', 'icf': 'processor-count-icf', 'iip': 'processor-count-iip', 'cbp': 'processor-count-cbp', } class CpcAddTempCapacityHandler(object): """ Handler class for operation: Add Temporary Capacity. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Add Temporary Capacity.""" assert wait_for_completion is True # no async cpc_oid = uri_parms[0] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['record-id', 'test']) # record_id = body['record-id'] # TODO: Implement # test = body['test'] # TODO: Implement # force = body.get('force', False) # TODO: Implement software_model = body.get('software-model', None) processor_info = body.get('processor-info', None) if software_model is not None: current_software_model = \ cpc.properties['software-model-permanent-plus-temporary'] if current_software_model is not None: raise BadRequestError( method, uri, reason=277, message="Cannot activate temporary software model {} " "because temporary software model {} is already active". format(software_model, current_software_model)) # We accept any software model, and imply the desired total number # of general purpose processors from the last two digits. pnum = int(software_model[1:]) pname = 'processor-count-general-purpose' ptype = 'cp' if pnum < cpc.properties[pname]: raise BadRequestError( method, uri, reason=276, message="Cannot activate temporary software model {} " "because its target number of {} {} processors is below " "the current number of {} {} processors". format(software_model, pnum, ptype, cpc.properties[pname], ptype)) cpc.properties[pname] = pnum cpc.properties['software-model-permanent-plus-temporary'] = \ software_model if processor_info is not None: for pitem in processor_info: ptype = pitem['processor-type'] psteps = pitem.get('num-processor-steps', None) if ptype not in CPC_PROPNAME_FROM_PROCTYPE: raise BadRequestError( method, uri, reason=276, message="Invalid processor type {} was specified in a " "processor-info entry".format(ptype)) pname = CPC_PROPNAME_FROM_PROCTYPE[ptype] if psteps is not None: # TODO: Check against installed number of processors cpc.properties[pname] += psteps class CpcRemoveTempCapacityHandler(object): """ Handler class for operation: Remove Temporary Capacity. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Remove Temporary Capacity.""" assert wait_for_completion is True # no async cpc_oid = uri_parms[0] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['record-id']) # record_id = body['record-id'] # TODO: Implement software_model = body.get('software-model', None) processor_info = body.get('processor-info', None) if software_model is not None: current_software_model = \ cpc.properties['software-model-permanent-plus-temporary'] if current_software_model is None: raise BadRequestError( method, uri, reason=277, message="Cannot deactivate temporary software model {} " "because no temporary software model is currently active". format(software_model)) # We accept any software model, and imply the desired total number # of general purpose processors from the last two digits. pnum = int(software_model[1:]) pname = 'processor-count-general-purpose' ptype = 'cp' if pnum > cpc.properties[pname]: raise BadRequestError( method, uri, reason=276, message="Cannot activate temporary software model {} " "because its target number of {} {} processors is above " "the current number of {} {} processors". format(software_model, pnum, ptype, cpc.properties[pname], ptype)) cpc.properties[pname] = pnum cpc.properties['software-model-permanent-plus-temporary'] = None if processor_info is not None: for pitem in processor_info: ptype = pitem['processor-type'] psteps = pitem.get('num-processor-steps', None) if ptype not in CPC_PROPNAME_FROM_PROCTYPE: raise BadRequestError( method, uri, reason=276, message="Invalid processor type {} was specified in a " "processor-info entry".format(ptype)) pname = CPC_PROPNAME_FROM_PROCTYPE[ptype] if psteps is not None: if cpc.properties[pname] - psteps < 1: raise BadRequestError( method, uri, reason=276, message="Cannot reduce the number of {} {} " "processors by {} because at least one processor " "must remain.". format(cpc.properties[pname], ptype, psteps)) cpc.properties[pname] -= psteps class MetricsContextsHandler(object): """ Handler class for HTTP methods on set of MetricsContext resources. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Create Metrics Context.""" assert wait_for_completion is True # always synchronous check_required_fields(method, uri, body, ['anticipated-frequency-seconds']) new_metrics_context = hmc.metrics_contexts.add(body) result = { 'metrics-context-uri': new_metrics_context.uri, 'metric-group-infos': new_metrics_context.get_metric_group_infos() } return result class MetricsContextHandler(object): """ Handler class for HTTP methods on single MetricsContext resource. """ @staticmethod def delete(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Delete Metrics Context.""" try: metrics_context = hmc.lookup_by_uri(uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError hmc.metrics_contexts.remove(metrics_context.oid) @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Get Metrics.""" try: metrics_context = hmc.lookup_by_uri(uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError result = metrics_context.get_metric_values_response() return result class AdaptersHandler(object): """ Handler class for HTTP methods on set of Adapter resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List Adapters of a CPC (empty result if not in DPM mode).""" cpc_oid = uri_parms[0] query_str = uri_parms[1] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError result_adapters = [] if cpc.dpm_enabled: filter_args = parse_query_parms(method, uri, query_str) for adapter in cpc.adapters.list(filter_args): result_adapter = {} for prop in adapter.properties: if prop in ('object-uri', 'name', 'status'): result_adapter[prop] = adapter.properties[prop] result_adapters.append(result_adapter) return {'adapters': result_adapters} @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Create Hipersocket (requires DPM mode).""" assert wait_for_completion is True cpc_oid = uri_parms[0] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError if not cpc.dpm_enabled: raise CpcNotInDpmError(method, uri, cpc) check_required_fields(method, uri, body, ['name']) # We need to emulate the behavior of this POST to always create a # hipersocket, but the add() method is used for adding all kinds of # faked adapters to the faked HMC. So we need to specify the adapter # type, but because the behavior of the Adapter resource object is # that it only has its input properties set, we add the 'type' # property on a copy of the input properties. body2 = body.copy() body2['type'] = 'hipersockets' try: new_adapter = cpc.adapters.add(body2) except InputError as exc: new_exc = BadRequestError(method, uri, reason=5, message=str(exc)) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.BadRequestError return {'object-uri': new_adapter.uri} class AdapterHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single Adapter resource. """ @staticmethod def delete(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Delete Hipersocket (requires DPM mode).""" try: adapter = hmc.lookup_by_uri(uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = adapter.manager.parent assert cpc.dpm_enabled adapter.manager.remove(adapter.oid) class AdapterChangeCryptoTypeHandler(object): """ Handler class for operation: Change Crypto Type. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Change Crypto Type (requires DPM mode).""" assert wait_for_completion is True # HMC operation is synchronous adapter_uri = uri.split('/operations/')[0] try: adapter = hmc.lookup_by_uri(adapter_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = adapter.manager.parent assert cpc.dpm_enabled check_required_fields(method, uri, body, ['crypto-type']) # Check the validity of the new crypto_type crypto_type = body['crypto-type'] if crypto_type not in ['accelerator', 'cca-coprocessor', 'ep11-coprocessor']: raise BadRequestError( method, uri, reason=8, message="Invalid value for 'crypto-type' field: %s" % crypto_type) # Reflect the result of changing the crypto type adapter.properties['crypto-type'] = crypto_type class AdapterChangeAdapterTypeHandler(object): """ Handler class for operation: Change Adapter Type. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Change Adapter Type (requires DPM mode).""" assert wait_for_completion is True # HMC operation is synchronous adapter_uri = uri.split('/operations/')[0] try: adapter = hmc.lookup_by_uri(adapter_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = adapter.manager.parent assert cpc.dpm_enabled check_required_fields(method, uri, body, ['type']) new_adapter_type = body['type'] # Check the validity of the adapter family adapter_family = adapter.properties.get('adapter-family', None) if adapter_family != 'ficon': raise BadRequestError( method, uri, reason=18, message="The adapter type cannot be changed for adapter " "family: %s" % adapter_family) # Check the adapter status adapter_status = adapter.properties.get('status', None) if adapter_status == 'exceptions': raise BadRequestError( method, uri, reason=18, message="The adapter type cannot be changed for adapter " "status: %s" % adapter_status) # Check the validity of the new adapter type if new_adapter_type not in ['fc', 'fcp', 'not-configured']: raise BadRequestError( method, uri, reason=8, message="Invalid new value for 'type' field: %s" % new_adapter_type) # Check that the new adapter type is not already set adapter_type = adapter.properties.get('type', None) if new_adapter_type == adapter_type: raise BadRequestError( method, uri, reason=8, message="New value for 'type' field is already set: %s" % new_adapter_type) # TODO: Reject if adapter is attached to a partition. # Reflect the result of changing the adapter type adapter.properties['type'] = new_adapter_type class NetworkPortHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single NetworkPort resource. """ pass class StoragePortHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single StoragePort resource. """ pass class PartitionsHandler(object): """ Handler class for HTTP methods on set of Partition resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List Partitions of a CPC (empty result if not in DPM mode).""" cpc_oid = uri_parms[0] query_str = uri_parms[1] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError # Reflect the result of listing the partition result_partitions = [] if cpc.dpm_enabled: filter_args = parse_query_parms(method, uri, query_str) for partition in cpc.partitions.list(filter_args): result_partition = {} for prop in partition.properties: if prop in ('object-uri', 'name', 'status'): result_partition[prop] = partition.properties[prop] result_partitions.append(result_partition) return {'partitions': result_partitions} @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Create Partition (requires DPM mode).""" assert wait_for_completion is True # async not supported yet cpc_oid = uri_parms[0] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError if not cpc.dpm_enabled: raise CpcNotInDpmError(method, uri, cpc) check_valid_cpc_status(method, uri, cpc) check_required_fields(method, uri, body, ['name', 'initial-memory', 'maximum-memory']) # TODO: There are some more input properties that are required under # certain conditions. # Reflect the result of creating the partition new_partition = cpc.partitions.add(body) return {'object-uri': new_partition.uri} class PartitionHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single Partition resource. """ # TODO: Add check_valid_cpc_status() in Update Partition Properties # TODO: Add check_partition_status(transitional) in Update Partition Props @staticmethod def delete(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Delete Partition.""" try: partition = hmc.lookup_by_uri(uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, valid_statuses=['stopped']) # Reflect the result of deleting the partition partition.manager.remove(partition.oid) class PartitionStartHandler(object): """ Handler class for operation: Start Partition. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Start Partition (requires DPM mode).""" assert wait_for_completion is True # async not supported yet partition_oid = uri_parms[0] partition_uri = '/api/partitions/' + partition_oid try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, valid_statuses=['stopped']) # Reflect the result of starting the partition partition.properties['status'] = 'active' return {} class PartitionStopHandler(object): """ Handler class for operation: Stop Partition. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Stop Partition (requires DPM mode).""" assert wait_for_completion is True # async not supported yet partition_oid = uri_parms[0] partition_uri = '/api/partitions/' + partition_oid try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, valid_statuses=['active', 'paused', 'terminated']) # TODO: Clarify with HMC team whether statuses 'degraded' and # 'reservation-error' should also be stoppable. Otherwise, the # partition cannot leave these states. # Reflect the result of stopping the partition partition.properties['status'] = 'stopped' return {} class PartitionScsiDumpHandler(object): """ Handler class for operation: Dump Partition. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Dump Partition (requires DPM mode).""" assert wait_for_completion is True # async not supported yet partition_oid = uri_parms[0] partition_uri = '/api/partitions/' + partition_oid try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, valid_statuses=['active', 'paused', 'terminated']) check_required_fields(method, uri, body, ['dump-load-hba-uri', 'dump-world-wide-port-name', 'dump-logical-unit-number']) # We don't reflect the dump in the mock state. return {} class PartitionStartDumpProgramHandler(object): """ Handler class for operation: Start Dump Program. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Start Dump Program (requires DPM mode).""" assert wait_for_completion is True # async not supported yet partition_oid = uri_parms[0] partition_uri = '/api/partitions/' + partition_oid try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, valid_statuses=['active', 'degraded', 'paused', 'terminated']) check_required_fields(method, uri, body, ['dump-program-info', 'dump-program-type']) # We don't reflect the dump in the mock state. return {} class PartitionPswRestartHandler(object): """ Handler class for operation: Perform PSW Restart. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Perform PSW Restart (requires DPM mode).""" assert wait_for_completion is True # async not supported yet partition_oid = uri_parms[0] partition_uri = '/api/partitions/' + partition_oid try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, valid_statuses=['active', 'paused', 'terminated']) # We don't reflect the PSW restart in the mock state. return {} class PartitionMountIsoImageHandler(object): """ Handler class for operation: Mount ISO Image. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Mount ISO Image (requires DPM mode).""" assert wait_for_completion is True # synchronous operation partition_oid = uri_parms[0] partition_uri = '/api/partitions/' + partition_oid try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, invalid_statuses=['starting', 'stopping']) # Parse and check required query parameters query_parms = parse_query_parms(method, uri, uri_parms[1]) try: image_name = query_parms['image-name'] except KeyError: new_exc = BadRequestError( method, uri, reason=1, message="Missing required URI query parameter 'image-name'") new_exc.__cause__ = None raise new_exc # zhmcclient_mock.BadRequestError try: ins_file_name = query_parms['ins-file-name'] except KeyError: new_exc = BadRequestError( method, uri, reason=1, message="Missing required URI query parameter 'ins-file-name'") new_exc.__cause__ = None raise new_exc # zhmcclient_mock.BadRequestError # Reflect the effect of mounting in the partition properties partition.properties['boot-iso-image-name'] = image_name partition.properties['boot-iso-ins-file'] = ins_file_name return {} class PartitionUnmountIsoImageHandler(object): """ Handler class for operation: Unmount ISO Image. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Unmount ISO Image (requires DPM mode).""" assert wait_for_completion is True # synchronous operation partition_oid = uri_parms[0] partition_uri = '/api/partitions/' + partition_oid try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, invalid_statuses=['starting', 'stopping']) # Reflect the effect of unmounting in the partition properties partition.properties['boot-iso-image-name'] = None partition.properties['boot-iso-ins-file'] = None return {} def ensure_crypto_config(partition): """ Ensure that the 'crypto-configuration' property on the faked partition is initialized. """ if 'crypto-configuration' not in partition.properties or \ partition.properties['crypto-configuration'] is None: partition.properties['crypto-configuration'] = {} crypto_config = partition.properties['crypto-configuration'] if 'crypto-adapter-uris' not in crypto_config or \ crypto_config['crypto-adapter-uris'] is None: crypto_config['crypto-adapter-uris'] = [] adapter_uris = crypto_config['crypto-adapter-uris'] if 'crypto-domain-configurations' not in crypto_config or \ crypto_config['crypto-domain-configurations'] is None: crypto_config['crypto-domain-configurations'] = [] domain_configs = crypto_config['crypto-domain-configurations'] return adapter_uris, domain_configs class PartitionIncreaseCryptoConfigHandler(object): """ Handler class for operation: Increase Crypto Configuration. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Increase Crypto Configuration (requires DPM mode).""" assert wait_for_completion is True # async not supported yet partition_oid = uri_parms[0] partition_uri = '/api/partitions/' + partition_oid try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, invalid_statuses=['starting', 'stopping']) check_required_fields(method, uri, body, []) # check just body adapter_uris, domain_configs = ensure_crypto_config(partition) add_adapter_uris = body.get('crypto-adapter-uris', []) add_domain_configs = body.get('crypto-domain-configurations', []) # We don't support finding errors in this simple-minded mock support, # so we assume that the input is fine (e.g. no invalid adapters) and # we just add it. for _uri in add_adapter_uris: if _uri not in adapter_uris: adapter_uris.append(_uri) for dc in add_domain_configs: if dc not in domain_configs: domain_configs.append(dc) class PartitionDecreaseCryptoConfigHandler(object): """ Handler class for operation: Decrease Crypto Configuration. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Decrease Crypto Configuration (requires DPM mode).""" assert wait_for_completion is True # async not supported yet partition_oid = uri_parms[0] partition_uri = '/api/partitions/' + partition_oid try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, invalid_statuses=['starting', 'stopping']) check_required_fields(method, uri, body, []) # check just body adapter_uris, domain_configs = ensure_crypto_config(partition) remove_adapter_uris = body.get('crypto-adapter-uris', []) remove_domain_indexes = body.get('crypto-domain-indexes', []) # We don't support finding errors in this simple-minded mock support, # so we assume that the input is fine (e.g. no invalid adapters) and # we just remove it. for _uri in remove_adapter_uris: if _uri in adapter_uris: adapter_uris.remove(_uri) for remove_di in remove_domain_indexes: for i, dc in enumerate(domain_configs): if dc['domain-index'] == remove_di: del domain_configs[i] class PartitionChangeCryptoConfigHandler(object): """ Handler class for operation: Change Crypto Configuration. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Change Crypto Configuration (requires DPM mode).""" assert wait_for_completion is True # async not supported yet partition_oid = uri_parms[0] partition_uri = '/api/partitions/' + partition_oid try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, invalid_statuses=['starting', 'stopping']) check_required_fields(method, uri, body, ['domain-index', 'access-mode']) _, domain_configs = ensure_crypto_config(partition) change_domain_index = body['domain-index'] change_access_mode = body['access-mode'] # We don't support finding errors in this simple-minded mock support, # so we assume that the input is fine (e.g. no invalid domain indexes) # and we just change it. for dc in domain_configs: if dc['domain-index'] == change_domain_index: dc['access-mode'] = change_access_mode class HbasHandler(object): """ Handler class for HTTP methods on set of Hba resources. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Create HBA (requires DPM mode).""" assert wait_for_completion is True # async not supported yet partition_uri = re.sub('/hbas$', '', uri) try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, invalid_statuses=['starting', 'stopping']) check_required_fields(method, uri, body, ['name', 'adapter-port-uri']) # Check the port-related input property port_uri = body['adapter-port-uri'] m = re.match(r'(^/api/adapters/[^/]+)/storage-ports/[^/]+$', port_uri) if not m: # We treat an invalid port URI like "port not found". raise InvalidResourceError(method, uri, reason=6, resource_uri=port_uri) adapter_uri = m.group(1) try: hmc.lookup_by_uri(adapter_uri) except KeyError: new_exc = InvalidResourceError(method, uri, reason=2, resource_uri=adapter_uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError try: hmc.lookup_by_uri(port_uri) except KeyError: new_exc = InvalidResourceError(method, uri, reason=6, resource_uri=port_uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError new_hba = partition.hbas.add(body) return {'element-uri': new_hba.uri} class HbaHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single Hba resource. """ # TODO: Add check_valid_cpc_status() in Update HBA Properties # TODO: Add check_partition_status(transitional) in Update HBA Properties @staticmethod def delete(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Delete HBA (requires DPM mode).""" try: hba = hmc.lookup_by_uri(uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError partition = hba.manager.parent cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, invalid_statuses=['starting', 'stopping']) partition.hbas.remove(hba.oid) class HbaReassignPortHandler(object): """ Handler class for operation: Reassign Storage Adapter Port. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Reassign Storage Adapter Port (requires DPM mode).""" assert wait_for_completion is True # async not supported yet partition_oid = uri_parms[0] partition_uri = '/api/partitions/' + partition_oid hba_oid = uri_parms[1] hba_uri = '/api/partitions/' + partition_oid + '/hbas/' + hba_oid try: hba = hmc.lookup_by_uri(hba_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError partition = hmc.lookup_by_uri(partition_uri) # assert it exists cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, invalid_statuses=['starting', 'stopping']) check_required_fields(method, uri, body, ['adapter-port-uri']) # Reflect the effect of the operation on the HBA new_port_uri = body['adapter-port-uri'] hba.properties['adapter-port-uri'] = new_port_uri class NicsHandler(object): """ Handler class for HTTP methods on set of Nic resources. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Create NIC (requires DPM mode).""" assert wait_for_completion is True # async not supported yet partition_uri = re.sub('/nics$', '', uri) try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, invalid_statuses=['starting', 'stopping']) check_required_fields(method, uri, body, ['name']) # Check the port-related input properties if 'network-adapter-port-uri' in body: port_uri = body['network-adapter-port-uri'] m = re.match(r'(^/api/adapters/[^/]+)/network-ports/[^/]+$', port_uri) if not m: # We treat an invalid port URI like "port not found". raise InvalidResourceError(method, uri, reason=6, resource_uri=port_uri) adapter_uri = m.group(1) try: hmc.lookup_by_uri(adapter_uri) except KeyError: new_exc = InvalidResourceError(method, uri, reason=2, resource_uri=adapter_uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError try: hmc.lookup_by_uri(port_uri) except KeyError: new_exc = InvalidResourceError(method, uri, reason=6, resource_uri=port_uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError elif 'virtual-switch-uri' in body: vswitch_uri = body['virtual-switch-uri'] try: hmc.lookup_by_uri(vswitch_uri) except KeyError: new_exc = InvalidResourceError(method, uri, reason=2, resource_uri=vswitch_uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError else: nic_name = body.get('name', None) raise BadRequestError( method, uri, reason=5, message="The input properties for creating a NIC {!r} in " "partition {!r} must specify either the " "'network-adapter-port-uri' or the " "'virtual-switch-uri' property.". format(nic_name, partition.name)) # We have ensured that the vswitch exists, so no InputError handling new_nic = partition.nics.add(body) return {'element-uri': new_nic.uri} class NicHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single Nic resource. """ # TODO: Add check_valid_cpc_status() in Update NIC Properties # TODO: Add check_partition_status(transitional) in Update NIC Properties @staticmethod def delete(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Delete NIC (requires DPM mode).""" try: nic = hmc.lookup_by_uri(uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError partition = nic.manager.parent cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, invalid_statuses=['starting', 'stopping']) partition.nics.remove(nic.oid) class VirtualFunctionsHandler(object): """ Handler class for HTTP methods on set of VirtualFunction resources. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Create Virtual Function (requires DPM mode).""" assert wait_for_completion is True # async not supported yet partition_uri = re.sub('/virtual-functions$', '', uri) try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, invalid_statuses=['starting', 'stopping']) check_required_fields(method, uri, body, ['name']) new_vf = partition.virtual_functions.add(body) return {'element-uri': new_vf.uri} class VirtualFunctionHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single VirtualFunction resource. """ # TODO: Add check_valid_cpc_status() in Update VF Properties # TODO: Add check_partition_status(transitional) in Update VF Properties @staticmethod def delete(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Delete Virtual Function (requires DPM mode).""" try: vf = hmc.lookup_by_uri(uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError partition = vf.manager.parent cpc = partition.manager.parent assert cpc.dpm_enabled check_valid_cpc_status(method, uri, cpc) check_partition_status(method, uri, partition, invalid_statuses=['starting', 'stopping']) partition.virtual_functions.remove(vf.oid) class VirtualSwitchesHandler(object): """ Handler class for HTTP methods on set of VirtualSwitch resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List Virtual Switches of a CPC (empty result if not in DPM mode).""" cpc_oid = uri_parms[0] query_str = uri_parms[1] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError result_vswitches = [] if cpc.dpm_enabled: filter_args = parse_query_parms(method, uri, query_str) for vswitch in cpc.virtual_switches.list(filter_args): result_vswitch = {} for prop in vswitch.properties: if prop in ('object-uri', 'name', 'type'): result_vswitch[prop] = vswitch.properties[prop] result_vswitches.append(result_vswitch) return {'virtual-switches': result_vswitches} class VirtualSwitchHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single VirtualSwitch resource. """ pass class VirtualSwitchGetVnicsHandler(object): """ Handler class for operation: Get Connected VNICs of a Virtual Switch. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Get Connected VNICs of a Virtual Switch (requires DPM mode).""" assert wait_for_completion is True # async not supported yet vswitch_oid = uri_parms[0] vswitch_uri = '/api/virtual-switches/' + vswitch_oid try: vswitch = hmc.lookup_by_uri(vswitch_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = vswitch.manager.parent assert cpc.dpm_enabled connected_vnic_uris = vswitch.properties['connected-vnic-uris'] return {'connected-vnic-uris': connected_vnic_uris} class StorageGroupsHandler(object): """ Handler class for HTTP methods on set of StorageGroup resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List Storage Groups (always global but with filters).""" query_str = uri_parms[0] filter_args = parse_query_parms(method, uri, query_str) result_storage_groups = [] for sg in hmc.consoles.console.storage_groups.list(filter_args): result_sg = {} for prop in sg.properties: if prop in ('object-uri', 'cpc-uri', 'name', 'status', 'fulfillment-state', 'type'): result_sg[prop] = sg.properties[prop] result_storage_groups.append(result_sg) return {'storage-groups': result_storage_groups} @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Create Storage Group.""" assert wait_for_completion is True # async not supported yet check_required_fields(method, uri, body, ['name', 'cpc-uri', 'type']) cpc_uri = body['cpc-uri'] try: cpc = hmc.lookup_by_uri(cpc_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError if not cpc.dpm_enabled: raise CpcNotInDpmError(method, uri, cpc) check_valid_cpc_status(method, uri, cpc) # Reflect the result of creating the storage group body2 = body.copy() sv_requests = body2.pop('storage-volumes', None) new_storage_group = hmc.consoles.console.storage_groups.add(body2) sv_uris = [] if sv_requests: for sv_req in sv_requests: check_required_fields(method, uri, sv_req, ['operation']) operation = sv_req['operation'] if operation == 'create': sv_props = sv_req.copy() del sv_props['operation'] if 'element-uri' in sv_props: raise BadRequestError( method, uri, 7, "The 'element-uri' field in storage-volumes is " "invalid for the create operation") sv_uri = new_storage_group.storage_volumes.add(sv_props) sv_uris.append(sv_uri) else: raise BadRequestError( method, uri, 5, "Invalid value for storage-volumes 'operation' " "field: %s" % operation) return { 'object-uri': new_storage_group.uri, 'element-uris': sv_uris, } class StorageGroupHandler(GenericGetPropertiesHandler): """ Handler class for HTTP methods on single StorageGroup resource. """ pass class StorageGroupModifyHandler(object): """ Handler class for operation: Modify Storage Group Properties. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Modify Storage Group Properties.""" assert wait_for_completion is True # async not supported yet # The URI is a POST operation, so we need to construct the SG URI storage_group_oid = uri_parms[0] storage_group_uri = '/api/storage-groups/' + storage_group_oid try: storage_group = hmc.lookup_by_uri(storage_group_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError # Reflect the result of modifying the storage group body2 = body.copy() sv_requests = body2.pop('storage-volumes', None) storage_group.update(body2) sv_uris = [] if sv_requests: for sv_req in sv_requests: check_required_fields(method, uri, sv_req, ['operation']) operation = sv_req['operation'] if operation == 'create': sv_props = sv_req.copy() del sv_props['operation'] if 'element-uri' in sv_props: raise BadRequestError( method, uri, 7, "The 'element-uri' field in storage-volumes is " "invalid for the create operation") sv_uri = storage_group.storage_volumes.add(sv_props) sv_uris.append(sv_uri) elif operation == 'modify': check_required_fields(method, uri, sv_req, ['element-uri']) sv_uri = sv_req['element-uri'] storage_volume = hmc.lookup_by_uri(sv_uri) storage_volume.update_properties(sv_props) elif operation == 'delete': check_required_fields(method, uri, sv_req, ['element-uri']) sv_uri = sv_req['element-uri'] storage_volume = hmc.lookup_by_uri(sv_uri) storage_volume.delete() else: raise BadRequestError( method, uri, 5, "Invalid value for storage-volumes 'operation' " "field: %s" % operation) return { 'element-uris': sv_uris, # SVs created, maintaining the order } class StorageGroupDeleteHandler(object): """ Handler class for operation: Delete Storage Group. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Delete Storage Group.""" assert wait_for_completion is True # async not supported yet # The URI is a POST operation, so we need to construct the SG URI storage_group_oid = uri_parms[0] storage_group_uri = '/api/storage-groups/' + storage_group_oid try: storage_group = hmc.lookup_by_uri(storage_group_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError # TODO: Check that the SG is detached from any partitions # Reflect the result of deleting the storage_group storage_group.manager.remove(storage_group.oid) class StorageGroupRequestFulfillmentHandler(object): """ Handler class for operation: Request Storage Group Fulfillment. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Request Storage Group Fulfillment.""" assert wait_for_completion is True # async not supported yet # The URI is a POST operation, so we need to construct the SG URI storage_group_oid = uri_parms[0] storage_group_uri = '/api/storage-groups/' + storage_group_oid try: hmc.lookup_by_uri(storage_group_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError # Reflect the result of requesting fulfilment for the storage group pass class StorageGroupAddCandidatePortsHandler(object): """ Handler class for operation: Add Candidate Adapter Ports to an FCP Storage Group. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Add Candidate Adapter Ports to an FCP Storage Group.""" assert wait_for_completion is True # async not supported yet # The URI is a POST operation, so we need to construct the SG URI storage_group_oid = uri_parms[0] storage_group_uri = '/api/storage-groups/' + storage_group_oid try: storage_group = hmc.lookup_by_uri(storage_group_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['adapter-port-uris']) # TODO: Check that storage group has type FCP # Reflect the result of adding the candidate ports candidate_adapter_port_uris = \ storage_group.properties['candidate-adapter-port-uris'] for ap_uri in body['adapter-port-uris']: if ap_uri in candidate_adapter_port_uris: raise ConflictError(method, uri, 483, "Adapter port is already in candidate " "list of storage group %s: %s" % (storage_group.name, ap_uri)) candidate_adapter_port_uris.append(ap_uri) class StorageGroupRemoveCandidatePortsHandler(object): """ Handler class for operation: Remove Candidate Adapter Ports from an FCP Storage Group. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Remove Candidate Adapter Ports from an FCP Storage Group.""" assert wait_for_completion is True # async not supported yet # The URI is a POST operation, so we need to construct the SG URI storage_group_oid = uri_parms[0] storage_group_uri = '/api/storage-groups/' + storage_group_oid try: storage_group = hmc.lookup_by_uri(storage_group_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['adapter-port-uris']) # TODO: Check that storage group has type FCP # Reflect the result of adding the candidate ports candidate_adapter_port_uris = \ storage_group.properties['candidate-adapter-port-uris'] for ap_uri in body['adapter-port-uris']: if ap_uri not in candidate_adapter_port_uris: raise ConflictError(method, uri, 479, "Adapter port is not in candidate " "list of storage group %s: %s" % (storage_group.name, ap_uri)) candidate_adapter_port_uris.remove(ap_uri) class CapacityGroupsHandler(object): """ Handler class for HTTP methods on set of CapacityGroup resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List Capacity Groups (always global but with filters).""" cpc_oid = uri_parms[0] cpc_uri = '/api/cpcs/' + cpc_oid try: cpc = hmc.lookup_by_uri(cpc_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError query_str = uri_parms[1] filter_args = parse_query_parms(method, uri, query_str) result_capacity_groups = [] for cg in cpc.capacity_groups.list(filter_args): result_cg = {} for prop in cg.properties: if prop in ('element-uri', 'name'): result_cg[prop] = cg.properties[prop] result_capacity_groups.append(result_cg) return {'capacity-groups': result_capacity_groups} @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Create Capacity Group.""" assert wait_for_completion is True # async not supported yet check_required_fields(method, uri, body, ['name']) cpc_oid = uri_parms[0] cpc_uri = '/api/cpcs/' + cpc_oid try: cpc = hmc.lookup_by_uri(cpc_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError if not cpc.dpm_enabled: raise CpcNotInDpmError(method, uri, cpc) check_valid_cpc_status(method, uri, cpc) # Reflect the result of creating the capacity group new_capacity_group = cpc.capacity_groups.add(body) return { 'element-uri': new_capacity_group.uri } class CapacityGroupHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler, GenericDeleteHandler): """ Handler class for HTTP methods on single CapacityGroup resource. """ @staticmethod def delete(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: Delete Capacity Group.""" try: capacity_group = hmc.lookup_by_uri(uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError # Check that Capacity Group is empty partition_uris = capacity_group.properties['partition-uris'] if partition_uris: raise ConflictError( method, uri, reason=110, message="Capacity group {!r} is not empty and contains " "partitions with URIs {!r}". format(capacity_group.name, partition_uris)) # Delete the mocked resource capacity_group.manager.remove(capacity_group.oid) class CapacityGroupAddPartitionHandler(object): """ Handler class for operation: Add Partition to Capacity Group. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Add Partition to Capacity Group.""" assert wait_for_completion is True # async not supported yet # The URI is a POST operation, so we need to construct the CG URI cpc_oid = uri_parms[0] cpc_uri = '/api/cpcs/' + cpc_oid try: cpc = hmc.lookup_by_uri(cpc_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cg_oid = uri_parms[1] cg_uri = cpc_uri + '/capacity-groups/' + cg_oid try: capacity_group = hmc.lookup_by_uri(cg_uri) except KeyError: new_exc = InvalidResourceError(method, uri, reason=150) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['partition-uri']) # Check the partition exists partition_uri = body['partition-uri'] try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri, reason=2) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError # Check the partition is in shared processor mode processor_mode = partition.properties.get('processor-mode', 'shared') if processor_mode != 'shared': raise ConflictError(method, uri, 170, "Partition %s is in %s processor mode" % (partition.name, processor_mode)) # Check the partition is not in this capacity group partition_uris = capacity_group.properties['partition-uris'] if partition.uri in partition_uris: raise ConflictError(method, uri, 130, "Partition %s is already a member of " "this capacity group %s" % (partition.name, capacity_group.name)) # Check the partition is not in any other capacity group for cg in cpc.capacity_groups.list(): if partition.uri in cg.properties['partition-uris']: raise ConflictError(method, uri, 120, "Partition %s is already a member of " "another capacity group %s" % (partition.name, cg.name)) # Reflect the result of adding the partition to the capacity group capacity_group.properties['partition-uris'].append(partition.uri) class CapacityGroupRemovePartitionHandler(object): """ Handler class for operation: Remove Partition from Capacity Group. """ @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Remove Partition from Capacity Group.""" assert wait_for_completion is True # async not supported yet # The URI is a POST operation, so we need to construct the CG URI cpc_oid = uri_parms[0] cpc_uri = '/api/cpcs/' + cpc_oid try: hmc.lookup_by_uri(cpc_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cg_oid = uri_parms[1] cg_uri = cpc_uri + '/capacity-groups/' + cg_oid try: capacity_group = hmc.lookup_by_uri(cg_uri) except KeyError: new_exc = InvalidResourceError(method, uri, reason=150) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError check_required_fields(method, uri, body, ['partition-uri']) # Check the partition exists partition_uri = body['partition-uri'] try: partition = hmc.lookup_by_uri(partition_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError # Check the partition is in this capacity group partition_uris = capacity_group.properties['partition-uris'] if partition.uri not in partition_uris: raise ConflictError(method, uri, 140, "Partition %s is not a member of " "capacity group %s" % (partition.name, capacity_group.name)) # Reflect the result of removing the partition from the capacity group capacity_group.properties['partition-uris'].remove(partition.uri) class LparsHandler(object): """ Handler class for HTTP methods on set of Lpar resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List Logical Partitions of CPC (empty result in DPM mode.""" cpc_oid = uri_parms[0] query_str = uri_parms[1] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError result_lpars = [] if not cpc.dpm_enabled: filter_args = parse_query_parms(method, uri, query_str) for lpar in cpc.lpars.list(filter_args): result_lpar = {} for prop in lpar.properties: if prop in ('object-uri', 'name', 'status'): result_lpar[prop] = lpar.properties[prop] result_lpars.append(result_lpar) return {'logical-partitions': result_lpars} class LparHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single Lpar resource. """ pass class LparActivateHandler(object): """ A handler class for the "Activate Logical Partition" operation. """ @staticmethod def get_status(): """ Status retrieval method that returns the status the faked Lpar will have after completion of the the "Activate Logical Partition" operation. This method returns the successful status 'not-operating', and can be mocked by testcases to return a different status (e.g. 'exceptions'). """ return 'not-operating' @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Activate Logical Partition (requires classic mode).""" assert wait_for_completion is True # async not supported yet lpar_oid = uri_parms[0] lpar_uri = '/api/logical-partitions/' + lpar_oid try: lpar = hmc.lookup_by_uri(lpar_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = lpar.manager.parent assert not cpc.dpm_enabled status = lpar.properties.get('status', None) force = body.get('force', False) if body else False if status == 'operating' and not force: raise ServerError(method, uri, reason=263, message="LPAR {!r} could not be activated " "because the LPAR is in status {} " "(and force was not specified).". format(lpar.name, status)) act_profile_name = body.get('activation-profile-name', None) if not act_profile_name: act_profile_name = lpar.properties.get( 'next-activation-profile-name', None) if act_profile_name is None: act_profile_name = '' # Perform the check between LPAR name and profile name if act_profile_name != lpar.name: raise ServerError(method, uri, reason=263, message="LPAR {!r} could not be activated " "because the name of the image activation " "profile {!r} is different from the LPAR name.". format(lpar.name, act_profile_name)) # Reflect the activation in the resource lpar.properties['status'] = LparActivateHandler.get_status() lpar.properties['last-used-activation-profile'] = act_profile_name class LparDeactivateHandler(object): """ A handler class for the "Deactivate Logical Partition" operation. """ @staticmethod def get_status(): """ Status retrieval method that returns the status the faked Lpar will have after completion of the the "Deactivate Logical Partition" operation. This method returns the successful status 'not-activated', and can be mocked by testcases to return a different status (e.g. 'exceptions'). """ return 'not-activated' @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Deactivate Logical Partition (requires classic mode).""" assert wait_for_completion is True # async not supported yet lpar_oid = uri_parms[0] lpar_uri = '/api/logical-partitions/' + lpar_oid try: lpar = hmc.lookup_by_uri(lpar_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = lpar.manager.parent assert not cpc.dpm_enabled status = lpar.properties.get('status', None) force = body.get('force', False) if body else False if status == 'not-activated' and not force: # Note that the current behavior (on EC12) is that force=True # still causes this error to be returned (different behavior # compared to the Activate and Load operations). raise ServerError(method, uri, reason=263, message="LPAR {!r} could not be deactivated " "because the LPAR is already deactivated " "(and force was not specified).". format(lpar.name)) if status == 'operating' and not force: raise ServerError(method, uri, reason=263, message="LPAR {!r} could not be deactivated " "because the LPAR is in status {} " "(and force was not specified).". format(lpar.name, status)) # Reflect the deactivation in the resource lpar.properties['status'] = LparDeactivateHandler.get_status() class LparLoadHandler(object): """ A handler class for the "Load Logical Partition" operation. """ @staticmethod def get_status(): """ Status retrieval method that returns the status the faked Lpar will have after completion of the "Load Logical Partition" operation. This method returns the successful status 'operating', and can be mocked by testcases to return a different status (e.g. 'exceptions'). """ return 'operating' @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): # pylint: disable=unused-argument """Operation: Load Logical Partition (requires classic mode).""" assert wait_for_completion is True # async not supported yet lpar_oid = uri_parms[0] lpar_uri = '/api/logical-partitions/' + lpar_oid try: lpar = hmc.lookup_by_uri(lpar_uri) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError cpc = lpar.manager.parent assert not cpc.dpm_enabled status = lpar.properties.get('status', None) force = body.get('force', False) if body else False clear_indicator = body.get('clear-indicator', True) if body else True store_status_indicator = body.get('store-status-indicator', False) if body else False if status == 'not-activated': raise ConflictError(method, uri, reason=0, message="LPAR {!r} could not be loaded " "because the LPAR is in status {}.". format(lpar.name, status)) if status == 'operating' and not force: raise ServerError(method, uri, reason=263, message="LPAR {!r} could not be loaded " "because the LPAR is already loaded " "(and force was not specified).". format(lpar.name)) load_address = body.get('load-address', None) if body else None if not load_address: # Starting with z14, this parameter is optional and a last-used # property is available. load_address = lpar.properties.get('last-used-load-address', None) if load_address is None: # TODO: Verify actual error for this case on a z14. raise BadRequestError(method, uri, reason=5, message="LPAR {!r} could not be loaded " "because a load address is not specified " "in the request or in the Lpar last-used " "property". format(lpar.name)) load_parameter = body.get('load-parameter', None) if body else None if not load_parameter: # Starting with z14, a last-used property is available. load_parameter = lpar.properties.get( 'last-used-load-parameter', None) if load_parameter is None: load_parameter = '' # Reflect the load in the resource if clear_indicator: lpar.properties['memory'] = '' if store_status_indicator: lpar.properties['stored-status'] = status else: lpar.properties['stored-status'] = None lpar.properties['status'] = LparLoadHandler.get_status() lpar.properties['last-used-load-address'] = load_address lpar.properties['last-used-load-parameter'] = load_parameter class ResetActProfilesHandler(object): """ Handler class for HTTP methods on set of ResetActProfile resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List Reset Activation Profiles (requires classic mode).""" cpc_oid = uri_parms[0] query_str = uri_parms[1] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError assert not cpc.dpm_enabled # TODO: Verify error or empty result? result_profiles = [] filter_args = parse_query_parms(method, uri, query_str) for profile in cpc.reset_activation_profiles.list(filter_args): result_profile = {} for prop in profile.properties: if prop in ('element-uri', 'name'): result_profile[prop] = profile.properties[prop] result_profiles.append(result_profile) return {'reset-activation-profiles': result_profiles} class ResetActProfileHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single ResetActProfile resource. """ pass class ImageActProfilesHandler(object): """ Handler class for HTTP methods on set of ImageActProfile resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List Image Activation Profiles (requires classic mode).""" cpc_oid = uri_parms[0] query_str = uri_parms[1] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError assert not cpc.dpm_enabled # TODO: Verify error or empty result? result_profiles = [] filter_args = parse_query_parms(method, uri, query_str) for profile in cpc.image_activation_profiles.list(filter_args): result_profile = {} for prop in profile.properties: if prop in ('element-uri', 'name'): result_profile[prop] = profile.properties[prop] result_profiles.append(result_profile) return {'image-activation-profiles': result_profiles} class ImageActProfileHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single ImageActProfile resource. """ pass class LoadActProfilesHandler(object): """ Handler class for HTTP methods on set of LoadActProfile resources. """ @staticmethod def get(method, hmc, uri, uri_parms, logon_required): # pylint: disable=unused-argument """Operation: List Load Activation Profiles (requires classic mode).""" cpc_oid = uri_parms[0] query_str = uri_parms[1] try: cpc = hmc.cpcs.lookup_by_oid(cpc_oid) except KeyError: new_exc = InvalidResourceError(method, uri) new_exc.__cause__ = None raise new_exc # zhmcclient_mock.InvalidResourceError assert not cpc.dpm_enabled # TODO: Verify error or empty result? result_profiles = [] filter_args = parse_query_parms(method, uri, query_str) for profile in cpc.load_activation_profiles.list(filter_args): result_profile = {} for prop in profile.properties: if prop in ('element-uri', 'name'): result_profile[prop] = profile.properties[prop] result_profiles.append(result_profile) return {'load-activation-profiles': result_profiles} class LoadActProfileHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): """ Handler class for HTTP methods on single LoadActProfile resource. """ pass # URIs to be handled # Note: This list covers only the HMC operations implemented in the zhmcclient. # The HMC supports several more operations. URIS = ( # (uri_regexp, handler_class) # In all modes: (r'/api/version', VersionHandler), (r'/api/console', ConsoleHandler), (r'/api/console/operations/restart', ConsoleRestartHandler), (r'/api/console/operations/shutdown', ConsoleShutdownHandler), (r'/api/console/operations/make-primary', ConsoleMakePrimaryHandler), (r'/api/console/operations/reorder-user-patterns', ConsoleReorderUserPatternsHandler), (r'/api/console/operations/get-audit-log(?:\?(.*))?', ConsoleGetAuditLogHandler), (r'/api/console/operations/get-security-log(?:\?(.*))?', ConsoleGetSecurityLogHandler), (r'/api/console/operations/list-unmanaged-cpcs(?:\?(.*))?', ConsoleListUnmanagedCpcsHandler), (r'/api/console/users(?:\?(.*))?', UsersHandler), (r'/api/users/([^/]+)', UserHandler), (r'/api/users/([^/]+)/operations/add-user-role', UserAddUserRoleHandler), (r'/api/users/([^/]+)/operations/remove-user-role', UserRemoveUserRoleHandler), (r'/api/console/user-roles(?:\?(.*))?', UserRolesHandler), (r'/api/user-roles/([^/]+)', UserRoleHandler), (r'/api/user-roles/([^/]+)/operations/add-permission', UserRoleAddPermissionHandler), (r'/api/user-roles/([^/]+)/operations/remove-permission', UserRoleRemovePermissionHandler), (r'/api/console/tasks(?:\?(.*))?', TasksHandler), (r'/api/console/tasks/([^/]+)', TaskHandler), (r'/api/console/user-patterns(?:\?(.*))?', UserPatternsHandler), (r'/api/console/user-patterns/([^/]+)', UserPatternHandler), (r'/api/console/password-rules(?:\?(.*))?', PasswordRulesHandler), (r'/api/console/password-rules/([^/]+)', PasswordRuleHandler), (r'/api/console/ldap-server-definitions(?:\?(.*))?', LdapServerDefinitionsHandler), (r'/api/console/ldap-server-definitions/([^/]+)', LdapServerDefinitionHandler), (r'/api/cpcs(?:\?(.*))?', CpcsHandler), (r'/api/cpcs/([^/]+)', CpcHandler), (r'/api/cpcs/([^/]+)/operations/set-cpc-power-save', CpcSetPowerSaveHandler), (r'/api/cpcs/([^/]+)/operations/set-cpc-power-capping', CpcSetPowerCappingHandler), (r'/api/cpcs/([^/]+)/energy-management-data', CpcGetEnergyManagementDataHandler), (r'/api/services/metrics/context', MetricsContextsHandler), (r'/api/services/metrics/context/([^/]+)', MetricsContextHandler), # Only in DPM mode: (r'/api/cpcs/([^/]+)/operations/start', CpcStartHandler), (r'/api/cpcs/([^/]+)/operations/stop', CpcStopHandler), (r'/api/cpcs/([^/]+)/operations/export-port-names-list', CpcExportPortNamesListHandler), (r'/api/cpcs/([^/]+)/adapters(?:\?(.*))?', AdaptersHandler), (r'/api/adapters/([^/]+)', AdapterHandler), (r'/api/adapters/([^/]+)/operations/change-crypto-type', AdapterChangeCryptoTypeHandler), (r'/api/adapters/([^/]+)/operations/change-adapter-type', AdapterChangeAdapterTypeHandler), (r'/api/adapters/([^/]+)/network-ports/([^/]+)', NetworkPortHandler), (r'/api/adapters/([^/]+)/storage-ports/([^/]+)', StoragePortHandler), (r'/api/cpcs/([^/]+)/partitions(?:\?(.*))?', PartitionsHandler), (r'/api/partitions/([^/]+)', PartitionHandler), (r'/api/partitions/([^/]+)/operations/start', PartitionStartHandler), (r'/api/partitions/([^/]+)/operations/stop', PartitionStopHandler), (r'/api/partitions/([^/]+)/operations/scsi-dump', PartitionScsiDumpHandler), (r'/api/partitions/([^/]+)/operations/start-dump-program', PartitionStartDumpProgramHandler), (r'/api/partitions/([^/]+)/operations/psw-restart', PartitionPswRestartHandler), (r'/api/partitions/([^/]+)/operations/mount-iso-image(?:\?(.*))?', PartitionMountIsoImageHandler), (r'/api/partitions/([^/]+)/operations/unmount-iso-image', PartitionUnmountIsoImageHandler), (r'/api/partitions/([^/]+)/operations/increase-crypto-configuration', PartitionIncreaseCryptoConfigHandler), (r'/api/partitions/([^/]+)/operations/decrease-crypto-configuration', PartitionDecreaseCryptoConfigHandler), (r'/api/partitions/([^/]+)/operations/change-crypto-domain-configuration', PartitionChangeCryptoConfigHandler), (r'/api/partitions/([^/]+)/hbas(?:\?(.*))?', HbasHandler), (r'/api/partitions/([^/]+)/hbas/([^/]+)', HbaHandler), (r'/api/partitions/([^/]+)/hbas/([^/]+)/operations/'\ 'reassign-storage-adapter-port', HbaReassignPortHandler), (r'/api/partitions/([^/]+)/nics(?:\?(.*))?', NicsHandler), (r'/api/partitions/([^/]+)/nics/([^/]+)', NicHandler), (r'/api/partitions/([^/]+)/virtual-functions(?:\?(.*))?', VirtualFunctionsHandler), (r'/api/partitions/([^/]+)/virtual-functions/([^/]+)', VirtualFunctionHandler), (r'/api/cpcs/([^/]+)/virtual-switches(?:\?(.*))?', VirtualSwitchesHandler), (r'/api/virtual-switches/([^/]+)', VirtualSwitchHandler), (r'/api/virtual-switches/([^/]+)/operations/get-connected-vnics', VirtualSwitchGetVnicsHandler), (r'/api/storage-groups(?:\?(.*))?', StorageGroupsHandler), (r'/api/storage-groups/([^/]+)', StorageGroupHandler), (r'/api/storage-groups/([^/]+)/operations/delete', StorageGroupDeleteHandler), (r'/api/storage-groups/([^/]+)/operations/modify', StorageGroupModifyHandler), (r'/api/storage-groups/([^/]+)/operations/request-fulfillment', StorageGroupRequestFulfillmentHandler), (r'/api/storage-groups/([^/]+)/operations/add-candidate-adapter-ports', StorageGroupAddCandidatePortsHandler), (r'/api/storage-groups/([^/]+)/operations/remove-candidate-adapter-ports', StorageGroupRemoveCandidatePortsHandler), (r'/api/cpcs/([^/]+)/capacity-groups(?:\?(.*))?', CapacityGroupsHandler), (r'/api/cpcs/([^/]+)/capacity-groups/([^/]+)', CapacityGroupHandler), (r'/api/cpcs/([^/]+)/capacity-groups/([^/]+)/operations/add-partition', CapacityGroupAddPartitionHandler), (r'/api/cpcs/([^/]+)/capacity-groups/([^/]+)/operations/remove-partition', CapacityGroupRemovePartitionHandler), # Only in classic (or ensemble) mode: (r'/api/cpcs/([^/]+)/operations/import-profiles', CpcImportProfilesHandler), (r'/api/cpcs/([^/]+)/operations/export-profiles', CpcExportProfilesHandler), (r'/api/cpcs/([^/]+)/operations/add-temp-capacity', CpcAddTempCapacityHandler), (r'/api/cpcs/([^/]+)/operations/remove-temp-capacity', CpcRemoveTempCapacityHandler), (r'/api/cpcs/([^/]+)/logical-partitions(?:\?(.*))?', LparsHandler), (r'/api/logical-partitions/([^/]+)', LparHandler), (r'/api/logical-partitions/([^/]+)/operations/activate', LparActivateHandler), (r'/api/logical-partitions/([^/]+)/operations/deactivate', LparDeactivateHandler), (r'/api/logical-partitions/([^/]+)/operations/load', LparLoadHandler), (r'/api/cpcs/([^/]+)/reset-activation-profiles(?:\?(.*))?', ResetActProfilesHandler), (r'/api/cpcs/([^/]+)/reset-activation-profiles/([^/]+)', ResetActProfileHandler), (r'/api/cpcs/([^/]+)/image-activation-profiles(?:\?(.*))?', ImageActProfilesHandler), (r'/api/cpcs/([^/]+)/image-activation-profiles/([^/]+)', ImageActProfileHandler), (r'/api/cpcs/([^/]+)/load-activation-profiles(?:\?(.*))?', LoadActProfilesHandler), (r'/api/cpcs/([^/]+)/load-activation-profiles/([^/]+)', LoadActProfileHandler), )