zhmcclient-0.22.0/0000755000076500000240000000000013414661056014322 5ustar maierastaff00000000000000zhmcclient-0.22.0/PKG-INFO0000644000076500000240000002162413414661056015424 0ustar maierastaff00000000000000Metadata-Version: 1.2 Name: zhmcclient Version: 0.22.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: Juergen Leopold, Andreas Maier Maintainer-email: leopoldj@de.ibm.com, maiera@de.ibm.com License: Apache License, Version 2.0 Description: .. 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://travis-ci.org/zhmcclient/python-zhmcclient.svg?branch=master :target: https://travis-ci.org/zhmcclient/python-zhmcclient :alt: Travis test status (master) .. image:: https://ci.appveyor.com/api/projects/status/i022iaeu3dao8j5x/branch/master?svg=true :target: https://ci.appveyor.com/project/leopoldjuergen/python-zhmcclient :alt: Appveyor test status (master) .. image:: https://readthedocs.org/projects/python-zhmcclient/badge/?version=latest :target: http://python-zhmcclient.readthedocs.io/en/latest/ :alt: Docs build status (latest) .. image:: https://img.shields.io/coveralls/zhmcclient/python-zhmcclient.svg :target: https://coveralls.io/r/zhmcclient/python-zhmcclient :alt: Test coverage (master) .. image:: https://codeclimate.com/github/zhmcclient/python-zhmcclient/badges/gpa.svg :target: https://codeclimate.com/github/zhmcclient/python-zhmcclient :alt: Code Climate .. 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/stable/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 ============= The zhmcclient documentation is on RTD: * `Documentation for latest version on Pypi`_ * `Documentation for master branch in Git repo`_ .. _Documentation for latest version on Pypi: http://python-zhmcclient.readthedocs.io/en/stable/ .. _Documentation for master branch in Git repo: http://python-zhmcclient.readthedocs.io/en/latest/ 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/stable/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 Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha 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 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 zhmcclient-0.22.0/CODE_OF_CONDUCT.md0000644000076500000240000000634713364325033017127 0ustar maierastaff00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a python-zhmcclient project maintainer, Juergen Leopold leopoldj@de.ibm.com, and/or Andreas Maier maiera@de.ibm.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ zhmcclient-0.22.0/provision.sh0000644000076500000240000000064313033142515016700 0ustar maierastaff00000000000000#!/usr/bin/env bash echo "Update Ubuntu ..." apt-get install software-properties-common # apt-add-repository ppa:ansible/ansible -y apt-get update >/dev/null 2>&1 apt-get -y upgrade >/dev/null 2>&1 echo "Install pip ..." apt-get install -y python-pip apt-get install -y build-essential apt-get install -y python-dev echo "Install virtualenv ..." pip install virtualenv echo "Install git ..." apt-get install -y git zhmcclient-0.22.0/design/0000755000076500000240000000000013414661056015573 5ustar maierastaff00000000000000zhmcclient-0.22.0/design/dpm-storage-model.rst0000644000076500000240000004353713364325033021655 0ustar maierastaff00000000000000.. 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. .. ======================================= Design for DPM storage management model ======================================= DPM introduces support for a new simplified way to manage the storage attached to a CPC. For simplicity, this design document uses the term "DPM storage management model" to mean all changes related to Storage Groups (part of LI1170), FICON Configuration (also part of LI1170), Feature List API (LI1421), and any other changes to existing WS-API functionality that is related to the new support. The DPM storage management model only applies to CPCs in DPM mode that have the "dpm-storage-management" feature enabled. Named features are also a newly introduced concept. The "dpm-storage-management" feature will be available for the z14 machine generation and onwards, and will not be rolled back to machine generations before z14. For the z14 machine generation (high end and mid range), the "dpm-storage-management" feature will roll out in the service stream for the machines. Once the service level containing support for it is applied to a CPC, that CPC will exhibit the new support. There is no means to enable or disable the "dpm-storage-management" feature for a CPC; it is always enabled once the service providing it has been applied. The service providing the "dpm-storage-management" feature is concurrent for the CPC but requires a reboot of SE and HMC. With the next machine generation after z14, the "dpm-storage-management" feature will be available from day 1. The DPM storage management model is incompatible for WS-API clients that used the storage related functionality so far. For example, HBA objects no longer exist. This design document describes at a high level how the zhmcclient API will expose the DPM storage management model. Resource model ============== The zhmcclient API implements a concept of Python manager objects and Python resource objects to expose the HMC objects: A Python manager object is part of its parent resource object and mainly provides methods for listing its own resource objects, and for creating new resource objects. A Python resource object represents a corresponding HMC object. Because the DPM storage management model makes broad use of having resource URIs back and forth, the zhmcclient resource model at the Python API level distinguishes between child resources of a resource (over which lifecycle control is exercised), and other referenced resources (over which no lifecycle control is exercised). Navigation from a Python resource object to its child resource objects and to its referenced resources normally happens in the same way: A resource object has an attribute for each type of child resource and for each type of referenced resource. The attribute value is the Python manager object for the set of child resources or referenced resources, respectively. The difference is to which parent resource object the manager object of these resource objects point back via its "parent" attribute: The manager object of child resource objects always point back to their parent resource object (who manages the lifecycle of the child resource), while the manager object of referenced resource objects do not point back to their referencing resource, but to their true parent resource (to which the referenced resource is a child resource). The previous paragraph described how it works for child resources or referenced resources with a multiplicity of 0 or more (*). Some referenced resources have a multiplicity of 1. For those referenced resources, the Python object setup is simplified, in that there is no manager object. Instead, the attribute value if directly the referenced resource object. The following diagram shows the resource hierarchy that will be exposed by the zhmcclient API. The manager objects (for resources with a multiplicity of *) are not shown in the diagram, for simplicity. The names shown are the Python class names of the resource classes. In parenthesis follow the multiplicity, whether the resource object is a child resource or a referenced resource, and optionally the role of these resources within their parent or referencing resource. The diagram shows resource classes for storage groups (which are commonly used for FCP and FICON storage), the resource classes specific to the FICON configuration, and existing resource classes to the extent they are relevant for the DPM storage management model. It is important to understand that the storage groups and the FICON configuration are scoped to a single CPC, and represent that CPC’s view of a set of storage resources. A second CPC’s storage groups and FICON configuration may include resources that represent the same physical or logical entities, but they are represented at the WS-API and thus at the zhmcclient API as separate resource objects. .. code-block:: text client | +-- CPC (*/child) | | | +-- Adapter (*/child, storage adapter, type: FCP or FICON) | | | | | +-- Port (*/child, storage port) | | | | | +-- StorageSwitch (1/ref, the switch connected to this port) | | | | | +-- StorageSubsystem (1/ref, the subsystem connected to this port) | | | +-- Partition (*/child) | | | | | +-- HBA (*/child readonly, future?) | | | +-- StorageGroup (*/child, type: FCP or FICON) | | | +-- StorageVolume (*/child) | | | | | +-- StorageControlUnit (*/ref, FICON SGs only) | | | +-- VirtualStorageResource (*/child) | | | | | +-- Partition (1/ref, partition to which this VSR is attached) | | | | | +-- Port (*/ref, actually used port, for FCP SGs) | | | | | +-- StorageVolume (*/ref, volume, for FICON SGs) | | | +-- Partition (*/ref, partitions to which this SG is attached) | | | +-- Port (*/ref, FCP SGs only, candidate ports to be used in this SG) | +-- StorageSite (1-2/child, FICON only) | | | +-- CPC (*/ref, the CPCs using this FICON configuration - currently always 1) | | | +-- StorageSubsystem (*/ref, the subsystems in this site) | | | +-- StorageSwitch (*/ref, the switches in this site) | +-- StorageFabric (*/child, FICON only) | | | +-- CPC (*/ref, the CPCs using this FICON configuration - currently always 1) | | | +-- StorageSwitch (*/ref, the switches in this fabric) | +-- StorageSwitch (*/child, FICON only, all switches) | | | +-- StorageSite (1/ref, the site this switch is in) | | | +-- StorageFabric (1/ref, the fabric this switch is in) | +-- StorageSubsystem (*/child, FICON only, all subsystems) | +-- StorageControlUnit (*/child, the logical control units of the subsystem) | | | +-- StoragePath (*/child, the paths that connect this control unit to a port on a switch and ultimately on an adapter of the CPC) | | | +-- StorageSwitch (0-1/ref, the exit switch for this path) | | | +-- Adapter (1/ref, the adapter for this path (its port is specified as a property) | +-- StorageSite (1/ref, the site this subsystem is in) | +-- StorageSwitch (*/ref, the connected switches) | +-- Adapter (*/ref, the connected adapters (their ports are specified as a property) Here are brief descriptions of the resource classes added by the DPM storage management model: Storage group support: * StorageGroup - A container for storage volumes (FCP or ECKD). A particular storage group can be attached to zero or more partitions. In case of more than one partition, the volumes in the group are being shared between the partitions. A particular partition may have multiple storage groups attached, which means that all volumes in these storage groups are attached to the partition. * StorageVolume - A storage volume (FCP or ECKD) on a storage subsystem. The storage volume resource objects can be created, but the act of actually allocating the volume on the storage subsystem is not performed by the creation operation and is instead performed separately. That act is termed "fulfillment". Thus, a storage volume resource object has a fulfillment status. When a storage group is attached to a partition, the group’s fulfilled storage volumes are virtualized for that partition and the partition’s view of them is represented by a set of virtual storage resource objects. * VirtualStorageResource - A storage volume (FCP or ECKD) that is attached to a partition. If the same StorageVolume resource is attached to two partitions, there is one VirtualStorageResource resource for each of thoser attachments. FICON configuration support: * StorageSite - A site housing storage subsystems and storage switches that are accessible to a CPC (FICON only). A storage site HMC object is a child resource of a CPC HMC object and represents the view of the CPC on its storage. The same physical storage subsystems and storage switches can be represented in multiple storage site resources. A primary storage site resource always exists by default for a CPC. A secondary storage site resource for a CPC can optionally be defined. Primary sites are typically local to the CPC. Secondary sites are typically remote to the CPC, and are often used for redundancy. * StorageSwitch - A physical storage switch (FICON only). * StorageSubsystem - A physical storage subsystem (storage unit) (FICON only). Storage subsystems are physically connected (cabled) to a set of storage switches or directly to a set of storage adapters in the CPC. Storage subsystems are subdivided into logical control units, which provide access to a subset of a subsystem’s storage resources. * StorageControlUnit - A logical control unit within a storage subsystem (FICON only). * StorageFabric - A logical construct that encompasses the set of all storage switches that are interconnected (FICON only). In a multi-site configuration, switches from both sites are interconnected, therefore in such configurations, a fabric will span multiple sites. The other resource classes shown in the diagram already exist today in the zhmcclient API: * Adapter * Port * Partition * CPC TBD: Info on features. TBD: Info on storage resources and operations that are different or not available anymore (e.g. HBA). Mapping HMC operations to the zhmcclient API ============================================ In the following tables, "mgr" refers to the Python manager object for the resource, and "res" refers to the Python resource object for the resource. **Storage group support (FCP only)** Partition --------- ===================================================== ============================== ========================================== HMC Operation Name zhmcclient API HTTP method and URI ===================================================== ============================== ========================================== Attach Storage Group res.attach_storage_group() POST /api/partitions/{partition-id}/operations/attach-storage-group Detach Storage Group res.detach_storage_group() POST /api/partitions/{partition-id}/operations/detach-storage-group ===================================================== ============================== ========================================== StorageGroup ------------ ========================== ============= =================================== Resource Attribute kind Python class ========================== ============= =================================== storage_volumes */child StorageVolumeManager virtual_storage_resources */child VirtualStorageResourceManager ========================== ============= =================================== ===================================================== ============================== ========================================== HMC Operation Name zhmcclient API HTTP method and URI ===================================================== ============================== ========================================== List Storage Groups mgr.list() GET /api/cpcs/{cpc-id}/storage-groups Create Storage Group mgr.create() POST /api/cpcs/{cpc-id}/storage-groups Delete Storage Group res.delete() DELETE /api/storage-groups/{storage-group-id} Get Storage Group Properties res.properties GET /api/storage-groups/{storage-group-id} Modify Storage Group Properties res.update_properties() POST /api/storage-groups/{storage-group-id} Add Candidate Adapter Ports to an FCP Sto.Grp. res.add_candidate_ports() POST /api/storage-groups/{storage-group-id}/operations/add-candidate-adapter-ports Remove Candidate Adapter Ports from an FCP Sto.Grp. res.remove_candidate_ports() POST /api/storage-groups/{storage-group-id}/operations/remove-candidate-adapter-ports Request Storage Group Fulfillment res.request_fulfillment() POST /api/storage-groups/{storage-group-id}/operations/request-fulfillment Get Partitions for a Storage Group res.list_attached_partitions() GET /api/storage-groups/{storage-group-id}/operations/get-partitions candidate-adapterport-uris property of storage group res.list_candidate_ports() ===================================================== ============================== ========================================== StorageVolume ------------- ===================================================== ============================== ========================================== HMC Operation Name zhmcclient API HTTP method and URI ===================================================== ============================== ========================================== List Storage Volumes of a Storage Group mgr.list() GET /api/storage-groups/{storage-group-id}/storage-volumes Modify Storage Group Properties (to create a volume) mgr.create() Modify Storage Group Properties (to delete a volume) mgr.delete() Get Storage Volume Properties res.properties GET /api/storage-groups/{storage-group-id}/storage-volumes/{storage-volume-id} Modify Storage Group Properties (to update a volume) res.update_properties() Fulfill FICON Storage Volume res.fulfill_ficon() POST /api/storage-groups/{storage-group-id}/storage-volumes/{storage-volume-id}/operations/fulfill-ficon-storage-volume Unfulfill FICON Storage Volume res.unfulfill_ficon() POST /api/storage-groups/{storage-group-id}/storage-volumes/{storage-volume-id}/operations/unfulfill-ficon-storage-volume Fulfill FCP Storage Volume res.fulfill_fcp() POST /api/storage-groups/{storage-group-id}/storage-volumes/{storage-volume-id}/operations/fulfill-fcp-storage-volume ===================================================== ============================== ========================================== VirtualStorageResource ---------------------- ========================== ============= =================================== Resource Attribute kind Python class ========================== ============= =================================== attached_partition 1/ref Partition ========================== ============= =================================== ===================================================== ============================== ========================================== HMC Operation Name zhmcclient API HTTP method and URI ===================================================== ============================== ========================================== List Virtual Storage Resources of a Storage Group mgr.list() GET /api/storage-groups/{storage-group-id}/virtual-storage-resources Get Virtual Storage Resource Properties res.properties GET /api/storage-groups/{storage-group-id}/virtual-storage-resources/{virtual-storage-resource-id} Update Virtual Storage Resource Properties res.update_properties() POST /api/storage-groups/{storage-group-id}/virtual-storage-resources/{virtual-storage-resource-id} adapter-port-uris property of VSR res.list_actual_adapter_ports() ===================================================== ============================== ========================================== **FICON configuration support** TBD zhmcclient-0.22.0/design/fake-client.rst0000644000076500000240000002060413364325033020505 0ustar maierastaff00000000000000.. 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. .. Design for fake client ====================== This document describes requirements and possible design options for a fake zhmcclient. Requirements ------------ * Must avoid the need for an actual HMC and still allow usage of the zhmcclient API. * Both read-only and read-write operations must be supported. * Read-write operations must reflect their changes properly (i.e. they must be visible in corresponding read operations). * The resource state of the faked HMC must be preloaded (e.g. for resources that cannot be created by clients), and its resource descriptions must be easy to use (i.e. not in terms of REST URIs. * It must be possible to define testcases with error injection. * The solution must be usable in unit tests of zhmcclient users. * The solution needs to perform fast, because it is used in a unit test. * The solution should not rely on external componentry such as a small HTTP server. It seems that with these requirements, we can assume that there is a need for a faked HMC that can represent its resources and properties as Python objects, so that changes can be applied consistently. Design options -------------- There are two basic design options for the fake client: * Complete re-implementation of the zhmcclient API, that behaves the same way as the zhmcclient package, but consists of a different classes that implement a faked HMC. * Usage of the zhmcclient package, and replacing its Session object with a fake session that implements a faked HMC. The first design option is probably less effort, but it has the drawback that subtle changes in the API may be forgotten. One example for that is the recent introduction of an optimization for findall() by name, which now uses a cached mapping of names to URIs, but had the subtle externally visible change that the resource objects returned by findall(name) now only have a very minimal set of properties, and no longer those that are returned by list() which was the earlier implementation. For example, the "status" property is no longer set in that case. The second design would automatically represent this example of a change. Design ------ In the following description, the second design option is used. There is a fake session object that the user of the zhmcclient package will use instead of `zhmcclient.Session`. The fake session object contains a faked HMC that can represent all resources relevant for the zhmcclient. This fake session object implements the same API as `zhmcclient.Session`, so that the rest of the zhmcclient classes do not need to be changed. The `get()`, `post()` and `delete()` methods of the fake session class will be redirected to operate on the faked HMC. The fake session object will have the ability to be populated with a resource state. This is supported in two ways that can both be used: * By adding an entire tree of resources. * By invoking add/remove methods for each resource. When adding an entire tree of resources, the resources are defined as a dictionary of nested dictionaries, following this example:: resources = { 'cpcs': [ # name of manager attribute for this resource { 'properties': { # object-id and object-uri are auto-generated 'name': 'cpc_1', . . . # more properties }, 'adapters': [ { 'properties': { # object-id and object-uri are auto-generated 'name': 'ad_1', . . . # more properties }, 'ports': [ { 'properties': { # element-id and element-uri are # auto-generated 'name': 'port_1', . . . # more properties } }, . . . # more Ports ], }, . . . # more Adapters ], . . . # more CPC child resources }, . . . # more CPCs ] } The dictionary keys for the resources in this dictionary structure are the names of the attributes for navigating to the child resources within their parent resources, in the zhmcclient package. For example, the 'cpcs' dictionary key corresponds to the `zhmcclient.Client.cpcs` attribute name. When invoking add/remove methods for each resource, the resource tree is built up by the user, from top to bottom. Besides just being an alternative to the bulk input with the resource dictionary, this approach also allows changing the resource state incrementally between test cases. In both approaches, the properties for object ID ("object-id" or "element-id") and URI ("object-uri" or "element-uri") are auto-generated, if not provided by the user. All other properties will only exist as provided by the user. In both approaches, there is no checking for invalid properties (neither for property name, nor for property type). Checking for invalid properties would require knowing the names of all properties of all resource types. Right now, the zhmcclient code and the code for the fake session/HMC have to know only a very small number of properties (object-id, object-uri, name). Plus, the set of properties depends on the HMC API version, and probably some properties even depend on the CPC machine generation. It saves a significant effort not having this knowledge in the zhmcclient code and in the code for the fake session/HMC. For all operations against the faked HMC, a successful operation is implemented by default. The unit testcases of users can use the `side_effects` approach of the `mock` package for error injection. The fake session/HMC does not need to do anything for that to work. Possible future extensions -------------------------- Specific operation behavior ^^^^^^^^^^^^^^^^^^^^^^^^^^^ If a need arises to have other behavior in the operations than the default implementation, the fake session object can have the ability to specify non-standard behavior of HMC operations, at the level of the HTTP interactions, following this example:: operations = [ { 'method': 'get', # used for matching the request 'uri': '/api/version', # used for matching the request 'request_body': None, # used for matching the request 'response_status': 200, # desired HTTP status code 'response_body': { # desired response body 'api-minor-version': '1' } }, { 'method': 'get', # used for matching the request 'uri': '/api/cpcs', # used for matching the request 'request_body': None, # used for matching the request 'response_status': 400, # desired HTTP status code 'response_error': { # desired error info 'reason': 25, # desired HMC reason code 'message': 'bla', # desired HMC message text } ] This information is stored in the fake session and used in matching HTTP requests instead of the standard, successful implementations. It allows specifying successful operations with responses deviating from the standard responses (see the first list item in the example above) as well as error responses (see the second list item). Because the zhmcclient only evaluates HTTP status code, HMC reason code and the message text, these are the only attributes that can be specified (to keep it simple). Note that normal error injection can already be done with the `side_effects` approach of the `mock` package. zhmcclient-0.22.0/design/network-errors.rst0000644000076500000240000003756213364325033021341 0ustar maierastaff00000000000000.. 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. .. ================================================ Design for handling network errors in zhmcclient ================================================ The zhmcclient package uses the Python `requests `_ package for any HMC REST API calls, and the Python `stomp `_ package for handling HMC JMS notifications. This design document covers error handling using the `requests` package. Some facts ========== Layering of components ---------------------- The following picture shows the layering of components w.r.t. networking: .. code-block:: text +---------------------+ | | | application (py) | | | +---------------------+ | | | zhmcclient (py) | | | +---------------------+ | | | requests (py) | | | +---------------------+ | | - Handles HTTP connection keep-alive and pooling | urllib3 (py) | - Handles HTTP basic/digest authentication | (part of requests) | - Handles HTTP request retrying +---------------------+ | | | httplib (py) | | | +---------------+ | | | | | socket (py) | | | | | +---------------+-----+ | _socket (py) | | | | _socketmodule.so | | | +---------------------+ - Handles read timeouts ?? | socket API | - Handles connection timeouts ?? | TCP/IP stack | - Handles SSL certificate verification ?? | | - Handles TCP packet retransmission +---------------------+ The following call flow for an HTTP GET request and response shows how these layers are used: .. code-block:: text -> zhmcclient/_session.py(392): Session.get() -> requests/sessions.py(501): Session.get() -> requests/sessions.py(488)request() -> requests/sessions.py(609)send() -> requests/adapters.py(423)send() -> requests/packages/urllib3/connectionpool.py(600)urlopen() -> requests/packages/urllib3/connectionpool.py(356)_make_request() -> httplib.py(1022): HTTPConnection.request() -> httplib.py(1056): HTTPConnection._send_request() -> httplib.py(1018): HTTPConnection.endheaders() -> httplib.py(869): HTTPConnection._send_output() -> httplib.py(829): HTTPConnection.send() -> _socketmodule.so: socket.connect(), if needed (not used by urllib3) -> _socketmodule.so: socket.sendall() -> requests/packages/urllib3/connectionpool.py(379)_make_request() -> httplib.py(1089): HTTPConnection.getresponse() -> httplib.py(444): HTTPResponse.begin() -> httplib.py(400): HTTPResponse._read_status() -> socket.py(424): _fileobject.readline() -> _socketmodule.so: socket.recv() -> requests/sessions.py(641)send() -> requests/models.py(797)content() -> requests/models.py(719)generate() -> requests/packages/urllib3/response.py(432)stream() -> requests/packages/urllib3/response.py(380)read() -> httplib.py(602): HTTPResponse.read() -> socket.py(355): _fileobject.read() -> _socketmodule.so: socket.recv() -> httplib.py(610): HTTPResponse.read() -> httplib.py(555): HTTPResponse.close() -> socket.py(284): _fileobject.close() -> socket.py(300): _fileobject.flush() -> _socketmodule.so: socket.sendall(), if anything remaining -> _socketmodule.so: socket.close() Timeouts -------- There are hard coded timeouts in the TCP/IP stack. The `requests` package allows specifying two timeouts (on HTTP methods such as ``get()``): * Connect timeout: Number of seconds the `requests` package will wait for the local machine to establish a TCP connection to a remote machine. This timeout is passed to the ``connect()`` call on the socket. The `requests` package recommends to set the connect timeout to slightly larger than a multiple of 3 (seconds), which is the default TCP packet retransmission window. This timeout is indicated by raising a ``requests.exceptions.ConnectTimeout`` exception. * Read timeout: Number of seconds the local machine will wait for the remote machine to send a response at the socket level. Specifically, it's the number of seconds that the local machine will wait *between* Bytes sent from the remote machine. However, in 99.9% of cases, this is the time before the remote machine sends the *first* Byte. This timeout is indicated by raising a ``requests.exceptions.ReadTimeout`` exception. The zhmcclient package currently does not set any of these timeouts, so the default of waiting forever applies. **TBD:** Despite the fact that the `requests` connection timeout is not set, a connection attempt times out after 60 sec, by raising ``requests.exceptions.ConnectionError``. It is not clear under which conditions this situation is indicated using ``requests.exceptions.ConnectTimeout``. The zhmcclient itself supports two timeouts at a higher level (as of `PR #195 `_ which is targeted for v0.11.0 of the `zhmcclient` package: * Operation timeout: Number of seconds the client will wait for completion of asynchronous HMC operations. This applies to ``Session.post()`` and to all resource methods with a ``wait_for_completion`` parameter (i.e. the asynchronous methods). The operation timeout can be specified with the ``operation_timeout`` parameter on these methods, and defaults to no timeout. This timeout is indicated by raising a ``zhmcclient.Timeout`` exception. * LPAR status timeout: Number of seconds the client will wait for the LPAR status to take on the value it is supposed to take on given the previous operation affecting the LPAR status. This applies to the ``Lpar.activate/deactivate/load()`` methods. The HMC operations issued by these methods exhibit "deferred status" behavior, which means that it takes a few seconds after successful completion of the asynchronous job that executes the operation, until the new status can be observed in the `status` property of the LPAR resource. These methods will poll the LPAR status until the desired status value is reached. The LPAR status timeout can be specified with the ``status_timeout`` parameter on these methods, and defaults to 1 hour. This timeout is also indicated by raising a ``zhmcclient.Timeout`` exception. Reference material: * `Timeouts in requests package `_ Exceptions ---------- The `requests` package wrappers all exceptions of underlying components, except for programming errors (e.g. ``TypeError``, ``ValueError``, ``KeyError``), into exceptions that are derived from ``requests.exceptions.RequestException``. The ``requests.exceptions.RequestException`` exception is never raised itself. All exceptions derived from ``requests.exceptions.RequestException`` will have the following attributes: * ``exc.args[0]``: - For ``HTTPError``, ``TooManyRedirects``, ``MissingSchema``, ``InvalidSchema``, ``InvalidURL``, ``InvalidHeader``, ``UnrewindableBodyError``: An error message generated by the `requests` package. - For ``ConnectionError``, ``ProxyError``, ``SSLError``, ``ConnectTimeout``, ``ReadTimeout``, ``ChunkedEncodingError``, ``ContentDecodingError``, ``RetryError``: The underlying exception that was raised. This is not documented, though. - For ``StreamConsumedError``: ``exc.args=None``. * ``exc.request`` - ``None``, or the ``requests.PreparedRequest`` object representing the HTTP request. * ``exc.response`` - ``None``, or the ``requests.Response`` object representing the HTTP response. The ``HTTPError`` exception is only raised when the caller requests that bad HTTP status codes should be returned as exceptions (via ``Session.status_as_exception()``). The zhmcclient package does not do that, so this exception is never raised, and bad HTTP status codes are checked after the HTTP method (e.g. ``get()``) returns normally. The inheritance hierarchy of the exceptions of the `requests` package can be gathered from the `requests.exceptions source code `_. The `zhmcclient` package in turn wrappers the exceptions of the `requests` package into: * ``zhmcclient.HTTPError`` - caused by most bad HTTP status codes and represents the parameters in the HMC error response. Some HTTP status codes are automatically recovered, such as status 403 / reason 5 (API session token expired) by re-logon, or status 202 by polling for asynchronous job completion. * ``zhmcclient.ParseError`` - caused by invalid JSON in the HTTP response. * ``zhmcclient.AuthError`` - caused by HTTP status 403, when reason != 5, or when reason == 5 and the resource did not require authentication. The latter case is merely a check against unexpected behavior of the HMC and is not really needed, or should be acted upon differently. * ``zhmcclient.ConnectionError`` - caused by all exceptions of the `requests` package. Retries ------- There are multiple levels of retries: - The TCP/IP stack retries sends on TCP sockets as part of its error recovery. - The `requests` package retries the sending of HTTP requests. Actually, this is handled by the `urllib3` package, but can be controlled through the `requests` package by setting an alternative transport adapter. Such adapters are matched by shortest prefix match, so the following works:: s = requests.Session(.....) s.mount('https://', HTTPAdapter(max_retries=10)) s.mount('http://', HTTPAdapter(max_retries=10)) This will replace the default transport adapters, which are set in `requests.Session()`:: self.mount('https://', HTTPAdapter()) self.mount('http://', HTTPAdapter()) The default for max_retries is 0. Design changes ============== The following design changes are proposed for the `zhmcclient` package: Proposal for timeouts --------------------- **Proposal(DONE):** Start using the timeouts of the `requests` package, by setting them as attributes on the ``zhmcclient.Session`` object, with reasonable default values, and with the ability to override the default values when creating a ``Session`` object. The proposed default values are: * connect timeout: 60 s * read timeout: 120 s Having a read timeout assumes that the HMC REST operations all respond within a maximum time. The asynchronous REST operations all respond rather quickly, indicating what the job is that performs the asynchronous operation. Some synchronous REST operations sometimes take long, e.g. up to 45 seconds. That's why the read timeout should be a good bit larger than that. Also, the design for the timeouts for async operation completion and LPAR status transition introduced in `PR #195 `_ should be changed to be consistent with the way timeouts are defined in this design. **Proposal(TODO):** Change after merging PR #195. Proposal for exceptions ----------------------- * ``zhmcclient.ConnectionError`` is currently raised for all exceptions of the `requests` package. When we start supporting the timeouts of the `requests` package, it is appropriate to distinguish timeouts from other errors. Also, it might be useful to separate errors that are likely caused by the networking environment (and that could therefore be retried) from errors that are not going to recover by retrying. Further, it might be useful to distinguish unrecoverable errors that need to be fixed on the client, from unrecoverable errors that need to be fixed on the server. **Proposal(DONE):** This proposal does not go as far as outlined above. It is proposed to handle the `requests` exceptions raised from HTTP methods such as ``get()``, as follows: - ``TooManyRedirects``, ``MissingSchema``, ``InvalidSchema``, ``InvalidURL``, ``InvalidHeader``, ``UnrewindableBodyError``, ``ConnectionError``, ``ProxyError``, ``SSLError``, ``ChunkedEncodingError``, ``ContentDecodingError``, ``StreamConsumedError``: These will be wrappered using ``zhmcclient.ConnectionError`` as today, but the exception message will be cleaned up as much as possible: - If ``exc.args[0]`` is an ``Exception``, this is the underlying exception that was wrapppered by the `requests` exception. Use that underlying exception instead. - Eliminate ``MaxRetryError`` and use the exception in its ``reason`` attribute instead. - Eliminate the representation of objects in the exception message, e.g. ``"NewConnectionError(': Failed to establish a new connection: [Errno 110] Connection timed out',)"`` - ``ConnectTimeout``, ``ResponseReadTimeout``, ``RequestRetriesExceeded``: These will be wrappered by new exceptions ``zhmcclient.ConnectTimeout``, ``zhmcclient.ReadTimeout``, ``zhmcclient.RetryError``. * As described above, ``zhmcclient.AuthError`` is also raised when the HMC indicates "API session token expired" for an operation that does not require logon (e.g. "Query API Version"). First, checking this is a bit overdoing it because there is no harm if the HMC decides that session checking is performed always, and second, the handling of this unexpected behavior as by raising ``zhmcclient.AuthError`` is misleading for the user. **Proposal(DONE):** It is proposed to not handle this situation also by re-logon, i.e. to no longer make the behavior dependent on whether the operation requires logon. * ``zhmcclient.VersionError`` currently stores the discovered version in ``exc.args[1:2]``. It is not recommended to use ``exc.args[]`` for anything else but the exception message, and to use additional instance attributes for that, instead. **Proposal(DONE):** It is proposed to store this information in additional instance attributes, and to remove it from the ``exc.args[]`` array. This is an incompatible change, but it is not very critical. No change is proposed for the other `zhmcclient` exceptions (``ParseError``, ``HTTPError``). Proposal for retries -------------------- **Proposal (TODO):** Start using the ``max_retries`` parameter of the ``HTTPAdapter`` transport adapter, by setting the max retries after connect timeouts and read timeouts as attributes on the ``zhmcclient.Session`` object, with a reasonable default value, and with the ability to override the default value when creating a ``Session`` object. The proposed default values are: * connect retries: 3 * read retries: 3 zhmcclient-0.22.0/.pylintrc0000644000076500000240000004167712750375442016211 0ustar maierastaff00000000000000# # PyLint configuration file for the zhmcclient project # # Originally generated by pylint 1.5.4, now manually maintained. # [MASTER] # Specify a configuration file. #rcfile= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Add files or directories to the blacklist. They should be base names, not # paths. ignore=.svn,.git # Pickle collected data for later comparisons. persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= # Use multiple processes to speed up Pylint. jobs=1 # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code extension-pkg-whitelist= # Allow optimization of some AST trees. This will activate a peephole AST # optimizer, which will apply various small optimizations. For instance, it can # be used to obtain the result of joining multiple strings with the addition # operator. Joining a lot of strings can lead to a maximum recursion error in # Pylint and this flag can prevent that. It has one side effect, the resulting # AST will be different than the one from reality. optimize-ast=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED confidence= # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time. See also the "--disable" option for examples. #enable= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once).You can also use "--disable=all" to # disable everything first and then reenable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" disable=I0011, too-many-locals, too-many-branches, too-many-statements, too-many-lines, too-many-public-methods, too-many-nested-blocks, too-many-return-statements, too-many-arguments, wildcard-import, unused-wildcard-import, locally-enabled, superfluous-parens, # Note: The too-many-* messages are excluded temporarily, and should be dealt # with at some point. # Note: wildcard related messages are excluded because the original external # interface uses wildcard imports and we cannot change that without # breaking compatibility. We could move the message control statements # into the source code, though. # Note: messages are described here: http://pylint-messages.wikidot.com/all-messages [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html. You can also give a reporter class, eg # mypackage.mymodule.MyReporterClass. output-format=text # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". files-output=no # Tells whether to display a full report or only the messages reports=no # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details: # http://docs.pylint.org/output.html#reports-section # Note: This format only affects output format text, but not html. # Note: The vim quickfix support by default recognizes the following format: # {path}:{line}: ... # {path}:{line}: {msg_id} {obj}: {msg} # Note: Eclipse needs the following format: # {C}:{line:3d},{column:2d}: {msg} ({symbol}) # However, some messages show the source code in additional lines, as # part of the {msg} field, and this causes Eclipse by mistake to suppress # the entire message. So for now, we omit the {msg} field, for Eclipse: # '{C}:{line:3d},{column:2d}: ({symbol})' # Note: The default format is: # {C}:{line:3d},{column:2d}: {msg} ({symbol}) # Note: Usage with geany editor # use output-format=parseable # No special message format. #msg-template='{C}:{line:3d},{column:2d}: {msg} ({symbol})' [VARIABLES] # Tells whether we should check for unused import in __init__ files. init-import=no # A regular expression matching the name of dummy variables (i.e. expectedly # not used). dummy-variables-rgx=_$|dummy_|unused_ # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_,_cb [BASIC] # List of builtins function names that should not be used, separated by a comma bad-functions=map,filter,apply,input # Good variable names which should always be accepted, separated by a comma # TODO KS: Move i,... to local var and inline var; move _ to arg and local var and inline var. good-names=_,f,i,j,k,m,n,__all__,__name__,__version__ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata,l,O,I # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= # Include a hint for the correct naming format with invalid-name include-naming-hint=no # Regular expression matching correct function names. # # The expression below allows "lower_case_with_underscores" and # "mixedCaseWithoutUnderscores"; leading underscores for privacy as well as # double trailing underscores are allowed. # Names matching this expression are compatible with PEP-8. function-rgx=_{0,2}([a-z][a-z0-9_]{0,28}[a-z0-9]|[a-z][a-zA-Z0-9]{1,29})(__){0,1}$ # TODO AM: Verify whether two leading underscores are allowed / supported with name mangling. # Naming hint for function names function-name-hint=_{0,2}([a-z][a-z0-9_]{0,28}[a-z0-9]|[a-z][a-zA-Z0-9]{1,29})(__){0,1}$ # Regular expression matching correct variable names. # # The expression below allows "UPPER_CASE_WITH_UNDERSCORES" for constants, # and "lower_case_with_underscores" and "mixedCaseWithoutUnderscores" for # other variables; one leading underscore is allowed, and one trailing # underscore is allowed to avoid conflists with built-in names. # Names matching this expression are compatible with PEP-8. variable-rgx=_{0,1}([A-Z]([A-Z0-9_]{0,38}[A-Z0-9])?|[a-z]([a-z0-9_]{0,38}[a-z0-9])?|[a-z]([a-zA-Z0-9]{1,39})?)_{0,1}$ # Naming hint for variable names variable-name-hint=_{0,1}([A-Z]([A-Z0-9_]{0,38}[A-Z0-9])?|[a-z]([a-z0-9_]{0,38}[a-z0-9])?|[a-z]([a-zA-Z0-9]{1,39})?)_{0,1}$ # Regular expression matching correct constant names. # # The expression below allows "UPPER_CASE_WITH_UNDERSCORES"; leading underscores # for privacy are allowed. # Names matching this expression are compatible with PEP-8. const-rgx=(_{0,1}[A-Z][A-Z0-9_]{0,38}[A-Z0-9]|(__.*__))$ # Naming hint for constant names const-name-hint=(_{0,1}[A-Z][A-Z0-9_]{0,38}[A-Z0-9]|(__.*__))$ # Regular expression matching correct attribute names. # # The expression below allows "lower_case_with_underscores" and # "mixedCaseWithoutUnderscores"; leading underscores for privacy are allowed. # Names matching this expression are compatible with PEP-8. attr-rgx=_{0,2}([a-z][a-z0-9_]{0,28}[a-z0-9]|[a-z][a-zA-Z0-9]{1,29})$ # Naming hint for attribute names attr-name-hint=_{0,2}([a-z][a-z0-9_]{0,28}[a-z0-9]|[a-z][a-zA-Z0-9]{1,29})$ # Regular expression matching correct argument names. # # The expression below allows "lower_case_with_underscores" and # "mixedCaseWithoutUnderscores"; leading underscores are NOT allowed, # but one trailing underscore is allowed to avoid conflists with built-in # names. # Names matching this expression are compatible with PEP-8. argument-rgx=([a-z][a-z0-9_]{0,28}[a-z0-9]|[a-z][a-zA-Z0-9]{1,29})_{0,1}$ # Naming hint for argument names argument-name-hint=([a-z][a-z0-9_]{0,28}[a-z0-9]|[a-z][a-zA-Z0-9]{1,29})_{0,1}$ # Regular expression matching correct class attribute names. # # The expression below allows "UPPER_CASE_WITH_UNDERSCORES" for constants, # and "lower_case_with_underscores" and "mixedCaseWithoutUnderscores" for # other variables; leading underscores for privacy are allowed. # Names matching this expression are compatible with PEP-8. class-attribute-rgx=_{0,2}([A-Z][A-Z0-9_]{0,38}[A-Z0-9]|[a-z][a-z0-9_]{0,38}[a-z0-9]|[a-z][a-zA-Z0-9]{0,39}|(__.*__))$ # Naming hint for class attribute names class-attribute-name-hint=_{0,2}([A-Z][A-Z0-9_]{0,38}[A-Z0-9]|[a-z][a-z0-9_]{0,38}[a-z0-9]|[a-z][a-zA-Z0-9]{0,39}|(__.*__))$ # Regular expression matching correct inline iteration names. # # The expression below allows "lower_case_with_underscores" and # "mixedCaseWithoutUnderscores"; leading underscores for privacy are allowed. inlinevar-rgx=_{0,1}([a-z][a-z0-9_]{0,28}[a-z0-9]|[a-z][a-zA-Z0-9]{0,29})$ # Naming hint for inline iteration names inlinevar-name-hint=_{0,1}([a-z][a-z0-9_]{0,28}[a-z0-9]|[a-z][a-zA-Z0-9]{0,29})$ # Regular expression matching correct class names. # # The expression below allows "CamelCaseWithoutUnderscores"; leading underscores # for privacy are allowed. # Names matching this expression are compatible with PEP-8, but PEP-8 also # allows that class names for callable classes can use function name syntax. # This is not reflected in this regexp; callable classes that follow that # convention should disable PyLint message C0103. class-rgx=_{0,1}[A-Z][a-zA-Z0-9]{1,29}$ # Naming hint for class names class-name-hint=_{0,1}[A-Z][a-zA-Z0-9]{1,29}$ # Regular expression matching correct module names. # # The expression below allows "lower_case_with_underscores"; leading underscores # for privacy as well as double trailing underscores are allowed. # Names matching this expression are compatible with PEP-8. module-rgx=_{0,2}[a-z][a-z0-9_]{0,28}[a-z0-9](__){0,1}$ # Naming hint for module names module-name-hint=_{0,2}[a-z][a-z0-9_]{0,28}[a-z0-9](__){0,1}$ # Regular expression matching correct method names. # # The expression below allows "lower_case_with_underscores" and # "mixedCaseWithoutUnderscores"; leading underscores for privacy as well as # double trailing underscores are allowed. # Names matching this expression are compatible with PEP-8. method-rgx=_{0,2}([a-z][a-z0-9_]{0,28}[a-z0-9]|[a-z][a-zA-Z0-9]{1,29})(__){0,1}$ # Naming hint for method names method-name-hint=_{0,2}([a-z][a-z0-9_]{0,28}[a-z0-9]|[a-z][a-zA-Z0-9]{1,29})(__){0,1}$ # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=__.*__ # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 [ELIF] # Maximum number of nested blocks for function / method body max-nested-blocks=5 [TYPECHECK] # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. # # Modules that create modules at run time cauase pylint to raise # "no-name-in-module" and/or "import-error". Such modules can be put # on the module ignore list. For details, see # https://bitbucket.org/logilab/pylint/issues/223/ignored-modules-should-turn-no-name-in ignored-modules=distutils,six,builtins,urllib, lxml,lxml.etree,ssl, # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamically set). This supports can work # with qualified names. ignored-classes=SQLObject # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members=REQUEST,acl_users,aq_parent [SPELLING] # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. spelling-dict= # List of comma separated words that should not be checked. spelling-ignore-words= # A path to a file that contains private dictionary; one word per line. spelling-private-dict-file= # Tells whether to store unknown words to indicated private dictionary in # --spelling-private-dict-file option instead of raising a message. spelling-store-unknown-words=no [LOGGING] # Logging modules to check that the string format arguments are in logging # function parameter format logging-modules=logging [SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines=8 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=no [FORMAT] # Maximum number of characters on a single line. max-line-length=79 # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. # `trailing-comma` allows a space between comma and closing bracket: (a, ). # `empty-line` allows space-only lines. no-space-check=trailing-comma,dict-separator # Maximum number of lines in a module max-module-lines=1000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format= [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME,XXX,TODO [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict,_fields,_replace,_source,_make [DESIGN] # Maximum number of arguments for function / method max-args=8 # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.* # Maximum number of locals for function / method body max-locals=20 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branches=15 # Maximum number of statements in function / method body # TODO AM: What counts as a statement? max-statements=50 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). # TODO AM: Are these just variables, without methods? class & instance? including private? including inherited? max-attributes=15 # Minimum number of public methods for a class (see R0903). # TODO AM: including inherited? min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=20 # Maximum number of boolean expressions in a if statement max-bool-expr=5 [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,TERMIOS,Bastion,rexec # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=Exception zhmcclient-0.22.0/zhmcclient/0000755000076500000240000000000013414661056016462 5ustar maierastaff00000000000000zhmcclient-0.22.0/zhmcclient/_metrics.py0000644000076500000240000010035213364325033020636 0ustar maierastaff00000000000000# 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 ._logging import get_logger, logged_api_call from ._exceptions import NotFound from ._utils import datetime_from_timestamp, repr_list __all__ = ['MetricsContextManager', 'MetricsContext', 'MetricGroupDefinition', 'MetricDefinition', 'MetricsResponse', 'MetricGroupValues', 'MetricObjectValues'] LOG = get_logger(__name__) 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): """ 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` """ 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): """ 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: raise ValueError("Invalid {} metric value: {!r}". format(metric_type.__class__.__name__, value_str)) 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 elif lower_str == 'false': return False else: 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.NotFound`: No resource found for this URI in the management scope of 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': filter_args = {'object-uri': resource_uri} resource = self.client.cpcs.find(**filter_args) elif resource_class == 'logical-partition': for cpc in self.client.cpcs.list(): try: filter_args = {'object-uri': resource_uri} resource = cpc.lpars.find(**filter_args) break except NotFound: pass # Try next CPC else: raise elif resource_class == 'partition': for cpc in self.client.cpcs.list(): try: filter_args = {'object-uri': resource_uri} resource = cpc.partitions.find(**filter_args) break except NotFound: pass # Try next CPC else: raise elif resource_class == 'adapter': for cpc in self.client.cpcs.list(): try: filter_args = {'object-uri': resource_uri} resource = cpc.adapters.find(**filter_args) break except NotFound: pass # Try next CPC else: raise elif resource_class == 'nic': for cpc in self.client.cpcs.list(): found = False for partition in cpc.partitions.list(): 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 else: raise ValueError( "Invalid resource class: {!r}".format(resource_class)) self._resource = resource return self._resource zhmcclient-0.22.0/zhmcclient/_console.py0000644000076500000240000004530413364325033020637 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call from ._utils import timestamp_from_datetime from ._storage_group import StorageGroupManager 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'] LOG = get_logger(__name__) 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._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 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.post(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.post(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 zhmcclient-0.22.0/zhmcclient/_constants.py0000644000076500000240000001136013364325033021204 0ustar maierastaff00000000000000# 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 zhmcclient-0.22.0/zhmcclient/_nic.py0000644000076500000240000002627313364325033017752 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['NicManager', 'Nic'] LOG = get_logger(__name__) 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` """ 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` """ 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) zhmcclient-0.22.0/zhmcclient/_storage_group.py0000644000076500000240000006205213364325033022054 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['StorageGroupManager', 'StorageGroup'] LOG = get_logger(__name__) 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): """ Create and configure a storage group. 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. 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:`~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 = 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: self.manager._append_query_parms(query_parms, 'name', name) if status is not None: self.manager._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) 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` """ 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 zhmcclient-0.22.0/zhmcclient/_logging.py0000644000076500000240000002015713364325033020622 0ustar maierastaff00000000000000# 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. """ This package supports logging using the standard Python :mod:`py:logging` module. The logging support provides two :class:`~py:logging.Logger` objects: * '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. In addition, there are loggers for each module with the module name, for situations like errors or warnings. For HMC operations and API calls that contain the HMC password, 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 = 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 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 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. 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 function here apifunc_frame = this_frame.f_back # the decorated API function 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 this_frame del apifunc_frame del apicaller_frame del apicaller_module # Log only if the caller is not from our package log_it = (apicaller_module_name.split('.')[0] != 'zhmcclient') if log_it: logger.debug("==> %s, args: %.500r, kwargs: %.500r", apifunc_str, args, kwargs) result = func(*args, **kwargs) if log_it: logger.debug("<== %s, result: %.1000r", apifunc_str, result) return result if 'decorate' in globals(): return decorate(func, log_api_call) else: return decorator(log_api_call, func) zhmcclient-0.22.0/zhmcclient/_lpar.py0000644000076500000240000012164013364325033020131 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['LparManager', 'Lpar'] LOG = get_logger(__name__) 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` """ 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 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 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"] 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): """ 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. The default value is `True`. 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 = {} 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 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 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 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 zhmcclient-0.22.0/zhmcclient/_virtual_switch.py0000644000076500000240000002252213364325033022241 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['VirtualSwitchManager', 'VirtualSwitch'] LOG = get_logger(__name__) 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` """ 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) zhmcclient-0.22.0/zhmcclient/_version.py0000644000076500000240000000276613364325033020667 0ustar maierastaff00000000000000# 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. """ Access to the package version, and check for supported Python versions. Note: The package version is not defined here, but determined dynamically by the `pbr` package from Git information. """ import sys import pbr.version __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.devD": Development level D of a not yet released assumed M.N.P #: version #: * "M.N.P": A released M.N.P version __version__ = pbr.version.VersionInfo('zhmcclient').release_string() # Check supported Python versions _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') elif _PYTHON_M == 3 and _PYTHON_N < 4: raise RuntimeError('On Python 3, zhmcclient requires Python 3.4 or higher') zhmcclient-0.22.0/zhmcclient/_user_role.py0000644000076500000240000003555013364325033021176 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['UserRoleManager', 'UserRole'] LOG = get_logger(__name__) 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` """ 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` """ 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): """ 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 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): """ 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 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) zhmcclient-0.22.0/zhmcclient/_hba.py0000644000076500000240000002621413364325033017726 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['HbaManager', 'Hba'] LOG = get_logger(__name__) 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` """ 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` """ 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) zhmcclient-0.22.0/zhmcclient/_adapter.py0000644000076500000240000005120713414644366020625 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call from ._utils import repr_dict, repr_manager, repr_timestamp __all__ = ['AdapterManager', 'Adapter'] LOG = get_logger(__name__) 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', } # 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', } # Port type, dependent on adapter family port_type_by_family = { 'ficon': 'storage', 'osa': 'network', 'roce': 'network', 'hipersockets': 'network', } 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` """ 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` """ 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) zhmcclient-0.22.0/zhmcclient/_resource.py0000644000076500000240000002722413364325033021025 0ustar maierastaff00000000000000# 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 ._logging import get_logger, logged_api_call from ._utils import repr_dict, repr_timestamp __all__ = ['BaseResource'] LOG = get_logger(__name__) 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): """ dict: The properties of this resource that are currently present in this Python object. Will not be `None`. * Key: Name of the property. * Value: Value of the property. 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. The properties in this dictionary are mutable. However, the properties of the actual manageable resources may or may not be mutable. Mutability for each resource property is indicated with the 'w' qualifier in its data model in the :term:`HMC API` book. """ return 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. """ # 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 zhmcclient-0.22.0/zhmcclient/__init__.py0000644000076500000240000000440113364325033020566 0ustar maierastaff00000000000000# 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 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 zhmcclient-0.22.0/zhmcclient/_session.py0000644000076500000240000014726613367665262020710 0ustar maierastaff00000000000000# 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 json import time import re import collections import six from copy import copy try: from collections import OrderedDict except ImportError: from ordereddict import OrderedDict import requests from requests.packages import urllib3 from ._exceptions import HTTPError, ServerAuthError, ClientAuthError, \ ConnectionError, ParseError, ConnectTimeout, ReadTimeout, \ RetriesExceeded, OperationTimeout 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'] LOG = get_logger(__name__) HMC_LOG = get_logger(HMC_LOGGER_NAME) _HMC_SCHEME = "https" _STD_HEADERS = { 'Content-type': 'application/json', 'Accept': '*/*' } 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) elif isinstance(exc, requests.exceptions.ReadTimeout): raise ReadTimeout(_request_exc_message(exc), exc, retry_timeout_config.read_timeout, retry_timeout_config.read_retries) elif isinstance(exc, requests.exceptions.RetryError): raise RetriesExceeded(_request_exc_message(exc), exc, retry_timeout_config.connect_retries) else: 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): """ 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 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): """ 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. """ self._host = host self._port = port self._userid = userid self._password = password 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. """ 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" ")".format(classname=self.__class__.__name__, id=id(self), s=self)) 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 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 = requests.packages.urllib3.Retry( total=None, 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 """ if method == 'POST' and url.endswith('/api/sessions'): content_dict = json.loads(content) content_dict['password'] = '********' content = json.dumps(content) HMC_LOG.debug("HMC request: %s %s, headers: %r, " "content(max.1000): %.1000r", method, url, headers, content) @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 """ HMC_LOG.debug("HMC response: %s %s, status: %s, headers: %r, " "content(max.1000): %.1000r", method, url, status, headers, content) @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=False, timeout=req_timeout) except requests.exceptions.RequestException 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) elif 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) else: msg = result_object.get('message', None) raise ServerAuthError("HTTP authentication failed: {}". format(msg), HTTPError(result_object)) else: 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=False, timeout=req_timeout) else: result = req.post(url, data=data, headers=headers, verify=False, timeout=req_timeout) except requests.exceptions.RequestException 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) elif result.status_code == 204: # No content return None elif 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 else: # 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) else: return job elif 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) else: msg = result_object.get('message', None) raise ServerAuthError("HTTP authentication failed: {}". format(msg), HTTPError(result_object)) else: 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=False, timeout=req_timeout) except requests.exceptions.RequestException 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 elif 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 else: msg = result_object.get('message', None) raise ServerAuthError("HTTP authentication failed: {}". format(msg), HTTPError(result_object)) else: 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: raise 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))) elif 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 elif 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 else: raise ParseError( "Unknown content type in HTTP response: {}. " "HTTP request: {} {}. " "Response status {}. " "Response content-type: {!r}. " "Content (max.1000, decoded using {}): {}". format(content_type, result.request.method, result.request.url, result.status_code, content_type, result.encoding, _text_repr(result.text, 1000))) zhmcclient-0.22.0/zhmcclient/_partition.py0000644000076500000240000012420013364325033021177 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['PartitionManager', 'Partition'] LOG = get_logger(__name__) 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'] @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): """ 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. 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/stop', wait_for_completion=wait_for_completion, operation_timeout=operation_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` """ 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` """ 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'. 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 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 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: cpc = self.manager.cpc for sg_uri in sg_uris: sg = cpc.storage_groups.resource_object(sg_uri) sg_list.append(sg) if full_properties: sg.pull_full_properties() return sg_list zhmcclient-0.22.0/zhmcclient/_user_pattern.py0000644000076500000240000002467713364325033021722 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['UserPatternManager', 'UserPattern'] LOG = get_logger(__name__) 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.manager.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` """ 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` """ 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) zhmcclient-0.22.0/zhmcclient/_timestats.py0000644000076500000240000002413113364325033021205 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['TimeStatsKeeper', 'TimeStats'] LOG = get_logger(__name__) 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() zhmcclient-0.22.0/zhmcclient/_client.py0000644000076500000240000001627313364325033020456 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call from ._exceptions import Error, OperationTimeout __all__ = ['Client'] LOG = get_logger(__name__) 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 except Exception: raise 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 zhmcclient-0.22.0/zhmcclient/_unmanaged_cpc.py0000644000076500000240000001405713364325033021762 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['UnmanagedCpcManager', 'UnmanagedCpc'] LOG = get_logger(__name__) 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) zhmcclient-0.22.0/zhmcclient/_virtual_function.py0000644000076500000240000002177213364325033022573 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['VirtualFunctionManager', 'VirtualFunction'] LOG = get_logger(__name__) 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` """ 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` """ 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) zhmcclient-0.22.0/zhmcclient/_cpc.py0000644000076500000240000014470713364325033017751 0ustar maierastaff00000000000000# 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 ._logging import get_logger, logged_api_call from ._exceptions import ParseError __all__ = ['CpcManager', 'Cpc'] LOG = get_logger(__name__) 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._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 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) _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 # Note: From HMC API version 2.24 on, the Cpc object supports a # 'maximum-partitions' property. Because that API version was # introduced while model 3907 was already avbailable, the property is # guaranteed to be available only on models after 3907. # TODO: Exploit the new 'maximum-partitions' property. } @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 / Rockhopper II 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') try: max_parts = self._MAX_PARTITIONS_BY_MACHINE_TYPE[machine_type] except KeyError: raise ValueError("Unknown machine type: {!r}".format(machine_type)) return max_parts @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'] @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` """ 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) zhmcclient-0.22.0/zhmcclient/_password_rule.py0000644000076500000240000002233013364325033022060 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['PasswordRuleManager', 'PasswordRule'] LOG = get_logger(__name__) 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` """ 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` """ 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)) zhmcclient-0.22.0/zhmcclient/_storage_volume.py0000644000076500000240000006024713364325033022233 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['StorageVolumeManager', 'StorageVolume'] LOG = get_logger(__name__) 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) 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) zhmcclient-0.22.0/zhmcclient/_exceptions.py0000644000076500000240000011312313364325033021351 0ustar maierastaff00000000000000# 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'] 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 __init__(self, *args): # Parameters: # *args: # A list of input arguments for the exception object. # The derived classes define more specific parameters. # These input arguments will be available as tuple items in the # ``args`` instance variable of the exception object. super(Error, self).__init__(*args) 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): """ 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): """ This exception indicates erors related to authentication. Exceptions of this class are not raised; only derived exceptions are raised. Derived from :exc:`~zhmcclient.Error`. """ def __init__(self, *args): # Parameters: # *args: # A list of input arguments for the exception object. # The derived classes define more specific parameters. # These input arguments will be available as tuple items in the # ``args`` instance variable of the exception object. super(AuthError, self).__init__(*args) class ClientAuthError(AuthError): """ This exception indicates an authentication related problem detected on the client side. Derived from :exc:`~zhmcclient.AuthError`. """ def __init__(self, msg): """ 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): """ :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 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. """ return "{},{}: {} [{} {}]".\ format(self.http_status, self.reason, self.message, self.request_method, self.request_uri) 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}, ...)". \ format(self.__class__.__name__, self.http_status, self.reason, self.message, self.request_method, self.request_uri) 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={}; request_method={}; request_uri={}; http_status={}; reason={}; message={}; """ # noqa: E501 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): """ :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 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): """ :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 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): """ :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 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]) zhmcclient-0.22.0/zhmcclient/_port.py0000644000076500000240000001662113364325033020161 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['PortManager', 'Port'] LOG = get_logger(__name__) 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` """ 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)) zhmcclient-0.22.0/zhmcclient/_task.py0000644000076500000240000001314013364325033020130 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['TaskManager', 'Task'] LOG = get_logger(__name__) 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) zhmcclient-0.22.0/zhmcclient/_ldap_server_definition.py0000644000076500000240000002232713364325033023713 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['LdapServerDefinitionManager', 'LdapServerDefinition'] LOG = get_logger(__name__) 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` """ 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` """ 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)) zhmcclient-0.22.0/zhmcclient/_utils.py0000644000076500000240000001546313364325033020340 0ustar maierastaff00000000000000# 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 import six from collections import OrderedDict, Mapping, MutableSequence, Iterable from datetime import datetime import pytz __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(_list, 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 _list is None: return 'None' if isinstance(_list, MutableSequence): bm = '[' em = ']' elif isinstance(_list, Iterable): bm = '(' em = ')' else: raise TypeError("Object must be an iterable, but is a %s" % type(_list)) ret = bm + '\n' for value in _list: ret += _indent('%r,\n' % value, 2) ret += em ret = repr_text(ret, indent=indent) return ret.lstrip(' ') def repr_dict(_dict, 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 _dict is None: return 'None' if not isinstance(_dict, Mapping): raise TypeError("Object must be a mapping, but is a %s" % type(_dict)) if isinstance(_dict, OrderedDict): kind = 'ordered' ret = '%s {\n' % kind # non standard syntax for the kind indicator for key in six.iterkeys(_dict): value = _dict[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(_dict)): value = _dict[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): """ Convert an :term:`HMC timestamp number ` into a :class:`~py:datetime.datetime` object. 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`. Returns: :class:`~py:datetime.datetime`: Point in time as a timezone-aware Python datetime object for timezone UTC. 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 try: dt = datetime.fromtimestamp(epoch_seconds, pytz.utc) except (ValueError, OSError) as exc: raise ValueError(str(exc)) 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 zhmcclient-0.22.0/zhmcclient/_user.py0000644000076500000240000002553313364325033020155 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['UserManager', 'User'] LOG = get_logger(__name__) 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` """ 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` """ 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) zhmcclient-0.22.0/zhmcclient/_virtual_storage_resource.py0000644000076500000240000002746413364325033024325 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['VirtualStorageResourceManager', 'VirtualStorageResource'] LOG = get_logger(__name__) 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` """ 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) zhmcclient-0.22.0/zhmcclient/_manager.py0000644000076500000240000010265713364325033020614 0ustar maierastaff00000000000000# 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 six import re from datetime import datetime, timedelta import warnings from requests.utils import quote from ._logging import get_logger, logged_api_call from ._exceptions import NotFound, NoUniqueMatch, HTTPError from ._utils import repr_list __all__ = ['BaseManager'] LOG = get_logger(__name__) 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: raise NotFound({self._manager._name_prop: name}, self._manager) 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. """ 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. """ 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) 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: self._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 _append_query_parms(self, query_parms, prop_name, prop_match): if isinstance(prop_match, (list, tuple)): for pm in prop_match: self._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) 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] else: 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) elif num_objs > 1: raise NoUniqueMatch(filter_args, self, obj_list) else: 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() zhmcclient-0.22.0/zhmcclient/_activation_profile.py0000644000076500000240000002272113364325033023054 0ustar maierastaff00000000000000# 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 get_logger, logged_api_call __all__ = ['ActivationProfileManager', 'ActivationProfile'] LOG = get_logger(__name__) 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` """ 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)) zhmcclient-0.22.0/zhmcclient/_notification.py0000644000076500000240000003001113364325033021650 0ustar maierastaff00000000000000#!/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..." % (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 KeyboardInterrupt: pass print("Closing OS message channel...") 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 stomp import json from ._logging import get_logger, logged_api_call from ._constants import DEFAULT_STOMP_PORT __all__ = ['NotificationReceiver'] LOG = get_logger(__name__) class NotificationReceiver(object): """ A class for receiving HMC notifications that are published to a particular single HMC notification topic. **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 a particular HMC notification topic. 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, host, userid, password, port=DEFAULT_STOMP_PORT): """ Parameters: topic (:term:`string`): Name of the HMC notification topic. 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`. """ self._topic = topic 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() 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.start() self._conn.connect(self._userid, self._password, wait=True) dest = "/topic/" + topic 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:: receiver = zhmcclient.NotificationReceiver(topic, hmc, userid, password) for headers, message in receiver.notifications(): . . . 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. """ while True: with self._handover_cond: # 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 yield (self._handover_dict['headers'], self._handover_dict['message']) # Indicate to MessageListener that we are ready for next # notification del self._handover_dict['headers'] del self._handover_dict['message'] 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. """ 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: # 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_cond.notifyAll() def on_error(self, headers, message): """ This event method should never be called, because the HMC does not issue JMS errors. For parameters, see :meth:`~zhmcclient.NotificationListener.on_message`. """ raise RuntimeError("Unexpectedly received a JMS error " "(JMS headers: %r)" % headers) 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: # 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 try: msg_obj = json.loads(message) except Exception: raise # TODO: Find better exception for this case self._handover_dict['message'] = msg_obj self._handover_cond.notifyAll() zhmcclient-0.22.0/remove_duplicate_setuptools.py0000755000076500000240000000350113364325033022522 0ustar maierastaff00000000000000#!/bin/env python """ This script removes duplicated dist-info directories of setuptools, which happen to exist on the Travis CI in their Ubuntu 14.04 (trusty) distro: site-packages/setuptools <- contains 36.3.0 site-packages/setuptools-36.0.1.dist-info <- duplicate to be removed site-packages/setuptools-36.3.0.dist-info """ import sys import os import re import glob import importlib import shutil def remove_duplicate_metadata_dirs(package_name): """Remove duplicate metadata directories of a package.""" print("Removing duplicate metadata directories of package: {}". format(package_name)) module = importlib.import_module(package_name) py_mn = "{}.{}".format(*sys.version_info[0:2]) print("Current Python version: {}".format(py_mn)) version = module.__version__ print("Version of imported {} package: {}".format(package_name, version)) site_dir = os.path.dirname(os.path.dirname(module.__file__)) print("Site packages directory of imported package: {}".format(site_dir)) metadata_dirs = [] metadata_dirs.extend(glob.glob(os.path.join( site_dir, '{}-*.dist-info'.format(package_name)))) metadata_dirs.extend(glob.glob(os.path.join( site_dir, '{}-*-py{}.egg-info'.format(package_name, py_mn)))) for d in metadata_dirs: m = re.search(r'/{}-([0-9.]+)(\.di|-py)'.format(package_name), d) if not m: print("Warning: Could not parse metadata directory: {}".format(d)) continue d_version = m.group(1) if d_version == version: print("Found matching metadata directory: {}".format(d)) continue print("Removing duplicate metadata directory: {}".format(d)) shutil.rmtree(d) if __name__ == '__main__': remove_duplicate_metadata_dirs('setuptools') zhmcclient-0.22.0/tools/0000755000076500000240000000000013414661056015462 5ustar maierastaff00000000000000zhmcclient-0.22.0/tools/cpcdata0000755000076500000240000003640713364325033017015 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Display information about CPCs and their basic resources in the data center. """ from __future__ import absolute_import, print_function import os import platform import sys import logging import argparse from datetime import datetime import requests.packages.urllib3 import yaml from platform import python_version import zhmcclient MYNAME = 'cpcdata' # Model information: MACH_TYPE_INFO = { # mach-type: (name, max-partitions) '2064': ('z900', 15), '2084': ('z990', 30), '2094': ('z9 EC', 60), '2097': ('z10 EC', 60), '2817': ('z196', 60), '2827': ('zEC12', 60), '2964': ('z13', 85), # Also LinuxONE Emperor '3906': ('z14', 85), # Also LinuxONE Emperor II '3907': ('z14-ZR1', 40), # Also LinuxONE Rockhopper II '2066': ('z800', 15), '2086': ('z890', 30), '2096': ('z9 BC', 30), # Some models have only 15 partitions '2098': ('z10 BC', 30), '2818': ('z114', 30), '2828': ('zBC12', 30), '2965': ('z13s', 40), # Also LinuxONE Rockhopper } # Status values for "running" partitions: PARTITION_RUNNING_STATI = ( 'starting', 'active', 'stopping', 'degraded', 'reservation-error', 'paused', ) LPAR_RUNNING_STATI = ( 'operating', 'exceptions', ) # Defines order of columns in CSV output. # The names are used both as column headings in the CSV output, and as # names in the cpc_info dictionary. CSV_FIELDS = ( 'timestamp', 'hmc', 'name', 'description', 'machine-type', 'machine-model', 'machine-type-name', 'dpm-enabled', 'is-ensemble-member', 'iml-mode', 'processors-ifl', 'processors-cp', 'memory-total', 'memory-available', 'partitions-maximum', 'partitions-defined', 'partitions-running', ) def main(): requests.packages.urllib3.disable_warnings() args = parse_args() config_file = args.config_file with open(args.config_file, 'r') as fp: config_root = yaml.load(fp) config_this = config_root.get(MYNAME, None) if config_this is None: raise ConfigError("'%s' item not found in config file %s" % (MYNAME, config_file)) config_hmcs = config_this.get("hmcs", None) if config_hmcs is None: raise ConfigError("'%s' / 'hmcs' item not found in config " "file %s" % (MYNAME, config_file)) config = argparse.Namespace() config.loglevel = config_this.get("loglevel", None) config.logmodule = config_this.get("logmodule", 'zhmcclient') config.timestats = config_this.get("timestats", False) config.verbose = args.verbose config.csv_file = args.csv_file config.timestamp = datetime.now().replace(second=0, microsecond=0) if config.loglevel is not None: level = getattr(logging, config.loglevel.upper(), None) if level is None: raise ConfigError("Invalid value for 'loglevel' item in " "config file %s: %s" % (config_file, config.loglevel)) logmodule = config.logmodule if config.logmodule is None: config.logmodule = '' # root logger handler = logging.StreamHandler() # log to stdout format_string = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' handler.setFormatter(logging.Formatter(format_string)) logger = logging.getLogger(logmodule) logger.addHandler(handler) logger.setLevel(level) if config.verbose: print("Logging to stdout for module %s with level %s" % (config.logmodule, config.loglevel)) try: print_csv_header(config) for hmc_host in config_hmcs: config_hmc = config_root.get(hmc_host, None) if config_hmc is None: raise ConfigError("'%s' item (credentials for that HMC) not " "found in config file %s" % config_file) hmc_userid = config_hmc.get('userid', None) if hmc_userid is None: raise ConfigError("'%s' / 'userid' item not found in config " "file %s" % config_file) hmc_password = config_hmc.get('password', None) if hmc_password is None: raise ConfigError("'%s' / 'password' item not found in config " "file %s" % config_file) process_hmc(config, hmc_host, hmc_userid, hmc_password) except zhmcclient.Error as exc: print("%s: %s" % (exc.__class__.__name__, exc)) # traceback.print_exc() sys.exit(1) except ConfigError as exc: print("%s: %s" % (exc.__class__.__name__, exc)) sys.exit(1) def process_hmc(config, hmc_host, hmc_userid, hmc_password): if config.verbose: print("Processing HMC %s" % hmc_host) # Test whether we can ping the HMC if config.verbose: print("Attempting to ping HMC ...") reachable = ping(hmc_host) if not reachable: print("Warning: Cannot ping HMC %s" % hmc_host) return try: session = zhmcclient.Session(hmc_host, hmc_userid, hmc_password) client = zhmcclient.Client(session) if config.timestats: session.time_stats_keeper.enable() # Test whether we can use an operation that does not require logon try: if config.verbose: print("Attempting to get HMC version ...") client.version_info() except zhmcclient.ConnectionError: print("Warning: Cannot connect to API on HMC %s" % hmc_host) return # This is the first operation that requires logon if config.verbose: print("Attempting to list managed CPCs ...") cpcs = client.cpcs.list() for cpc in sorted(cpcs, key=lambda cpc: cpc.prop('name', '')): process_cpc(config, cpc, hmc_host) session.logoff() if config.timestats: print(session.time_stats_keeper) except zhmcclient.Error as exc: print("Warning: %s on HMC %s: %s" % (exc.__class__.__name__, hmc_host, exc)) return def process_cpc(config, cpc, hmc_host): if config.verbose: print("Attempting to list partitions on CPC %s ..." % cpc.prop('name')) if cpc.dpm_enabled: partitions = cpc.partitions.list() else: partitions = cpc.lpars.list() if config.verbose: print("Attempting to retrieve properties of CPC %s ..." % cpc.prop('name')) cpc_info = {} cpc_info['timestamp'] = config.timestamp cpc_info['hmc'] = hmc_host cpc_info['name'] = cpc.prop('name') cpc_info['description'] = cpc.prop('description') cpc_info['status'] = cpc.prop('status') cpc_info['machine-type'] = cpc.prop('machine-type') cpc_info['machine-model'] = cpc.prop('machine-model') cpc_info['machine-type-name'] = model_name(cpc) cpc_info['dpm-enabled'] = cpc.prop('dpm-enabled', False) cpc_info['is-ensemble-member'] = cpc.prop('is-ensemble-member', False) cpc_info['iml-mode'] = cpc.prop('iml-mode') cpc_info['processors-ifl'] = cpc.prop('processor-count-ifl') cpc_info['processors-cp'] = cpc.prop('processor-count-general-purpose') # in MiB, may be None on older models: cpc_info['memory-total'] = cpc.prop('storage-customer', None) # in MiB, may be None on older models: cpc_info['memory-available'] = cpc.prop('storage-customer-available', None) # may be None if unknown: cpc_info['partitions-maximum'] = max_partitions(cpc) cpc_info['partitions-defined'] = defined_partitions(partitions) cpc_info['partitions-running'] = running_partitions(partitions) print_cpc_as_text(config, cpc_info) print_cpc_as_csv(config, cpc_info) def print_cpc_as_text(config, cpc_info): print("CPC {name} managed by HMC {hmc}:".format(**cpc_info)) print(" Description: {description}".format(**cpc_info)) print(" Machine: {machine-type}-{machine-model} ({machine-type-name})". format(**cpc_info)) print(" DPM enabled: {dpm-enabled}".format(**cpc_info)) print(" Member of ensemble: {is-ensemble-member}".format(**cpc_info)) print(" IML mode: {iml-mode}".format(**cpc_info)) print(" Status: {status}".format(**cpc_info)) print(" Processors: CPs: {processors-cp}, IFLs: {processors-ifl}". format(**cpc_info)) mem_total = ("{} GiB".format(cpc_info['memory-total'] / 1024)) \ if cpc_info['memory-total'] else "N/A" mem_avail = ("{} GiB".format(cpc_info['memory-available'] / 1024)) \ if cpc_info['memory-available'] else "N/A" print(" Memory for partitions: total: {}, available: {}". format(mem_total, mem_avail)) print(" Partitions: max-active: {}, defined: {}, running: {}". format(cpc_info['partitions-maximum'] or "N/A", cpc_info['partitions-defined'] or "N/A", cpc_info['partitions-running'] or "N/A")) def print_cpc_as_csv(config, cpc_info): if config.csv_file: with open(config.csv_file, "a") as fp: data_line = ','.join( ['"{}"'.format(cpc_info[col]) for col in CSV_FIELDS]) fp.write(data_line) fp.write('\n') def print_csv_header(config): if config.csv_file: if not os.path.isfile(config.csv_file): if config.verbose: print("Creating new CSV output file: %s" % config.csv_file) with open(config.csv_file, "w") as fp: header_line = ','.join( ['"{}"'.format(col) for col in CSV_FIELDS]) fp.write(header_line) fp.write('\n') else: if config.verbose: print("Appending to existing CSV output file: %s" % config.csv_file) def parse_args(): """ Parse command line arguments and return the parsed args. In case of argument errors, print an error message and exit. """ version = zhmcclient.__version__ usage = "%(prog)s [options] CONFIGFILE" desc = "Gather data about all CPCs managed by a set of HMCs. The data " \ "is displayed and optionally written to a CSV-formatted spreadsheet." epilog = "" argparser = argparse.ArgumentParser( prog=MYNAME, usage=usage, description=desc, epilog=epilog, add_help=False) pos_arggroup = argparser.add_argument_group( 'Positional arguments') pos_arggroup.add_argument( 'config_file', metavar='CONFIGFILE', nargs='?', default=None, help='File path of config file for this tool. See --help-config ' 'for details about the config file format.') general_arggroup = argparser.add_argument_group('Options') version_str = '%s/zhmcclient %s, Python %s' %\ (MYNAME, version, python_version()) general_arggroup.add_argument( '--csv', dest='csv_file', metavar='CSVFILE', help='Write/append data to a CSV spreadsheet file.') general_arggroup.add_argument( '-v', '--verbose', dest='verbose', action='store_true', help='Show more messages while processing.') general_arggroup.add_argument( '--version', action='version', version=version_str, help='Show the relevant versions and exit.') general_arggroup.add_argument( '-h', '--help', action='help', help='Show this help message and exit.') general_arggroup.add_argument( '-hc', '--help-config', dest='help_config', action='store_true', help='Show help about the config file format and exit.') args = argparser.parse_args() if args.help_config: help_config() if not args.config_file: argparser.error('No config file specified (--help-config for details)') return args def help_config(): """ Displpay help about the config file. """ print(""" Format of config file. The config file is a YAML file with the following entries. Unknown entries are ignored. This format is compatible to the HMC credential file format used by the zhmcclient examples, so the same file can be used. The following template shows the format. Anything in angle brackets <> is meant to be replaced by a real value:: {myname}: hmcs: - "" - "" - ... loglevel: logmodule: timestats: "": userid: password: "": userid: password: ... Notes: - HMC hosts can be specified as IP v4/v6 addresses or long or short host names. - The "hmcs" entry defines which HMCs are contacted. All CPCs managed by these HMCs are shown. - If multiple choices are shown (e.g. for loglevel), the first choice is always the default. """.format(myname=MYNAME)) sys.exit(2) class ConfigError(Exception): pass def ping(host, timeout=10): """ Ping a host with one ICMP packet and return whether it responded to the ping request. Parameters: host (string): IP address or host name. timeout (integer): Timeout in seconds. Returns: bool: Host has responded. """ if platform.system() == "Windows": ping_options = "-n 1 -w %d" % (timeout * 1000) ping_drop_output = ">nul 2>&1" else: # Linux or OS-X ping_options = "-c 1 -W %d" % timeout ping_drop_output = ">/dev/null 2>&1" rc = os.system("ping %s %s %s" % (ping_options, host, ping_drop_output)) return rc == 0 def model_name(cpc): """ Return the model name for a CPC. """ mach_type = cpc.prop('machine-type') try: _model_name = MACH_TYPE_INFO[mach_type][0] except KeyError: _model_name = None if _model_name: return _model_name else: return "unknown" def max_partitions(cpc): """ Return the maxiumum number of user partitions or LPARs for a CPC. """ mach_type = cpc.prop('machine-type') try: max_parts = MACH_TYPE_INFO[mach_type][1] except KeyError: max_parts = None if max_parts: return max_parts else: return "?" def defined_partitions(partitions): """ Return the defined number of user partitions or LPARs. """ return len(partitions) def running_partitions(partitions): """ Return the number of running user partitions or LPARs. """ count = 0 for p in partitions: if isinstance(p, zhmcclient.Partition): running_stati = PARTITION_RUNNING_STATI else: running_stati = LPAR_RUNNING_STATI if p.prop('status') in running_stati: count += 1 return count if __name__ == '__main__': main() zhmcclient-0.22.0/tools/cpcinfo0000755000076500000240000002540013364325033017026 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Display information about a CPC. """ from __future__ import absolute_import, print_function import sys import argparse from getpass import getpass from datetime import datetime import requests.packages.urllib3 from tabulate import tabulate import progressbar from platform import python_version import zhmcclient def parse_args(): """ Parse command line arguments and return the parsed args. In case of argument errors, print an error message and exit. """ prog = "cpcinfo" # Name of this program, used for help etc. version = zhmcclient.__version__ usage = '%(prog)s [options] hmc [cpcname ...]' desc = 'Display information about CPCs.' epilog = """ Example: %s -u ensadmin -p 1234 9.152.150.65 P0000P30 """ % prog argparser = argparse.ArgumentParser( prog=prog, usage=usage, description=desc, epilog=epilog, add_help=False) pos_arggroup = argparser.add_argument_group( 'Positional arguments') pos_arggroup.add_argument( 'hmc', metavar='hmc', help='IP address or hostname of the HMC managing the CPCs.') pos_arggroup.add_argument( 'cpcnames', metavar='cpcname', nargs='*', help='Name of the CPC. Can be repeated. ' 'Default: All CPCs managed by the HMC.') general_arggroup = argparser.add_argument_group( 'Options') general_arggroup.add_argument( '-u', '--user', dest='user', metavar='user', help='Required: User name for authenticating with the HMC.') general_arggroup.add_argument( '-p', '--password', dest='password', metavar='password', help='Password for authenticating with the HMC.\n' 'Default: Prompt for a password.') pos_arggroup.add_argument( '-m', '--mcl', dest='mcl', action='store_true', help='Add information about the MCL level of each component.') pos_arggroup.add_argument( '-a', '--adapters', dest='adapters', action='store_true', help='Add information about the adapters in the CPC.') general_arggroup.add_argument( '-t', '--timestats', dest='timestats', action='store_true', help='Display time statistics for the HMC operations that were used.') version_str = '%s/zhmcclient %s, Python %s' %\ (prog, version, python_version()) general_arggroup.add_argument( '--version', action='version', version=version_str, help='Show the versions of this program etc. and exit') general_arggroup.add_argument( '-h', '--help', action='help', help='Show this help message and exit') args = argparser.parse_args() if not args.hmc: argparser.error('No HMC specified') if not args.user: argparser.error('No HMC userid specified (-u/--userid option)') if not args.password: args.password = getpass('Enter password for %s: ' % args.user) return args class ProgressBar(object): """ A progress bar, based upon the progressbar2 package. """ def __init__(self, max_value): self._max = max_value self._current = 0 self._widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ', progressbar.ETA()] self._bar = progressbar.ProgressBar(widgets=self._widgets, max_value=max_value) def progress(self): self._current += 1 self._bar.update(self._current) @property def current_value(self): return self._current def change_max(self, max_value): self._max = max_value self._bar = progressbar.ProgressBar(widgets=self._widgets, initial_value=self._current, max_value=self._max) def start(self): self._bar.start() def finish(self): self._bar.finish() def main(): args = parse_args() requests.packages.urllib3.disable_warnings() try: print("Using HMC %s with userid %s" % (args.hmc, args.user)) session = zhmcclient.Session(args.hmc, args.user, args.password) client = zhmcclient.Client(session) if args.timestats: session.time_stats_keeper.enable() dt_start = datetime.now() if args.cpcnames: cpcs = [] for cpcname in args.cpcnames: try: cpc = client.cpcs.find(name=cpcname) except zhmcclient.NotFound: raise zhmcclient.Error("Could not find CPC %s on HMC %s" % (cpcname, args.hmc)) cpcs.append(cpc) else: cpcs = client.cpcs.list() for cpc in sorted(cpcs, key=lambda cpc: cpc.prop('name', '')): print("\nRetrieving CPC %s ..." % cpc.prop('name')) sys.stdout.flush() bar = ProgressBar(max_value=30) bar.start() if cpc.dpm_enabled: bar.progress() # dpm_enabled performs "Get CPC Properties" partitions_kind = "Partitions" partition_header = ("Name", "Status", "OS Type", "OS Version") partitions = cpc.partitions.list() bar.progress() bar.change_max(bar.current_value + # noqa: W504 len(partitions) + 1 + # noqa: W504 (1 if args.adapters else 0)) partition_rows = [] for partition in sorted(partitions, key=lambda p: p.prop('name', '')): row = (partition.prop('name'), partition.prop('status'), partition.prop('os-type'), partition.prop('os-version')) bar.progress() partition_rows.append(row) else: bar.progress() partitions_kind = "LPARs" partition_header = ("Name", "Status", "OS Type", "OS Level") partitions = cpc.lpars.list() bar.progress() bar.change_max(bar.current_value + # noqa: W504 len(partitions) + 1 + # noqa: W504 (1 if args.adapters else 0)) partition_rows = [] for partition in sorted(partitions, key=lambda p: p.prop('name', '')): row = (partition.prop('name'), partition.prop('status'), partition.prop('os-type'), partition.prop('os-level')) bar.progress() partition_rows.append(row) machine_gen = "z" + cpc.prop('se-version').split('.')[1] bar.progress() if args.adapters: adapter_header = ("Location", "Card", "Type", "Ports") adapters = cpc.adapters.list() bar.progress() adapter_rows = [] for adapter in sorted(adapters, key=lambda p: p.prop('card-location', '')): row = (adapter.prop('card-location'), adapter.prop('detected-card-type'), adapter.prop('type'), adapter.prop('port-count')) adapter_rows.append(row) bar.finish() print("CPC %s" % cpc.prop('name')) print(" Machine: %s-%s (%s)" % (cpc.prop('machine-type'), cpc.prop('machine-model'), machine_gen)) print(" IML mode: %s" % cpc.prop('iml-mode')) print("\nCPUs:") print(" CP: %s" % cpc.prop('processor-count-general-purpose')) print(" IFL: %s" % cpc.prop('processor-count-ifl')) print(" IIP: %s" % cpc.prop('processor-count-iip')) print(" AAP: %s" % cpc.prop('processor-count-aap')) print(" ICF: %s" % cpc.prop('processor-count-icf')) print(" SAP: %s" % cpc.prop('processor-count-service-assist')) print(" spare: %s" % cpc.prop('processor-count-spare')) print(" defective: %s" % cpc.prop('processor-count-defective')) mem_cust = int(cpc.prop('storage-customer', '0')) / 1024 mem_hsa = int(cpc.prop('storage-hardware-system-area', '0')) /\ 1024 print("\nMemory:") print(" Customer: %d GB" % mem_cust) print(" HSA: %d GB" % mem_hsa) if args.mcl: mc_desc = cpc.prop('ec-mcl-description') mc_header = ["Component", "Level"] mc_rows = [] for ec in sorted(mc_desc['ec'], key=lambda ec: ec['description']): mc_component = ec['description'] mc_level = 'unknown' for mcl in ec['mcl']: if mcl['type'] == 'activated': mc_level = mcl['level'] break mc_rows.append((mc_component, mc_level)) print("\nActive microcode levels:") print(tabulate(mc_rows, mc_header)) if args.adapters: print("\nAdapters:") print(tabulate(adapter_rows, adapter_header)) print("\n%s:" % partitions_kind) print(tabulate(partition_rows, partition_header)) session.logoff() dt_end = datetime.now() delta = dt_end - dt_start print("Total time for retrieving the information: %d s" % delta.total_seconds()) if args.timestats: print(session.time_stats_keeper) except zhmcclient.Error as exc: print("%s: %s" % (exc.__class__.__name__, exc)) sys.exit(1) if __name__ == '__main__': main() zhmcclient-0.22.0/LICENSE0000644000076500000240000002363712743123376015344 0ustar maierastaff00000000000000 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. zhmcclient-0.22.0/requirements.txt0000644000076500000240000000154113367665262017621 0ustar maierastaff00000000000000# 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>=4.0.10 # new BSD pbr>=1.10.0 # Apache-2.0 pytz>=2016.10 # MIT requests>=2.20.0 # Apache-2.0 six>=1.10.0 # MIT stomp.py>=4.1.15 # Apache # Indirect dependencies (commented out, only listed to document their license): # certifi # ISC, from requests>=2.20 # chardet # LGPL, from requests>=2.20 # idna # BSD-like, from requests>=2.20 # urllib3 # MIT, from requests>=2.20 zhmcclient-0.22.0/ChangeLog0000644000076500000240000007156713414661055016113 0ustar maierastaff00000000000000CHANGES ======= 0.22.0 ------ * Release 0.22.0 * Mitigated that fIltering by adapter-id returns empty result for hex digits * Several fixes in Travis control file for OS-X * Fixed install of latest reqs * Docs: Updated how to release a version * Fixed incorrect HMC method in a comment on 202 processing * Start 0.22.0 0.21.0 ------ * Release 0.21.0 * Update requests package to 2.20.0 in requirements.txt * FIxed metrics example by adding sleep * Start 0.21.0 0.20.0 ------ * Release 0.20.0 * Fixed flake8 issues * notification: add parameter port * tests: add test for HMC port * session: add parameter port * notification: move constant \_STOMP\_PORT to constants * session: move constant \_HMC\_PORT to constants * Lpar: add support for reset clear * lpar: add support for stop * lpar: add support for SCSI load * Add tests for store-status-indicator * lpar: allow to specify store-status-indicator on Load * Add tests for clear-indicator * lpar: allow to specify clear-indicator on Load * lpar: fix typo * Fixed several bugs in DPM storage group support * Upgraded dependent package versions from zhmc-ansible-modules * Fixed Sphinx build on Python 3.7 with AutoAutoSummary extension * Added Adapter.change\_adapter\_type() operation * Added end2end test for storage groups * Implementation of DPM storage group support for FCP * Added design for DPM storage management feature * Added method and attribute summary lists to class docs * Inspection of firmware features * Navigate from Cpc to Client * Direct access to the one Console object * Fixed issue with None input in \_utils.repr\_\*() * Added conf.py to make check * Added z14-ZR1 and Rockhopper II support * Converted function tests to end2end tests * Fixed warning on changes.rst during builddoc * Added support for Python 3.7 * Enhanced faked resources to automatically set parent prop * Pinned version of pytest-cov to <2.6 * Improved repr() output of faked and real resource and manager classes * Travis: Consistent .travis.yml between external and IBM Travis * Improved error handling in determination of package version in Makefile * Consolidated internal nic\_/partition\_object() methods to resource\_object() * Travis: Moved venv directory on OS-X out of work directory * Travis: Enabled caching for pip * Fixed release description for RTD stable builds * Streamlined and extended 'how to release' section in docs * Restarted 0.20.0 * Excluded .DS\_Store file (on OS-X) from git * Updated change log with missing entry about appveyor changes * Removed debug info for RTD but left a few prints in * More debug info and a test tag * Fixed package version on latest/master build on RTD * Lowered version reqs for dependent packages * Fixed Git rebase in Appveyor CI * Made makefile consistent with zhmccli and fixed minor errors * Docs: Improved docs for installing without Internet and sel. Python env * Made makefile consistent with zhmccli and fixed minor errors * Improved Makefile with extra check for empty package version * Docs: Resolved Sphinx build warnings * Added debug info for package version issue to Sphinx build * Started 0.20.0 release 0.19.0 ------ * Updated change log for 0.19.0 release * Fix use of double brackets in make shell() * Using == insteaf of === in constraints file * Added unit tests for \_metrics module; Fixed metrics mock support * Fixed TypeError in metrics support; improved metrics example * Changed make install to be editable; makefile improvements * Added .pytest\_cache directory to .gitignore * Travis for OS-X: Fixed virtualenv for Python 3 and enabled py3 * Supressed makefile init error messages * Travis for OS-X: Fixed changed Python install and added py2 run * Extended mock by class and child res; added FT for Act.Profiles * Added function test concept, accomodating pytest 3.3.0 * Small improvement in setup of zhmcclient logging * Added support for CPC energy management operations * Disabled coveralls in manual-ci-run 0.18.0 ------ * Added ability to mock status of Lpar act/deact/load * Made load address optional for Lpar.load() to support z14 * Increased CodeClimate complexity threshold to 'D' * Improved unit tests for \_lpar module * Added force and act.profile name parms to Lpar act/deact/load * Restructured tests subtree * Migrated remaining test cases from unittest to py.test * Disabled the unit test functions that print datetime info * Fixes and improvements in Makefile * Enabled Travis and Appveyor for manual-ci-run * Started 0.19.0 * Released 0.18.0 * Fix build\_clib issue on Travis by removing duplicate setuptools * Removed zhmc CLI from this project * Started 0.18.0 release 0.17.0 ------ * Updated change log for 0.17.0 release * Add CLI support for mount and unmount an ISO * Disabled OS-X runs in Travis CI * Added support to zhmcclient for Console resource and its children * Docs: Fixed HMC API version and API book ref. for z14 * Improved the docs for Metric\*Definition named tuples * Fixed TypeError in unit test of metrics mock support * Fixed in zhmc CLI that session create used existing session ID * Fix TypeError in zhmcclient\_mock/\_urihandler.py * NIC: Migrated unit test to py.test, Changes in mock support * Migrated HBA unit tests to py.test; Small fixes to Hba * Extended InvalidResourceError in mock support * Improved mock support to return HTTP 400 instead of raising ValueError * Test BaseResource: Improved assertion info, consistent URIs * Added CPC and Partition status checks to mock support where missing + cleanup * Migrated \_client,\_exceptions modules to py.test; changes to exceptions * Eliminated pip warnings when using minimum-constraints.txt * Changed Makefile to use pbr for package version * Circumvented ImportError for build\_clib on Travis with Py 2.7.13 * Added tests for null props to Partition + Image Act. Profile * Added '\_\_repr\_\_()' to 'Session' and 'FakedSession' classes * Fixed in zhmc CLI that session create used existing session ID * Fixed empty PIP\_CMD in cron-triggered Travis runs * Added mock support for the zhmcclient Metrics support * Added timestamp related utility functions * Access to partition from nic and vfunction via respective manager * Expanded the regular cron-triggered Travis run * CLI: Fixed AttributeError when aborting confirmation question * Minor doc improvements in Development section * Eliminated DeprecationWarning for invalid escape sequences on Py3 * Removed sudo requirement in Travis for OS-X * Removed sudo requirement from Travis * Started 0.17.0 release 0.16.0 ------ * Updated change log for 0.16.0 release * CLI: Remove defaults for options for 'partition update' * Add Code Climate Badge * Customized codeclimate * Started 0.16.0 release 0.15.0 ------ * Updated change log for 0.15.0 release * CLI: Fixed AttributeError with dict.iteritems() on Python 3 * Rename z Systems family to IBM Z * Fix Code of Conduct link * Add 1.4 of Contributor Covenent Code of Conduct * Add config file for Code Climate tool * Update link to 'How to Get Your Change Into the ...' * Improved Cpc.get\_free\_crypto\_domains() * Editorial changes on package reqs * Updated package requirements * Improved error message for WS not enabled * CLI support for processing weights * Docs: Fixed supported environments in intro * CLI support for enable partition controls * Minor fixes in documentation * Minor optimizations in Travis control file * Restricted 'make test' to look just in 'tests' dir * Fixed Travis CI for OS-X * Added support for z14; Added new API versions for z13 * Optimized build of distribution archives * CLI: Fixed several Python 3 errors * Fixes and small improvements for metrics CLI * Fixed test errors * Metrics: Adjusted CLI to API changes and improved it; added metrics * [WIP] Add Metrics CLI commands * Improved the tools/cpcdata script * Reworked structured access to metrics response * Reflected review comments to metrics support * [WIP] Add Metrics Support * Started 0.15.0 release 0.14.0 ------ * Updated change log for 0.14.0 release * Mock: Added support for Get Connected VNICs operation * Mock: Improvements and testcases for partition crypto ops * Mock: Added support for remaining partition ops in URI handler * Fixed AttributeError in log module for jupyter/interactive * Improved the tutorial 04 error handling * Mock: Added support for HBA Reassign Port operation * Mock: Added support for Change Crypto Type of Adapter * Mock: Fixed fields check in URI handler of increase/decrease crypto * Mock: Cleanup and improvements in URI handler * Reduced Travis CI further * Docs: Improved Nic/Hba.create() descriptions * Test: Migrated unit tests to py.test + zhmcclient mock, part 1 * Mock: Fixed partition.start/stop() result * Mock: Added checking for status conflicts for Partition.start/stop() * Mock: Fixed partition.start/stop() result * Reduced test variations for Appveyor CI * Mock: Added checking for status conflicts for Partition.start/stop() * Mock: Added URI handler for CPC Export / Import Profiles * Mock: Several fixes in URI handler for CPC Export WWPN List * Fixed updating the name-to-URI cache when renaming * Added update\_properties() to Cpc and Lpar * Added update\_properties() to Cpc and Lpar * Reduced Travs CI runs on Linux * Added support for listing free crypto domains (experimental) * Fixed card type of Crypto Express 5s adapter * Mock: Added checking of required input props in Create ops * Fixed optimized lookup by object-id * vlan-id must be an integer * Mock: Fixed FakedBaseResource init when properties are empty * Created tests/unit/utils.py with shared code between the testcases * Added properties for max partitions and max crypto domains * Fixed JSON serialization issue in increase/decrease\_crypto\_config() * Added support for Get Inventory operation * Corrected parameters of Partition.mount\_iso\_image() * Improved performance of lookup by object-id * Improved misleading exception message in mock support * Fixed errors in unit testcases for Act.Profile * Added circumvention for duplicate port URIs in FCP adapter * Moved PyLint invocation into its own 'pylint' make target * Improved NoUniqueMatch exception to show matching resources * Added waiting for desired status to Partition.start() * Change log for crypto support, documented missing mock support * Added mock support for crypto operations * Fixed names of request body parms in change\_crypto\_config() * Updated the description of crypto functions * Add Crypto Support for Partitions * Fixed shallow copy errors for input properties of resources * Fix ssc-dns-servers option handling in CLI * Fixed TypeError with find\_partition(), find\_lpar() * Added wait\_for\_status() methods to Lpar and Partition * Added support for selecting the format of CLI error messages * Updated IBM copyright statement to reflect 2017 * Made code-block highlighting in docs consistent * CLI: Added support for an OS console into partition/LPAR * Circumvented urllib3 issue with requests 2.17.1 and 2.17.2 * Improved exceptions by splitting classes and adding attributes * Improved description of CLI output formats * CPI: Improvements for dealing with LPAR status 'exceptions' * Increased default status timeout from 60s to 15min * Improved ParseError message for better diagnosing * Updated CLI docs to show all subcommands * Improved test coverage of adapter module * Improved test coverage of activation\_profile module and used mock * Improved test coverage of manager module * Improved test coverage for logging module * Improved testability by using assert * Improved test coverage of exceptions module * Improved test coverage and docs for timestats module * Changed return value of TimeStatsKeeper.snapshot() from tuple list to dict * Doc fixes for return value of asynchronous Partition methods * Fixed docs for releasing and starting new release * Remove empty list items in change log * Started 0.14.0 release 0.13.0 ------ * Updated change log for 0.13.0 release * Add Secure Service Container Support * Fixed filter args for properties that are not on every resource * Improved repr() and str() in various places * Fix when providing 'load-parameter' option * Deprecated flush() and renamed invalidate\_name\_uri\_cache() * Improvements and fixes in mock support * Fixed incorrect result of Cpc.dpm\_enabled for z13 in classic mode * Fixed client-side filter matching * Fixed find(name) on element resources * Improved description of filter arguments * Fixes in unit tests for update\_properties() and delete() * Fixed stale name-to-URI cache and other improvements * Fixed unconditional addition of ifl-processors in CLI * Restricted read retries to just HTTP GET * Mitigated issue with read retries and DELETE * Improved issue template * Fixed defaults for 'zhmc partition create' * Wrote Concepts chapter * Started 0.13.0 release 0.12.0 ------ * Updated change log for 0.12.0 release * Changed documentation heading * Add load-parameter to 'zhmc lpar load' * Docs: Added internet-less install; minor other improvements * Improved testcases for object-id/element-id in mock support * Improved description of Pypi release process * Fixed package dependencies for minimum pkg level * Changed logging options of CLI to be more flexible * Added logging support to CLI * Fixed typo in description of FakedHmc * Added support for 'error' field in 'job-results' * Added -p/--password option; Added host+userid to get\_password() * Improved robustness of timestats unit tests * Added OS-X to Travis CI environment * Fixed minor doc build issues * In CLI, add support for -y option * Fixed display of CLI spinner in output * Added testcases for Faked\* repr() and attrs * Fixes and improvements in mock support * Moved logging constants to new \_constants module * Improved the log calls * Assign WWPN for HBA in mock framework * Start development of v0.12.0 0.11.0 ------ * Updated change log for 0.11.0 release * Add more output formats to the CLI * Change DEFAULT\_READ\_TIMEOUT to 5 min * Rebased on PR 206; session defaults for ops and status timeouts * Fixed errors when waiting for completion of LPAR status * Timeout support for async methods; deferred status polling for LPAR * Moved retry and timeout default values into a new \_constants module * Improved handling of network errors * Added support for tolerating HTML response content * Improved help of zhmc CLI * Testing with minimum/latest versions of dependent Python packages * Added automatic devno setting for NIC, HBA, VF in mock support * Increased minimum version of 'click-spinner' dependency to 0.1.7 * Minor docstring correction to send\_os\_command methods * Added tools to check target and fixed flake8 errors * Added minimum version 4.0.0 to 'decorate' dependency * Added send\_os\_command method to partition and lpar * Docs: Improved README file and example in intro chapter * Added Python 3.6 to PyPI metadata * Minor formatting fixes for return values in docs * Fixes #178: Redesigned return value of async. operation methods * Started development of v0.11.0 0.10.0 ------ * Update change log for 0.10.0 release * Updated change log; Declared NotificationReceiver as experimental * Added termination of notification receiver and unit tests * Mitigated multiple coveralls comments per Travis build * Added Python 3.6 to Travis CI and 3.4,3.5,3.6 to Appveyor CI * Fixed incorrect copyright year in new files * Fixed incorrect use of partition when it was lpar * Improved example lpar\_operations * Added a JMS notification receiver and examples for os messages * Changed open\_os\_message\_channel() method to return string directly * Added open\_os\_message\_channel() method to lpar and partition classes * Mock support for zhmcclient, stage 2: Faked Session (partial) * Mocking support for zhmcclient, stage 1: Fake HMC * Fix unrecognized field ('adapter-port') * Fix access 'cpc' from 'partition' * Added documentation of auth reqs * Added support for server-side resource filtering * Rename files in examples folder * Fix error in example5.py * Add Appveyor status badge * Reflected comments from PR 144 * Design for fake client, for review * Fixed assertion error in TimeStats testing on Windows * Start v0.10.0 development 0.9.0 ----- * Updated change log for 0.9.0 release * lpar.load(): Added support for load-parameter field * Fixing regression in findall(name=..) * Improved the examples * Added initial support for Appyevor CI * Fixes #137: Accessing properties['name'] may raise KeyError * Add example for CLI zhmc * Links to HMC API books in docs no longer require IBM ID * Started v0.9.0 development 0.8.0 ----- * Updated change log for 0.8.0 release * Added a 'cpcdata' tool for showing CPC info in a data center * Fixed some flake8 errors (real and style) * Improvements in CLI list commands (options, ordering, always a name) * Added support for lazy initialization of resource name * In CLI, fixed boolean update opt, help texts; optimized ops * Fixed an issue in the new find\_by\_name * Added messages to assertions; removed an assertion * FIxes and small improvements for CLI * Added support in CLI for remaining cmds; client improvements * Upgraded click-spinner to v0.1.5 and simplified its use * Started v0.8.0 development 0.7.0 ----- * Fixed #120: Pin click-spinner to 0.1.3 * Fixed incorrect package version for Pypi upload * Started 0.7.0 release * For zhmc CLI, added RTD documentation and improved help texts 0.6.0 ----- * Updated change log * Enabled and fixed flake8/pylint checking for zhmccli, and other stuff * Renamed two modules in zhmccli * Changes for zhmccli * Command line interface for the zhmcclient * Improvements to Session initialization, mainly docs * Session object can be created with session id * Improved VersionError exception class and removed number-of-args tests * Added issue template * Fixed new Flake8 issue E305 * Added time statistics entry for overall async operations * Updatd example5 to allow for CPC being in 'service-required' state * Fix typo in help message of cpcinfo * Fixes #101: Fix documentation of field Partition.hbas * Fixes #99: KeyError: 'status' when running example5.py * PyLint fixes and a real fix in dump\_partition() * Improved AuthError and ConnectionError exceptions * Improved ParseError and its use * Removed not-yet-used hint on some exceptions in tutorial 4 * Fixed change log to represent v0.5.0 and improved how-to-release section * Further improvements in tutorial 3 and new tutorial 4 * Simplified install from repo and enabled it for users without github key 0.5.0 ----- * Improved tutorial notebooks * Fixed HTTPError in dpm\_enabled * Moved unit tests to tests/unit and added tests/function * Made unit tests consistent * Fixed error in unit test for exceptions module * Added HTTP method and URI to HTTPError information * Fixed issue where cpcinfo -a failed on python 3 * Improved Tutorials section * Added pypy Python to Travis CI environments * Added an install test to Travis CI and to Tox * Added install test to Travis CI * Several logistical cpcinfo fixes * Moved change log section one heading level down * Docs: Added change log section * Docs: Updated resource model * Added more HBA/storage related methods * Added adapter information to the cpcinfo tool * Adjusted mocking in CPC testcases to new impl of dpm\_enabled; fixed error * Optimized dpm\_enabled implementation for better performance * Added cpcinfo tool and prop() method for resources * Removed downloads button in readme and added bigquery in comment * Renamed get\_connected\_vnics() to get\_connected\_nics() * Docs: Added section for releasing to PyPI * Consolidated Python resource object creation into resource modules * Changed connected\_vnics() to return Nic objects 0.4.0 ----- * Fixed doc links to resource types * Made documentation of Port / PortManager consistent with other resources * Resolved comments on PR * Add more unit tests for CPC class * Add Port Support * Improved stomp example; fixed flake8 error in session * Added get\_notification\_topics() and stomp job-completion message example * Added Repository section to Development chapter * Updated documentation of resources classes * Improved documentation of resource modules and renaming of a method argument * Update example5 * Changed create...() to return resource object instead of URI * Add Virtual Switch Support * Add more partition operations * Add Virtual Function Support * Added Resource Model in Appendix * Update documentation header * Add HBA Support * Add NIC Support * Add Adapter support * Simplify quickstart example 0.3.0 ----- * Editorial update in dev heading of README * Integrated CONTRIBUTING.rst content in Development section of docs * Changed link to Installation section in README to be to Pypi/stable version * Minor improvements in README.rst * Add string representation of BaseResource * Update install documentation * Added 'Development' chapter to the documentation * Added an 'Issue reporting' section to the Intro chapter * Updated Introduction chapter * Fixed incorrect args/kwargs prototype generated when using \_log\_call * Added Unit Tests for Profile Activation class * Added Unit Tests for Partition class * Fixed flake8 problems * Added Unit Tests for Lpar class * Sorted the operations in TimeStats display * Fixed missing parameters for BaseResource.get\_property() * Restructured and improved the documentation * Added image for unit test coverage * Improved documentation for logging * Added support for Coveralls * Updated Unit Tests because of removal of DPM checking * Remove internal DPM check * Added more status images to README.rst * Updated several fields in setup.cfg * Removed trailing spaces * Update build handling in Makefile * Addressed PyLint issues and added uri property to BaseResource class 0.2.0 ----- * Improved description of supported environments * Disabled Travis notification emails * Improved description of git sign-off line in CONTRIBUTING.rst * Fixed all flake8 issues and enforced issue-free flake8 in make check * Fixed links to Jupyter otebooks in tutorial section * Added links to RTD documentation to README.rst * Enabled documentation for activation profiles and fixed minor markup issue * Embedding Status Images in README.md * Fixed github links in documentation * Fixed vagrant setup typo * Added Travis CI control file * Added unit tests for query\_job\_status and delete\_completed\_job\_status * Updated README file to fix link to CONTRIBUTING.rst * Updated README file to explain how to start using it, and how to contribute * Modified example4 to call delete\_completed\_job\_status() * Issue #14 Delete completed job * Updated example for activation profiles * Added update writable properties method to activation profile class * Adapt resource unit test due to activation profile changes * Updated documentation for activation profile changes * Fixed issue #18 * Removed statement about AUTHORS file in contribution description (incorrect and not needed) * Added example for Activation Profile handling * Added Activation Profiles handling * Improvements in build environment (.mailmap, AUTHORS/ChangeLog, MANIFEST\*, make clean) * Documented optional identifier in summary line * Added unittest for client class * Fixed two underscores in \_api\_\_version * Issue #11: Provide query\_api\_version() which provides the results of GET /api/version * Maintain order in HTTP response body when returned as json object * Improved logging module and its use * Issue #12: Add license of the library as comment in requirements.txt and dev-requirements.txt * Improved description of asynchronous operations for CPC start and stop * Moved the setting of data into the branch where it is used * Fixes and implementations for CRUD ops in Partitions * Fixes #12: example4.py: NameError: name 'logging' is not defined * Fixed and improved description of asynchronous operations to match current impl * Updated contribution rules with comments by Viktor 0.1.0 ----- * fix the unit tests for the session object * Adds an example how we could log the entry/exit of HMC calls * Updated documentation for operations in asynchronous mode and example4 * Update example4.py to se yaml file for parameterization * Support for asynchronous handling (non-blocking) of operations (jobs) * Resolved merge conflicts in exampl2.py * Added document describing contributions, and DCO rules * Allow for a local 'try' directory by adding it to .gitignore * Addressed comments by Markus Zoeller and fixed exception handling in post job get * Fixed timestats and added unit tests * Added time statistics for measuring HMC response times * Session improvements (connection pooling, testcases, PyLint) * Resolved flake8 and PyLint issues and Python 3 test failures for \`\_exceptions\` module * Fixed flake8 issues and PyLint issues for \`\_cpc\` module * Revert "Adds an example how we could log the entry/exit of HMC calls" * Reduced PyLint output to just the messages (no reports) * Adds an example how we could log the entry/exit of HMC calls * Removed \`PYTHONPATH=.\` from commands in the Makefile * Added prints and logging to the examples * Added mocked testcases for \`\_cpc\` module * add regression test for issue 7 * fix the need to add PYTHONPATH=. for unit test execution * Reworked unit test cases for \_resource module * Fixes and improvements in README.rst * updated README.rst to reflect tox changes * Improvements for running \`tox\` * Solved merge conflicts * Update example4.py to se yaml file for parameterization * Support for asynchronous handling (non-blocking) of operations (jobs) * Use a tuble instead of a list * On success of logoff operation, HTTP status code 204 (No Content) is returned with no response body * Improvements and unit tests for exceptions * Support for asynchronous handling (non-blocking) of operations (jobs) * examples: Use yaml file for example parameterization * Implemented partition create() * Documented the possible exceptions * Made body argument of post() optional, with None = empty body being the default * Updated examples * Added definition of the three CPC modes * Minor improvements in Partition, Lpar and CPC * Updates for Merge Request #18 * Resolved merge conflicts * Merged review comment changes * Added \`\_partition\` module to \`\_\_init\_\_.py\` and to documentation * First release for DPM support * Solved merge conflicts * Merged review comment changes * Added \`\_partition\` module to \`\_\_init\_\_.py\` and to documentation * First release for DPM support * Apply review findings for branch cpc info vs cpc full properties * Updated README.rst to better describe goal and current state of the project * Added \`\_partition\` module to \`\_\_init\_\_.py\` and to documentation * First release for DPM support * Added means to get the full number properties for resource objects like CPC and LPAR * Added document for full\_properties bool parameter * Added example3.py for showing usage of full properties option in cpc.list() * Added full\_properties option to cpc.list() * Removed hashbang from python modules * Removed merge conflicts * Simplified handling of temp files in Makefile * Fixes for Flake8 usage: Removed flake8.log file in \`make clobber\`; Downgraded package dependency from flake8>=3.0 to flake8>=2.0 * Addressed issues raised by Flake8; Adjusted line length of PyLint to 79 for Flake8 consistency * Added copyright header to examples, rst files and tests files * Added support for checking with flake8 * Cleaned up to resolve Pylint issues * Added license information * Removed pyyaml dependency from requirements.txt again, because it is in dev-requirements.txt * Minor fixes in documentation * Changed representation of resource properties such that the resource object has a new member 'properties' that is a dictionary with the properties * Changed representation of resource properties such that the resource object is a dictionary with the properties * Simplified commands in tox.ini file * Changed output format of coverage checks to be HTML * Improved class descriptions of base and derived resource classes * Improved documentation (Intro, CPCs, LPARs) * Added BaseResource and moved common functions and properties into base classes, for both resource and manager classes * Redesigned a little bit to move version\_info() away from Session, plus some more * Added credentials yaml file to examples. Adjusted lpar module to client redesign * Improved and documented Session class and improved HTTPError exception class * Fixed URLs in README.rst and setup.cfg to use renamed repo name * Fixed URLs in README.rst and setup.cfg to use renamed repo name * Fixed examples by adjusting to the previous changes * Added simple error handling for JSON parsiung in HTTPError exception * Initial stab at defining exceptions for the client. Not yet ready * Added support for testing locally using 'tox' * Added Makefile with test, coverage, Sphinx docu, Pypi upload, etc * Initial changes for discussion of Andy's proposal * Added two examples to use the library * Removed print statement * Added status update method to lpar class * Added load operator to Lpar class * Implemnented activate and deactivate methods * Adde BaseManager class with common methods like find * Added CPC find method * Implemented lpar classes * Added first release for client, cpc, lpar, session and version * Initial packaging setup with setuptools and pbr * Added intial empty implementation * Added initial .gitignore file * Added Vagrant test environment * Added initial README file zhmcclient-0.22.0/dev-requirements.txt0000644000076500000240000001156013364325033020361 0ustar maierastaff00000000000000# Pip requirements file for development 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. # Runtime dependencies: -r requirements.txt # Direct dependencies: # zhmcclient examples (imports into the example scripts): PyYAML>=3.13 # MIT # Unit test (imports into testcases): pytest>=3.0.5 # MIT mock>=2.0.0 # BSD requests-mock>=1.2.0 # Apache-2.0 testfixtures>=4.13.3 # Apache-2.0 # Unit test (no imports, invoked via py.test script): # TODO: Remove the pinning of the pytest-cov version again once issue # https://github.com/z4r/python-coveralls/issues/66 # is resolved. # Background: pytest-cov 2.6.0 has increased the version # requirement for the coverage package from >=3.7.1 to # >=4.4, which is in conflict with the version requirement # defined by the python-coveralls package for coverage==4.0.3. pytest-cov>=2.4.0,<2.6 # BSD # Coverage reporting (no imports, invoked via coveralls script): python-coveralls>=2.9.0 # Apache-2.0 # Sphinx (no imports, invoked via sphinx-build script): Sphinx>=1.7.6 # BSD sphinx-git>=10.1.1 # GPL GitPython>=2.1.1 # BSD # PyLint (no imports, invoked via pylint script): pylint>=1.6.4; python_version == '2.7' # Flake8 (no imports, invoked via flake8 script): flake8>=3.2.1 # MIT # Twine (no imports, invoked via twine script): twine>=1.8.1 # Apache-2.0 # Jupyter Notebook (no imports, invoked via jupyter script): jupyter>=1.0.0 # BSD # Indirect dependencies (normally commented out, only listed to document their license): # alabaster # BSD, from Sphinx # appnope # BSD, from ipython for darwin -> ipywidgets -> jupyter # args # BSD, from clint -> twine<1.9.1 # astroid # LGPL, from pylint for py=2.7 # Babel # BSD, from Sphinx # backports-abc # PSFL, from tornado -> notebook -> jupyter # backports.functools-lru-cache # MIT, from pylint # backports.shutil-get-terminal-size # MIT, from ipython -> ipywidgets -> jupyter # backports.ssl-match-hostname # PSFL, from tornado -> notebook -> jupyter # bleach # Apache, from nbconvert -> jupyter # clint # ISCL, from twine<1.9.1 # configparser # MIT, from pylint and from flake8 for py<3.2 # coverage # Apache-2.0, from pytest-cov # docutils # public domain | Python | 2-Clause BSD | GPL 3, from Sphinx # entrypoints # MIT, from nbconvert -> jupyter # enum34 # BSD, from astroid # funcsigs # Apache, from mock for py<3.3 # functools32 # PSFL, from jsonschema for py=2.7 -> nbformat -> ipywidgets -> jupyter # gitdb2 # BSD, from GitPython # html5lib # MIT, from bleach -> nbconvert -> jupyter # imagesize # MIT, from Sphinx # ipykernel # BSD, from jupyter # ipython # BSD, from ipywidgets -> jupyter # ipython_genutils # BSD, from notebook -> jupyter # ipywidgets # BSD, from jupyter # isort # MIT, from pylint # Jinja2 # BSD, from Sphinx # jsonschema # MIT, from nbformat -> ipywidgets -> jupyter # jupyter_client # BSD, from notebook -> jupyter # jupyter_console # BSD, from jupyter # jupyter_core # BSD, from notebook -> jupyter # lazy-object-proxy # BSD, from astroid # MarkupSafe # BSD, from Jinja2 -> Sphinx # mccabe # MIT, from pylint and from flake8 # mistune # BSD, from nbconvert -> jupyter # nbconvert # BSD, from jupyter # nbformat # BSD, from ipywidgets -> jupyter # notebook # BSD, from jupyter # pandocfilters # BSD, from nbconvert -> jupyter # pathlib2 # MIT, from ipython -> ipywidgets -> jupyter # pexpect # ISCL, ipython -> ipywidgets -> jupyter # pickleshare # MIT, from ipython -> ipywidgets -> jupyter # pkginfo # MIT, from twine # ptyprocess # ISCL, from terminado -> notebook -> jupyter # py # MIT, from pytest # pycodestyle # MIT, from flake8 # pyflakes # MIT, from flake8 # Pygments # BSD, from Sphinx # python-dateutil # BSD, from jupyter-client -> notebook -> jupyter # pyzmq # LGPL + BSD, from jupyter-client -> notebook -> jupyter # qtconsole # BSD, from jupyter # requests-toolbelt # Apache 2.0, from twine # scandir # New BSD, from pathlib2 for py<3.5 -> python_version -> ipython -> ipywidgets -> jupyter # simplegeneric # ZPL 2.1, from ipython -> ipywidgets -> jupyter # singledispatch # MIT, from pylint # smmap2 # BSD, from gitdb2 -> GitPython # snowballstemmer # BSD, from Sphinx # sphinxcontrib-websupport # BSD, from Sphinx>=1.6.1 # terminado # BSD, from notebook for non-Windows -> jupyter # testpath # MIT, from nbconvert -> jupyter # tornado # Apache 2.0, from notebook -> jupyter # tqdm # MPL 2.0, MIT, from twine>=1.9.1 # traitlets # BSD, from ipywidgets -> jupyter # typing # PSFL, from Sphinx>=1.6.1 for py<3.5 # webencodings # BSD, from html5lib -> bleach -> nbconvert -> jupyter # widgetsnbextension # BSD, from ipywidgets -> jupyter # wrapt # BSD, from astroid zhmcclient-0.22.0/AUTHORS0000644000076500000240000000101013414661055015361 0ustar maierastaff00000000000000ANDREW C. BREZOVSKY ANDREW C. BREZOVSKY Andreas Maier Andreas Maier Andreas Maier Andreas Scheuring Andrew Brezovsky Juergen Leopold Marc Hartmayer Markus Zoeller Viktor Mihajlovski leopoldjuergen sree zhmcclient-0.22.0/Makefile0000644000076500000240000003402213367665262015775 0ustar maierastaff00000000000000# ------------------------------------------------------------------------------ # Makefile for zhmcclient project # # Basic prerequisites for running this Makefile, to be provided manually: # One of these OS platforms: # Windows with CygWin # Linux (any) # OS-X # These commands on all OS platforms: # make (GNU make) # bash # rm, mv, find, tee, which # These commands on all OS platforms in the active Python environment: # python (or python3 on OS-X) # twine # These commands on Linux and OS-X: # uname # Environment variables: # PYTHON_CMD: Python command to use (OS-X needs to distinguish Python 2/3) # PIP_CMD: Pip command to use (OS-X needs to distinguish Python 2/3) # PACKAGE_LEVEL: minimum/latest - Level of Python dependent packages to use # Additional prerequisites for running this Makefile are installed by running: # make develop # ------------------------------------------------------------------------------ # Python / Pip commands ifndef PYTHON_CMD PYTHON_CMD := python endif ifndef PIP_CMD PIP_CMD := pip endif # Package level ifndef PACKAGE_LEVEL PACKAGE_LEVEL := latest endif ifeq ($(PACKAGE_LEVEL),minimum) pip_level_opts := -c minimum-constraints.txt pip_level_opts_new := else ifeq ($(PACKAGE_LEVEL),latest) pip_level_opts := --upgrade pip_level_opts_new := --upgrade-strategy eager else $(error Error: Invalid value for PACKAGE_LEVEL variable: $(PACKAGE_LEVEL)) endif endif # Determine OS platform make runs on ifeq ($(OS),Windows_NT) PLATFORM := Windows else # Values: Linux, Darwin PLATFORM := $(shell uname -s) endif # Name of this Python package (top-level Python namespace + Pypi package name) package_name := zhmcclient mock_package_name := zhmcclient_mock # Package version (full version, including any pre-release suffixes, e.g. "0.1.0-alpha1") # May end up being empty, if pbr cannot determine the version. package_version := $(shell $(PYTHON_CMD) -c "$$(printf 'try:\n from pbr.version import VersionInfo\nexcept ImportError:\n pass\nelse:\n print(VersionInfo(\042$(package_name)\042).release_string())\n')") # Python major version python_major_version := $(shell $(PYTHON_CMD) -c "import sys; sys.stdout.write('%s'%sys.version_info[0])") # Python major+minor version for use in file names python_version_fn := $(shell $(PYTHON_CMD) -c "import sys; sys.stdout.write('%s%s'%(sys.version_info[0],sys.version_info[1]))") # Directory for the generated distribution files dist_dir := dist # Distribution archives (as built by setup.py) bdist_file := $(dist_dir)/$(package_name)-$(package_version)-py2.py3-none-any.whl sdist_file := $(dist_dir)/$(package_name)-$(package_version).tar.gz # Windows installable (as built by setup.py) win64_dist_file := $(dist_dir)/$(package_name)-$(package_version).win-amd64.exe # dist_files := $(bdist_file) $(sdist_file) $(win64_dist_file) dist_files := $(bdist_file) $(sdist_file) # Source files in the packages package_py_files := \ $(wildcard $(package_name)/*.py) \ $(wildcard $(package_name)/*/*.py) \ $(wildcard $(mock_package_name)/*.py) \ $(wildcard $(mock_package_name)/*/*.py) \ # Directory for generated API documentation doc_build_dir := build_doc # Directory where Sphinx conf.py is located doc_conf_dir := docs # Documentation generator command doc_cmd := sphinx-build doc_opts := -v -d $(doc_build_dir)/doctrees -c $(doc_conf_dir) . # Dependents for Sphinx documentation build doc_dependent_files := \ $(doc_conf_dir)/conf.py \ $(wildcard $(doc_conf_dir)/*.rst) \ $(wildcard $(doc_conf_dir)/notebooks/*.ipynb) \ $(package_py_files) \ # Directory with test source files test_dir := tests # Test log test_unit_log_file := test_unit_$(python_version_fn).log test_end2end_log_file := test_end2end_$(python_version_fn).log # Source files with test code test_unit_py_files := \ $(wildcard $(test_dir)/unit/*.py) \ $(wildcard $(test_dir)/unit/*/*.py) \ $(wildcard $(test_dir)/unit/*/*/*.py) \ test_end2end_py_files := \ $(wildcard $(test_dir)/end2end/*.py) \ $(wildcard $(test_dir)/end2end/*/*.py) \ $(wildcard $(test_dir)/end2end/*/*/*.py) \ test_common_py_files := \ $(wildcard $(test_dir)/common/*.py) \ $(wildcard $(test_dir)/common/*/*.py) \ $(wildcard $(test_dir)/common/*/*/*.py) \ # Determine whether py.test has the --no-print-logs option. pytest_no_log_opt := $(shell py.test --help 2>/dev/null |grep '\--no-print-logs' >/dev/null; if [ $$? -eq 0 ]; then echo '--no-print-logs'; else echo ''; fi) # Flake8 config file flake8_rc_file := setup.cfg # PyLint config file pylint_rc_file := .pylintrc # Source files for check (with PyLint and Flake8) check_py_files := \ setup.py \ $(package_py_files) \ $(test_unit_py_files) \ $(test_end2end_py_files) \ $(test_common_py_files) \ $(doc_conf_dir)/conf.py \ $(wildcard docs/notebooks/*.py) \ $(wildcard tools/cpcinfo) \ $(wildcard tools/cpcdata) \ ifdef TESTCASES pytest_opts := -k $(TESTCASES) else pytest_opts := endif # Files to be built ifeq ($(PLATFORM),Windows) build_files := $(win64_dist_file) else build_files := $(bdist_file) $(sdist_file) endif # Files the distribution archive depends upon. dist_dependent_files := \ setup.py setup.cfg \ README.rst \ requirements.txt \ $(wildcard *.py) \ $(package_py_files) \ # No built-in rules needed: .SUFFIXES: .PHONY: help help: @echo 'Makefile for $(package_name) project' @echo 'Package version will be: $(package_version)' @echo 'Uses the currently active Python environment: Python $(python_version_fn)' @echo 'Valid targets are (they do just what is stated, i.e. no automatic prereq targets):' @echo ' install - Install package in active Python environment' @echo ' develop - Prepare the development environment by installing prerequisites' @echo ' check - Run Flake8 on sources and save results in: flake8.log' @echo ' pylint - Run PyLint on sources and save results in: pylint.log' @echo ' test - Run unit tests (and test coverage) and save results in: $(test_unit_log_file)' @echo ' Does not include install but depends on it, so make sure install is current.' @echo ' Env.var TESTCASES can be used to specify a py.test expression for its -k option' @echo ' end2end - Run end2end tests and save results in: $(test_end2end_log_file)' @echo ' Env.var TESTCPC can be used to specify the name of a real CPC (default: mocked CPC)' @echo ' Env.var TESTCASES can be used to specify a py.test expression for its -k option' @echo ' build - Build the distribution files in: $(dist_dir)' @echo ' On Windows, builds: $(win64_dist_file)' @echo ' On Linux + OSX, builds: $(bdist_file) $(sdist_file)' @echo ' builddoc - Build documentation in: $(doc_build_dir)' @echo ' all - Do all of the above' @echo ' uninstall - Uninstall package from active Python environment' @echo ' upload - Upload the distribution files to PyPI (includes uninstall+build)' @echo ' clean - Remove any temporary files' @echo ' clobber - Remove any build products (includes uninstall+clean)' @echo ' pyshow - Show location and version of the python and pip commands' @echo 'Environment variables:' @echo ' PACKAGE_LEVEL="minimum" - Install minimum version of dependent Python packages' @echo ' PACKAGE_LEVEL="latest" - Default: Install latest version of dependent Python packages' @echo ' PYTHON_CMD=... - Name of python command. Default: python' @echo ' PIP_CMD=... - Name of pip command. Default: pip' .PHONY: _check_version _check_version: ifeq (,$(package_version)) @echo 'Error: Package version could not be determine: (requires pbr; run "make develop")' @false else @true endif .PHONY: _pip _pip: $(PYTHON_CMD) remove_duplicate_setuptools.py @echo 'Installing/upgrading pip, setuptools, wheel and pbr with PACKAGE_LEVEL=$(PACKAGE_LEVEL)' $(PYTHON_CMD) -m pip install $(pip_level_opts) pip setuptools wheel pbr .PHONY: develop develop: _pip dev-requirements.txt requirements.txt @echo 'Installing runtime and development requirements with PACKAGE_LEVEL=$(PACKAGE_LEVEL)' $(PIP_CMD) install $(pip_level_opts) $(pip_level_opts_new) -r dev-requirements.txt @echo '$@ done.' .PHONY: build build: $(build_files) @echo '$@ done.' .PHONY: builddoc builddoc: html @echo '$@ done.' .PHONY: html html: $(doc_build_dir)/html/docs/index.html @echo '$@ done.' $(doc_build_dir)/html/docs/index.html: Makefile $(doc_dependent_files) rm -fv $@ $(doc_cmd) -b html $(doc_opts) $(doc_build_dir)/html @echo "Done: Created the HTML pages with top level file: $@" .PHONY: pdf pdf: Makefile $(doc_dependent_files) rm -fv $@ $(doc_cmd) -b latex $(doc_opts) $(doc_build_dir)/pdf @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(doc_build_dir)/pdf all-pdf @echo "Done: Created the PDF files in: $(doc_build_dir)/pdf/" @echo '$@ done.' .PHONY: man man: Makefile $(doc_dependent_files) rm -fv $@ $(doc_cmd) -b man $(doc_opts) $(doc_build_dir)/man @echo "Done: Created the manual pages in: $(doc_build_dir)/man/" @echo '$@ done.' .PHONY: docchanges docchanges: $(doc_cmd) -b changes $(doc_opts) $(doc_build_dir)/changes @echo @echo "Done: Created the doc changes overview file in: $(doc_build_dir)/changes/" @echo '$@ done.' .PHONY: doclinkcheck doclinkcheck: $(doc_cmd) -b linkcheck $(doc_opts) $(doc_build_dir)/linkcheck @echo @echo "Done: Look for any errors in the above output or in: $(doc_build_dir)/linkcheck/output.txt" @echo '$@ done.' .PHONY: doccoverage doccoverage: $(doc_cmd) -b coverage $(doc_opts) $(doc_build_dir)/coverage @echo "Done: Created the doc coverage results in: $(doc_build_dir)/coverage/python.txt" @echo '$@ done.' .PHONY: pyshow pyshow: which $(PYTHON_CMD) $(PYTHON_CMD) --version which $(PIP_CMD) $(PIP_CMD) --version @echo '$@ done.' .PHONY: check check: flake8.log @echo '$@ done.' .PHONY: pylint pylint: pylint.log @echo '$@ done.' .PHONY: install install: _pip requirements.txt setup.py setup.cfg $(package_py_files) @echo 'Installing $(package_name) (editable) with PACKAGE_LEVEL=$(PACKAGE_LEVEL)' $(PIP_CMD) install $(pip_level_opts) $(pip_level_opts_new) -r requirements.txt $(PIP_CMD) install -e . $(PYTHON_CMD) -c "import $(package_name); print('ok, version=%r'%$(package_name).__version__)" $(PYTHON_CMD) -c "import $(mock_package_name); print('ok')" @echo 'Done: Installed $(package_name)' @echo '$@ done.' .PHONY: uninstall uninstall: bash -c '$(PIP_CMD) show $(package_name) >/dev/null; if [ $$? -eq 0 ]; then $(PIP_CMD) uninstall -y $(package_name); fi' @echo '$@ done.' .PHONY: test test: $(test_unit_log_file) @echo '$@ done.' .PHONY: clobber clobber: uninstall clean rm -Rf $(doc_build_dir) htmlcov .tox rm -f pylint.log flake8.log test_*.log $(bdist_file) $(sdist_file) $(win64_dist_file) @echo 'Done: Removed all build products to get to a fresh state.' @echo '$@ done.' .PHONY: clean clean: rm -Rf build .cache $(package_name).egg-info .eggs rm -f MANIFEST MANIFEST.in AUTHORS ChangeLog .coverage find . -name "*.pyc" -delete -o -name "__pycache__" -delete -o -name "*.tmp" -delete -o -name "tmp_*" -delete @echo 'Done: Cleaned out all temporary files.' @echo '$@ done.' .PHONY: all all: develop install check pylint test build builddoc @echo '$@ done.' .PHONY: upload upload: _check_version uninstall $(dist_files) ifeq (,$(findstring .dev,$(package_version))) @echo '==> This will upload $(package_name) version $(package_version) to PyPI!' @echo -n '==> Continue? [yN] ' @bash -c 'read answer; if [ "$$answer" != "y" ]; then echo "Aborted."; false; fi' twine upload $(dist_files) @echo 'Done: Uploaded $(package_name) version to PyPI: $(package_version)' @echo '$@ done.' else @echo 'Error: A development version $(package_version) of $(package_name) cannot be uploaded to PyPI!' @false endif # Distribution archives. $(bdist_file): _check_version Makefile $(dist_dependent_files) ifneq ($(PLATFORM),Windows) rm -Rfv $(package_name).egg-info .eggs build $(PYTHON_CMD) setup.py bdist_wheel -d $(dist_dir) --universal @echo 'Done: Created binary distribution archive: $@' else @echo 'Error: Creating binary distribution archive requires to run on Linux or OSX' @false endif $(sdist_file): _check_version Makefile $(dist_dependent_files) ifneq ($(PLATFORM),Windows) rm -Rfv $(package_name).egg-info .eggs build $(PYTHON_CMD) setup.py sdist -d $(dist_dir) @echo 'Done: Created source distribution archive: $@' else @echo 'Error: Creating source distribution archive requires to run on Linux or OSX' @false endif $(win64_dist_file): _check_version Makefile $(dist_dependent_files) ifeq ($(PLATFORM),Windows) rm -Rfv $(package_name).egg-info .eggs build $(PYTHON_CMD) setup.py bdist_wininst -d $(dist_dir) -o -t "$(package_name) v$(package_version)" @echo 'Done: Created Windows installable: $@' else @echo 'Error: Creating Windows installable requires to run on Windows' @false endif # TODO: Once PyLint has no more errors, remove the dash "-" pylint.log: Makefile $(pylint_rc_file) $(check_py_files) ifeq ($(python_major_version), 2) rm -fv $@ -bash -c 'set -o pipefail; pylint --rcfile=$(pylint_rc_file) --output-format=text $(check_py_files) 2>&1 |tee $@.tmp' mv -f $@.tmp $@ @echo 'Done: Created PyLint log file: $@' else @echo 'Info: PyLint requires Python 2; skipping this step on Python $(python_major_version)' endif flake8.log: Makefile $(flake8_rc_file) $(check_py_files) rm -fv $@ bash -c 'set -o pipefail; flake8 $(check_py_files) 2>&1 |tee $@.tmp' mv -f $@.tmp $@ @echo 'Done: Created Flake8 log file: $@' $(test_unit_log_file): Makefile $(package_py_files) $(test_unit_py_files) $(test_common_py_files) .coveragerc rm -fv $@ bash -c 'set -o pipefail; PYTHONWARNINGS=default py.test --color=yes $(pytest_no_log_opt) -s $(test_dir)/unit --cov $(package_name) --cov $(mock_package_name) --cov-config .coveragerc --cov-report=html $(pytest_opts) 2>&1 |tee $@.tmp' mv -f $@.tmp $@ @echo 'Done: Created unit test log file: $@' .PHONY: end2end end2end: Makefile $(package_py_files) $(test_end2end_py_files) $(test_common_py_files) py.test $(pytest_no_log_opt) -s $(test_dir)/end2end $(pytest_opts) @echo '$@ done.' zhmcclient-0.22.0/zhmcclient.egg-info/0000755000076500000240000000000013414661056020154 5ustar maierastaff00000000000000zhmcclient-0.22.0/zhmcclient.egg-info/PKG-INFO0000644000076500000240000002162413414661055021255 0ustar maierastaff00000000000000Metadata-Version: 1.2 Name: zhmcclient Version: 0.22.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: Juergen Leopold, Andreas Maier Maintainer-email: leopoldj@de.ibm.com, maiera@de.ibm.com License: Apache License, Version 2.0 Description: .. 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://travis-ci.org/zhmcclient/python-zhmcclient.svg?branch=master :target: https://travis-ci.org/zhmcclient/python-zhmcclient :alt: Travis test status (master) .. image:: https://ci.appveyor.com/api/projects/status/i022iaeu3dao8j5x/branch/master?svg=true :target: https://ci.appveyor.com/project/leopoldjuergen/python-zhmcclient :alt: Appveyor test status (master) .. image:: https://readthedocs.org/projects/python-zhmcclient/badge/?version=latest :target: http://python-zhmcclient.readthedocs.io/en/latest/ :alt: Docs build status (latest) .. image:: https://img.shields.io/coveralls/zhmcclient/python-zhmcclient.svg :target: https://coveralls.io/r/zhmcclient/python-zhmcclient :alt: Test coverage (master) .. image:: https://codeclimate.com/github/zhmcclient/python-zhmcclient/badges/gpa.svg :target: https://codeclimate.com/github/zhmcclient/python-zhmcclient :alt: Code Climate .. 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/stable/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 ============= The zhmcclient documentation is on RTD: * `Documentation for latest version on Pypi`_ * `Documentation for master branch in Git repo`_ .. _Documentation for latest version on Pypi: http://python-zhmcclient.readthedocs.io/en/stable/ .. _Documentation for master branch in Git repo: http://python-zhmcclient.readthedocs.io/en/latest/ 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/stable/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 Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha 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 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 zhmcclient-0.22.0/zhmcclient.egg-info/not-zip-safe0000644000076500000240000000000113414661055022401 0ustar maierastaff00000000000000 zhmcclient-0.22.0/zhmcclient.egg-info/SOURCES.txt0000644000076500000240000001034213414661056022040 0ustar maierastaff00000000000000.codeclimate.yml .coveragerc .mailmap .pylintrc .travis.yml AUTHORS CODE_OF_CONDUCT.md ChangeLog DCO1.1.txt LICENSE Makefile README.rst Vagrantfile appveyor.yml dev-requirements.txt minimum-constraints.txt provision.sh remove_duplicate_setuptools.py requirements.txt setup.cfg setup.py tox.ini .github/ISSUE_TEMPLATE.md design/dpm-storage-model.rst design/fake-client.rst design/network-errors.rst docs/appendix.rst docs/changes.rst docs/concepts.rst docs/conf.py docs/development.rst docs/general.rst docs/index.rst docs/intro.rst docs/mocksupport.rst docs/notifications.rst docs/resources.rst docs/tutorial.rst docs/_extra/.dummy docs/_static/basic.css docs/_static/classic.css docs/notebooks/01_notebook_basics.ipynb docs/notebooks/02_connections.ipynb docs/notebooks/03_datamodel.ipynb docs/notebooks/04_error_handling.ipynb docs/notebooks/tututils.py examples/activation_profiles.py examples/adapter_port_vswitch.py examples/api_version.py examples/async_operation_polling.py examples/example_hmccreds.yaml examples/get_inventory.py examples/get_partial_and_full_properties.py examples/jms_notifications.py examples/list_cpc_and_lpar.py examples/list_free_crypto_domains.py examples/list_storage_groups.py examples/lpar_operations.py examples/metrics.py examples/mount_iso.py examples/partition_lifecycle.py examples/show_os_messages.py tests/__init__.py tests/common/__init__.py tests/common/utils.py tests/end2end/__init__.py tests/end2end/test_activation_profiles.py tests/end2end/test_hmc_credentials_file.py tests/end2end/test_partition_lifecycle.py tests/end2end/test_storage_group.py tests/unit/__init__.py tests/unit/test_example.py tests/unit/tests/__init__.py tests/unit/tests/common/__init__.py tests/unit/tests/common/test_utils.py tests/unit/zhmcclient/__init__.py tests/unit/zhmcclient/test_activation_profile.py tests/unit/zhmcclient/test_adapter.py tests/unit/zhmcclient/test_client.py tests/unit/zhmcclient/test_console.py tests/unit/zhmcclient/test_cpc.py tests/unit/zhmcclient/test_exceptions.py tests/unit/zhmcclient/test_hba.py tests/unit/zhmcclient/test_ldap_server_definition.py tests/unit/zhmcclient/test_logging.py tests/unit/zhmcclient/test_lpar.py tests/unit/zhmcclient/test_manager.py tests/unit/zhmcclient/test_metrics.py tests/unit/zhmcclient/test_nic.py tests/unit/zhmcclient/test_notification.py tests/unit/zhmcclient/test_partition.py tests/unit/zhmcclient/test_password_rule.py tests/unit/zhmcclient/test_port.py tests/unit/zhmcclient/test_resource.py tests/unit/zhmcclient/test_session.py tests/unit/zhmcclient/test_storage_group.py tests/unit/zhmcclient/test_task.py tests/unit/zhmcclient/test_timestats.py tests/unit/zhmcclient/test_unmanaged_cpc.py tests/unit/zhmcclient/test_user.py tests/unit/zhmcclient/test_user_pattern.py tests/unit/zhmcclient/test_user_role.py tests/unit/zhmcclient/test_utils.py tests/unit/zhmcclient/test_virtual_function.py tests/unit/zhmcclient/test_virtual_switch.py tests/unit/zhmcclient_mock/test_hmc.py tests/unit/zhmcclient_mock/test_idpool.py tests/unit/zhmcclient_mock/test_urihandler.py tools/cpcdata tools/cpcinfo zhmcclient/__init__.py zhmcclient/_activation_profile.py zhmcclient/_adapter.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_volume.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.egg-info/PKG-INFO zhmcclient.egg-info/SOURCES.txt zhmcclient.egg-info/dependency_links.txt zhmcclient.egg-info/not-zip-safe zhmcclient.egg-info/pbr.json zhmcclient.egg-info/requires.txt zhmcclient.egg-info/top_level.txt zhmcclient_mock/__init__.py zhmcclient_mock/_hmc.py zhmcclient_mock/_idpool.py zhmcclient_mock/_session.py zhmcclient_mock/_urihandler.pyzhmcclient-0.22.0/zhmcclient.egg-info/pbr.json0000644000076500000240000000005713414661055021633 0ustar maierastaff00000000000000{"git_version": "69a61f0", "is_release": false}zhmcclient-0.22.0/zhmcclient.egg-info/requires.txt0000644000076500000240000000013213414661055022547 0ustar maierastaff00000000000000decorator>=4.0.10 pbr>=1.10.0 pytz>=2016.10 requests>=2.20.0 six>=1.10.0 stomp.py>=4.1.15 zhmcclient-0.22.0/zhmcclient.egg-info/top_level.txt0000644000076500000240000000003313414661055022701 0ustar maierastaff00000000000000zhmcclient zhmcclient_mock zhmcclient-0.22.0/zhmcclient.egg-info/dependency_links.txt0000644000076500000240000000000113414661055024221 0ustar maierastaff00000000000000 zhmcclient-0.22.0/zhmcclient_mock/0000755000076500000240000000000013414661056017473 5ustar maierastaff00000000000000zhmcclient-0.22.0/zhmcclient_mock/_hmc.py0000644000076500000240000033514413364325033020761 0ustar maierastaff00000000000000# 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 six import re import copy from ._idpool import IdPool from zhmcclient._utils import repr_dict, repr_manager, repr_list, \ timestamp_from_datetime __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', ] 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): 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): 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): 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.enable() 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): """ 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): """ 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): """ 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): """ 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): """ 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): """ 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): """ 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): """ 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): """ 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): """ 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._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" " _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), _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 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): """ 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): """ 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: raise InputError("The virtual switch specified in the " "'virtual-switch-uri' property does not " "exist: {!r}".format(vswitch_uri)) 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): """ 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): """ 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): """ 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): """ 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 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): """ 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 n, 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): """ 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): """ 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 zhmcclient-0.22.0/zhmcclient_mock/__init__.py0000644000076500000240000000157513364325033021610 0ustar maierastaff00000000000000# 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 from ._urihandler import * # noqa: F401 from ._hmc import * # noqa: F401 from ._idpool import * # noqa: F401 zhmcclient-0.22.0/zhmcclient_mock/_session.py0000644000076500000240000002716413364325033021675 0ustar maierastaff00000000000000# 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, ConnectionError, URIS __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: raise zhmcclient.HTTPError(exc.response()) except ConnectionError as exc: raise zhmcclient.ConnectionError(exc.message, None) 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: raise zhmcclient.HTTPError(exc.response()) except ConnectionError as exc: raise zhmcclient.ConnectionError(exc.message, None) 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: raise zhmcclient.HTTPError(exc.response()) except ConnectionError as exc: raise zhmcclient.ConnectionError(exc.message, None) zhmcclient-0.22.0/zhmcclient_mock/_urihandler.py0000644000076500000240000032415113364325033022343 0ustar maierastaff00000000000000# 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 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): def __init__(self, method, uri, http_status, reason, message): self.method = method self.uri = uri self.http_status = http_status self.reason = reason self.message = message def response(self): return { 'request-method': self.method, 'request-uri': self.uri, 'http-status': self.http_status, 'reason': self.reason, 'message': self.message, } class ConnectionError(Exception): def __init__(self, message): self.message = message class InvalidResourceError(HTTPError): 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): 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): def __init__(self, method, uri, reason, message): super(BadRequestError, self).__init__( method, uri, http_status=400, reason=reason, message=message) class ConflictError(HTTPError): 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): 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)) else: # 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)) else: # 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): 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 raise InvalidResourceError(method, uri) def get(self, hmc, uri, logon_required): 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): 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): 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): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """Operation: Get Properties.""" try: resource = hmc.lookup_by_uri(uri) except KeyError: raise InvalidResourceError(method, uri) return resource.properties class GenericUpdatePropertiesHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """Operation: Update Properties.""" assert wait_for_completion is True # async not supported yet try: resource = hmc.lookup_by_uri(uri) except KeyError: raise InvalidResourceError(method, uri) resource.update(body) class GenericDeleteHandler(object): @staticmethod def delete(method, hmc, uri, uri_parms, logon_required): """Operation: Delete .""" try: resource = hmc.lookup_by_uri(uri) except KeyError: raise InvalidResourceError(method, uri) resource.manager.remove(resource.oid) class VersionHandler(object): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): 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): pass class ConsoleRestartHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """Operation: Restart Console.""" assert wait_for_completion is True # synchronous operation console_uri = '/api/console' try: hmc.lookup_by_uri(console_uri) except KeyError: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """Operation: Shutdown Console.""" assert wait_for_completion is True # synchronous operation console_uri = '/api/console' try: hmc.lookup_by_uri(console_uri) except KeyError: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) # Nothing to do, as long as the faked HMC does not need to know whether # it is primary or alternate. class ConsoleReorderUserPatternsHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """Operation: Get Console Audit Log.""" assert wait_for_completion is True # synchronous operation console_uri = '/api/console' try: hmc.lookup_by_uri(console_uri) except KeyError: raise InvalidResourceError(method, uri) resp = [] # TODO: Add the ability to return audit log entries in mock support. return resp class ConsoleGetSecurityLogHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """Operation: Get Console Security Log.""" assert wait_for_completion is True # synchronous operation console_uri = '/api/console' try: hmc.lookup_by_uri(console_uri) except KeyError: raise InvalidResourceError(method, uri) resp = [] # TODO: Add the ability to return security log entries in mock support. return resp class ConsoleListUnmanagedCpcsHandler(object): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """Operation: List Unmanaged CPCs.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: raise InvalidResourceError(method, uri) 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): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """Operation: List Users.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: raise InvalidResourceError(method, uri) 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): """Operation: Create User.""" assert wait_for_completion is True # synchronous operation try: console = hmc.consoles.lookup_by_oid(None) except KeyError: raise InvalidResourceError(method, uri) 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): # TODO: Add post() for Update User that rejects name update @staticmethod def delete(method, hmc, uri, uri_parms, logon_required): """Operation: Delete User.""" try: user = hmc.lookup_by_uri(uri) except KeyError: raise InvalidResourceError(method, uri) # 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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: raise InvalidResourceError(method, user_role_uri, reason=2) if user.properties.get('user-roles', None) is None: user.properties['user-roles'] = [] user.properties['user-roles'].append(user_role_uri) class UserRemoveUserRoleHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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: raise InvalidResourceError(method, user_role_uri, reason=2) 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): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """Operation: List User Roles.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: raise InvalidResourceError(method, uri) 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): """Operation: Create User Role.""" assert wait_for_completion is True # synchronous operation try: console = hmc.consoles.lookup_by_oid(None) except KeyError: raise InvalidResourceError(method, uri) 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): 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """Operation: List Tasks.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: raise InvalidResourceError(method, uri) 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): pass class UserPatternsHandler(object): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """Operation: List User Patterns.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: raise InvalidResourceError(method, uri) 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): """Operation: Create User Pattern.""" assert wait_for_completion is True # synchronous operation try: console = hmc.consoles.lookup_by_oid(None) except KeyError: raise InvalidResourceError(method, uri) 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): pass class PasswordRulesHandler(object): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """Operation: List Password Rules.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: raise InvalidResourceError(method, uri) 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): """Operation: Create Password Rule.""" assert wait_for_completion is True # synchronous operation try: console = hmc.consoles.lookup_by_oid(None) except KeyError: raise InvalidResourceError(method, uri) 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): pass # TODO: Add post() for Update PasswordRule that rejects name update class LdapServerDefinitionsHandler(object): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """Operation: List LDAP Server Definitions.""" query_str = uri_parms[0] try: console = hmc.consoles.lookup_by_oid(None) except KeyError: raise InvalidResourceError(method, uri) 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): """Operation: Create LDAP Server Definition.""" assert wait_for_completion is True # synchronous operation try: console = hmc.consoles.lookup_by_oid(None) except KeyError: raise InvalidResourceError(method, uri) 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): pass # TODO: Add post() for Update LdapServerDefinition that rejects name update class CpcsHandler(object): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """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): pass class CpcSetPowerSaveHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) if not cpc.dpm_enabled: raise CpcNotInDpmError(method, uri, cpc) cpc.properties['status'] = 'active' class CpcStopHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) if not cpc.dpm_enabled: raise CpcNotInDpmError(method, uri, cpc) cpc.properties['status'] = 'not-operating' class CpcImportProfilesHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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 } class MetricsContextsHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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): @staticmethod def delete(method, hmc, uri, uri_parms, logon_required): """Operation: Delete Metrics Context.""" try: metrics_context = hmc.lookup_by_uri(uri) except KeyError: raise InvalidResourceError(method, uri) hmc.metrics_contexts.remove(metrics_context.oid) @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """Operation: Get Metrics.""" try: metrics_context = hmc.lookup_by_uri(uri) except KeyError: raise InvalidResourceError(method, uri) result = metrics_context.get_metric_values_response() return result class AdaptersHandler(object): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """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: raise InvalidResourceError(method, uri) 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): """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: raise InvalidResourceError(method, uri) 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: raise BadRequestError(method, uri, reason=5, message=str(exc)) return {'object-uri': new_adapter.uri} class AdapterHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): @staticmethod def delete(method, hmc, uri, uri_parms, logon_required): """Operation: Delete Hipersocket (requires DPM mode).""" try: adapter = hmc.lookup_by_uri(uri) except KeyError: raise InvalidResourceError(method, uri) cpc = adapter.manager.parent assert cpc.dpm_enabled adapter.manager.remove(adapter.oid) class AdapterChangeCryptoTypeHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): pass class StoragePortHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): pass class PartitionsHandler(object): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """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: raise InvalidResourceError(method, uri) # 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): """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: raise InvalidResourceError(method, uri) 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): # 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): """Operation: Delete Partition.""" try: partition = hmc.lookup_by_uri(uri) except KeyError: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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 PartitionPswRestartHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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: raise BadRequestError( method, uri, reason=1, message="Missing required URI query parameter 'image-name'") try: ins_file_name = query_parms['ins-file-name'] except KeyError: raise BadRequestError( method, uri, reason=1, message="Missing required URI query parameter 'ins-file-name'") # 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """Operation: Perform PSW Restart (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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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']) adapter_uris, 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 i, dc in enumerate(domain_configs): if dc['domain-index'] == change_domain_index: dc['access-mode'] = change_access_mode class HbasHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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: raise InvalidResourceError(method, uri, reason=2, resource_uri=adapter_uri) try: hmc.lookup_by_uri(port_uri) except KeyError: raise InvalidResourceError(method, uri, reason=6, resource_uri=port_uri) new_hba = partition.hbas.add(body) return {'element-uri': new_hba.uri} class HbaHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): # 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): """Operation: Delete HBA (requires DPM mode).""" try: hba = hmc.lookup_by_uri(uri) except KeyError: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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: raise InvalidResourceError(method, uri, reason=2, resource_uri=adapter_uri) try: hmc.lookup_by_uri(port_uri) except KeyError: raise InvalidResourceError(method, uri, reason=6, resource_uri=port_uri) elif 'virtual-switch-uri' in body: vswitch_uri = body['virtual-switch-uri'] try: hmc.lookup_by_uri(vswitch_uri) except KeyError: raise InvalidResourceError(method, uri, reason=2, resource_uri=vswitch_uri) 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): # 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): """Operation: Delete NIC (requires DPM mode).""" try: nic = hmc.lookup_by_uri(uri) except KeyError: raise InvalidResourceError(method, uri) 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): # 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): """Operation: Delete Virtual Function (requires DPM mode).""" try: vf = hmc.lookup_by_uri(uri) except KeyError: raise InvalidResourceError(method, uri) 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): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """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: raise InvalidResourceError(method, uri) 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): pass class VirtualSwitchGetVnicsHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """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): """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: raise InvalidResourceError(method, uri) 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): pass class StorageGroupModifyHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) # 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) # 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): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) # Reflect the result of requesting fulfilment for the storage group pass class StorageGroupAddCandidatePortsHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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)) else: candidate_adapter_port_uris.append(ap_uri) class StorageGroupRemoveCandidatePortsHandler(object): @staticmethod def post(method, hmc, uri, uri_parms, body, logon_required, wait_for_completion): """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: raise InvalidResourceError(method, uri) 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)) else: candidate_adapter_port_uris.remove(ap_uri) class LparsHandler(object): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """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: raise InvalidResourceError(method, uri) 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): 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): """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: raise InvalidResourceError(method, uri) 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): """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: raise InvalidResourceError(method, uri) 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)) elif 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): """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: raise InvalidResourceError(method, uri) 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)) elif 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): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """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: raise InvalidResourceError(method, uri) 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): pass class ImageActProfilesHandler(object): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """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: raise InvalidResourceError(method, uri) 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): pass class LoadActProfilesHandler(object): @staticmethod def get(method, hmc, uri, uri_parms, logon_required): """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: raise InvalidResourceError(method, uri) 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): 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/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), # Only in classic (or ensemble) mode: (r'/api/cpcs/([^/]+)/operations/import-profiles', CpcImportProfilesHandler), (r'/api/cpcs/([^/]+)/operations/export-profiles', CpcExportProfilesHandler), (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), ) zhmcclient-0.22.0/zhmcclient_mock/_idpool.py0000644000076500000240000000746413364325033021501 0ustar maierastaff00000000000000# 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): """ 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): """ 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): 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) zhmcclient-0.22.0/DCO1.1.txt0000644000076500000240000000235713021231665015710 0ustar maierastaff00000000000000Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved.zhmcclient-0.22.0/tests/0000755000076500000240000000000013414661056015464 5ustar maierastaff00000000000000zhmcclient-0.22.0/tests/unit/0000755000076500000240000000000013414661056016443 5ustar maierastaff00000000000000zhmcclient-0.22.0/tests/unit/zhmcclient/0000755000076500000240000000000013414661056020603 5ustar maierastaff00000000000000zhmcclient-0.22.0/tests/unit/zhmcclient/test_utils.py0000644000076500000240000002433113364325033023353 0ustar maierastaff00000000000000# 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. """ Unit tests for _utils module. """ from __future__ import absolute_import, print_function import pytest import os import sys from datetime import datetime, MAXYEAR import time import pytz from zhmcclient._utils import datetime_from_timestamp, timestamp_from_datetime # The Unix epoch EPOCH_DT = datetime(1970, 1, 1, 0, 0, 0, 0, pytz.utc) # HMC timestamp numbers (milliseconds since the Unix epoch) TS_2000_02_29 = 951782400000 # last day in february in leap year TS_2001_02_28 = 983318400000 # last day in february in non-leap year TS_2017_08_15 = 1502755200000 # some day in summer TS_2038_LIMIT = 2147483647000 # 2038-01-19 03:14:07 UTC (Year 2038 Problem) TS_3000_12_31 = 32535129600000 TS_3001_LIMIT = 32535244799000 # 3001-01-01 07:59:59 UTC (Visual C++ limit) TS_MAX = 253402300799999 # 9999-12-31 23:59:59.999 UTC # Some durations in milliseconds SEC_MS = 1000 MIN_MS = SEC_MS * 60 HOUR_MS = MIN_MS * 60 DAY_MS = HOUR_MS * 24 # Test cases for datetime / timestamp conversion functions DATETIME_TIMESTAMP_TESTCASES = [ # (datetime_tuple(y,m,d,h,m,s,us), timestamp) ((1970, 1, 1, 0, 0, 0, 0), 0), ((1970, 1, 1, 0, 0, 0, 123000), 123), ((1970, 1, 1, 0, 2, 3, 456000), 123456), ((1970, 1, 2, 10, 17, 36, 789000), 123456789), ((1973, 11, 29, 21, 33, 9, 123000), 123456789123), ((2000, 2, 29, 0, 0, 0, 0), TS_2000_02_29), ((2000, 3, 1, 0, 0, 0, 0), TS_2000_02_29 + DAY_MS), ((2001, 2, 28, 0, 0, 0, 0), TS_2001_02_28), ((2001, 3, 1, 0, 0, 0, 0), TS_2001_02_28 + DAY_MS), ((2017, 8, 15, 0, 0, 0, 0), TS_2017_08_15), ((2038, 1, 19, 3, 14, 7, 0), TS_2038_LIMIT), ((2038, 1, 19, 3, 14, 7, 1000), TS_2038_LIMIT + 1), ((3000, 12, 31, 23, 59, 59, 999000), TS_3000_12_31 + DAY_MS - 1), ((3001, 1, 1, 7, 59, 59, 0), TS_3001_LIMIT), ((3001, 1, 1, 8, 0, 0, 0), TS_3001_LIMIT + 1000), ((MAXYEAR - 1, 12, 31, 0, 0, 0, 0), TS_MAX + 1 - DAY_MS - 365 * DAY_MS), ((MAXYEAR, 12, 30, 0, 0, 0, 0), TS_MAX + 1 - 2 * DAY_MS), ((MAXYEAR, 12, 30, 23, 59, 59, 0), TS_MAX + 1 - 2 * DAY_MS + \ 23 * HOUR_MS + 59 * MIN_MS + 59 * SEC_MS), # The following testcases would be in range but are too close to the max # for pytz due to an implementation limitation: pytz.localize() checks the # input +/- 1 day. # ((MAXYEAR, 12, 31, 0, 0, 0, 0), TS_MAX + 1 - 1 * DAY_MS), # ((MAXYEAR, 12, 31, 23, 59, 59, 0), TS_MAX - 999), # ((MAXYEAR, 12, 31, 23, 59, 59, 998000), TS_MAX - 1), # ((MAXYEAR, 12, 31, 23, 59, 59, 999000), TS_MAX), ] def find_max_value(test_func, initial_value): """ Starting from an initial number (integer or float), find the maximum value for which the test function does not yet fail, and return that maximum value. """ assert isinstance(initial_value, int) and initial_value > 0 fails = FailsArray(test_func) value = initial_value # Advance the value exponentially beyond the max value while fails[value] == 0: value *= 2 # Search for the exact max value in the previous range. We search for the # boundary where the fails array goes from 0 to 1. boundary = 0.5 value = binary_search(fails, boundary, value // 2, value) max_value = value - 1 # Verify that we found exactly the maximum: assert fails[max_value] == 0 and fails[max_value + 1] == 1, \ "max_value={}, fails[+-2]: {}, {}, {}, {}, {}".\ format(max_value, fails[max_value - 2], fails[max_value - 1], fails[max_value], fails[max_value + 1], fails[max_value + 2]) return max_value class FailsArray: """ An array that when accessed at an index returns 1 if the array value at that index causes an exception to be raised when passed to a test function, and 0 otherwise. """ def __init__(self, test_func): """ test_func is a test function that gets one integer argument and that raises an exception if the argument is out of range. """ self.test_func = test_func def __getitem__(self, test_value): """ Array access function, returning 1 if the given test value caused the test function to fail, and 0 otherwise. """ try: self.test_func(test_value) except Exception: return 1 return 0 def binary_search(haystack, needle, lo, hi): """ Binary search: Return index of value needle (or the next one if no exact match) in array haystack, within index range lo to hi. """ while lo < hi: mid = (lo + hi) // 2 if haystack[mid] > needle: hi = mid elif haystack[mid] < needle: lo = mid + 1 else: return mid return hi # TODO: Add testcases for repr_text() (currently indirectly tested) # TODO: Add testcases for repr_list() (currently indirectly tested) # TODO: Add testcases for repr_dict() (currently indirectly tested) # TODO: Add testcases for repr_timestamp() (currently indirectly tested) # TODO: Add testcases for repr_manager() (currently indirectly tested) class TestPythonDatetime(object): """ Some tests for Python date & time related functions that we use. """ def test_gmtime_epoch(self): """Test that time.gmtime() is based upon the UNIX epoch.""" epoch_st = time.struct_time([1970, 1, 1, 0, 0, 0, 3, 1, 0]) st = time.gmtime(0) assert st == epoch_st def x_test_print_gmtime_max(self): """Print the maximum for time.gmtime().""" max_ts = find_max_value(time.gmtime, 1) max_st = time.gmtime(max_ts) print("\nMax Unix timestamp value for Python time.gmtime(): {} ({!r})". format(max_ts, max_st)) sys.stdout.flush() def x_test_print_fromtimestamp_max(self): """Print the maximum for datetime.fromtimestamp(utc).""" def datetime_fromtimestamp_utc(ts): return datetime.fromtimestamp(ts, pytz.utc) max_ts = find_max_value(datetime_fromtimestamp_utc, 1) max_dt = datetime_fromtimestamp_utc(max_ts) print("\nMax Unix timestamp value for Python " "datetime.fromtimestamp(utc): {} ({!r})". format(max_ts, max_dt)) sys.stdout.flush() def x_test_print_datetime_max(self): """Print datetime.max.""" print("\nMax value for Python datetime (datetime.max): {!r}". format(datetime.max)) sys.stdout.flush() class TestDatetimeFromTimestamp(object): """ All tests for the datetime_from_timestamp() function. """ @pytest.mark.parametrize( "datetime_tuple, timestamp", DATETIME_TIMESTAMP_TESTCASES ) def test_success_datetime_from_timestamp(self, datetime_tuple, timestamp): """Test successful calls to datetime_from_timestamp().""" if os.name == 'nt' and timestamp > TS_3001_LIMIT: # Skip this test case, due to the lower limit on Windows return # Create the expected datetime result (always timezone-aware in UTC) dt_unaware = datetime(*datetime_tuple) exp_dt = pytz.utc.localize(dt_unaware) # Execute the code to be tested dt = datetime_from_timestamp(timestamp) # Verify the result assert dt == exp_dt @pytest.mark.parametrize( "timestamp, exc_type", [ (None, ValueError), (-1, ValueError), (TS_MAX + 1, ValueError), ] ) def test_error_datetime_from_timestamp(self, timestamp, exc_type): """Test failing calls to datetime_from_timestamp().""" with pytest.raises(Exception) as exc_info: # Execute the code to be tested datetime_from_timestamp(timestamp) # Verify the result assert isinstance(exc_info.value, exc_type) def x_test_print_max_datetime_from_timestamp(self): """Print the maximum for datetime_from_timestamp().""" max_ts = find_max_value(datetime_from_timestamp, 1) max_dt = datetime_from_timestamp(max_ts) print("\nMax HMC timestamp value for zhmcclient." "datetime_from_timestamp(): {} ({!r})".format(max_ts, max_dt)) sys.stdout.flush() class TestTimestampFromDatetime(object): """ All tests for the timestamp_from_datetime() function. """ @pytest.mark.parametrize( "tz_name", [None, 'UTC', 'US/Eastern', 'Europe/Berlin'] ) @pytest.mark.parametrize( "datetime_tuple, timestamp", DATETIME_TIMESTAMP_TESTCASES ) def test_success(self, datetime_tuple, timestamp, tz_name): """Test successful calls to timestamp_from_datetime().""" # Create a timezone-naive datetime object dt = datetime(*datetime_tuple) offset = 0 # because of default UTC if tz_name is not None: # Make the datetime object timezone-aware tz = pytz.timezone(tz_name) offset = tz.utcoffset(dt).total_seconds() dt = tz.localize(dt) exp_ts = timestamp - offset * 1000 # Execute the code to be tested ts = timestamp_from_datetime(dt) # Verify the result assert ts == exp_ts @pytest.mark.parametrize( "datetime_, exc_type", [ (None, ValueError), ] ) def test_error(self, datetime_, exc_type): """Test failing calls to timestamp_from_datetime().""" with pytest.raises(Exception) as exc_info: # Execute the code to be tested timestamp_from_datetime(datetime_) # Verify the result assert isinstance(exc_info.value, exc_type) def test_datetime_max(self): """Test timestamp_from_datetime() with datetime.max.""" # The test is that it does not raise an exception: timestamp_from_datetime(datetime.max) zhmcclient-0.22.0/tests/unit/zhmcclient/test_user_pattern.py0000644000076500000240000003476713364325033024744 0ustar maierastaff00000000000000# 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. """ Unit tests for _user_pattern module. """ from __future__ import absolute_import, print_function import pytest import re import copy from zhmcclient import Client, HTTPError, NotFound, UserPattern from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources class TestUserPattern(object): """All tests for the UserPattern and UserPatternManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked Console without any child resources. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) self.faked_console = self.session.hmc.consoles.add({ 'object-id': None, # object-uri will be automatically set 'parent': None, 'class': 'console', 'name': 'fake-console1', 'description': 'Console #1', }) self.console = self.client.consoles.find(name=self.faked_console.name) def add_user_pattern(self, name, pattern, type_, user_template_uri): faked_user_pattern = self.faked_console.user_patterns.add({ 'element-id': 'oid-{}'.format(name), # element-uri will be automatically set 'parent': '/api/console', 'class': 'user-pattern', 'name': name, 'description': 'User Pattern {}'.format(name), 'pattern': pattern, 'type': type_, 'retention-time': 0, 'user-template-uri': user_template_uri, }) return faked_user_pattern def add_user(self, name, type_): faked_user = self.faked_console.users.add({ 'object-id': 'oid-{}'.format(name), # object-uri will be automatically set 'parent': '/api/console', 'class': 'user', 'name': name, 'description': 'User {}'.format(name), 'type': type_, 'authentication-type': 'local', }) return faked_user def test_user_pattern_manager_repr(self): """Test UserPatternManager.__repr__().""" user_pattern_mgr = self.console.user_patterns # Execute the code to be tested repr_str = repr(user_pattern_mgr) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=user_pattern_mgr.__class__.__name__, id=id(user_pattern_mgr)), repr_str) def test_user_pattern_manager_initial_attrs(self): """Test initial attributes of UserPatternManager.""" user_pattern_mgr = self.console.user_patterns # Verify all public properties of the manager object assert user_pattern_mgr.resource_class == UserPattern assert user_pattern_mgr.class_name == 'user-pattern' assert user_pattern_mgr.session is self.session assert user_pattern_mgr.parent is self.console assert user_pattern_mgr.console is self.console @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(full_properties=False), ['element-uri']), (dict(full_properties=True), ['element-uri', 'name']), (dict(), # test default for full_properties (True) ['element-uri', 'name']), ] ) @pytest.mark.parametrize( "filter_args, exp_names", [ (None, ['a', 'b']), ({}, ['a', 'b']), ({'name': 'a'}, ['a']), ] ) def test_user_pattern_manager_list( self, filter_args, exp_names, full_properties_kwargs, prop_names): """Test UserPatternManager.list().""" faked_user1 = self.add_user(name='a', type_='standard') faked_user2 = self.add_user(name='b', type_='standard') faked_user_pattern1 = self.add_user_pattern( name='a', pattern='a_*', type_='glob-like', user_template_uri=faked_user1.uri) faked_user_pattern2 = self.add_user_pattern( name='b', pattern='b_.*', type_='regular-expression', user_template_uri=faked_user2.uri) faked_user_patterns = [faked_user_pattern1, faked_user_pattern2] exp_faked_user_patterns = [u for u in faked_user_patterns if u.name in exp_names] user_pattern_mgr = self.console.user_patterns # Execute the code to be tested user_patterns = user_pattern_mgr.list(filter_args=filter_args, **full_properties_kwargs) assert_resources(user_patterns, exp_faked_user_patterns, prop_names) @pytest.mark.parametrize( "input_props, exp_prop_names, exp_exc", [ ({}, # props missing None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X'}, # props missing None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X', 'name': 'a', 'pattern': 'a*'}, # several missing None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X', 'name': 'a', 'pattern': 'a*'}, # several missing None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X', 'name': 'a', 'pattern': 'a*', 'type': 'glob-like'}, # props missing None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X', 'name': 'a', 'pattern': 'a*', 'type': 'glob-like', 'retention-time': 0}, # props missing None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X', 'name': 'a', 'pattern': 'a*', 'type': 'glob-like', 'retention-time': 28, 'user-template-uri': '/api/users/oid-tpl'}, ['element-uri', 'name', 'description', 'pattern', 'type', 'retention-time', 'user-template-uri'], None), ] ) def test_user_pattern_manager_create(self, input_props, exp_prop_names, exp_exc): """Test UserPatternManager.create().""" faked_user_template = self.add_user(name='tpl', type_='template') assert faked_user_template.uri == '/api/users/oid-tpl' user_pattern_mgr = self.console.user_patterns if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested user_pattern_mgr.create(properties=input_props) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason else: # Execute the code to be tested. user_pattern = user_pattern_mgr.create(properties=input_props) # Check the resource for consistency within itself assert isinstance(user_pattern, UserPattern) user_pattern_name = user_pattern.name exp_user_pattern_name = user_pattern.properties['name'] assert user_pattern_name == exp_user_pattern_name user_pattern_uri = user_pattern.uri exp_user_pattern_uri = user_pattern.properties['element-uri'] assert user_pattern_uri == exp_user_pattern_uri # Check the properties against the expected names and values for prop_name in exp_prop_names: assert prop_name in user_pattern.properties if prop_name in input_props: value = user_pattern.properties[prop_name] exp_value = input_props[prop_name] assert value == exp_value def test_user_pattern_repr(self): """Test UserPattern.__repr__().""" faked_user1 = self.add_user(name='a', type_='standard') faked_user_pattern1 = self.add_user_pattern( name='a', pattern='a_*', type_='glob-like', user_template_uri=faked_user1.uri) user_pattern1 = self.console.user_patterns.find( name=faked_user_pattern1.name) # Execute the code to be tested repr_str = repr(user_pattern1) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=user_pattern1.__class__.__name__, id=id(user_pattern1)), repr_str) @pytest.mark.parametrize( "input_props, exp_exc", [ ({'name': 'a', 'description': 'fake description X', 'pattern': 'a*', 'type': 'glob-like', 'retention-time': 28, 'user-template-uri': '/api/users/oid-tpl'}, None), ] ) def test_user_pattern_delete(self, input_props, exp_exc): """Test UserPattern.delete().""" faked_user_pattern = self.add_user_pattern( name=input_props['name'], pattern=input_props['pattern'], type_=input_props['type'], user_template_uri=input_props['user-template-uri']) user_pattern_mgr = self.console.user_patterns user_pattern = user_pattern_mgr.find(name=faked_user_pattern.name) if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested user_pattern.delete() exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the user pattern still exists user_pattern_mgr.find(name=faked_user_pattern.name) else: # Execute the code to be tested. user_pattern.delete() # Check that the user pattern no longer exists with pytest.raises(NotFound) as exc_info: user_pattern_mgr.find(name=faked_user_pattern.name) def test_user_pattern_delete_create_same_name(self): """Test UserPattern.delete() followed by create() with same name.""" user_pattern_name = 'faked_a' faked_user1 = self.add_user(name='a', type_='standard') # Add the user pattern to be tested self.add_user_pattern( name=user_pattern_name, pattern='a_*', type_='glob-like', user_template_uri=faked_user1.uri) # Input properties for a user pattern with the same name sn_user_pattern_props = { 'name': user_pattern_name, 'description': 'User Pattern with same name', 'pattern': 'a*', 'type': 'glob-like', 'retention-time': 28, 'user-template-uri': '/api/users/oid-tpl', } user_pattern_mgr = self.console.user_patterns user_pattern = user_pattern_mgr.find(name=user_pattern_name) # Execute the deletion code to be tested user_pattern.delete() # Check that the user pattern no longer exists with pytest.raises(NotFound): user_pattern_mgr.find(name=user_pattern_name) # Execute the creation code to be tested. user_pattern_mgr.create(sn_user_pattern_props) # Check that the user pattern exists again under that name sn_user_pattern = user_pattern_mgr.find(name=user_pattern_name) description = sn_user_pattern.get_property('description') assert description == sn_user_pattern_props['description'] @pytest.mark.parametrize( "input_props", [ {}, {'description': 'New user pattern description'}, ] ) def test_user_pattern_update_properties(self, input_props): """Test UserPattern.update_properties().""" user_pattern_name = 'faked_a' faked_user1 = self.add_user(name='a', type_='standard') # Add the user pattern to be tested self.add_user_pattern( name=user_pattern_name, pattern='a_*', type_='glob-like', user_template_uri=faked_user1.uri) user_pattern_mgr = self.console.user_patterns user_pattern = user_pattern_mgr.find(name=user_pattern_name) user_pattern.pull_full_properties() saved_properties = copy.deepcopy(user_pattern.properties) # Execute the code to be tested user_pattern.update_properties(properties=input_props) # Verify that the resource object already reflects the property # updates. for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in user_pattern.properties prop_value = user_pattern.properties[prop_name] assert prop_value == exp_prop_value, \ "Unexpected value for property {!r}".format(prop_name) # Refresh the resource object and verify that the resource object # still reflects the property updates. user_pattern.pull_full_properties() for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in user_pattern.properties prop_value = user_pattern.properties[prop_name] assert prop_value == exp_prop_value zhmcclient-0.22.0/tests/unit/zhmcclient/test_task.py0000644000076500000240000001113513364325033023153 0ustar maierastaff00000000000000# 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. """ Unit tests for _task module. """ from __future__ import absolute_import, print_function import pytest import re from zhmcclient import Client, Task from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources class TestTask(object): """All tests for the Task and TaskManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked Console without any child resources. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) self.faked_console = self.session.hmc.consoles.add({ 'object-id': None, # object-uri will be automatically set 'parent': None, 'class': 'console', 'name': 'fake-console1', 'description': 'Console #1', }) self.console = self.client.consoles.find(name=self.faked_console.name) def add_task(self, name, view_only=True): faked_task = self.faked_console.tasks.add({ 'element-id': 'oid-{}'.format(name), # element-uri will be automatically set 'parent': '/api/console', 'class': 'task', 'name': name, 'description': 'Task {}'.format(name), 'view-only-mode-supported': view_only, }) return faked_task def test_task_manager_repr(self): """Test TaskManager.__repr__().""" task_mgr = self.console.tasks # Execute the code to be tested repr_str = repr(task_mgr) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=task_mgr.__class__.__name__, id=id(task_mgr)), repr_str) def test_task_manager_initial_attrs(self): """Test initial attributes of TaskManager.""" task_mgr = self.console.tasks # Verify all public properties of the manager object assert task_mgr.resource_class == Task assert task_mgr.class_name == 'task' assert task_mgr.session is self.session assert task_mgr.parent is self.console assert task_mgr.console is self.console @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(full_properties=False), ['element-uri']), (dict(full_properties=True), ['element-uri', 'name']), (dict(), # test default for full_properties (True) ['element-uri', 'name']), ] ) @pytest.mark.parametrize( "filter_args, exp_names", [ (None, ['a', 'b']), ({}, ['a', 'b']), ({'name': 'a'}, ['a']), ] ) def test_task_manager_list( self, filter_args, exp_names, full_properties_kwargs, prop_names): """Test TaskManager.list().""" faked_task1 = self.add_task(name='a') faked_task2 = self.add_task(name='b') faked_tasks = [faked_task1, faked_task2] exp_faked_tasks = [u for u in faked_tasks if u.name in exp_names] task_mgr = self.console.tasks # Execute the code to be tested tasks = task_mgr.list(filter_args=filter_args, **full_properties_kwargs) assert_resources(tasks, exp_faked_tasks, prop_names) def test_task_repr(self): """Test Task.__repr__().""" faked_task1 = self.add_task(name='a') task1 = self.console.tasks.find(name=faked_task1.name) # Execute the code to be tested repr_str = repr(task1) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=task1.__class__.__name__, id=id(task1)), repr_str) zhmcclient-0.22.0/tests/unit/zhmcclient/test_user.py0000644000076500000240000004003113364325033023164 0ustar maierastaff00000000000000# 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. """ Unit tests for _user module. """ from __future__ import absolute_import, print_function import pytest import re import copy from zhmcclient import Client, HTTPError, NotFound, User from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources class TestUser(object): """All tests for the User and UserManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked Console without any child resources. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) self.faked_console = self.session.hmc.consoles.add({ 'object-id': None, # object-uri will be automatically set 'parent': None, 'class': 'console', 'name': 'fake-console1', 'description': 'Console #1', }) self.console = self.client.consoles.find(name=self.faked_console.name) def add_user(self, name, type_): faked_user = self.faked_console.users.add({ 'object-id': 'oid-{}'.format(name), # object-uri will be automatically set 'parent': '/api/console', 'class': 'user', 'name': name, 'description': 'User {}'.format(name), 'type': type_, 'authentication-type': 'local', }) return faked_user def add_user_role(self, name, type_): faked_user_role = self.faked_console.user_roles.add({ 'object-id': 'oid-{}'.format(name), # object-uri will be automatically set 'parent': '/api/console', 'class': 'user-role', 'name': name, 'description': 'User Role {}'.format(name), 'type': type_, }) return faked_user_role def test_user_manager_repr(self): """Test UserManager.__repr__().""" user_mgr = self.console.users # Execute the code to be tested repr_str = repr(user_mgr) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=user_mgr.__class__.__name__, id=id(user_mgr)), repr_str) def test_user_manager_initial_attrs(self): """Test initial attributes of UserManager.""" user_mgr = self.console.users # Verify all public properties of the manager object assert user_mgr.resource_class == User assert user_mgr.class_name == 'user' assert user_mgr.session is self.session assert user_mgr.parent is self.console assert user_mgr.console is self.console @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(full_properties=False), ['object-uri']), (dict(full_properties=True), ['object-uri', 'name']), (dict(), # test default for full_properties (True) ['object-uri', 'name']), ] ) @pytest.mark.parametrize( "filter_args, exp_names", [ (None, ['a', 'b']), ({}, ['a', 'b']), ({'name': 'a'}, ['a']), ] ) def test_user_manager_list( self, filter_args, exp_names, full_properties_kwargs, prop_names): """Test UserManager.list().""" faked_user1 = self.add_user(name='a', type_='standard') faked_user2 = self.add_user(name='b', type_='standard') faked_users = [faked_user1, faked_user2] exp_faked_users = [u for u in faked_users if u.name in exp_names] user_mgr = self.console.users # Execute the code to be tested users = user_mgr.list(filter_args=filter_args, **full_properties_kwargs) assert_resources(users, exp_faked_users, prop_names) @pytest.mark.parametrize( "input_props, exp_prop_names, exp_exc", [ ({}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X'}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-name-x'}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-name-x', 'type': 'standard'}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-name-x', 'type': 'standard', 'authentication-type': 'local'}, ['object-uri', 'name', 'type', 'authentication-type'], None), ({'name': 'fake-name-x', 'type': 'standard', 'authentication-type': 'local', 'description': 'fake description X'}, ['object-uri', 'name', 'type', 'authentication-type', 'description'], None), ] ) def test_user_manager_create(self, input_props, exp_prop_names, exp_exc): """Test UserManager.create().""" user_mgr = self.console.users if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested user_mgr.create(properties=input_props) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason else: # Execute the code to be tested. user = user_mgr.create(properties=input_props) # Check the resource for consistency within itself assert isinstance(user, User) user_name = user.name exp_user_name = user.properties['name'] assert user_name == exp_user_name user_uri = user.uri exp_user_uri = user.properties['object-uri'] assert user_uri == exp_user_uri # Check the properties against the expected names and values for prop_name in exp_prop_names: assert prop_name in user.properties if prop_name in input_props: value = user.properties[prop_name] exp_value = input_props[prop_name] assert value == exp_value def test_user_repr(self): """Test User.__repr__().""" faked_user1 = self.add_user(name='a', type_='standard') user1 = self.console.users.find(name=faked_user1.name) # Execute the code to be tested repr_str = repr(user1) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=user1.__class__.__name__, id=id(user1)), repr_str) @pytest.mark.parametrize( "input_name, input_type, exp_exc", [ ('a', 'standard', None), ('b', 'template', None), ('c', 'pattern-based', HTTPError({'http-status': 400, 'reason': 312})), ('d', 'system-defined', None), ] ) def test_user_delete(self, input_name, input_type, exp_exc): """Test User.delete().""" faked_user = self.add_user(name=input_name, type_=input_type) user_mgr = self.console.users user = user_mgr.find(name=faked_user.name) if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested user.delete() exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the user still exists user_mgr.find(name=faked_user.name) else: # Execute the code to be tested. user.delete() # Check that the user no longer exists with pytest.raises(NotFound) as exc_info: user_mgr.find(name=faked_user.name) def test_user_delete_create_same_name(self): """Test User.delete() followed by create() with same name.""" user_name = 'faked_a' # Add the user to be tested self.add_user(name=user_name, type_='standard') # Input properties for a user with the same name sn_user_props = { 'name': user_name, 'description': 'User with same name', 'type': 'standard', 'authentication-type': 'local', } user_mgr = self.console.users user = user_mgr.find(name=user_name) # Execute the deletion code to be tested user.delete() # Check that the user no longer exists with pytest.raises(NotFound): user_mgr.find(name=user_name) # Execute the creation code to be tested. user_mgr.create(sn_user_props) # Check that the user exists again under that name sn_user = user_mgr.find(name=user_name) description = sn_user.get_property('description') assert description == sn_user_props['description'] @pytest.mark.parametrize( "input_props", [ {}, {'description': 'New user description'}, {'authentication-type': 'ldap', 'description': 'New user description'}, ] ) def test_user_update_properties(self, input_props): """Test User.update_properties().""" # Add the user to be tested faked_user = self.add_user(name='a', type_='standard') user_mgr = self.console.users user = user_mgr.find(name=faked_user.name) user.pull_full_properties() saved_props = copy.deepcopy(user.properties) # Execute the code to be tested user.update_properties(properties=input_props) # Verify that the resource object already reflects the property # updates. for prop_name in saved_props: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_props[prop_name] assert prop_name in user.properties prop_value = user.properties[prop_name] assert prop_value == exp_prop_value, \ "Unexpected value for property {!r}".format(prop_name) # Refresh the resource object and verify that the resource object # still reflects the property updates. user.pull_full_properties() for prop_name in saved_props: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_props[prop_name] assert prop_name in user.properties prop_value = user.properties[prop_name] assert prop_value == exp_prop_value @pytest.mark.parametrize( "user_name, user_type, exp_exc", [ ('a', 'standard', None), ('b', 'template', None), ('c', 'pattern-based', HTTPError({'http-status': 400, 'reason': 314})), ('d', 'system-defined', HTTPError({'http-status': 400, 'reason': 314})), ] ) @pytest.mark.parametrize( "role_name, role_type", [ ('ra', 'user-defined'), ('rb', 'system-defined'), ] ) def test_user_add_user_role( self, role_name, role_type, user_name, user_type, exp_exc): """Test User.add_user_role().""" faked_user = self.add_user(name=user_name, type_=user_type) user_mgr = self.console.users user = user_mgr.find(name=faked_user.name) faked_user_role = self.add_user_role(name=role_name, type_=role_type) user_role_mgr = self.console.user_roles user_role = user_role_mgr.find(name=faked_user_role.name) if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested user.add_user_role(user_role) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the user does not have that user role user.pull_full_properties() if 'user-roles' in user.properties: user_role_uris = user.properties['user-roles'] user_role_uri = user_role.uri assert user_role_uri not in user_role_uris else: # Execute the code to be tested. ret = user.add_user_role(user_role) assert ret is None # Check that the user has that user role user.pull_full_properties() assert 'user-roles' in user.properties user_role_uris = user.properties['user-roles'] user_role_uri = user_role.uri assert user_role_uri in user_role_uris @pytest.mark.parametrize( "user_name, user_type, exp_exc", [ ('a', 'standard', None), ('b', 'template', None), ('c', 'pattern-based', HTTPError({'http-status': 400, 'reason': 314})), ('d', 'system-defined', HTTPError({'http-status': 400, 'reason': 314})), ] ) @pytest.mark.parametrize( "role_name, role_type", [ ('ra', 'user-defined'), ('rb', 'system-defined'), ] ) def test_user_remove_user_role( self, role_name, role_type, user_name, user_type, exp_exc): """Test User.remove_user_role().""" faked_user = self.add_user(name=user_name, type_=user_type) user_mgr = self.console.users user = user_mgr.find(name=faked_user.name) faked_user_role = self.add_user_role(name=role_name, type_=role_type) user_role_mgr = self.console.user_roles user_role = user_role_mgr.find(name=faked_user_role.name) # Prepare the user with the initial user role if 'user-roles' not in faked_user.properties: faked_user.properties['user-roles'] = [] faked_user.properties['user-roles'].append(faked_user_role.uri) if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested user.remove_user_role(user_role) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the user still has that user role user.pull_full_properties() if 'user-roles' in user.properties: user_role_uris = user.properties['user-roles'] user_role_uri = user_role.uri assert user_role_uri in user_role_uris else: # Execute the code to be tested. ret = user.remove_user_role(user_role) assert ret is None # Check that the user no longer has that user role user.pull_full_properties() assert 'user-roles' in user.properties user_role_uris = user.properties['user-roles'] user_role_uri = user_role.uri assert user_role_uri not in user_role_uris zhmcclient-0.22.0/tests/unit/zhmcclient/test_password_rule.py0000644000076500000240000003044413364325033025106 0ustar maierastaff00000000000000# 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. """ Unit tests for _password_rule module. """ from __future__ import absolute_import, print_function import pytest import re import copy from zhmcclient import Client, HTTPError, NotFound, PasswordRule from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources class TestPasswordRule(object): """All tests for the PasswordRule and PasswordRuleManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked Console without any child resources. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) self.faked_console = self.session.hmc.consoles.add({ 'object-id': None, # object-uri will be automatically set 'parent': None, 'class': 'console', 'name': 'fake-console1', 'description': 'Console #1', }) self.console = self.client.consoles.find(name=self.faked_console.name) def add_password_rule(self, name, type_): faked_password_rule = self.faked_console.password_rules.add({ 'element-id': 'oid-{}'.format(name), # element-uri will be automatically set 'parent': '/api/console', 'class': 'password-rule', 'name': name, 'description': 'Password Rule {}'.format(name), 'type': type_, }) return faked_password_rule def add_user(self, name, type_): faked_user = self.faked_console.users.add({ 'object-id': 'oid-{}'.format(name), # object-uri will be automatically set 'parent': '/api/console', 'class': 'user', 'name': name, 'description': 'User {}'.format(name), 'type': type_, 'authentication-type': 'local', }) return faked_user def test_password_rule_manager_repr(self): """Test PasswordRuleManager.__repr__().""" password_rule_mgr = self.console.password_rules # Execute the code to be tested repr_str = repr(password_rule_mgr) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=password_rule_mgr.__class__.__name__, id=id(password_rule_mgr)), repr_str) def test_password_rule_manager_initial_attrs(self): """Test initial attributes of PasswordRuleManager.""" password_rule_mgr = self.console.password_rules # Verify all public properties of the manager object assert password_rule_mgr.resource_class == PasswordRule assert password_rule_mgr.class_name == 'password-rule' assert password_rule_mgr.session is self.session assert password_rule_mgr.parent is self.console assert password_rule_mgr.console is self.console @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(full_properties=False), ['element-uri']), (dict(full_properties=True), ['element-uri', 'name']), (dict(), # test default for full_properties (True) ['element-uri', 'name']), ] ) @pytest.mark.parametrize( "filter_args, exp_names", [ (None, ['a', 'b']), ({}, ['a', 'b']), ({'name': 'a'}, ['a']), ] ) def test_password_rule_manager_list( self, filter_args, exp_names, full_properties_kwargs, prop_names): """Test PasswordRuleManager.list().""" faked_password_rule1 = self.add_password_rule( name='a', type_='user-defined') faked_password_rule2 = self.add_password_rule( name='b', type_='system-defined') faked_password_rules = [faked_password_rule1, faked_password_rule2] exp_faked_password_rules = [u for u in faked_password_rules if u.name in exp_names] password_rule_mgr = self.console.password_rules # Execute the code to be tested password_rules = password_rule_mgr.list(filter_args=filter_args, **full_properties_kwargs) assert_resources(password_rules, exp_faked_password_rules, prop_names) @pytest.mark.parametrize( "input_props, exp_prop_names, exp_exc", [ ({}, # props missing None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X'}, # props missing None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X', 'name': 'a'}, ['element-uri', 'name', 'description'], None), ] ) def test_password_rule_manager_create(self, input_props, exp_prop_names, exp_exc): """Test PasswordRuleManager.create().""" password_rule_mgr = self.console.password_rules if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested password_rule_mgr.create(properties=input_props) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason else: # Execute the code to be tested. password_rule = password_rule_mgr.create(properties=input_props) # Check the resource for consistency within itself assert isinstance(password_rule, PasswordRule) password_rule_name = password_rule.name exp_password_rule_name = password_rule.properties['name'] assert password_rule_name == exp_password_rule_name password_rule_uri = password_rule.uri exp_password_rule_uri = password_rule.properties['element-uri'] assert password_rule_uri == exp_password_rule_uri # Check the properties against the expected names and values for prop_name in exp_prop_names: assert prop_name in password_rule.properties if prop_name in input_props: value = password_rule.properties[prop_name] exp_value = input_props[prop_name] assert value == exp_value def test_password_rule_repr(self): """Test PasswordRule.__repr__().""" faked_password_rule1 = self.add_password_rule( name='a', type_='user-defined') password_rule1 = self.console.password_rules.find( name=faked_password_rule1.name) # Execute the code to be tested repr_str = repr(password_rule1) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=password_rule1.__class__.__name__, id=id(password_rule1)), repr_str) @pytest.mark.parametrize( "input_props, exp_exc", [ ({'name': 'a', 'type': 'user-defined'}, None), ({'name': 'b', 'type': 'system-defined'}, None), ] ) def test_password_rule_delete(self, input_props, exp_exc): """Test PasswordRule.delete().""" faked_password_rule = self.add_password_rule( name=input_props['name'], type_=input_props['type']) password_rule_mgr = self.console.password_rules password_rule = password_rule_mgr.find(name=faked_password_rule.name) if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested password_rule.delete() exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the Password Rule still exists password_rule_mgr.find(name=faked_password_rule.name) else: # Execute the code to be tested. password_rule.delete() # Check that the Password Rule no longer exists with pytest.raises(NotFound) as exc_info: password_rule_mgr.find(name=faked_password_rule.name) def test_password_rule_delete_create_same_name(self): """Test PasswordRule.delete() followed by create() with same name.""" password_rule_name = 'faked_a' # Add the Password Rule to be tested self.add_password_rule(name=password_rule_name, type_='user-defined') # Input properties for a Password Rule with the same name sn_password_rule_props = { 'name': password_rule_name, 'description': 'Password Rule with same name', 'type': 'user-defined', } password_rule_mgr = self.console.password_rules password_rule = password_rule_mgr.find(name=password_rule_name) # Execute the deletion code to be tested password_rule.delete() # Check that the Password Rule no longer exists with pytest.raises(NotFound): password_rule_mgr.find(name=password_rule_name) # Execute the creation code to be tested. password_rule_mgr.create(sn_password_rule_props) # Check that the Password Rule exists again under that name sn_password_rule = password_rule_mgr.find(name=password_rule_name) description = sn_password_rule.get_property('description') assert description == sn_password_rule_props['description'] @pytest.mark.parametrize( "input_props", [ {}, {'description': 'New Password Rule description'}, ] ) def test_password_rule_update_properties(self, input_props): """Test PasswordRule.update_properties().""" password_rule_name = 'faked_a' # Add the Password Rule to be tested self.add_password_rule(name=password_rule_name, type_='user-defined') password_rule_mgr = self.console.password_rules password_rule = password_rule_mgr.find(name=password_rule_name) password_rule.pull_full_properties() saved_properties = copy.deepcopy(password_rule.properties) # Execute the code to be tested password_rule.update_properties(properties=input_props) # Verify that the resource object already reflects the property # updates. for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in password_rule.properties prop_value = password_rule.properties[prop_name] assert prop_value == exp_prop_value, \ "Unexpected value for property {!r}".format(prop_name) # Refresh the resource object and verify that the resource object # still reflects the property updates. password_rule.pull_full_properties() for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in password_rule.properties prop_value = password_rule.properties[prop_name] assert prop_value == exp_prop_value zhmcclient-0.22.0/tests/unit/zhmcclient/test_resource.py0000644000076500000240000004011113364325033024034 0ustar maierastaff00000000000000# 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. """ Unit tests for _resource module. """ from __future__ import absolute_import, print_function import time import re from collections import OrderedDict from zhmcclient import BaseResource, BaseManager, Session class MyResource(BaseResource): """ A derived resource for testing the (abstract) BaseResource class. """ # This init method is not part of the external API, so this testcase may # need to be updated if the API changes. def __init__(self, manager, uri, name, properties): super(MyResource, self).__init__(manager, uri, name, properties) class MyManager(BaseManager): """ A derived resource manager for testing purposes. It is only needed because BaseResource needs it; it is not subject of test in this unit test module. """ # This init method is not part of the external API, so this testcase may # need to be updated if the API changes. def __init__(self, session): super(MyManager, self).__init__( resource_class=MyResource, class_name='myresource', session=session, parent=None, # a top-level resource base_uri='/api/myresources', oid_prop='fake-oid-prop', uri_prop='fake-uri-prop', name_prop='fake-name-prop', query_props=['qp1', 'qp2']) def list(self, full_properties=False, filter_args=None): # We have this method here just to avoid the warning about # an unimplemented abstract method. It is not being used in this # set of testcases. raise NotImplementedError class ResourceTestCase(object): """ Base class for all tests in this file. """ def setup_method(self): self.session = Session(host='fake-host') self.mgr = MyManager(self.session) self.uri = self.mgr._base_uri + '/deadbeef-beef-beef-beef-deadbeefbeef' self.name = "fake-name" self.uri_prop = 'fake-uri-prop' # same as in MyManager self.name_prop = 'fake-name-prop' # same as in MyManager def assert_properties(self, resource, exp_props): """ Assert that the properties of a resource object are as expected. """ # Check that the properties member is a dict assert isinstance(resource.properties, dict) # Verify that the resource properties are as expected assert len(resource.properties) == len(exp_props), \ "Set of properties does not match. Expected {!r}, got {!r}". \ format(resource.properties.keys(), exp_props.keys()) for name, exp_value in exp_props.items(): act_value = resource.properties[name] assert act_value == exp_value, \ "Property {!r} does not match. Expected {!r}, got {!r}". \ format(name, exp_value, act_value) class TestInit(ResourceTestCase): """Test BaseResource initialization.""" def test_empty_name(self): """Test with an empty set of input properties, with 'name'.""" init_props = {} res_props = { self.uri_prop: self.uri, self.name_prop: self.name, } res = MyResource(self.mgr, self.uri, self.name, init_props) assert res.manager is self.mgr assert res.uri == self.uri assert res.name == self.name self.assert_properties(res, res_props) assert int(time.time()) - res.properties_timestamp <= 1 assert res.full_properties is False def test_empty_no_name(self): """Test with an empty set of input properties, without 'name'.""" init_props = {} res_props = { self.uri_prop: self.uri, } res = MyResource(self.mgr, self.uri, None, init_props) assert res.manager is self.mgr assert res.uri == self.uri self.assert_properties(res, res_props) assert int(time.time()) - res.properties_timestamp <= 1 assert res.full_properties is False def test_simple(self): """Test with a simple set of input properties.""" init_props = { 'prop1': 'abc', 'prop2': 100042 } res_props = { self.uri_prop: self.uri, 'prop1': 'abc', 'prop2': 100042 } res = MyResource(self.mgr, self.uri, None, init_props) assert res.manager is self.mgr assert res.uri == self.uri self.assert_properties(res, res_props) assert int(time.time()) - res.properties_timestamp <= 1 assert res.full_properties is False def test_prop_case(self): """Test case sensitivity for the input properties.""" init_props = { 'prop1': 'abc', 'Prop1': 100042, } res_props = { self.uri_prop: self.uri, 'prop1': 'abc', 'Prop1': 100042, } res = MyResource(self.mgr, self.uri, None, init_props) assert res.manager is self.mgr assert res.uri == self.uri self.assert_properties(res, res_props) assert int(time.time()) - res.properties_timestamp <= 1 assert res.full_properties is False def test_invalid_type(self): """Test that input properties with an invalid type fail.""" init_props = 42 try: MyResource(self.mgr, self.uri, None, init_props) except TypeError: pass else: self.fail("TypeError was not raised when initializing resource " "with invalid properties: %r" % init_props) def test_str(self): """Test BaseResource.__str__().""" init_props = { 'prop1': 'abc', 'Prop1': 100042, } resource = MyResource(self.mgr, self.uri, None, init_props) str_str = str(resource) str_str = str_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s*\(.*'. format(classname=resource.__class__.__name__), str_str) def test_repr(self): """Test BaseResource.__repr__().""" init_props = { 'prop1': 'abc', 'Prop1': 100042, } resource = MyResource(self.mgr, self.uri, None, init_props) repr_str = repr(resource) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=resource.__class__.__name__, id=id(resource)), repr_str) class TestPropertySet(ResourceTestCase): """Test BaseResource by setting properties.""" def test_add_to_empty(self): """Test setting a property in a resource object with no properties.""" init_props = {} set_props = { 'prop1': 'abc', 'prop2': 100042, } res_props = { self.uri_prop: self.uri, 'prop1': 'abc', 'prop2': 100042, } res = MyResource(self.mgr, self.uri, None, init_props) for key, value in set_props.items(): res.properties[key] = value self.assert_properties(res, res_props) def test_replace_one_add_one(self): """Test replacing and adding a property in a resource object.""" init_props = { 'prop1': 42, } set_props = { 'prop1': 'abc', 'prop2': 100042, } res_props = { self.uri_prop: self.uri, 'prop1': 'abc', 'prop2': 100042, } res = MyResource(self.mgr, self.uri, None, init_props) for key, value in set_props.items(): res.properties[key] = value self.assert_properties(res, res_props) class TestPropertyDel(ResourceTestCase): """Test BaseResource by deleting properties.""" def test_del_one(self): """Test deleting a property in a resource object.""" init_props = { 'prop1': 'abc', 'prop2': 100042, } del_keys = ('prop1',) res_props = { self.uri_prop: self.uri, 'prop2': 100042, } res = MyResource(self.mgr, self.uri, None, init_props) for key in del_keys: del res.properties[key] self.assert_properties(res, res_props) def test_del_all_input(self): """Test deleting all input properties in a resource object.""" init_props = { 'prop1': 'abc', 'prop2': 100042, } del_keys = ('prop1', 'prop2') res_props = { self.uri_prop: self.uri, } res = MyResource(self.mgr, self.uri, None, init_props) for key in del_keys: del res.properties[key] self.assert_properties(res, res_props) def test_del_invalid(self): """Test deleting an invalid property in a resource object.""" init_props = { 'prop1': 'abc', 'prop2': 100042, } org_init_props = dict(init_props) res = MyResource(self.mgr, self.uri, None, init_props) invalid_key = 'inv1' try: del res.properties[invalid_key] except KeyError: pass else: self.fail("KeyError was not raised when deleting invalid key %r " "in resource properties %r" % (invalid_key, org_init_props)) def test_clear(self): """Test clearing the properties in a resource object.""" init_props = { 'prop1': 'abc', 'prop2': 100042, } res = MyResource(self.mgr, self.uri, None, init_props) res.properties.clear() assert len(res.properties) == 0 class TestManagerDivideFilter(ResourceTestCase): """Test the _divide_filter_args() method of BaseManager.""" # Reserved chars are defined in RFC 3986 as gen-delims and sub-delims. reserved_chars = [ ':', '/', '?', '#', '[', ']', '@', # gen-delims '!', '$', '&', "'", '(', ')', '*', '+', ',', ';', '=' # sub-delims ] # Percent-escapes for the reserved chars, in the same order. reserved_escapes = [ '%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', # gen-delims '%21', '%24', '%26', '%27', '%28', '%29', '%2A', # sub-delims '%2B', '%2C', '%3B', '%3D', # sub-delims ] def test_none(self): """Test with None as filter arguments.""" filter_args = None parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '' assert cf_args == {} def test_empty(self): """Test with an empty set of filter arguments.""" filter_args = {} parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '' assert cf_args == {} def test_one_string_qp(self): """Test with one string filter argument that is a query parm.""" filter_args = {'qp1': 'bar'} parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '?qp1=bar' assert cf_args == {} def test_one_string_cf(self): """Test with one string filter argument that is a client filter.""" filter_args = {'foo': 'bar'} parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '' assert cf_args == {'foo': 'bar'} def test_one_integer_qp(self): """Test with one integer filter argument that is a query parm.""" filter_args = {'qp2': 42} parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '?qp2=42' assert cf_args == {} def test_one_integer_cf(self): """Test with one integer filter argument that is a client filter.""" filter_args = {'foo': 42} parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '' assert cf_args == {'foo': 42} def test_one_str_reserved_val_qp(self): """Test with one string filter argument with reserved URI chars in its value that is a query parm.""" char_str = '_'.join(self.reserved_chars) escape_str = '_'.join(self.reserved_escapes) filter_args = {'qp1': char_str} parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '?qp1={}'.format(escape_str) assert cf_args == {} def test_one_str_reserved_val_cf(self): """Test with one string filter argument with reserved URI chars in its value that is a client filter.""" char_str = '_'.join(self.reserved_chars) filter_args = {'foo': char_str} parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '' assert cf_args == {'foo': char_str} def test_one_str_dash_name_qp(self): """Test with one string filter argument with a dash in its name that is a query parm.""" filter_args = {'foo-boo': 'bar'} self.mgr._query_props.append('foo-boo') parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '?foo-boo=bar' assert cf_args == {} def test_one_str_reserved_name_qp(self): """Test with one string filter argument with reserved URI chars in its name that is a query parm.""" char_str = '_'.join(self.reserved_chars) escape_str = '_'.join(self.reserved_escapes) filter_args = {char_str: 'bar'} self.mgr._query_props.append(char_str) parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '?{}=bar'.format(escape_str) assert cf_args == {} def test_two_qp(self): """Test with two filter arguments that are query parms.""" filter_args = OrderedDict([('qp1', 'bar'), ('qp2', 42)]) parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '?qp1=bar&qp2=42' assert cf_args == {} def test_two_qp_cf(self): """Test with two filter arguments where one is a query parm and one is a client filter.""" filter_args = OrderedDict([('qp1', 'bar'), ('foo', 42)]) parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '?qp1=bar' assert cf_args == {'foo': 42} def test_two_cf_qp(self): """Test with two filter arguments where one is a client filter and one is a query parm.""" filter_args = OrderedDict([('foo', 'bar'), ('qp1', 42)]) parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '?qp1=42' assert cf_args == {'foo': 'bar'} def test_two_two_qp(self): """Test with two filter arguments, one of which is a list of two, and both are query parms.""" filter_args = OrderedDict([('qp1', 'bar'), ('qp2', [42, 7])]) parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '?qp1=bar&qp2=42&qp2=7' assert cf_args == {} def test_two_str_reserved_val_qp(self): """Test with two filter arguments, one of which is a list of two, and has reserved URI chars, and both are query parms.""" char_str = '_'.join(self.reserved_chars) escape_str = '_'.join(self.reserved_escapes) filter_args = OrderedDict([('qp1', 'bar'), ('qp2', [42, char_str])]) parm_str, cf_args = self.mgr._divide_filter_args(filter_args) assert parm_str == '?qp1=bar&qp2=42&qp2={}'.format(escape_str) assert cf_args == {} zhmcclient-0.22.0/tests/unit/zhmcclient/test_logging.py0000644000076500000240000002231013364325033023634 0ustar maierastaff00000000000000# 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. """ Unit tests for _logging module. """ from __future__ import absolute_import, print_function import logging import pytest from testfixtures import LogCapture from zhmcclient._logging import logged_api_call, get_logger # # Various uses of the @logged_api_call decorator # @logged_api_call def decorated_global_function(): """A decorated function at the global (module) level.""" pass def global1_function(): @logged_api_call def decorated_inner1_function(): """A decorated inner function defined in a global function.""" pass return decorated_inner1_function def get_decorated_inner1_function(): return global1_function() def global2_function(): def inner1_function(): @logged_api_call def decorated_inner2_function(): """A decorated inner function defined in another inner function that is defined in a global function.""" pass return decorated_inner2_function return inner1_function() def get_decorated_inner2_function(): return global2_function() class Decorator1Class(object): @logged_api_call def decorated_method(self): """A decorated method of a class.""" pass class Decorator2Class(object): @staticmethod def method(): @logged_api_call def decorated_inner_function(): """A decorated inner function defined in a method of a class.""" pass return decorated_inner_function @staticmethod def get_decorated_inner_function(): return Decorator2Class.method() # # Supporting definitions # class CallerClass(object): @staticmethod def call_from_method(func, *args, **kwargs): """ A supporting method that calls the specified function with the specified arguments and keyword arguments. This is used by the test cases so that this function acts as a caller for the decorated API function. """ return func(*args, **kwargs) def call_from_global(func, *args, **kwargs): """ A supporting global function that calls the specified function with the specified arguments and keyword arguments. This is used by the test cases so that this function acts as a caller for the decorated API function. """ return func(*args, **kwargs) # Some expected values that are constant _EXP_LOGGER_NAME = 'zhmcclient.api' _EXP_LOG_LEVEL = 'DEBUG' _EXP_LOG_MSG_ENTER = "==> %s, args: %.500r, kwargs: %.500r" _EXP_LOG_MSG_LEAVE = "<== %s, result: %.1000r" @pytest.fixture() def capture(): """ This way of defining a fixture works around the issue that when using the decorator testfixtures.log_capture() instead, pytest fails with "fixture 'capture' not found". """ with LogCapture(level=logging.DEBUG) as log: yield log # # Test cases # class TestLoggingDecorator(object): """All test cases for the @logged_api_call decorator.""" def assert_log_capture(self, log_capture, exp_apifunc): assert len(log_capture.records) == 2 enter_record = log_capture.records[0] assert enter_record.name == _EXP_LOGGER_NAME assert enter_record.levelname == _EXP_LOG_LEVEL assert enter_record.msg == _EXP_LOG_MSG_ENTER assert enter_record.args[0] == exp_apifunc # We don't check the positional args and keyword args leave_record = log_capture.records[1] assert leave_record.name == _EXP_LOGGER_NAME assert leave_record.levelname == _EXP_LOG_LEVEL assert leave_record.msg == _EXP_LOG_MSG_LEAVE assert leave_record.args[0] == exp_apifunc # We don't check the positional args and keyword args def test_1a_global_from_global(self, capture): """Simple test calling a decorated global function from a global function.""" call_from_global(decorated_global_function) self.assert_log_capture(capture, 'decorated_global_function()') def test_1b_global_from_method(self, capture): """Simple test calling a decorated global function from a method.""" CallerClass().call_from_method(decorated_global_function) self.assert_log_capture(capture, 'decorated_global_function()') def test_2a_global_inner1_from_global(self, capture): """Simple test calling a decorated inner function defined in a global function from a global function.""" decorated_inner1_function = get_decorated_inner1_function() call_from_global(decorated_inner1_function) self.assert_log_capture(capture, 'global1_function.decorated_inner1_function()') def test_2b_global_inner1_from_method(self, capture): """Simple test calling a decorated inner function defined in a global function from a method.""" decorated_inner1_function = get_decorated_inner1_function() CallerClass().call_from_method(decorated_inner1_function) self.assert_log_capture(capture, 'global1_function.decorated_inner1_function()') def test_3a_global_inner2_from_global(self, capture): """Simple test calling a decorated inner function defined in an inner function defined in a global function from a global function.""" decorated_inner2_function = get_decorated_inner2_function() call_from_global(decorated_inner2_function) self.assert_log_capture(capture, 'inner1_function.decorated_inner2_function()') def test_3b_global_inner1_from_method(self, capture): """Simple test calling a decorated inner function defined in an inner function defined in a global function from a method.""" decorated_inner2_function = get_decorated_inner2_function() CallerClass().call_from_method(decorated_inner2_function) self.assert_log_capture(capture, 'inner1_function.decorated_inner2_function()') def test_4a_method_from_global(self, capture): """Simple test calling a decorated method from a global function.""" decorated_method = Decorator1Class.decorated_method d = Decorator1Class() call_from_global(decorated_method, d) self.assert_log_capture(capture, 'Decorator1Class.decorated_method()') def test_4b_method_from_method(self, capture): """Simple test calling a decorated method from a method.""" decorated_method = Decorator1Class.decorated_method d = Decorator1Class() CallerClass().call_from_method(decorated_method, d) self.assert_log_capture(capture, 'Decorator1Class.decorated_method()') def test_5a_method_from_global(self, capture): """Simple test calling a decorated inner function defined in a method from a global function.""" decorated_inner_function = \ Decorator2Class.get_decorated_inner_function() call_from_global(decorated_inner_function) self.assert_log_capture(capture, 'method.decorated_inner_function()') def test_5b_method_from_method(self, capture): """Simple test calling a decorated inner function defined in a method from a method.""" decorated_inner_function = \ Decorator2Class.get_decorated_inner_function() CallerClass().call_from_method(decorated_inner_function) self.assert_log_capture(capture, 'method.decorated_inner_function()') def test_decorated_class(self): """Test that using the decorator on a class raises TypeError.""" with pytest.raises(TypeError): @logged_api_call class DecoratedClass(object): pass def test_decorated_property(self): """Test that using the decorator on a property raises TypeError.""" with pytest.raises(TypeError): class Class(object): @logged_api_call @property def decorated_property(self): return self class TestGetLogger(object): """All test cases for get_logger().""" def test_root_logger(self): """Test that get_logger('') returns the Python root logger.""" py_logger = logging.getLogger() zhmc_logger = get_logger('') assert zhmc_logger is py_logger def test_foo_logger(self): """Test that get_logger('zhmcclient.foo') returns the same-named Python logger and has at least one handler.""" py_logger = logging.getLogger('zhmcclient.foo') zhmc_logger = get_logger('zhmcclient.foo') assert zhmc_logger is py_logger assert len(zhmc_logger.handlers) >= 1 zhmcclient-0.22.0/tests/unit/zhmcclient/test_port.py0000644000076500000240000002410113364325033023172 0ustar maierastaff00000000000000# 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. """ Unit tests for _port module. """ from __future__ import absolute_import, print_function # FIXME: Migrate requests_mock to zhmcclient_mock. import requests_mock from zhmcclient import Session, Client class TestPort(object): """All tests for Port and PortManager classes.""" def setup_method(self): self.session = Session('port-dpm-host', 'port-user', 'port-pwd') self.client = Client(self.session) with requests_mock.mock() as m: # Because logon is deferred until needed, we perform it # explicitly in order to keep mocking in the actual test simple. m.post('/api/sessions', json={'api-session': 'port-session-id'}) self.session.logon() self.cpc_mgr = self.client.cpcs with requests_mock.mock() as m: result = { 'cpcs': [ { 'object-uri': '/api/cpcs/port-cpc-id-1', 'name': 'CPC', 'status': 'service-required', } ] } m.get('/api/cpcs', json=result) cpcs = self.cpc_mgr.list() self.cpc = cpcs[0] adapter_mgr = self.cpc.adapters with requests_mock.mock() as m: self.result = { 'adapters': [ { 'adapter-family': 'ficon', 'adapter-id': '18C', 'type': 'fcp', 'status': 'active', 'object-uri': '/api/adapters/fake-adapter-id-1', 'name': 'FCP Adapter 1', 'port-count': 1, 'storage-port-uris': [ '/api/adapters/fake-adapter-id-1/storage-ports/0' ] }, { 'adapter-family': 'osa', 'adapter-id': '1C4', 'type': 'osd', 'status': 'active', 'object-uri': '/api/adapters/fake-adapter-id-2', 'name': 'OSD Adapter 1', 'port-count': 2, 'network-port-uris': [ '/api/adapters/fake-adapter-id-2/network-ports/0', '/api/adapters/fake-adapter-id-2/network-ports/1' ] }, { 'status': 'not-active', 'configured-capacity': 3, 'description': '', 'parent': '/api/cpcs//port-cpc-id-1', 'object-id': 'fake-adapter-id-3', 'detected-card-type': 'zedc-express', 'class': 'adapter', 'name': 'zEDC 01CC Z15B-23', 'used-capacity': 0, 'adapter-id': '1CC', 'maximum-total-capacity': 15, 'adapter-family': 'accelerator', 'allowed-capacity': 15, 'state': 'reserved', 'object-uri': '/api/adapters/fake-adapter-id-3', 'card-location': 'Z15B-LG23', 'type': 'zedc' } ] } m.get('/api/cpcs/port-cpc-id-1/adapters', json=self.result) adapters = adapter_mgr.list(full_properties=False) self.adapters = adapters def teardown_method(self): with requests_mock.mock() as m: m.delete('/api/sessions/this-session', status_code=204) self.session.logoff() def test_init(self): """Test __init__() on PortManager instance in Adapter.""" port_mgr = self.adapters[0].ports assert port_mgr.adapter == self.adapters[0] def test_list_short_ok(self): """ Test successful list() with short set of properties on PortManager instance in Adapter. """ adapters = self.adapters for idy, adapter in enumerate(adapters): with requests_mock.mock() as m: m.get(adapter.uri, json=adapter.properties) port_mgr = adapter.ports ports = port_mgr.list(full_properties=False) if len(ports) != 0: result_adapter = self.result['adapters'][idy] if 'storage-port-uris' in result_adapter: storage_uris = result_adapter['storage-port-uris'] uris = storage_uris else: network_uris = result_adapter['network-port-uris'] uris = network_uris assert adapter.properties['port-count'] == len(uris) else: uris = [] assert len(ports) == len(uris) for idx, port in enumerate(ports): assert port.properties['element-uri'] in uris assert not port.full_properties assert port.manager == port_mgr def test_list_full_ok(self): """ Test successful list() with full set of properties on PortManager instance in Adapter. """ adapters = self.adapters adapter = adapters[0] port_mgr = adapter.ports with requests_mock.mock() as m: mock_result_port1 = { 'parent': '/api/adapters/fake-adapter-id-1', 'index': 0, 'fabric-id': '', 'description': '', 'element-uri': '/api/adapters/fake-adapter-id-1/storage-ports/0', 'element-id': '0', 'class': 'storage-port', 'name': 'Port 0' } m.get('/api/adapters/fake-adapter-id-1/storage-ports/0', json=mock_result_port1) ports = port_mgr.list(full_properties=True) if len(ports) != 0: storage_uris = self.result['adapters'][0]['storage-port-uris'] assert adapter.properties['port-count'] == len(storage_uris) else: storage_uris = [] assert len(ports) == len(storage_uris) for idx, port in enumerate(ports): assert port.properties['element-uri'] == storage_uris[idx] assert port.full_properties assert port.manager == port_mgr def test_list_filter_name_ok(self): """ Test successful list() with filter arguments using the 'name' property on a PortManager instance in a partition. """ adapters = self.adapters adapter = adapters[0] port_mgr = adapter.ports with requests_mock.mock() as m: mock_result_port1 = { 'parent': '/api/adapters/fake-adapter-id-1', 'index': 0, 'fabric-id': '', 'description': '', 'element-uri': '/api/adapters/fake-adapter-id-1/storage-ports/0', 'element-id': '0', 'class': 'storage-port', 'name': 'Port 0' } m.get('/api/adapters/fake-adapter-id-1/storage-ports/0', json=mock_result_port1) filter_args = {'name': 'Port 0'} ports = port_mgr.list(filter_args=filter_args) assert len(ports) == 1 port = ports[0] assert port.name == 'Port 0' assert port.uri == \ '/api/adapters/fake-adapter-id-1/storage-ports/0' assert port.properties['name'] == 'Port 0' assert port.properties['element-id'] == '0' assert port.manager == port_mgr def test_list_filter_elementid_ok(self): """ Test successful list() with filter arguments using the 'element-id' property on a PortManager instance in a partition. """ adapters = self.adapters adapter = adapters[0] port_mgr = adapter.ports with requests_mock.mock() as m: mock_result_port1 = { 'parent': '/api/adapters/fake-adapter-id-1', 'index': 0, 'fabric-id': '', 'description': '', 'element-uri': '/api/adapters/fake-adapter-id-1/storage-ports/0', 'element-id': '0', 'class': 'storage-port', 'name': 'Port 0' } m.get('/api/adapters/fake-adapter-id-1/storage-ports/0', json=mock_result_port1) filter_args = {'element-id': '0'} ports = port_mgr.list(filter_args=filter_args) assert len(ports) == 1 port = ports[0] assert port.name == 'Port 0' assert port.uri == \ '/api/adapters/fake-adapter-id-1/storage-ports/0' assert port.properties['name'] == 'Port 0' assert port.properties['element-id'] == '0' assert port.manager == port_mgr def test_update_properties(self): """ This tests the 'Update Port Properties' operation. """ port_mgr = self.adapters[0].ports ports = port_mgr.list(full_properties=False) port = ports[0] with requests_mock.mock() as m: m.post('/api/adapters/fake-adapter-id-1/storage-ports/0', status_code=204) port.update_properties(properties={}) zhmcclient-0.22.0/tests/unit/zhmcclient/test_console.py0000644000076500000240000002433613364325033023662 0ustar maierastaff00000000000000# 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. """ Unit tests for _console module. """ from __future__ import absolute_import, print_function import pytest import re from zhmcclient import Client, Error, Console, UserManager, UserRoleManager, \ UserPatternManager, PasswordRuleManager, TaskManager, \ LdapServerDefinitionManager from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources class TestConsole(object): """All tests for the Console and ConsoleManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked Console without any child resources. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) self.faked_console = self.session.hmc.consoles.add({ 'object-id': None, # object-uri will be automatically set 'parent': None, 'class': 'console', 'name': 'fake-console1', 'description': 'Console #1', }) def test_consolemanager_initial_attrs(self): """Test initial attributes of ConsoleManager.""" console_mgr = self.client.consoles # Verify all public properties of the manager object assert console_mgr.resource_class == Console assert console_mgr.class_name == 'console' assert console_mgr.session == self.session assert console_mgr.parent is None assert console_mgr.client == self.client # TODO: Test for ConsoleManager.__repr__() @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(full_properties=False), ['object-uri']), (dict(full_properties=True), ['object-uri', 'name']), (dict(), # test default for full_properties (True) ['object-uri', 'name']), ] ) @pytest.mark.parametrize( "filter_args", [ # will be ignored None, {}, {'name': 'foo'}, ] ) def test_consolemanager_list( self, filter_args, full_properties_kwargs, prop_names): """Test ConsoleManager.list().""" exp_faked_consoles = [self.faked_console] console_mgr = self.client.consoles # Execute the code to be tested consoles = console_mgr.list( filter_args=filter_args, **full_properties_kwargs) assert_resources(consoles, exp_faked_consoles, prop_names) def test_console_initial_attrs(self): """Test initial attributes of Console.""" console_mgr = self.client.consoles console = console_mgr.find(name=self.faked_console.name) # Verify all public properties of the resource object (except those # of its BaseResource superclass which are tested at that level). assert isinstance(console.users, UserManager) assert isinstance(console.user_roles, UserRoleManager) assert isinstance(console.user_patterns, UserPatternManager) assert isinstance(console.password_rules, PasswordRuleManager) assert isinstance(console.tasks, TaskManager) assert isinstance(console.ldap_server_definitions, LdapServerDefinitionManager) def test_console_repr(self): """Test Console.__repr__().""" console_mgr = self.client.consoles console = console_mgr.find(name=self.faked_console.name) # Execute the code to be tested repr_str = repr(console) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=console.__class__.__name__, id=id(console)), repr_str) @pytest.mark.parametrize( "wait", [ True, # False, # TODO: Re-enable once implemented ] ) @pytest.mark.parametrize( "force", [ True, False, ] ) def test_console_restart(self, force, wait): """Test Console.restart().""" console_mgr = self.client.consoles console = console_mgr.find(name=self.faked_console.name) # Note: The force parameter is passed in, but we expect the restart # to always succeed. This means we are not testing cases with other # HMC users being logged on, that would either cause a non-forced # restart to be rejected, or a forced restart to succeed despite them. # Execute the code to be tested. ret = console.restart( force=force, wait_for_available=wait, operation_timeout=600) assert ret is None if wait: # The HMC is expected to be up again, and therefore a simple # operation is expected to succeed: try: self.client.query_api_version() except Error as exc: pytest.fail( "Unexpected zhmcclient exception during " "query_api_version() after HMC restart: %s" % exc) else: # The HMC is expected to still be in the restart process, and # therefore a simple operation is expected to fail. This test # may end up to be timing dependent. try: self.client.query_api_version() except Error: pass except Exception as exc: pytest.fail( "Unexpected non-zhmcclient exception during " "query_api_version() after HMC restart: %s" % exc) else: pytest.fail( "Unexpected success of query_api_version() after HMC " "restart.") @pytest.mark.parametrize( "force", [ True, False, ] ) def test_console_shutdown(self, force): """Test Console.shutdown().""" console_mgr = self.client.consoles console = console_mgr.find(name=self.faked_console.name) # Note: The force parameter is passed in, but we expect the shutdown # to always succeed. This means we are not testing cases with other # HMC users being logged on, that would either cause a non-forced # shutdown to be rejected, or a forced shutdown to succeed despite # them. # Execute the code to be tested. ret = console.shutdown(force=force) assert ret is None # The HMC is expected to be offline, and therefore a simple operation # is expected to fail. try: self.client.query_api_version() except Error: pass except Exception as exc: pytest.fail( "Unexpected non-zhmcclient exception during " "query_api_version() after HMC shutdown: %s" % exc) else: pytest.fail( "Unexpected success of query_api_version() after HMC " "shutdown.") def test_console_audit_log(self): """Test Console.get_audit_log().""" # TODO: Add begin/end_time once mocked audit log is supported console_mgr = self.client.consoles console = console_mgr.find(name=self.faked_console.name) # Execute the code to be tested. log_items = console.get_audit_log() assert isinstance(log_items, list) # TODO: Verify log items once mocked audit log is supported def test_console_security_log(self): """Test Console.get_security_log().""" # TODO: Add begin/end_time once mocked security log is supported console_mgr = self.client.consoles console = console_mgr.find(name=self.faked_console.name) # Execute the code to be tested. log_items = console.get_security_log() assert isinstance(log_items, list) # TODO: Verify log items once mocked security log is supported @pytest.mark.parametrize( "name, exp_cpc_names, prop_names", [ (None, ['ucpc_name_1', 'ucpc_name_2'], ['object-uri', 'name']), ('.*', ['ucpc_name_1', 'ucpc_name_2'], ['object-uri', 'name']), ('ucpc_name_.*', ['ucpc_name_1', 'ucpc_name_2'], ['object-uri', 'name']), ('ucpc_name_1', ['ucpc_name_1'], ['object-uri', 'name']), ('ucpc_name_1.*', ['ucpc_name_1'], ['object-uri', 'name']), ('ucpc_name_1.+', [], ['object-uri', 'name']), ] ) def test_console_list_unmanaged_cpcs( self, name, exp_cpc_names, prop_names): """Test Console.list_unmanaged_cpcs() and UnmanagedCpcManager.list().""" console_mgr = self.client.consoles console = console_mgr.find(name=self.faked_console.name) # Add two unmanaged faked CPCs faked_ucpc1 = self.faked_console.unmanaged_cpcs.add({ 'object-id': 'ucpc-oid-1', # object-uri is set up automatically 'name': 'ucpc_name_1', }) faked_ucpc2 = self.faked_console.unmanaged_cpcs.add({ 'object-id': 'ucpc-oid-2', # object-uri is set up automatically 'name': 'ucpc_name_2', }) faked_ucpcs = [faked_ucpc1, faked_ucpc2] exp_faked_ucpcs = [cpc for cpc in faked_ucpcs if cpc.name in exp_cpc_names] # Execute the code to be tested. # This indirectly tests UnmanagedCpcManager.list(). ucpcs = console.list_unmanaged_cpcs(name) assert_resources(ucpcs, exp_faked_ucpcs, prop_names) zhmcclient-0.22.0/tests/unit/zhmcclient/test_metrics.py0000644000076500000240000003332713364325033023666 0ustar maierastaff00000000000000# Copyright 2017-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. """ Unit tests for _metrics module. """ from __future__ import absolute_import, print_function import pytest import re from zhmcclient import Client, MetricsContext, HTTPError, NotFound from zhmcclient_mock import FakedSession, FakedMetricGroupDefinition from tests.common.utils import assert_resources # Object IDs of our faked metrics contexts: MC1_OID = 'mc1-oid' MC2_OID = 'mc2-oid' # Names of our faked metric groups: MG1_NAME = 'mg1-name' MG2_NAME = 'mg2-name' # Base URI for metrics contexts (Object ID will be added to get URI): MC_BASE_URI = '/api/services/metrics/context/' class TestMetricsContext(object): """All tests for the MetricsContext and MetricsContextManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked CPC in DPM mode without any child resources. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) def add_metricgroupdefinition1(self): """Add metric group definition 1.""" faked_metricgroupdefinition = FakedMetricGroupDefinition( name=MG1_NAME, types=[ ('faked-metric11', 'integer-metric'), ('faked-metric12', 'string-metric'), ] ) self.session.hmc.metrics_contexts.add_metric_group_definition( faked_metricgroupdefinition) return faked_metricgroupdefinition def add_metricgroupdefinition2(self): """Add metric group definition 2.""" faked_metricgroupdefinition = FakedMetricGroupDefinition( name=MG2_NAME, types=[ ('faked-metric21', 'boolean-metric'), ] ) self.session.hmc.metrics_contexts.add_metric_group_definition( faked_metricgroupdefinition) return faked_metricgroupdefinition def add_metricscontext1(self): """Add faked metrics context 1.""" faked_metricscontext = self.session.hmc.metrics_contexts.add({ 'fake-id': MC1_OID, 'anticipated-frequency-seconds': 10, 'metric-groups': [MG1_NAME, MG2_NAME], }) return faked_metricscontext def add_metricscontext2(self): """Add faked metrics context 2.""" faked_metricscontext = self.session.hmc.metrics_contexts.add({ 'fake-id': MC2_OID, 'anticipated-frequency-seconds': 10, 'metric-groups': [MG1_NAME], }) return faked_metricscontext def create_metricscontext1(self): """create (non-faked) metrics context 1.""" mc_props = { 'anticipated-frequency-seconds': 10, 'metric-groups': [MG1_NAME, MG2_NAME], } metricscontext = self.client.metrics_contexts.create(mc_props) return metricscontext def create_metricscontext2(self): """create (non-faked) metrics context 2.""" mc_props = { 'anticipated-frequency-seconds': 10, 'metric-groups': [MG1_NAME], } metricscontext = self.client.metrics_contexts.create(mc_props) return metricscontext def test_metricscontextmanager_initial_attrs(self): """Test initial attributes of MetricsContextManager.""" metricscontext_mgr = self.client.metrics_contexts # Verify all public properties of the manager object assert metricscontext_mgr.resource_class == MetricsContext assert metricscontext_mgr.session == self.session assert metricscontext_mgr.parent is None assert metricscontext_mgr.client == self.client # TODO: Test for MetricsContextManager.__repr__() @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(), ['anticipated-frequency-seconds', 'metric-groups']), (dict(full_properties=False), ['anticipated-frequency-seconds', 'metric-groups']), (dict(full_properties=True), ['anticipated-frequency-seconds', 'metric-groups']), ] ) def test_metricscontextmanager_list_full_properties( self, full_properties_kwargs, prop_names): """Test MetricsContextManager.list() with full_properties.""" # Add faked metric groups self.add_metricgroupdefinition1() self.add_metricgroupdefinition2() # Create (non-faked) metrics contexts (list() will only return those) metricscontext1 = self.create_metricscontext1() metricscontext2 = self.create_metricscontext2() exp_metricscontexts = [metricscontext1, metricscontext2] metricscontext_mgr = self.client.metrics_contexts # Execute the code to be tested metricscontexts = metricscontext_mgr.list(**full_properties_kwargs) assert_resources(metricscontexts, exp_metricscontexts, prop_names) @pytest.mark.parametrize( "filter_args, exp_names", [ ({'object-id': MC1_OID}, [MG1_NAME]), ({'object-id': MC2_OID}, [MG2_NAME]), ({'object-id': [MC1_OID, MC2_OID]}, [MG1_NAME, MG2_NAME]), ({'object-id': [MC1_OID, MC1_OID]}, [MG1_NAME]), ({'object-id': MC1_OID + 'foo'}, []), ({'object-id': [MC1_OID, MC2_OID + 'foo']}, [MG1_NAME]), ({'object-id': [MC2_OID + 'foo', MC1_OID]}, [MG1_NAME]), ({'name': MG1_NAME}, [MG1_NAME]), ({'name': MG2_NAME}, [MG2_NAME]), ({'name': [MG1_NAME, MG2_NAME]}, [MG1_NAME, MG2_NAME]), ({'name': MG1_NAME + 'foo'}, []), ({'name': [MG1_NAME, MG2_NAME + 'foo']}, [MG1_NAME]), ({'name': [MG2_NAME + 'foo', MG1_NAME]}, [MG1_NAME]), ({'name': [MG1_NAME, MG1_NAME]}, [MG1_NAME]), ({'name': '.*part 1'}, [MG1_NAME]), ({'name': 'part 1.*'}, [MG1_NAME]), ({'name': 'part .'}, [MG1_NAME, MG2_NAME]), ({'name': '.art 1'}, [MG1_NAME]), ({'name': '.+'}, [MG1_NAME, MG2_NAME]), ({'name': 'part 1.+'}, []), ({'name': '.+part 1'}, []), ({'name': MG1_NAME, 'object-id': MC1_OID}, [MG1_NAME]), ({'name': MG1_NAME, 'object-id': MC1_OID + 'foo'}, []), ({'name': MG1_NAME + 'foo', 'object-id': MC1_OID}, []), ({'name': MG1_NAME + 'foo', 'object-id': MC1_OID + 'foo'}, []), ] ) @pytest.mark.skip # TODO: Test for MetricsContextManager.list() w/ filter def test_metricscontextmanager_list_filter_args( self, filter_args, exp_names): """Test MetricsContextManager.list() with filter_args.""" # Add faked metric groups self.add_metricgroupdefinition1() self.add_metricgroupdefinition2() # Create (non-faked) metrics contexts (list() will only return those) self.create_metricscontext1() self.create_metricscontext2() metricscontext_mgr = self.client.metrics_contexts # Execute the code to be tested metricscontexts = metricscontext_mgr.list(filter_args=filter_args) names = [p.properties['name'] for p in metricscontexts] assert set(names) == set(exp_names) @pytest.mark.parametrize( "input_props, exp_prop_names, exp_exc", [ ({}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X'}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-part-x'}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-part-x', 'initial-memory': 1024}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-part-x', 'initial-memory': 1024, 'maximum-memory': 1024}, ['object-uri', 'name', 'initial-memory', 'maximum-memory'], None), ({'name': 'fake-part-x', 'initial-memory': 1024, 'maximum-memory': 1024, 'description': 'fake description X'}, ['object-uri', 'name', 'initial-memory', 'maximum-memory', 'description'], None), ] ) @pytest.mark.skip # TODO: Test for MetricsContextManager.create() def test_metricscontextmanager_create( self, input_props, exp_prop_names, exp_exc): """Test MetricsContextManager.create().""" metricscontext_mgr = self.client.metrics_contexts if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested metricscontext = metricscontext_mgr.create( properties=input_props) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason else: # Execute the code to be tested. # Note: the MetricsContext object returned by # MetricsContext.create() has the input properties plus # 'object-uri'. metricscontext = metricscontext_mgr.create(properties=input_props) # Check the resource for consistency within itself assert isinstance(metricscontext, MetricsContext) metricscontext_name = metricscontext.name exp_metricscontext_name = metricscontext.properties['name'] assert metricscontext_name == exp_metricscontext_name metricscontext_uri = metricscontext.uri exp_metricscontext_uri = metricscontext.properties['object-uri'] assert metricscontext_uri == exp_metricscontext_uri # Check the properties against the expected names and values for prop_name in exp_prop_names: assert prop_name in metricscontext.properties if prop_name in input_props: value = metricscontext.properties[prop_name] exp_value = input_props[prop_name] assert value == exp_value @pytest.mark.skip # TODO: Test for MetricsContextManager.__repr__() def test_metricscontext_repr(self): """Test MetricsContext.__repr__().""" # Add a faked metrics context faked_metricscontext = self.add_metricscontext1() metricscontext_mgr = self.client.metrics_contexts metricscontext = metricscontext_mgr.find( name=faked_metricscontext.name) # Execute the code to be tested repr_str = repr(metricscontext) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=metricscontext.__class__.__name__, id=id(metricscontext)), repr_str) @pytest.mark.parametrize( "initial_status, exp_exc", [ ('stopped', None), ('terminated', HTTPError({'http-status': 409, 'reason': 1})), ('starting', HTTPError({'http-status': 409, 'reason': 1})), ('active', HTTPError({'http-status': 409, 'reason': 1})), ('stopping', HTTPError({'http-status': 409, 'reason': 1})), ('degraded', HTTPError({'http-status': 409, 'reason': 1})), ('reservation-error', HTTPError({'http-status': 409, 'reason': 1})), ('paused', HTTPError({'http-status': 409, 'reason': 1})), ] ) @pytest.mark.skip # TODO: Test for MetricsContext.delete() def test_metricscontext_delete(self, initial_status, exp_exc): """Test MetricsContext.delete().""" # Add a faked metrics context faked_metricscontext = self.add_metricscontext1() # Set the initial status of the faked metrics context faked_metricscontext.properties['status'] = initial_status metricscontext_mgr = self.client.metrics_contexts metricscontext = metricscontext_mgr.find( name=faked_metricscontext.name) if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested metricscontext.delete() exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the metrics context still exists metricscontext_mgr.find(name=faked_metricscontext.name) else: # Execute the code to be tested. metricscontext.delete() # Check that the metrics context no longer exists with pytest.raises(NotFound) as exc_info: metricscontext_mgr.find(name=faked_metricscontext.name) zhmcclient-0.22.0/tests/unit/zhmcclient/test_virtual_function.py0000644000076500000240000003045713364325033025614 0ustar maierastaff00000000000000# 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. """ Unit tests for _virtual_function module. """ from __future__ import absolute_import, print_function # FIXME: Migrate requests_mock to zhmcclient_mock. import requests_mock from zhmcclient import Session, Client, VirtualFunction class TestVirtualFunction(object): """ All tests for VirtualFunction and VirtualFunctionManager classes. """ def setup_method(self): self.session = Session('test-dpm-host', 'test-user', 'test-id') self.client = Client(self.session) with requests_mock.mock() as m: # Because logon is deferred until needed, we perform it # explicitly in order to keep mocking in the actual test simple. m.post('/api/sessions', json={'api-session': 'test-session-id'}) self.session.logon() self.cpc_mgr = self.client.cpcs with requests_mock.mock() as m: result = { 'cpcs': [ { 'object-uri': '/api/cpcs/fake-cpc-id-1', 'name': 'CPC1', 'status': '', } ] } m.get('/api/cpcs', json=result) # self.cpc = self.cpc_mgr.find(name="CPC1", full_properties=False) cpcs = self.cpc_mgr.list() self.cpc = cpcs[0] partition_mgr = self.cpc.partitions with requests_mock.mock() as m: result = { 'partitions': [ { 'status': 'active', 'object-uri': '/api/partitions/fake-part-id-1', 'name': 'PART1' }, { 'status': 'stopped', 'object-uri': '/api/partitions/fake-part-id-2', 'name': 'PART2' } ] } m.get('/api/cpcs/fake-cpc-id-1/partitions', json=result) mock_result_part1 = { 'status': 'active', 'object-uri': '/api/partitions/fake-part-id-1', 'name': 'PART1', 'description': 'Test Partition', 'more_properties': 'bliblablub', 'virtual-function-uris': [ '/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-1', '/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-2' ] } m.get('/api/partitions/fake-part-id-1', json=mock_result_part1) mock_result_part2 = { 'status': 'stopped', 'object-uri': '/api/partitions/fake-lpar-id-2', 'name': 'PART2', 'description': 'Test Partition', 'more_properties': 'bliblablub', 'virtual-function-uris': [ '/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-1', '/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-2' ] } m.get('/api/partitions/fake-part-id-2', json=mock_result_part2) partitions = partition_mgr.list(full_properties=True) self.partition = partitions[0] def teardown_method(self): with requests_mock.mock() as m: m.delete('/api/sessions/this-session', status_code=204) self.session.logoff() def test_init(self): """Test __init__() on VirtualFunctionManager instance in Partition.""" vf_mgr = self.partition.virtual_functions assert vf_mgr.partition == self.partition def test_list_short_ok(self): """ Test successful list() with short set of properties on VirtualFunctionManager instance in partition. """ vf_mgr = self.partition.virtual_functions vfs = vf_mgr.list(full_properties=False) assert len(vfs) == \ len(self.partition.properties['virtual-function-uris']) for idx, vf in enumerate(vfs): assert vf.properties['element-uri'] == \ self.partition.properties['virtual-function-uris'][idx] assert vf.uri == \ self.partition.properties['virtual-function-uris'][idx] assert not vf.full_properties assert vf.manager == vf_mgr def test_list_full_ok(self): """ Test successful list() with full set of properties on VirtualFunctionManager instance in partition. """ vf_mgr = self.partition.virtual_functions with requests_mock.mock() as m: mock_result_vf1 = { 'parent': '/api/partitions/fake-part-id-1', 'name': 'vf1', 'element-uri': '/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-1', 'class': 'virtual-function', 'element-id': 'fake-vf-id-1', 'description': '', 'more_properties': 'bliblablub' } m.get('/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-1', json=mock_result_vf1) mock_result_vf2 = { 'parent': '/api/partitions/fake-part-id-1', 'name': 'vf2', 'element-uri': '/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-2', 'class': 'virtual-function', 'element-id': 'fake-vf-id-2', 'description': '', 'more_properties': 'bliblablub' } m.get('/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-2', json=mock_result_vf2) vfs = vf_mgr.list(full_properties=True) assert len(vfs) == \ len(self.partition.properties['virtual-function-uris']) for idx, vf in enumerate(vfs): assert vf.properties['element-uri'] == \ self.partition.properties['virtual-function-uris'][idx] assert vf.uri == \ self.partition.properties['virtual-function-uris'][idx] assert vf.full_properties assert vf.manager == vf_mgr def test_list_filter_name_ok(self): """ Test successful list() with filter arguments using the 'name' property on a VirtualFunctionManager instance in a partition. """ vf_mgr = self.partition.virtual_functions with requests_mock.mock() as m: mock_result_vf1 = { 'parent': '/api/partitions/fake-part-id-1', 'name': 'vf1', 'element-uri': '/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-1', 'class': 'virtual-function', 'element-id': 'fake-vf-id-1', 'description': '', 'more_properties': 'bliblablub' } m.get('/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-1', json=mock_result_vf1) mock_result_vf2 = { 'parent': '/api/partitions/fake-part-id-1', 'name': 'vf2', 'element-uri': '/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-2', 'class': 'virtual-function', 'element-id': 'fake-vf-id-2', 'description': '', 'more_properties': 'bliblablub' } m.get('/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-2', json=mock_result_vf2) filter_args = {'name': 'vf2'} vfs = vf_mgr.list(filter_args=filter_args) assert len(vfs) == 1 vf = vfs[0] assert vf.name == 'vf2' assert vf.uri == \ '/api/partitions/fake-part-id-1/virtual-functions/' \ 'fake-vf-id-2' assert vf.properties['name'] == 'vf2' assert vf.properties['element-id'] == 'fake-vf-id-2' assert vf.manager == vf_mgr def test_list_filter_elementid_ok(self): """ Test successful list() with filter arguments using the 'element-id' property on a VirtualFunctionManager instance in a partition. """ vf_mgr = self.partition.virtual_functions with requests_mock.mock() as m: mock_result_vf1 = { 'parent': '/api/partitions/fake-part-id-1', 'name': 'vf1', 'element-uri': '/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-1', 'class': 'virtual-function', 'element-id': 'fake-vf-id-1', 'description': '', 'more_properties': 'bliblablub' } m.get('/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-1', json=mock_result_vf1) mock_result_vf2 = { 'parent': '/api/partitions/fake-part-id-1', 'name': 'vf2', 'element-uri': '/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-2', 'class': 'virtual-function', 'element-id': 'fake-vf-id-2', 'description': '', 'more_properties': 'bliblablub' } m.get('/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-2', json=mock_result_vf2) filter_args = {'element-id': 'fake-vf-id-2'} vfs = vf_mgr.list(filter_args=filter_args) assert len(vfs) == 1 vf = vfs[0] assert vf.name == 'vf2' assert vf.uri == \ '/api/partitions/fake-part-id-1/virtual-functions/' \ 'fake-vf-id-2' assert vf.properties['name'] == 'vf2' assert vf.properties['element-id'] == 'fake-vf-id-2' assert vf.manager == vf_mgr def test_create(self): """ This tests the 'Create Virtual Function' operation. """ vf_mgr = self.partition.virtual_functions with requests_mock.mock() as m: result = { 'element-uri': '/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-1' } m.post('/api/partitions/fake-part-id-1/virtual-functions', json=result) vf = vf_mgr.create(properties={}) assert isinstance(vf, VirtualFunction) assert vf.properties == result assert vf.uri == result['element-uri'] def test_delete(self): """ This tests the 'Delete Virtual Function' operation. """ vf_mgr = self.partition.virtual_functions vfs = vf_mgr.list(full_properties=False) vf = vfs[0] with requests_mock.mock() as m: m.delete('/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-1', status_code=204) vf.delete() def test_update_properties(self): """ This tests the 'Update Virtual Function Properties' operation. """ vf_mgr = self.partition.virtual_functions vfs = vf_mgr.list(full_properties=False) vf = vfs[0] with requests_mock.mock() as m: m.post('/api/partitions/fake-part-id-1/virtual-functions/' 'fake-vf-id-1', status_code=204) vf.update_properties(properties={}) zhmcclient-0.22.0/tests/unit/zhmcclient/test_session.py0000644000076500000240000006264013364325033023703 0ustar maierastaff00000000000000# 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. """ Unit tests for _session module. """ from __future__ import absolute_import, print_function import time import json import re import requests import requests_mock import mock import pytest from zhmcclient import Session, ParseError, Job, HTTPError, OperationTimeout, \ ClientAuthError, DEFAULT_HMC_PORT class TestSession(object): """ All tests for the Session class. """ # TODO: Test Session.get() in all variations (including errors) # TODO: Test Session.post() in all variations (including errors) # TODO: Test Session.delete() in all variations (including errors) @staticmethod def mock_server_1(m): """ Set up the mocked responses for a simple HMC server that supports logon and logoff. """ m.register_uri('POST', '/api/sessions', json={'api-session': 'fake-session-id'}, headers={'X-Request-Id': 'fake-request-id'}) m.register_uri('DELETE', '/api/sessions/this-session', headers={'X-Request-Id': 'fake-request-id'}, status_code=204) @pytest.mark.parametrize( "host, userid, password, use_get_password, session_id, kwargs", [ ('fake-host', None, None, False, None, {}), ('fake-host', 'fake-userid', None, False, None, {}), ('fake-host', 'fake-userid', 'fake-pw', False, None, {}), ('fake-host', 'fake-userid', 'fake-pw', True, None, {}), ('fake-host', 'fake-userid', 'fake-pw', True, None, {'port': 1234}), ] ) def test_init(self, host, userid, password, use_get_password, session_id, kwargs): """Test initialization of Session object.""" # TODO: Add support for input parameter: retry_timeout_config # TODO: Add support for input parameter: time_stats_keeper if use_get_password: def get_password(host, userid): pw = 'fake-pw-{}-{}'.format(host, userid) return pw else: get_password = None session = Session(host, userid, password, session_id, get_password, **kwargs) assert session.host == host assert session.userid == userid assert session._password == password assert session.session_id == session_id assert session.get_password == get_password assert session.port == kwargs.get('port', DEFAULT_HMC_PORT) base_url = 'https://{}:{!s}'.format(session.host, session.port) assert session.base_url == base_url assert session.headers['Content-type'] == 'application/json' assert session.headers['Accept'] == '*/*' if session_id is None: assert session.session is None assert 'X-API-Session' not in session.headers assert len(session.headers) == 2 else: assert isinstance(session.session, requests.Session) assert session.headers['X-API-Session'] == session_id assert len(session.headers) == 3 def test_repr(self): """Test Session.__repr__().""" session = Session('fake-host', 'fake-user', 'fake-pw') repr_str = repr(session) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=session.__class__.__name__, id=id(session)), repr_str) @pytest.mark.parametrize( "host, userid, password, use_get_password, exp_exc", [ ('fake-host', None, None, False, ClientAuthError), ('fake-host', 'fake-userid', None, False, ClientAuthError), ('fake-host', 'fake-userid', 'fake-pw', False, None), ('fake-host', 'fake-userid', 'fake-pw', True, None), ('fake-host', 'fake-userid', None, True, None), ] ) def test_logon( self, host, userid, password, use_get_password, exp_exc): """Test Session.logon() (and also Session.is_logon()).""" with requests_mock.Mocker() as m: self.mock_server_1(m) if use_get_password: get_password = mock.MagicMock() get_password.return_value = \ 'fake-pw-{}-{}'.format(host, userid) else: get_password = None # Create a session in logged-off state session = Session(host, userid, password, None, get_password) assert session.session_id is None assert 'X-API-Session' not in session.headers assert session.session is None logged_on = session.is_logon() assert not logged_on if exp_exc: try: # The code to be tested: session.logon() except exp_exc: pass logged_on = session.is_logon() assert not logged_on else: # The code to be tested: session.logon() assert session.session_id == 'fake-session-id' assert 'X-API-Session' in session.headers assert isinstance(session.session, requests.Session) if get_password: if password is None: get_password.assert_called_with(host, userid) assert session._password == get_password.return_value else: get_password.assert_not_called() logged_on = session.is_logon() assert logged_on def test_logoff(self): """Test Session.logoff() (and also Session.is_logon()).""" with requests_mock.Mocker() as m: self.mock_server_1(m) # Create a session in logged-off state session = Session('fake-host', 'fake-userid', 'fake-pw') session.logon() logged_on = session.is_logon() assert logged_on # The code to be tested: session.logoff() assert session.session_id is None assert session.session is None assert 'X-API-Session' not in session.headers assert len(session.headers) == 2 logged_on = session.is_logon() assert not logged_on def _do_parse_error_logon(self, m, json_content, exp_msg_pattern, exp_line, exp_col): """ Perform a session logon, and mock the provided (invalid) JSON content for the response so that a JSON parsing error is triggered. Assert that this is surfaced via a `zhmcclient.ParseError` exception, with the expected message (as a regexp pattern), line and column. """ m.register_uri('POST', '/api/sessions', content=json_content, headers={'X-Request-Id': 'fake-request-id'}) session = Session('fake-host', 'fake-user', 'fake-pw') exp_pe_pattern = \ r"^JSON parse error in HTTP response: %s\. " \ r"HTTP request: [^ ]+ [^ ]+\. " \ r"Response status .*" % \ exp_msg_pattern with pytest.raises(ParseError) as exc_info: session.logon() exc = exc_info.value assert re.match(exp_pe_pattern, str(exc)) assert exc.line == exp_line assert exc.column == exp_col # TODO: Merge the next 3 test functions into one that is parametrized @requests_mock.mock() def test_logon_error_invalid_delim(self, *args): """ Logon with invalid JSON response that has an invalid delimiter. """ m = args[0] json_content = b'{\n"api-session"; "fake-session-id"\n}' exp_msg_pattern = r"Expecting ':' delimiter: .*" exp_line = 2 exp_col = 14 self._do_parse_error_logon(m, json_content, exp_msg_pattern, exp_line, exp_col) @requests_mock.mock() def test_logon_error_invalid_quotes(self, *args): """ Logon with invalid JSON response that incorrectly uses single quotes. """ m = args[0] json_content = b'{\'api-session\': \'fake-session-id\'}' exp_msg_pattern = r"Expecting property name enclosed in double " \ "quotes: .*" exp_line = 1 exp_col = 2 self._do_parse_error_logon(m, json_content, exp_msg_pattern, exp_line, exp_col) @requests_mock.mock() def test_logon_error_extra_closing(self, *args): """ Logon with invalid JSON response that has an extra closing brace. """ m = args[0] json_content = b'{"api-session": "fake-session-id"}}' exp_msg_pattern = r"Extra data: .*" exp_line = 1 exp_col = 35 self._do_parse_error_logon(m, json_content, exp_msg_pattern, exp_line, exp_col) def test_get_notification_topics(self): """ This tests the 'Get Notification Topics' operation. """ session = Session('fake-host', 'fake-user', 'fake-id') with requests_mock.mock() as m: # Because logon is deferred until needed, we perform it # explicitly in order to keep mocking in the actual test simple. m.post('/api/sessions', json={'api-session': 'fake-session-id'}) session.logon() gnt_uri = "/api/sessions/operations/get-notification-topics" gnt_result = { "topics": [ { 'topic-name': 'ensadmin.145', 'topic-type': 'object-notification', }, { 'topic-name': 'ensadmin.145job', 'topic-type': 'job-notification', }, { 'topic-name': 'ensadmin.145aud', 'topic-type': 'audit-notification', }, { 'topic-name': 'ensadmin.145sec', 'topic-type': 'security-notification', } ] } m.get(gnt_uri, json=gnt_result) result = session.get_notification_topics() assert result == gnt_result['topics'] m.delete('/api/sessions/this-session', status_code=204) session.logoff() def test_get_error_html_1(self): """ This tests a dummy GET with a 500 response with HTML content. """ session = Session('fake-host', 'fake-user', 'fake-id') with requests_mock.mock() as m: get_uri = "/api/version" get_resp_status = 500 get_resp_content_type = 'text/html; charset=ISO-5589-1' get_resp_headers = { 'content-type': get_resp_content_type, } get_resp_content = u"""\ \ \ \ Console Internal Error\ \ \ \

Console Internal Error

\

\

Details:

\


HTTP status code: 500\


The server encountered an internal error that prevented it from\ fulfilling this request.\


\

javax.servlet.ServletException: Web Services are not enabled.
\tat com.ibm.hwmca.fw.api.ApiServlet.execute(ApiServlet.java:135)
\t. . .
\
\ \ """ m.get(get_uri, text=get_resp_content, headers=get_resp_headers, status_code=get_resp_status) # The following expected results reflect what is done in # _session._result_object(). exp_reason = 900 exp_message = \ "Console Configuration Error: " \ "Web Services API is not enabled on the HMC." with pytest.raises(HTTPError) as exc_info: session.get(get_uri, logon_required=False) exc = exc_info.value assert exc.http_status == get_resp_status assert exc.reason == exp_reason assert exc.message == exp_message assert exc.request_uri.endswith(get_uri) assert exc.request_method == 'GET' class TestJob(object): """ Test the ``Job`` class. """ job_uri = '/api/jobs/fake-job-uri' @staticmethod def mock_server_1(m): """ Set up the mocked responses for a simple HMC server that supports logon, logoff. """ m.register_uri('POST', '/api/sessions', json={'api-session': 'fake-session-id'}, headers={'X-Request-Id': 'fake-request-id'}) m.register_uri('DELETE', '/api/sessions/this-session', headers={'X-Request-Id': 'fake-request-id'}, status_code=204) # TODO: Add parametrization to the next test function. def test_init(self): """Test initialization of Job object.""" session = Session('fake-host', 'fake-user', 'fake-pw') # Jobs exist only for POST, but we want to test that the specified HTTP # method comes back regardless: op_method = 'GET' op_uri = '/api/bla' job = Job(session, self.job_uri, op_method, op_uri) assert job.uri == self.job_uri assert job.session == session assert job.op_method == op_method assert job.op_uri == op_uri # TODO: Merge the next 7 test functions into one that is parametrized def test_check_incomplete(self): """Test check_for_completion() with incomplete job.""" with requests_mock.mock() as m: self.mock_server_1(m) session = Session('fake-host', 'fake-user', 'fake-pw') op_method = 'POST' op_uri = '/api/foo' job = Job(session, self.job_uri, op_method, op_uri) query_job_status_result = { 'status': 'running', } m.get(self.job_uri, json=query_job_status_result) m.delete(self.job_uri, status_code=204) job_status, op_result = job.check_for_completion() assert job_status == 'running' assert op_result is None def test_check_complete_success_noresult(self): """Test check_for_completion() with successful complete job without result.""" with requests_mock.mock() as m: self.mock_server_1(m) session = Session('fake-host', 'fake-user', 'fake-pw') op_method = 'POST' op_uri = '/api/foo' job = Job(session, self.job_uri, op_method, op_uri) query_job_status_result = { 'status': 'complete', 'job-status-code': 200, # 'job-reason-code' omitted because HTTP status good # 'job-results' is optional and is omitted } m.get(self.job_uri, json=query_job_status_result) m.delete(self.job_uri, status_code=204) job_status, op_result = job.check_for_completion() assert job_status == 'complete' assert op_result is None def test_check_complete_success_result(self): """Test check_for_completion() with successful complete job with a result.""" with requests_mock.mock() as m: self.mock_server_1(m) session = Session('fake-host', 'fake-user', 'fake-pw') op_method = 'POST' op_uri = '/api/foo' job = Job(session, self.job_uri, op_method, op_uri) exp_op_result = { 'foo': 'bar', } query_job_status_result = { 'status': 'complete', 'job-status-code': 200, # 'job-reason-code' omitted because HTTP status good 'job-results': exp_op_result, } m.get(self.job_uri, json=query_job_status_result) m.delete(self.job_uri, status_code=204) job_status, op_result = job.check_for_completion() assert job_status == 'complete' assert op_result == exp_op_result def test_check_complete_error1(self): """Test check_for_completion() with complete job in error (1).""" with requests_mock.mock() as m: self.mock_server_1(m) session = Session('fake-host', 'fake-user', 'fake-pw') op_method = 'POST' op_uri = '/api/foo' job = Job(session, self.job_uri, op_method, op_uri) query_job_status_result = { 'status': 'complete', 'job-status-code': 500, 'job-reason-code': 42, # no 'job-results' field (it is not guaranteed to be there) } m.get(self.job_uri, json=query_job_status_result) m.delete(self.job_uri, status_code=204) with pytest.raises(HTTPError) as exc_info: job_status, op_result = job.check_for_completion() exc = exc_info.value assert exc.http_status == 500 assert exc.reason == 42 assert exc.message is None def test_check_complete_error2(self): """Test check_for_completion() with complete job in error (2).""" with requests_mock.mock() as m: self.mock_server_1(m) session = Session('fake-host', 'fake-user', 'fake-pw') op_method = 'POST' op_uri = '/api/foo' job = Job(session, self.job_uri, op_method, op_uri) query_job_status_result = { 'status': 'complete', 'job-status-code': 500, 'job-reason-code': 42, 'job-results': {}, # it is not guaranteed to have any content } m.get(self.job_uri, json=query_job_status_result) m.delete(self.job_uri, status_code=204) with pytest.raises(HTTPError) as exc_info: job_status, op_result = job.check_for_completion() exc = exc_info.value assert exc.http_status == 500 assert exc.reason == 42 assert exc.message is None def test_check_complete_error3(self): """Test check_for_completion() with complete job in error (3).""" with requests_mock.mock() as m: self.mock_server_1(m) session = Session('fake-host', 'fake-user', 'fake-pw') op_method = 'POST' op_uri = '/api/foo' job = Job(session, self.job_uri, op_method, op_uri) query_job_status_result = { 'status': 'complete', 'job-status-code': 500, 'job-reason-code': 42, 'job-results': { # Content is not documented for the error case. # Some failures result in an 'error' field. 'error': 'bla message', }, } m.get(self.job_uri, json=query_job_status_result) m.delete(self.job_uri, status_code=204) with pytest.raises(HTTPError) as exc_info: job_status, op_result = job.check_for_completion() exc = exc_info.value assert exc.http_status == 500 assert exc.reason == 42 assert exc.message == 'bla message' def test_check_complete_error4(self): """Test check_for_completion() with complete job in error (4).""" with requests_mock.mock() as m: self.mock_server_1(m) session = Session('fake-host', 'fake-user', 'fake-pw') op_method = 'POST' op_uri = '/api/foo' job = Job(session, self.job_uri, op_method, op_uri) query_job_status_result = { 'status': 'complete', 'job-status-code': 500, 'job-reason-code': 42, 'job-results': { # Content is not documented for the error case. # Some failures result in an 'message' field. 'message': 'bla message', }, } m.get(self.job_uri, json=query_job_status_result) m.delete(self.job_uri, status_code=204) with pytest.raises(HTTPError) as exc_info: job_status, op_result = job.check_for_completion() exc = exc_info.value assert exc.http_status == 500 assert exc.reason == 42 assert exc.message == 'bla message' # TODO: Merge the next 3 test functions into one that is parametrized def test_wait_complete1_success_result(self): """Test wait_for_completion() with successful complete job with a result.""" with requests_mock.mock() as m: self.mock_server_1(m) session = Session('fake-host', 'fake-user', 'fake-pw') op_method = 'POST' op_uri = '/api/foo' job = Job(session, self.job_uri, op_method, op_uri) exp_op_result = { 'foo': 'bar', } query_job_status_result = { 'status': 'complete', 'job-status-code': 200, # 'job-reason-code' omitted because HTTP status good 'job-results': exp_op_result, } m.get(self.job_uri, json=query_job_status_result) m.delete(self.job_uri, status_code=204) op_result = job.wait_for_completion() assert op_result == exp_op_result def test_wait_complete3_success_result(self): """Test wait_for_completion() with successful complete job with a result.""" with requests_mock.mock() as m: self.mock_server_1(m) session = Session('fake-host', 'fake-user', 'fake-pw') op_method = 'POST' op_uri = '/api/foo' job = Job(session, self.job_uri, op_method, op_uri) exp_op_result = { 'foo': 'bar', } m.get(self.job_uri, [ {'text': result_running_callback}, {'text': result_complete_callback}, ]) m.delete(self.job_uri, status_code=204) op_result = job.wait_for_completion() assert op_result == exp_op_result def test_wait_complete3_timeout(self): """Test wait_for_completion() with timeout.""" with requests_mock.mock() as m: self.mock_server_1(m) session = Session('fake-host', 'fake-user', 'fake-pw') op_method = 'POST' op_uri = '/api/foo' job = Job(session, self.job_uri, op_method, op_uri) m.get(self.job_uri, [ {'text': result_running_callback}, {'text': result_running_callback}, {'text': result_complete_callback}, ]) m.delete(self.job_uri, status_code=204) # Here we provoke a timeout, by setting the timeout to less than # the time it would take to return the completed job status. # The time it would take is the sum of the following: # - 2 * 1 s (1 s is the sleep time in Job.wait_for_completion(), # and this happens before each repeated status retrieval) # - 3 * 1 s (1 s is the sleep time in result_*_callback() in this # module, and this happens in each mocked status retrieval) # Because status completion is given priority over achieving the # timeout duration, the timeout value needed to provoke the # timeout exception needs to be shorter by the last status # retrieval (the one that completes the job), so 3 s is the # boundary for the timeout value. operation_timeout = 2.9 try: start_time = time.time() job.wait_for_completion(operation_timeout=operation_timeout) duration = time.time() - start_time self.fail("No OperationTimeout raised. Actual duration: %s s, " "timeout: %s s" % (duration, operation_timeout)) except OperationTimeout as exc: msg = exc.args[0] assert msg.startswith("Waiting for completion of job") def result_running_callback(request, context): job_result_running = { 'status': 'running', } time.sleep(1) return json.dumps(job_result_running) def result_complete_callback(request, context): exp_op_result = { 'foo': 'bar', } job_result_complete = { 'status': 'complete', 'job-status-code': 200, # 'job-reason-code' omitted because HTTP status good 'job-results': exp_op_result, } time.sleep(1) return json.dumps(job_result_complete) zhmcclient-0.22.0/tests/unit/zhmcclient/test_cpc.py0000644000076500000240000012327113364325033022763 0ustar maierastaff00000000000000# 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. """ Unit tests for _cpc module. """ from __future__ import absolute_import, print_function import pytest import re import copy from zhmcclient import Client, Cpc, HTTPError from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources # Object IDs and names of our faked CPCs: CPC1_NAME = 'cpc 1' # z13s in DPM mode CPC1_OID = 'cpc1-oid' CPC1_MAX_CRYPTO_DOMAINS = 40 # Crypto Express5S on a z13s CPC2_NAME = 'cpc 2' # z13s in classic mode CPC2_OID = 'cpc2-oid' CPC3_NAME = 'cpc 3' # zEC12 CPC3_OID = 'cpc3-oid' CPC4_NAME = 'cpc 4' # z14-ZR1 in DPM mode CPC4_OID = 'cpc4-oid' HTTPError_404_1 = HTTPError({'http-status': 404, 'reason': 1}) HTTPError_409_5 = HTTPError({'http-status': 409, 'reason': 5}) HTTPError_409_4 = HTTPError({'http-status': 409, 'reason': 4}) HTTPError_400_7 = HTTPError({'http-status': 400, 'reason': 7}) # Names of our faked crypto adapters: CRYPTO1_NAME = 'crypto 1' CRYPTO2_NAME = 'crypto 2' CPC1_UNUSED_CRYPTO_DOMAINS = list(range(4, CPC1_MAX_CRYPTO_DOMAINS)) GET_FREE_CRYPTO_DOMAINS_ENVIRONMENTS = { 'env0-example': { 'desc': "The example from the description of method " "Cpc.get_free_crypto_domains()", 'cpc_name': CPC1_NAME, 'adapter_names': [ CRYPTO1_NAME, CRYPTO2_NAME, ], 'partitions': [ { 'name': 'part-A', 'adapter_names': [ CRYPTO1_NAME, ], 'domain_configs': [ {'domain-index': 0, 'access-mode': 'control-usage'}, {'domain-index': 1, 'access-mode': 'control'}, # We leave domain index 4 and higher untouched ], }, { 'name': 'part-B', 'adapter_names': [ CRYPTO2_NAME, ], 'domain_configs': [ {'domain-index': 0, 'access-mode': 'control'}, {'domain-index': 1, 'access-mode': 'control-usage'}, {'domain-index': 2, 'access-mode': 'control-usage'}, # We leave domain index 4 and higher untouched ], }, { 'name': 'part-C', 'adapter_names': [ CRYPTO1_NAME, CRYPTO2_NAME, ], 'domain_configs': [ {'domain-index': 0, 'access-mode': 'control'}, {'domain-index': 1, 'access-mode': 'control'}, {'domain-index': 3, 'access-mode': 'control-usage'}, # We leave domain index 4 and higher untouched ], }, ] }, 'env1-ocdu': { 'desc': "Overlapped control of domains, but disjoint usage " "on all adapters", 'cpc_name': CPC1_NAME, 'adapter_names': [ CRYPTO1_NAME, CRYPTO2_NAME, ], 'partitions': [ { 'name': 'part-0', 'adapter_names': [ CRYPTO1_NAME, CRYPTO2_NAME, ], 'domain_configs': [ {'domain-index': 0, 'access-mode': 'control-usage'}, {'domain-index': 1, 'access-mode': 'control'}, {'domain-index': 2, 'access-mode': 'control'}, {'domain-index': 3, 'access-mode': 'control'}, # We leave domain index 4 and higher untouched ], }, { 'name': 'part-1', 'adapter_names': [ CRYPTO1_NAME, CRYPTO2_NAME, ], 'domain_configs': [ {'domain-index': 1, 'access-mode': 'control-usage'}, # We leave domain index 4 and higher untouched ], }, ] }, 'env2-dcdu': { 'desc': "Disjoint control and usage of domains on all adapters", 'cpc_name': CPC1_NAME, 'adapter_names': [ CRYPTO1_NAME, CRYPTO2_NAME, ], 'partitions': [ { 'name': 'part-0', 'adapter_names': [ CRYPTO1_NAME, CRYPTO2_NAME, ], 'domain_configs': [ {'domain-index': 0, 'access-mode': 'control-usage'}, {'domain-index': 1, 'access-mode': 'control'}, # We leave domain index 4 and higher untouched ], }, { 'name': 'part-2', 'adapter_names': [ CRYPTO1_NAME, CRYPTO2_NAME, ], 'domain_configs': [ {'domain-index': 2, 'access-mode': 'control-usage'}, {'domain-index': 3, 'access-mode': 'control'}, # We leave domain index 4 and higher untouched ], }, ] }, 'env3-dcou': { 'desc': "Disjoint control of domains, but overlapping usage on all " "adapters (this prevents activating the partitions at the same time)", 'cpc_name': CPC1_NAME, 'adapter_names': [ CRYPTO1_NAME, CRYPTO2_NAME, ], 'partitions': [ { 'name': 'part-1', 'adapter_names': [ CRYPTO1_NAME, CRYPTO2_NAME, ], 'domain_configs': [ {'domain-index': 1, 'access-mode': 'control-usage'}, {'domain-index': 2, 'access-mode': 'control'}, # We leave domain index 4 and higher untouched ], }, { 'name': 'part-2', 'adapter_names': [ CRYPTO1_NAME, CRYPTO2_NAME, ], 'domain_configs': [ {'domain-index': 1, 'access-mode': 'control-usage'}, {'domain-index': 3, 'access-mode': 'control'}, # We leave domain index 4 and higher untouched ], }, ] }, } GET_FREE_CRYPTO_DOMAINS_SUCCESS_TESTCASES = [ # (env_name, parm_adapter_names, exp_free_domains) # test cases for environment 'env0-example': ( 'env0-example', [], None, ), ( 'env0-example', [CRYPTO1_NAME], [1, 2] + CPC1_UNUSED_CRYPTO_DOMAINS, ), ( 'env0-example', [CRYPTO2_NAME], [0] + CPC1_UNUSED_CRYPTO_DOMAINS, ), ( 'env0-example', [CRYPTO1_NAME, CRYPTO2_NAME], [] + CPC1_UNUSED_CRYPTO_DOMAINS, ), ( 'env0-example', None, [] + CPC1_UNUSED_CRYPTO_DOMAINS, ), # test cases for environment 'env1-ocdu': ( 'env1-ocdu', [], None, ), ( 'env1-ocdu', [CRYPTO1_NAME], [2, 3] + CPC1_UNUSED_CRYPTO_DOMAINS, ), ( 'env1-ocdu', [CRYPTO2_NAME], [2, 3] + CPC1_UNUSED_CRYPTO_DOMAINS, ), ( 'env1-ocdu', [CRYPTO1_NAME, CRYPTO2_NAME], [2, 3] + CPC1_UNUSED_CRYPTO_DOMAINS, ), ( 'env1-ocdu', None, [2, 3] + CPC1_UNUSED_CRYPTO_DOMAINS, ), # test cases for environment 'env2-dcdu': ( 'env2-dcdu', [], None, ), ( 'env2-dcdu', [CRYPTO1_NAME], [1, 3] + CPC1_UNUSED_CRYPTO_DOMAINS, ), ( 'env2-dcdu', [CRYPTO2_NAME], [1, 3] + CPC1_UNUSED_CRYPTO_DOMAINS, ), ( 'env2-dcdu', [CRYPTO1_NAME, CRYPTO2_NAME], [1, 3] + CPC1_UNUSED_CRYPTO_DOMAINS, ), ( 'env2-dcdu', None, [1, 3] + CPC1_UNUSED_CRYPTO_DOMAINS, ), # test cases for environment 'env3-dcou': ( 'env3-dcou', [], None, ), ( 'env3-dcou', [CRYPTO1_NAME], [0, 2, 3] + CPC1_UNUSED_CRYPTO_DOMAINS, ), ( 'env3-dcou', [CRYPTO2_NAME], [0, 2, 3] + CPC1_UNUSED_CRYPTO_DOMAINS, ), ( 'env3-dcou', [CRYPTO1_NAME, CRYPTO2_NAME], [0, 2, 3] + CPC1_UNUSED_CRYPTO_DOMAINS, ), ( 'env3-dcou', None, [0, 2, 3] + CPC1_UNUSED_CRYPTO_DOMAINS, ), ] class TestCpc(object): """All tests for the Cpc and CpcManager classes.""" def setup_method(self): """ Set up a faked session. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) def add_cpc(self, cpc_name): """Add a faked CPC.""" if cpc_name == CPC1_NAME: faked_cpc = self.session.hmc.cpcs.add({ 'object-id': CPC1_OID, # object-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': CPC1_NAME, 'description': 'CPC #1 (z13s in DPM mode)', 'status': 'active', 'dpm-enabled': True, 'is-ensemble-member': False, 'iml-mode': 'dpm', 'machine-type': '2965', }) elif cpc_name == CPC2_NAME: faked_cpc = self.session.hmc.cpcs.add({ 'object-id': CPC2_OID, # object-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': CPC2_NAME, 'description': 'CPC #2 (z13s in classic mode)', 'status': 'operating', 'dpm-enabled': False, 'is-ensemble-member': False, 'iml-mode': 'lpar', 'machine-type': '2965', }) elif cpc_name == CPC3_NAME: faked_cpc = self.session.hmc.cpcs.add({ 'object-id': CPC3_OID, # object-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': CPC3_NAME, 'description': 'CPC #3 (zEC12)', 'status': 'operating', # zEC12 does not have a dpm-enabled property 'is-ensemble-member': False, 'iml-mode': 'lpar', 'machine-type': '2827', }) elif cpc_name == CPC4_NAME: faked_cpc = self.session.hmc.cpcs.add({ 'object-id': CPC4_OID, # object-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': CPC4_NAME, 'description': 'CPC #4 (z14-ZR1 in DPM mode)', 'status': 'active', 'dpm-enabled': True, 'is-ensemble-member': False, 'iml-mode': 'dpm', 'machine-type': '3607', 'available-features-list': [], }) else: raise ValueError("Invalid value for cpc_name: %s" % cpc_name) return faked_cpc def add_crypto_adapter(self, faked_cpc, adapter_name): """Add a faked crypto adapter to a faked CPC.""" if adapter_name == CRYPTO1_NAME: faked_crypto_adapter = faked_cpc.adapters.add({ 'object-id': adapter_name + '-oid', # object-uri is automatically set 'parent': faked_cpc.uri, 'class': 'adapter', 'name': adapter_name, 'status': 'active', 'type': 'crypto', 'adapter-family': 'crypto', 'detected-card-type': 'crypto-express-5s', 'crypto-type': 'ep11-coprocessor', 'crypto-number': 1, 'adapter-id': '12C', }) elif adapter_name == CRYPTO2_NAME: faked_crypto_adapter = faked_cpc.adapters.add({ 'object-id': adapter_name + '-oid', # object-uri is automatically set 'parent': faked_cpc.uri, 'class': 'adapter', 'name': adapter_name, 'status': 'active', 'type': 'crypto', 'adapter-family': 'crypto', 'detected-card-type': 'crypto-express-5s', 'crypto-type': 'cca-coprocessor', 'crypto-number': 2, 'adapter-id': '12D', }) else: raise ValueError("Invalid value for crypto_name: %s" % adapter_name) return faked_crypto_adapter def add_partition(self, faked_cpc, part_name): """Add a faked partition to a faked CPC, with standard properties.""" faked_partition = faked_cpc.partitions.add({ 'object-id': part_name + '-oid', # object-uri is automatically set 'parent': faked_cpc.uri, 'class': 'partition', 'name': part_name, 'status': 'active', 'ifl-processors': 2, 'initial-memory': 1024, 'maximum-memory': 1024, }) return faked_partition def test_cpcmanager_initial_attrs(self): """Test initial attributes of CpcManager.""" cpc_mgr = self.client.cpcs # Verify all public properties of the manager object assert cpc_mgr.resource_class is Cpc assert cpc_mgr.session is self.session assert cpc_mgr.parent is None assert cpc_mgr.client is self.client # TODO: Test for CpcManager.__repr__() @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(), ['object-uri', 'name', 'status']), (dict(full_properties=False), ['object-uri', 'name', 'status']), (dict(full_properties=True), None), ] ) def test_cpcmanager_list_full_properties( self, full_properties_kwargs, prop_names): """Test CpcManager.list() with full_properties.""" # Add two faked CPCs faked_cpc1 = self.add_cpc(CPC1_NAME) faked_cpc2 = self.add_cpc(CPC2_NAME) exp_faked_cpcs = [faked_cpc1, faked_cpc2] cpc_mgr = self.client.cpcs # Execute the code to be tested cpcs = cpc_mgr.list(**full_properties_kwargs) assert_resources(cpcs, exp_faked_cpcs, prop_names) @pytest.mark.parametrize( "filter_args, exp_names", [ ({'object-id': CPC1_OID}, [CPC1_NAME]), ({'object-id': CPC2_OID}, [CPC2_NAME]), ({'object-id': [CPC1_OID, CPC2_OID]}, [CPC1_NAME, CPC2_NAME]), ({'object-id': [CPC1_OID, CPC1_OID]}, [CPC1_NAME]), ({'object-id': CPC1_OID + 'foo'}, []), ({'object-id': [CPC1_OID, CPC2_OID + 'foo']}, [CPC1_NAME]), ({'object-id': [CPC2_OID + 'foo', CPC1_OID]}, [CPC1_NAME]), ({'name': CPC1_NAME}, [CPC1_NAME]), ({'name': CPC2_NAME}, [CPC2_NAME]), ({'name': [CPC1_NAME, CPC2_NAME]}, [CPC1_NAME, CPC2_NAME]), ({'name': CPC1_NAME + 'foo'}, []), ({'name': [CPC1_NAME, CPC2_NAME + 'foo']}, [CPC1_NAME]), ({'name': [CPC2_NAME + 'foo', CPC1_NAME]}, [CPC1_NAME]), ({'name': [CPC1_NAME, CPC1_NAME]}, [CPC1_NAME]), ({'name': '.*cpc 1'}, [CPC1_NAME]), ({'name': 'cpc 1.*'}, [CPC1_NAME]), ({'name': 'cpc .'}, [CPC1_NAME, CPC2_NAME]), ({'name': '.pc 1'}, [CPC1_NAME]), ({'name': '.+'}, [CPC1_NAME, CPC2_NAME]), ({'name': 'cpc 1.+'}, []), ({'name': '.+cpc 1'}, []), ({'name': CPC1_NAME, 'object-id': CPC1_OID}, [CPC1_NAME]), ({'name': CPC1_NAME, 'object-id': CPC1_OID + 'foo'}, []), ({'name': CPC1_NAME + 'foo', 'object-id': CPC1_OID}, []), ({'name': CPC1_NAME + 'foo', 'object-id': CPC1_OID + 'foo'}, []), ] ) def test_cpcmanager_list_filter_args(self, filter_args, exp_names): """Test CpcManager.list() with filter_args.""" # Add two faked CPCs self.add_cpc(CPC1_NAME) self.add_cpc(CPC2_NAME) cpc_mgr = self.client.cpcs # Execute the code to be tested cpcs = cpc_mgr.list(filter_args=filter_args) assert len(cpcs) == len(exp_names) if exp_names: names = [ad.properties['name'] for ad in cpcs] assert set(names) == set(exp_names) # TODO: Test for initial Cpc attributes (lpars, partitions, adapters, # virtual_switches, reset_activation_profiles, # image_activation_profiles, load_activation_profiles, ) def test_cpc_repr(self): """Test Cpc.__repr__().""" # Add a faked CPC faked_cpc = self.add_cpc(CPC1_NAME) cpc_mgr = self.client.cpcs cpc = cpc_mgr.find(name=faked_cpc.name) # Execute the code to be tested repr_str = repr(cpc) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=cpc.__class__.__name__, id=id(cpc)), repr_str) @pytest.mark.parametrize( "cpc_name, exp_dpm_enabled", [ (CPC1_NAME, True), (CPC2_NAME, False), (CPC3_NAME, False), ] ) def test_cpc_dpm_enabled(self, cpc_name, exp_dpm_enabled): """Test Cpc.dpm_enabled.""" # Add a faked CPC self.add_cpc(cpc_name) cpc_mgr = self.client.cpcs cpc = cpc_mgr.find(name=cpc_name) # Execute the code to be tested dpm_enabled = cpc.dpm_enabled assert dpm_enabled == exp_dpm_enabled # TODO: Test for Cpc.maximum_active_partitions @pytest.mark.parametrize( "desc, cpc_name, available_features, feature_name, " "exp_feature_enabled, exp_exc", [ ( "No feature support on the CPC", CPC1_NAME, None, 'fake-feature1', None, ValueError() ), ( "Feature not available on the CPC (empty feature list)", CPC4_NAME, [], 'fake-feature1', None, ValueError() ), ( "Feature not available on the CPC (one other feature avail)", CPC4_NAME, [ dict(name='fake-feature-foo', state=True), ], 'fake-feature1', None, ValueError() ), ( "Feature disabled (the only feature available)", CPC4_NAME, [ dict(name='fake-feature1', state=False), ], 'fake-feature1', False, None ), ( "Feature enabled (the only feature available)", CPC4_NAME, [ dict(name='fake-feature1', state=True), ], 'fake-feature1', True, None ), ] ) def test_cpc_feature_enabled( self, desc, cpc_name, available_features, feature_name, exp_feature_enabled, exp_exc): """Test Cpc.feature_enabled().""" # Add a faked CPC faked_cpc = self.add_cpc(cpc_name) # Set up the firmware feature list if available_features is not None: faked_cpc.properties['available-features-list'] = \ available_features cpc_mgr = self.client.cpcs cpc = cpc_mgr.find(name=cpc_name) if exp_exc: with pytest.raises(exp_exc.__class__): # Execute the code to be tested cpc.feature_enabled(feature_name) else: # Execute the code to be tested act_feature_enabled = cpc.feature_enabled(feature_name) assert act_feature_enabled == exp_feature_enabled @pytest.mark.parametrize( "desc, cpc_name, available_features, exp_exc", [ ( "No feature support on the CPC", CPC1_NAME, None, ValueError() ), ( "Feature not available on the CPC (empty feature list)", CPC4_NAME, [], None ), ( "Feature not available on the CPC (one other feature avail)", CPC4_NAME, [ dict(name='fake-feature-foo', state=True), ], None ), ( "Feature disabled (the only feature available)", CPC4_NAME, [ dict(name='fake-feature1', state=False), ], None ), ( "Feature enabled (the only feature available)", CPC4_NAME, [ dict(name='fake-feature1', state=True), ], None ), ] ) def test_cpc_feature_info( self, desc, cpc_name, available_features, exp_exc): """Test Cpc.feature_info().""" # Add a faked CPC faked_cpc = self.add_cpc(cpc_name) # Set up the firmware feature list if available_features is not None: faked_cpc.properties['available-features-list'] = \ available_features cpc_mgr = self.client.cpcs cpc = cpc_mgr.find(name=cpc_name) if exp_exc: with pytest.raises(exp_exc.__class__): # Execute the code to be tested cpc.feature_info() else: # Execute the code to be tested act_features = cpc.feature_info() assert act_features == available_features @pytest.mark.parametrize( "input_props", [ {}, {'description': 'New CPC description'}, {'acceptable-status': ['active', 'degraded'], 'description': 'New CPC description'}, ] ) def test_cpc_update_properties(self, input_props): """Test Cpc.update_properties().""" # Add a faked CPC faked_cpc = self.add_cpc(CPC1_NAME) cpc_mgr = self.client.cpcs cpc = cpc_mgr.find(name=faked_cpc.name) cpc.pull_full_properties() saved_properties = copy.deepcopy(cpc.properties) # Execute the code to be tested cpc.update_properties(properties=input_props) # Verify that the resource object already reflects the property # updates. for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in cpc.properties prop_value = cpc.properties[prop_name] assert prop_value == exp_prop_value # Refresh the resource object and verify that the resource object # still reflects the property updates. cpc.pull_full_properties() for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in cpc.properties prop_value = cpc.properties[prop_name] assert prop_value == exp_prop_value @pytest.mark.parametrize( "wait_for_completion", [True] ) @pytest.mark.parametrize( "cpc_name, initial_status, exp_status, exp_error", [ (CPC1_NAME, 'not-operating', 'active', None), (CPC2_NAME, 'not-operating', None, HTTPError_409_5), (CPC3_NAME, 'not-operating', None, HTTPError_409_5), ] ) def test_cpc_start(self, cpc_name, initial_status, exp_status, exp_error, wait_for_completion): """Test Cpc.start().""" # wait_for_completion=False not implemented in mock support: assert wait_for_completion is True # Add a faked CPC faked_cpc = self.add_cpc(cpc_name) # Set initial status of the CPC for this test faked_cpc.properties['status'] = initial_status cpc_mgr = self.client.cpcs cpc = cpc_mgr.find(name=cpc_name) if exp_error: with pytest.raises(HTTPError) as exc_info: # Execute the code to be tested result = cpc.start(wait_for_completion=wait_for_completion) exc = exc_info.value assert exc.http_status == exp_error.http_status assert exc.reason == exp_error.reason else: # Execute the code to be tested result = cpc.start(wait_for_completion=wait_for_completion) if wait_for_completion: assert result is None else: raise NotImplementedError cpc.pull_full_properties() status = cpc.properties['status'] assert status == exp_status @pytest.mark.parametrize( "wait_for_completion", [True] ) @pytest.mark.parametrize( "cpc_name, initial_status, exp_status, exp_error", [ (CPC1_NAME, 'active', 'not-operating', None), (CPC2_NAME, 'operating', None, HTTPError_409_5), (CPC3_NAME, 'operating', None, HTTPError_409_5), ] ) def test_cpc_stop(self, cpc_name, initial_status, exp_status, exp_error, wait_for_completion): """Test Cpc.stop().""" # wait_for_completion=False not implemented in mock support: assert wait_for_completion is True # Add a faked CPC faked_cpc = self.add_cpc(cpc_name) # Set initial status of the CPC for this test faked_cpc.properties['status'] = initial_status cpc_mgr = self.client.cpcs cpc = cpc_mgr.find(name=cpc_name) if exp_error: with pytest.raises(HTTPError) as exc_info: # Execute the code to be tested result = cpc.stop(wait_for_completion=wait_for_completion) exc = exc_info.value assert exc.http_status == exp_error.http_status assert exc.reason == exp_error.reason else: # Execute the code to be tested result = cpc.stop(wait_for_completion=wait_for_completion) if wait_for_completion: assert result is None else: raise NotImplementedError cpc.pull_full_properties() status = cpc.properties['status'] assert status == exp_status @pytest.mark.parametrize( "wait_for_completion", [True] ) @pytest.mark.parametrize( "cpc_name, exp_error", [ (CPC1_NAME, HTTPError_409_4), (CPC2_NAME, None), (CPC3_NAME, None), ] ) def test_cpc_import_profiles(self, cpc_name, exp_error, wait_for_completion): """Test Cpc.import_profiles().""" # wait_for_completion=False not implemented in mock support: assert wait_for_completion is True # Add a faked CPC self.add_cpc(cpc_name) cpc_mgr = self.client.cpcs cpc = cpc_mgr.find(name=cpc_name) profile_area = 1 if exp_error: with pytest.raises(HTTPError) as exc_info: # Execute the code to be tested result = cpc.import_profiles( profile_area, wait_for_completion=wait_for_completion) exc = exc_info.value assert exc.http_status == exp_error.http_status assert exc.reason == exp_error.reason else: # Execute the code to be tested result = cpc.import_profiles( profile_area, wait_for_completion=wait_for_completion) if wait_for_completion: assert result is None else: raise NotImplementedError @pytest.mark.parametrize( "wait_for_completion", [True] ) @pytest.mark.parametrize( "cpc_name, exp_error", [ (CPC1_NAME, HTTPError_409_4), (CPC2_NAME, None), (CPC3_NAME, None), ] ) def test_cpc_export_profiles(self, cpc_name, exp_error, wait_for_completion): """Test Cpc.export_profiles().""" # wait_for_completion=False not implemented in mock support: assert wait_for_completion is True # Add a faked CPC self.add_cpc(cpc_name) cpc_mgr = self.client.cpcs cpc = cpc_mgr.find(name=cpc_name) profile_area = 1 if exp_error: with pytest.raises(HTTPError) as exc_info: # Execute the code to be tested result = cpc.export_profiles( profile_area, wait_for_completion=wait_for_completion) exc = exc_info.value assert exc.http_status == exp_error.http_status assert exc.reason == exp_error.reason else: # Execute the code to be tested result = cpc.export_profiles( profile_area, wait_for_completion=wait_for_completion) if wait_for_completion: assert result is None else: raise NotImplementedError @pytest.mark.parametrize( "cpc_name, exp_error", [ (CPC1_NAME, None), (CPC2_NAME, HTTPError_409_5), (CPC3_NAME, HTTPError_409_5), ] ) def test_cpc_get_wwpns(self, cpc_name, exp_error): """Test Cpc.get_wwpns().""" # Add a faked CPC faked_cpc = self.add_cpc(cpc_name) faked_fcp1 = faked_cpc.adapters.add({ 'object-id': 'fake-fcp1-oid', # object-uri is automatically set 'parent': faked_cpc.uri, 'class': 'adapter', 'name': 'fake-fcp1-name', 'description': 'FCP #1 in CPC #1', 'status': 'active', 'type': 'fcp', 'port-count': 1, 'adapter-id': '12F', # network-port-uris is automatically set when adding port }) faked_port11 = faked_fcp1.ports.add({ 'element-id': 'fake-port11-oid', # element-uri is automatically set 'parent': faked_fcp1.uri, 'class': 'storage-port', 'index': 0, 'name': 'fake-port11-name', 'description': 'FCP #1 Port #1', }) faked_part1 = faked_cpc.partitions.add({ 'object-id': 'fake-part1-oid', # object-uri is automatically set 'parent': faked_cpc.uri, 'class': 'partition', 'name': 'fake-part1-name', 'description': 'Partition #1', 'status': 'active', }) faked_hba1 = faked_part1.hbas.add({ 'element-id': 'fake-hba1-oid', # element-uri is automatically set 'parent': faked_part1.uri, 'class': 'hba', 'name': 'fake-hba1-name', 'description': 'HBA #1 in Partition #1', 'wwpn': 'AABBCCDDEC000082', 'adapter-port-uri': faked_port11.uri, 'device-number': '012F', }) faked_part2 = faked_cpc.partitions.add({ 'object-id': 'fake-part2-oid', # object-uri is automatically set 'parent': faked_cpc.uri, 'class': 'partition', 'name': 'fake-part2-name', 'description': 'Partition #2', 'status': 'active', }) faked_hba2 = faked_part2.hbas.add({ 'element-id': 'fake-hba2-oid', # element-uri is automatically set 'parent': faked_part2.uri, 'class': 'hba', 'name': 'fake-hba2-name', 'description': 'HBA #2 in Partition #2', 'wwpn': 'AABBCCDDEC000084', 'adapter-port-uri': faked_port11.uri, 'device-number': '012E', }) cpc_mgr = self.client.cpcs cpc = cpc_mgr.find(name=cpc_name) partitions = cpc.partitions.list() if exp_error: with pytest.raises(HTTPError) as exc_info: # Execute the code to be tested wwpn_list = cpc.get_wwpns(partitions) exc = exc_info.value assert exc.http_status == exp_error.http_status assert exc.reason == exp_error.reason else: # Execute the code to be tested wwpn_list = cpc.get_wwpns(partitions) exp_wwpn_list = [ {'wwpn': faked_hba1.properties['wwpn'], 'partition-name': faked_part1.properties['name'], 'adapter-id': faked_fcp1.properties['adapter-id'], 'device-number': faked_hba1.properties['device-number']}, {'wwpn': faked_hba2.properties['wwpn'], 'partition-name': faked_part2.properties['name'], 'adapter-id': faked_fcp1.properties['adapter-id'], 'device-number': faked_hba2.properties['device-number']}, ] assert wwpn_list == exp_wwpn_list @pytest.mark.parametrize( "env_name, parm_adapter_names, exp_free_domains", GET_FREE_CRYPTO_DOMAINS_SUCCESS_TESTCASES) def test_cpc_get_free_crypto_domains(self, env_name, parm_adapter_names, exp_free_domains): """Test Cpc.get_free_crypto_domains().""" env = GET_FREE_CRYPTO_DOMAINS_ENVIRONMENTS[env_name] cpc_name = env['cpc_name'] # Add the faked CPC faked_cpc = self.add_cpc(cpc_name) # Add the faked crypto adapters faked_adapters = {} # faked crypto adapters by name for adapter_name in env['adapter_names']: faked_adapter = self.add_crypto_adapter(faked_cpc, adapter_name) faked_adapters[adapter_name] = faked_adapter # Add the faked partitions for part in env['partitions']: faked_part = self.add_partition(faked_cpc, part['name']) part_adapter_uris = [faked_adapters[name].uri for name in part['adapter_names']] part_domain_configs = part['domain_configs'] crypto_config = { 'crypto-adapter-uris': part_adapter_uris, 'crypto-domain-configurations': part_domain_configs, } faked_part.properties['crypto-configuration'] = crypto_config # Set up input parameters cpc = self.client.cpcs.find(name=cpc_name) if parm_adapter_names is None: parm_adapters = None else: parm_adapters = [cpc.adapters.find(name=name) for name in parm_adapter_names] # Execute the code to be tested act_free_domains = cpc.get_free_crypto_domains(parm_adapters) # Verify the result assert act_free_domains == exp_free_domains @pytest.mark.parametrize( "wait_for_completion", [True] ) @pytest.mark.parametrize( "cpc_name, power_saving, exp_error", [ (CPC1_NAME, 'high-performance', None), (CPC1_NAME, 'low-power', None), (CPC1_NAME, 'custom', None), (CPC1_NAME, None, HTTPError_400_7), (CPC1_NAME, 'invalid', HTTPError_400_7), ] ) def test_cpc_set_power_save(self, cpc_name, power_saving, exp_error, wait_for_completion): """Test Cpc.set_power_save().""" # wait_for_completion=False not implemented in mock support: assert wait_for_completion is True # Add a faked CPC self.add_cpc(cpc_name) cpc_mgr = self.client.cpcs cpc = cpc_mgr.find(name=cpc_name) if exp_error: with pytest.raises(HTTPError) as exc_info: # Execute the code to be tested result = cpc.set_power_save( power_saving, wait_for_completion=wait_for_completion) exc = exc_info.value assert exc.http_status == exp_error.http_status assert exc.reason == exp_error.reason else: # Execute the code to be tested result = cpc.set_power_save( power_saving, wait_for_completion=wait_for_completion) if wait_for_completion: assert result is None else: raise NotImplementedError cpc.pull_full_properties() assert cpc.properties['cpc-power-saving'] == power_saving assert cpc.properties['cpc-power-saving-state'] == power_saving assert cpc.properties['zcpc-power-saving'] == power_saving assert cpc.properties['zcpc-power-saving-state'] == power_saving @pytest.mark.parametrize( "wait_for_completion", [True] ) @pytest.mark.parametrize( "cpc_name, power_capping_state, power_cap, exp_error", [ (CPC1_NAME, 'disabled', None, None), (CPC1_NAME, 'enabled', 20000, None), (CPC1_NAME, 'enabled', None, HTTPError_400_7), (CPC1_NAME, None, None, HTTPError_400_7), (CPC1_NAME, 'invalid', None, HTTPError_400_7), ] ) def test_cpc_set_power_capping(self, cpc_name, power_capping_state, power_cap, exp_error, wait_for_completion): """Test Cpc.set_power_capping().""" # wait_for_completion=False not implemented in mock support: assert wait_for_completion is True # Add a faked CPC self.add_cpc(cpc_name) cpc_mgr = self.client.cpcs cpc = cpc_mgr.find(name=cpc_name) if exp_error: with pytest.raises(HTTPError) as exc_info: # Execute the code to be tested result = cpc.set_power_capping( power_capping_state, power_cap, wait_for_completion=wait_for_completion) exc = exc_info.value assert exc.http_status == exp_error.http_status assert exc.reason == exp_error.reason else: # Execute the code to be tested result = cpc.set_power_capping( power_capping_state, power_cap, wait_for_completion=wait_for_completion) if wait_for_completion: assert result is None else: raise NotImplementedError cpc.pull_full_properties() assert cpc.properties['cpc-power-capping-state'] == \ power_capping_state assert cpc.properties['cpc-power-cap-current'] == power_cap assert cpc.properties['zcpc-power-capping-state'] == \ power_capping_state assert cpc.properties['zcpc-power-cap-current'] == power_cap @pytest.mark.parametrize( "cpc_name, energy_props", [ (CPC1_NAME, { 'cpc-power-consumption': 14423, 'cpc-power-rating': 28000, 'cpc-power-save-allowed': 'allowed', 'cpc-power-saving': 'high-performance', 'cpc-power-saving-state': 'high-performance', 'zcpc-ambient-temperature': 26.7, 'zcpc-dew-point': 8.4, 'zcpc-exhaust-temperature': 29.0, 'zcpc-heat-load': 49246, 'zcpc-heat-load-forced-air': 10370, 'zcpc-heat-load-water': 38877, 'zcpc-humidity': 31, 'zcpc-maximum-potential-heat-load': 57922, 'zcpc-maximum-potential-power': 16964, 'zcpc-power-consumption': 14423, 'zcpc-power-rating': 28000, 'zcpc-power-save-allowed': 'under-group-control', 'zcpc-power-saving': 'high-performance', 'zcpc-power-saving-state': 'high-performance', }), ] ) def test_cpc_get_energy_management_properties( self, cpc_name, energy_props): """Test Cpc.get_energy_management_properties().""" # Add a faked CPC faked_cpc = self.add_cpc(cpc_name) faked_cpc.properties.update(energy_props) cpc_mgr = self.client.cpcs cpc = cpc_mgr.find(name=cpc_name) # Execute the code to be tested act_energy_props = cpc.get_energy_management_properties() assert isinstance(act_energy_props, dict) cpc.pull_full_properties() for p in energy_props: exp_value = energy_props[p] # Verify returned energy properties assert p in act_energy_props assert act_energy_props[p] == exp_value # Verify consistency of returned energy properties with CPC props assert p in cpc.properties assert cpc.properties[p] == exp_value zhmcclient-0.22.0/tests/unit/zhmcclient/test_unmanaged_cpc.py0000644000076500000240000001132013364325033024771 0ustar maierastaff00000000000000# 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. """ Unit tests for _unmanaged_cpc module. """ from __future__ import absolute_import, print_function import pytest import re from zhmcclient import Client, UnmanagedCpc from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources class TestUnmanagedCpc(object): """All tests for the UnmanagedCpc and UnmanagedCpcManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked Console without any child resources. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) self.faked_console = self.session.hmc.consoles.add({ 'object-id': None, # object-uri will be automatically set 'parent': None, 'class': 'console', 'name': 'fake-console1', 'description': 'Console #1', }) self.console = self.client.consoles.find(name=self.faked_console.name) def add_unmanaged_cpc(self, name): faked_unmanaged_cpc = self.faked_console.unmanaged_cpcs.add({ 'object-id': 'oid-{}'.format(name), # object-uri will be automatically set 'parent': '/api/console', 'class': 'cpc', 'name': name, 'description': 'Unmanaged CPC {}'.format(name), }) return faked_unmanaged_cpc def test_ucpc_manager_repr(self): """Test UnmanagedCpcManager.__repr__().""" ucpc_mgr = self.console.unmanaged_cpcs # Execute the code to be tested repr_str = repr(ucpc_mgr) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=ucpc_mgr.__class__.__name__, id=id(ucpc_mgr)), repr_str) def test_ucpc_manager_initial_attrs(self): """Test initial attributes of UnmanagedCpcManager.""" ucpc_mgr = self.console.unmanaged_cpcs # Verify all public properties of the manager object assert ucpc_mgr.resource_class == UnmanagedCpc assert ucpc_mgr.class_name == 'cpc' assert ucpc_mgr.session is self.session assert ucpc_mgr.parent is self.console assert ucpc_mgr.console is self.console @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(full_properties=False), ['object-uri']), (dict(full_properties=True), ['object-uri', 'name']), (dict(), # test default for full_properties (True) ['object-uri', 'name']), ] ) @pytest.mark.parametrize( "filter_args, exp_names", [ (None, ['a', 'b']), ({}, ['a', 'b']), ({'name': 'a'}, ['a']), ] ) def test_ucpc_manager_list( self, filter_args, exp_names, full_properties_kwargs, prop_names): """Test UnmanagedCpcManager.list().""" faked_ucpc1 = self.add_unmanaged_cpc(name='a') faked_ucpc2 = self.add_unmanaged_cpc(name='b') faked_ucpcs = [faked_ucpc1, faked_ucpc2] exp_faked_ucpcs = [u for u in faked_ucpcs if u.name in exp_names] ucpc_mgr = self.console.unmanaged_cpcs # Execute the code to be tested ucpcs = ucpc_mgr.list(filter_args=filter_args, **full_properties_kwargs) assert_resources(ucpcs, exp_faked_ucpcs, prop_names) def test_ucpc_repr(self): """Test UnmanagedCpc.__repr__().""" faked_ucpc1 = self.add_unmanaged_cpc(name='a') ucpc1 = self.console.unmanaged_cpcs.find(name=faked_ucpc1.name) # Execute the code to be tested repr_str = repr(ucpc1) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=ucpc1.__class__.__name__, id=id(ucpc1)), repr_str) zhmcclient-0.22.0/tests/unit/zhmcclient/test_exceptions.py0000644000076500000240000013625713364325033024407 0ustar maierastaff00000000000000# 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. """ Unit tests for _exceptions module. """ from __future__ import absolute_import, print_function import pytest import re import six from zhmcclient import Error, ConnectionError, ConnectTimeout, ReadTimeout, \ RetriesExceeded, AuthError, ClientAuthError, ServerAuthError, ParseError, \ VersionError, HTTPError, OperationTimeout, StatusTimeout, NoUniqueMatch, \ NotFound, Client from zhmcclient_mock import FakedSession # Some HTTPError response bodies, used by multiple testcases: HTTP_ERROR_1 = { 'http-status': 404, 'reason': 42, 'message': 'abc def', 'request-method': 'POST', 'request-uri': '/api/cpcs/cpc1', 'request-query-parms': ['properties=abc,def'], 'request-headers': { 'content-type': 'application/json', }, 'request-authenticated-as': 'fake_user', 'request-body': None, 'request-body-as-string': "fake request body", 'request-body-as-string-partial': None, 'stack': None, 'error-details': None, } HTTP_ERROR_2 = { 'http-status': 404, 'reason': 42, 'message': 'abc def', } HTTP_ERROR_3 = { 'message': 'abc def', } HTTP_ERROR_4 = { } def func_args(arg_values, arg_names): """ Convert args and arg_names into positional args and keyword args. """ posargs = [] kwargs = {} for i, name in enumerate(arg_names): value = arg_values[i] if name is not None: kwargs[name] = value else: posargs.append(value) return posargs, kwargs class MyError(Error): """ Concrete class to get instances of abstract base class ``Error``. """ def __init__(self, *args, **kwargs): super(MyError, self).__init__(*args, **kwargs) class TestError(object): """ All tests for exception class Error. Because this is an abstract base class, we use our own derived class MyError. """ @pytest.mark.parametrize( # Input and expected arguments. "args", [ # (arg1, ...) (), # no args ('zaphod',), # one arg ('zaphod', 42), # two args (('zaphod', 42),), # one arg that is a tuple ] ) def test_error_initial_attrs(self, args): """Test initial attributes of Error.""" # Execute the code to be tested exc = MyError(*args) assert isinstance(exc, Error) assert exc.args == args class TestConnectionError(object): """All tests for exception class ConnectionError.""" @pytest.mark.parametrize( # Input and expected arguments. "args", [ # (msg, details) ("fake msg", ValueError("fake value error")), ("", None), (None, None), ] ) @pytest.mark.parametrize( # Whether each input arg is passed as pos.arg (None) or keyword arg # (arg name), or is defaulted (omitted from right). "arg_names", [ (None, None), (None, 'details'), ('msg', 'details'), ] ) def test_connectionerror_initial_attrs(self, arg_names, args): """Test initial attributes of ConnectionError.""" msg, details = args posargs, kwargs = func_args(args, arg_names) # Execute the code to be tested exc = ConnectionError(*posargs, **kwargs) assert isinstance(exc, Error) assert len(exc.args) == 1 assert exc.args[0] == msg assert exc.details == details @pytest.mark.parametrize( "msg, details", [ ("fake msg", ValueError("fake value error")), ("", None), (None, None), ] ) def test_connectionerror_repr(self, msg, details): """All tests for ConnectionError.__repr__().""" exc = ConnectionError(msg, details) classname = exc.__class__.__name__ # Execute the code to be tested repr_str = repr(exc) # We check the one-lined string just roughly repr_str = repr_str.replace('\n', '\\n') assert re.match(r'^{}\s*\(.*\)$'.format(classname), repr_str) @pytest.mark.parametrize( "msg, details", [ ("fake msg", ValueError("fake value error")), ("", None), (None, None), ] ) def test_connectionerror_str(self, msg, details): """All tests for ConnectionError.__str__().""" exc = ConnectionError(msg, details) exp_str = str(exc.args[0]) # Execute the code to be tested str_str = str(exc) assert str_str == exp_str @pytest.mark.parametrize( "msg, details", [ ("fake msg", ValueError("fake value error")), ("", None), (None, None), ] ) def test_connectionerror_str_def(self, msg, details): """All tests for ConnectionError.str_def().""" exc = ConnectionError(msg, details) classname = exc.__class__.__name__ # Execute the code to be tested str_def = exc.str_def() str_def = ' ' + str_def assert str_def.find(' classname={!r};'.format(classname)) >= 0 assert str_def.find(' message={!r};'.format(msg)) >= 0 class TestConnectTimeout(object): """All tests for exception class ConnectTimeout.""" @pytest.mark.parametrize( # Input and expected arguments. "args", [ # (msg, details, connect_timeout, connect_retries) ("fake msg", ValueError("fake value error"), 30, 3), ("", None, 30, 3), (None, None, 0, 0), ] ) @pytest.mark.parametrize( # Whether each input arg is passed as pos.arg (None) or keyword arg # (arg name), or is defaulted (omitted from right). "arg_names", [ (None, None, None, None), ('msg', 'details', 'connect_timeout', 'connect_retries'), ] ) def test_connecttimeout_initial_attrs(self, arg_names, args): """Test initial attributes of ConnectTimeout.""" msg, details, connect_timeout, connect_retries = args posargs, kwargs = func_args(args, arg_names) # Execute the code to be tested exc = ConnectTimeout(*posargs, **kwargs) assert isinstance(exc, ConnectionError) assert len(exc.args) == 1 assert exc.args[0] == msg assert exc.details == details assert exc.connect_timeout == connect_timeout assert exc.connect_retries == connect_retries @pytest.mark.parametrize( "msg, details, connect_timeout, connect_retries", [ ("fake msg", ValueError("fake value error"), 30, 3), ("", None, 30, 3), (None, None, 0, 0), ] ) def test_connecttimeout_repr( self, msg, details, connect_timeout, connect_retries): """All tests for ConnectTimeout.__repr__().""" exc = ConnectTimeout(msg, details, connect_timeout, connect_retries) classname = exc.__class__.__name__ # Execute the code to be tested repr_str = repr(exc) # We check the one-lined string just roughly repr_str = repr_str.replace('\n', '\\n') assert re.match(r'^{}\s*\(.*\)$'.format(classname), repr_str) @pytest.mark.parametrize( "msg, details, connect_timeout, connect_retries", [ ("fake msg", ValueError("fake value error"), 30, 3), ("", None, 30, 3), (None, None, 0, 0), ] ) def test_connecttimeout_str( self, msg, details, connect_timeout, connect_retries): """All tests for ConnectTimeout.__str__().""" exc = ConnectTimeout(msg, details, connect_timeout, connect_retries) exp_str = str(exc.args[0]) # Execute the code to be tested str_str = str(exc) assert str_str == exp_str @pytest.mark.parametrize( "msg, details, connect_timeout, connect_retries", [ ("fake msg", ValueError("fake value error"), 30, 3), ("", None, 30, 3), (None, None, 0, 0), ] ) def test_connecttimeout_str_def( self, msg, details, connect_timeout, connect_retries): """All tests for ConnectTimeout.str_def().""" exc = ConnectTimeout(msg, details, connect_timeout, connect_retries) classname = exc.__class__.__name__ # Execute the code to be tested str_def = exc.str_def() str_def = ' ' + str_def assert str_def.find(' classname={!r};'.format(classname)) >= 0 assert str_def.find(' message={!r};'.format(msg)) >= 0 assert str_def.find(' connect_timeout={!r};'. format(connect_timeout)) >= 0 assert str_def.find(' connect_retries={!r};'. format(connect_retries)) >= 0 class TestReadTimeout(object): """All tests for exception class ReadTimeout.""" @pytest.mark.parametrize( # Input and expected arguments. "args", [ # (msg, details, read_timeout, read_retries) ("fake msg", ValueError("fake value error"), 30, 3), ("", None, 30, 3), (None, None, 0, 0), ] ) @pytest.mark.parametrize( # Whether each input arg is passed as pos.arg (None) or keyword arg # (arg name), or is defaulted (omitted from right). "arg_names", [ (None, None, None, None), ('msg', 'details', 'read_timeout', 'read_retries'), ] ) def test_readtimeout_initial_attrs(self, arg_names, args): """Test initial attributes of ReadTimeout.""" msg, details, read_timeout, read_retries = args posargs, kwargs = func_args(args, arg_names) # Execute the code to be tested exc = ReadTimeout(*posargs, **kwargs) assert isinstance(exc, ConnectionError) assert len(exc.args) == 1 assert exc.args[0] == msg assert exc.details == details assert exc.read_timeout == read_timeout assert exc.read_retries == read_retries @pytest.mark.parametrize( "msg, details, read_timeout, read_retries", [ ("fake msg", ValueError("fake value error"), 30, 3), ("", None, 30, 3), (None, None, 0, 0), ] ) def test_readtimeout_repr( self, msg, details, read_timeout, read_retries): """All tests for ReadTimeout.__repr__().""" exc = ReadTimeout(msg, details, read_timeout, read_retries) classname = exc.__class__.__name__ # Execute the code to be tested repr_str = repr(exc) # We check the one-lined string just roughly repr_str = repr_str.replace('\n', '\\n') assert re.match(r'^{}\s*\(.*\)$'.format(classname), repr_str) @pytest.mark.parametrize( "msg, details, read_timeout, read_retries", [ ("fake msg", ValueError("fake value error"), 30, 3), ("", None, 30, 3), (None, None, 0, 0), ] ) def test_readtimeout_str( self, msg, details, read_timeout, read_retries): """All tests for ReadTimeout.__str__().""" exc = ReadTimeout(msg, details, read_timeout, read_retries) exp_str = str(exc.args[0]) # Execute the code to be tested str_str = str(exc) assert str_str == exp_str @pytest.mark.parametrize( "msg, details, read_timeout, read_retries", [ ("fake msg", ValueError("fake value error"), 30, 3), ("", None, 30, 3), (None, None, 0, 0), ] ) def test_readtimeout_str_def( self, msg, details, read_timeout, read_retries): """All tests for ReadTimeout.str_def().""" exc = ReadTimeout(msg, details, read_timeout, read_retries) classname = exc.__class__.__name__ # Execute the code to be tested str_def = exc.str_def() str_def = ' ' + str_def assert str_def.find(' classname={!r};'.format(classname)) >= 0 assert str_def.find(' message={!r};'.format(msg)) >= 0 assert str_def.find(' read_timeout={!r};'.format(read_timeout)) >= 0 assert str_def.find(' read_retries={!r};'.format(read_retries)) >= 0 class TestRetriesExceeded(object): """All tests for exception class RetriesExceeded.""" @pytest.mark.parametrize( # Input and expected arguments. "args", [ # (msg, details, connect_retries) ("fake msg", ValueError("fake value error"), 3), ("", None, 3), (None, None, 0), ] ) @pytest.mark.parametrize( # Whether each input arg is passed as pos.arg (None) or keyword arg # (arg name), or is defaulted (omitted from right). "arg_names", [ (None, None, None), ('msg', 'details', 'connect_retries'), ] ) def test_retriesexceeded_initial_attrs(self, arg_names, args): """Test initial attributes of RetriesExceeded.""" msg, details, connect_retries = args posargs, kwargs = func_args(args, arg_names) # Execute the code to be tested exc = RetriesExceeded(*posargs, **kwargs) assert isinstance(exc, ConnectionError) assert len(exc.args) == 1 assert exc.args[0] == msg assert exc.details == details assert exc.connect_retries == connect_retries @pytest.mark.parametrize( "msg, details, connect_retries", [ ("fake msg", ValueError("fake value error"), 3), ("", None, 3), (None, None, 0), ] ) def test_retriesexceeded_repr( self, msg, details, connect_retries): """All tests for RetriesExceeded.__repr__().""" exc = RetriesExceeded(msg, details, connect_retries) classname = exc.__class__.__name__ # Execute the code to be tested repr_str = repr(exc) # We check the one-lined string just roughly repr_str = repr_str.replace('\n', '\\n') assert re.match(r'^{}\s*\(.*\)$'.format(classname), repr_str) @pytest.mark.parametrize( "msg, details, connect_retries", [ ("fake msg", ValueError("fake value error"), 3), ("", None, 3), (None, None, 0), ] ) def test_retriesexceeded_str( self, msg, details, connect_retries): """All tests for RetriesExceeded.__str__().""" exc = RetriesExceeded(msg, details, connect_retries) exp_str = str(exc.args[0]) # Execute the code to be tested str_str = str(exc) assert str_str == exp_str @pytest.mark.parametrize( "msg, details, connect_retries", [ ("fake msg", ValueError("fake value error"), 3), ("", None, 3), (None, None, 0), ] ) def test_retriesexceeded_str_def( self, msg, details, connect_retries): """All tests for RetriesExceeded.str_def().""" exc = RetriesExceeded(msg, details, connect_retries) classname = exc.__class__.__name__ # Execute the code to be tested str_def = exc.str_def() str_def = ' ' + str_def assert str_def.find(' classname={!r};'.format(classname)) >= 0 assert str_def.find(' message={!r};'.format(msg)) >= 0 assert str_def.find(' connect_retries={!r};'. format(connect_retries)) >= 0 class TestClientAuthError(object): """All tests for exception class ClientAuthError.""" @pytest.mark.parametrize( # Input and expected arguments. "args", [ # (msg,) ("fake msg",), (None,), ] ) @pytest.mark.parametrize( # Whether each input arg is passed as pos.arg (None) or keyword arg # (arg name), or is defaulted (omitted from right). "arg_names", [ (None,), ('msg',), ] ) def test_clientautherror_initial_attrs(self, arg_names, args): """Test initial attributes of ClientAuthError.""" msg = args[0] posargs, kwargs = func_args(args, arg_names) # Execute the code to be tested exc = ClientAuthError(*posargs, **kwargs) assert isinstance(exc, AuthError) assert len(exc.args) == 1 assert exc.args[0] == msg @pytest.mark.parametrize( "msg", [ ("fake msg"), (""), (None), ] ) def test_clientautherror_repr(self, msg): """All tests for ClientAuthError.__repr__().""" exc = ClientAuthError(msg) classname = exc.__class__.__name__ # Execute the code to be tested repr_str = repr(exc) # We check the one-lined string just roughly repr_str = repr_str.replace('\n', '\\n') assert re.match(r'^{}\s*\(.*\)$'.format(classname), repr_str) @pytest.mark.parametrize( "msg", [ ("fake msg"), (""), (None), ] ) def test_clientautherror_str(self, msg): """All tests for ClientAuthError.__str__().""" exc = ClientAuthError(msg) exp_str = str(exc.args[0]) # Execute the code to be tested str_str = str(exc) assert str_str == exp_str @pytest.mark.parametrize( "msg", [ ("fake msg"), (""), (None), ] ) def test_clientautherror_str_def(self, msg): """All tests for ClientAuthError.str_def().""" exc = ClientAuthError(msg) classname = exc.__class__.__name__ # Execute the code to be tested str_def = exc.str_def() str_def = ' ' + str_def assert str_def.find(' classname={!r};'.format(classname)) >= 0 assert str_def.find(' message={!r};'.format(msg)) >= 0 class TestServerAuthError(object): """All tests for exception class ServerAuthError.""" @pytest.mark.parametrize( # Input and expected arguments. "args", [ # (msg, details) ("fake msg", HTTPError(HTTP_ERROR_1)), ("", HTTPError(HTTP_ERROR_1)), (None, HTTPError(HTTP_ERROR_1)), ] ) @pytest.mark.parametrize( # Whether each input arg is passed as pos.arg (None) or keyword arg # (arg name), or is defaulted (omitted from right). "arg_names", [ (None, None), (None, 'details'), ('msg', 'details'), ] ) def test_serverautherror_initial_attrs(self, arg_names, args): """Test initial attributes of ServerAuthError.""" msg, details = args posargs, kwargs = func_args(args, arg_names) # Execute the code to be tested exc = ServerAuthError(*posargs, **kwargs) assert isinstance(exc, AuthError) assert len(exc.args) == 1 assert exc.args[0] == msg assert exc.details == details @pytest.mark.parametrize( "msg, details", [ ("fake msg", HTTPError(HTTP_ERROR_1)), ("", HTTPError(HTTP_ERROR_1)), (None, HTTPError(HTTP_ERROR_1)), ] ) def test_serverautherror_repr(self, msg, details): """All tests for ServerAuthError.__repr__().""" exc = ServerAuthError(msg, details) classname = exc.__class__.__name__ # Execute the code to be tested repr_str = repr(exc) # We check the one-lined string just roughly repr_str = repr_str.replace('\n', '\\n') assert re.match(r'^{}\s*\(.*\)$'.format(classname), repr_str) @pytest.mark.parametrize( "msg, details", [ ("fake msg", HTTPError(HTTP_ERROR_1)), ("", HTTPError(HTTP_ERROR_1)), (None, HTTPError(HTTP_ERROR_1)), ] ) def test_serverautherror_str(self, msg, details): """All tests for ServerAuthError.__str__().""" exc = ServerAuthError(msg, details) exp_str = str(exc.args[0]) # Execute the code to be tested str_str = str(exc) assert str_str == exp_str @pytest.mark.parametrize( "msg, details", [ ("fake msg", HTTPError(HTTP_ERROR_1)), ("", HTTPError(HTTP_ERROR_1)), (None, HTTPError(HTTP_ERROR_1)), ] ) def test_serverautherror_str_def(self, msg, details): """All tests for ServerAuthError.str_def().""" exc = ServerAuthError(msg, details) classname = exc.__class__.__name__ request_method = details.request_method request_uri = details.request_uri http_status = details.http_status reason = details.reason # Execute the code to be tested str_def = exc.str_def() str_def = ' ' + str_def assert str_def.find(' classname={!r};'.format(classname)) >= 0 assert str_def.find(' request_method={!r};'. format(request_method)) >= 0 assert str_def.find(' request_uri={!r};'.format(request_uri)) >= 0 assert str_def.find(' http_status={!r};'.format(http_status)) >= 0 assert str_def.find(' reason={!r};'.format(reason)) >= 0 assert str_def.find(' message={!r};'.format(msg)) >= 0 class TestParseError(object): """All tests for exception class ParseError.""" @pytest.mark.parametrize( # Input and expected arguments. "args, exp_line, exp_column", [ # args: (msg,) (("Bla: line 42 column 7 (char 6)",), 42, 7), (("Bla line 42 column 7 (char 6)",), None, None), (("Bla: line 42, column 7 (char 6)",), None, None), (("Bla: line 42 column 7, (char 6)",), None, None), (("",), None, None), ((None,), None, None), ] ) @pytest.mark.parametrize( # Whether each input arg is passed as pos.arg (None) or keyword arg # (arg name), or is defaulted (omitted from right). "arg_names", [ (None,), ('msg',), ] ) def test_parseerror_initial_attrs( self, arg_names, args, exp_line, exp_column): """Test initial attributes of ParseError.""" msg = args[0] posargs, kwargs = func_args(args, arg_names) # Execute the code to be tested exc = ParseError(*posargs, **kwargs) assert isinstance(exc, Error) assert len(exc.args) == 1 assert exc.args[0] == msg assert exc.line == exp_line assert exc.column == exp_column @pytest.mark.parametrize( "msg", [ ("Bla: line 42 column 7 (char 6)"), ("fake msg"), (""), (None), ] ) def test_parseerror_repr(self, msg): """All tests for ParseError.__repr__().""" exc = ParseError(msg) classname = exc.__class__.__name__ # Execute the code to be tested repr_str = repr(exc) # We check the one-lined string just roughly repr_str = repr_str.replace('\n', '\\n') assert re.match(r'^{}\s*\(.*\)$'.format(classname), repr_str) @pytest.mark.parametrize( "msg", [ ("Bla: line 42 column 7 (char 6)"), ("fake msg"), (""), (None), ] ) def test_parseerror_str(self, msg): """All tests for ParseError.__str__().""" exc = ParseError(msg) exp_str = str(exc.args[0]) # Execute the code to be tested str_str = str(exc) assert str_str == exp_str @pytest.mark.parametrize( "msg", [ ("Bla: line 42 column 7 (char 6)"), ("fake msg"), (""), (None), ] ) def test_parseerror_str_def(self, msg): """All tests for ParseError.str_def().""" exc = ParseError(msg) classname = exc.__class__.__name__ # Execute the code to be tested str_def = exc.str_def() str_def = ' ' + str_def assert str_def.find(' classname={!r};'.format(classname)) >= 0 assert str_def.find(' line={!r};'.format(exc.line)) >= 0 assert str_def.find(' column={!r};'.format(exc.column)) >= 0 assert str_def.find(' message={!r};'.format(msg)) >= 0 class TestVersionError(object): """All tests for exception class VersionError.""" @pytest.mark.parametrize( # Input and expected arguments. "args", [ # (msg, min_api_version, api_version) ("fake msg", (2, 1), (1, 2)), ("", (2, 1), (1, 2)), (None, (2, 1), (1, 2)), ] ) @pytest.mark.parametrize( # Whether each input arg is passed as pos.arg (None) or keyword arg # (arg name), or is defaulted (omitted from right). "arg_names", [ (None, None, None), ('msg', 'min_api_version', 'api_version'), ] ) def test_versionerror_initial_attrs(self, arg_names, args): """Test initial attributes of VersionError.""" msg, min_api_version, api_version = args posargs, kwargs = func_args(args, arg_names) # Execute the code to be tested exc = VersionError(*posargs, **kwargs) assert isinstance(exc, Error) assert len(exc.args) == 1 assert exc.args[0] == msg assert exc.min_api_version == min_api_version assert exc.api_version == api_version @pytest.mark.parametrize( "msg, min_api_version, api_version", [ ("fake msg", (2, 1), (1, 2)), ("", (2, 1), (1, 2)), (None, (2, 1), (1, 2)), ] ) def test_versionerror_repr( self, msg, min_api_version, api_version): """All tests for VersionError.__repr__().""" exc = VersionError(msg, min_api_version, api_version) classname = exc.__class__.__name__ # Execute the code to be tested repr_str = repr(exc) # We check the one-lined string just roughly repr_str = repr_str.replace('\n', '\\n') assert re.match(r'^{}\s*\(.*\)$'.format(classname), repr_str) @pytest.mark.parametrize( "msg, min_api_version, api_version", [ ("fake msg", (2, 1), (1, 2)), ("", (2, 1), (1, 2)), (None, (2, 1), (1, 2)), ] ) def test_versionerror_str( self, msg, min_api_version, api_version): """All tests for VersionError.__str__().""" exc = VersionError(msg, min_api_version, api_version) exp_str = str(exc.args[0]) # Execute the code to be tested str_str = str(exc) assert str_str == exp_str @pytest.mark.parametrize( "msg, min_api_version, api_version", [ ("fake msg", (2, 1), (1, 2)), ("", (2, 1), (1, 2)), (None, (2, 1), (1, 2)), ] ) def test_versionerror_str_def( self, msg, min_api_version, api_version): """All tests for VersionError.str_def().""" exc = VersionError(msg, min_api_version, api_version) classname = exc.__class__.__name__ # Execute the code to be tested str_def = exc.str_def() str_def = ' ' + str_def assert str_def.find(' classname={!r};'.format(classname)) >= 0 assert str_def.find(' message={!r};'.format(msg)) >= 0 assert str_def.find(' min_api_version={!r};'. format(min_api_version)) >= 0 assert str_def.find(' api_version={!r};'. format(api_version)) >= 0 class TestHTTPError(object): """All tests for exception class HTTPError.""" @pytest.mark.parametrize( # Input and expected arguments. "args", [ # (body,) (HTTP_ERROR_1,), (HTTP_ERROR_2,), (HTTP_ERROR_3,), (HTTP_ERROR_4,), ] ) @pytest.mark.parametrize( # Whether each input arg is passed as pos.arg (None) or keyword arg # (arg name), or is defaulted (omitted from right). "arg_names", [ (None,), ('body',), ] ) def test_httperror_initial_attrs(self, arg_names, args): """Test initial attributes of HTTPError.""" body = args[0] posargs, kwargs = func_args(args, arg_names) # Execute the code to be tested exc = HTTPError(*posargs, **kwargs) assert isinstance(exc, Error) assert len(exc.args) == 1 assert exc.args[0] == body.get('message', None) assert exc.http_status == body.get('http-status', None) assert exc.reason == body.get('reason', None) assert exc.message == body.get('message', None) assert exc.request_method == body.get('request-method', None) assert exc.request_uri == body.get('request-uri', None) assert exc.request_query_parms == body.get('request-query-parms', None) assert exc.request_headers == body.get('request-headers', None) assert exc.request_authenticated_as == \ body.get('request-authenticated-as', None) assert exc.request_body == body.get('request-body', None) assert exc.request_body_as_string == \ body.get('request-body-as-string', None) assert exc.request_body_as_string_partial == \ body.get('request-body-as-string-partial', None) assert exc.stack == body.get('stack', None) assert exc.error_details == body.get('error-details', None) @pytest.mark.parametrize( "body", [ HTTP_ERROR_1, ] ) def test_httperror_repr(self, body): """All tests for HTTPError.__repr__().""" exc = HTTPError(body) classname = exc.__class__.__name__ # Execute the code to be tested repr_str = repr(exc) # We check the one-lined string just roughly repr_str = repr_str.replace('\n', '\\n') assert re.match(r'^{}\s*\(.*\)$'.format(classname), repr_str) @pytest.mark.parametrize( "body", [ HTTP_ERROR_1, ] ) def test_httperror_str(self, body): """All tests for HTTPError.__str__().""" exc = HTTPError(body) exp_str = "{http-status},{reason}: {message} [{request-method} "\ "{request-uri}]".format(**body) # Execute the code to be tested str_str = str(exc) assert str_str == exp_str @pytest.mark.parametrize( "body", [ HTTP_ERROR_1, ] ) def test_httperror_str_def(self, body): """All tests for HTTPError.str_def().""" exc = HTTPError(body) classname = exc.__class__.__name__ request_method = exc.request_method request_uri = exc.request_uri http_status = exc.http_status reason = exc.reason msg = exc.message # Execute the code to be tested str_def = exc.str_def() str_def = ' ' + str_def assert str_def.find(' classname={!r};'.format(classname)) >= 0 assert str_def.find(' request_method={!r};'. format(request_method)) >= 0 assert str_def.find(' request_uri={!r};'.format(request_uri)) >= 0 assert str_def.find(' http_status={!r};'.format(http_status)) >= 0 assert str_def.find(' reason={!r};'.format(reason)) >= 0 assert str_def.find(' message={!r};'.format(msg)) >= 0 class TestOperationTimeout(object): """All tests for exception class OperationTimeout.""" @pytest.mark.parametrize( # Input and expected arguments. "args", [ # (msg, operation_timeout) ("fake msg", 3), ("", 3), (None, 0), ] ) @pytest.mark.parametrize( # Whether each input arg is passed as pos.arg (None) or keyword arg # (arg name), or is defaulted (omitted from right). "arg_names", [ (None, None), ('msg', 'operation_timeout'), ] ) def test_operationtimeout_initial_attrs(self, arg_names, args): """Test initial attributes of OperationTimeout.""" msg, operation_timeout = args posargs, kwargs = func_args(args, arg_names) # Execute the code to be tested exc = OperationTimeout(*posargs, **kwargs) assert isinstance(exc, Error) assert len(exc.args) == 1 assert exc.args[0] == msg assert exc.operation_timeout == operation_timeout @pytest.mark.parametrize( "msg, operation_timeout", [ ("fake msg", 3), ("", 3), (None, 0), ] ) def test_operationtimeout_repr( self, msg, operation_timeout): """All tests for OperationTimeout.__repr__().""" exc = OperationTimeout(msg, operation_timeout) classname = exc.__class__.__name__ # Execute the code to be tested repr_str = repr(exc) # We check the one-lined string just roughly repr_str = repr_str.replace('\n', '\\n') assert re.match(r'^{}\s*\(.*\)$'.format(classname), repr_str) @pytest.mark.parametrize( "msg, operation_timeout", [ ("fake msg", 3), ("", 3), (None, 0), ] ) def test_operationtimeout_str( self, msg, operation_timeout): """All tests for OperationTimeout.__str__().""" exc = OperationTimeout(msg, operation_timeout) exp_str = str(exc.args[0]) # Execute the code to be tested str_str = str(exc) assert str_str == exp_str @pytest.mark.parametrize( "msg, operation_timeout", [ ("fake msg", 3), ("", 3), (None, 0), ] ) def test_operationtimeout_str_def( self, msg, operation_timeout): """All tests for OperationTimeout.str_def().""" exc = OperationTimeout(msg, operation_timeout) classname = exc.__class__.__name__ # Execute the code to be tested str_def = exc.str_def() str_def = ' ' + str_def assert str_def.find(' classname={!r};'.format(classname)) >= 0 assert str_def.find(' message={!r};'.format(msg)) >= 0 assert str_def.find(' operation_timeout={!r};'. format(operation_timeout)) >= 0 class TestStatusTimeout(object): """All tests for exception class StatusTimeout.""" @pytest.mark.parametrize( # Input and expected arguments. "args", [ # (msg, actual_status, desired_statuses, status_timeout) ("fake msg", 'foo off', ['foo on', 'bla'], 3), ("", '', [], 3), (None, None, [], 0), ] ) @pytest.mark.parametrize( # Whether each input arg is passed as pos.arg (None) or keyword arg # (arg name), or is defaulted (omitted from right). "arg_names", [ (None, None, None, None), ('msg', 'actual_status', 'desired_statuses', 'status_timeout'), ] ) def test_statustimeout_initial_attrs(self, arg_names, args): """Test initial attributes of StatusTimeout.""" msg, actual_status, desired_statuses, status_timeout = args posargs, kwargs = func_args(args, arg_names) # Execute the code to be tested exc = StatusTimeout(*posargs, **kwargs) assert isinstance(exc, Error) assert len(exc.args) == 1 assert exc.args[0] == msg assert exc.actual_status == actual_status assert exc.desired_statuses == desired_statuses assert exc.status_timeout == status_timeout @pytest.mark.parametrize( "msg, actual_status, desired_statuses, status_timeout", [ ("fake msg", 'foo off', ['foo on', 'bla'], 3), ("", '', [], 3), (None, None, [], 0), ] ) def test_statustimeout_repr( self, msg, actual_status, desired_statuses, status_timeout): """All tests for StatusTimeout.__repr__().""" exc = StatusTimeout( msg, actual_status, desired_statuses, status_timeout) classname = exc.__class__.__name__ # Execute the code to be tested repr_str = repr(exc) # We check the one-lined string just roughly repr_str = repr_str.replace('\n', '\\n') assert re.match(r'^{}\s*\(.*\)$'.format(classname), repr_str) @pytest.mark.parametrize( "msg, actual_status, desired_statuses, status_timeout", [ ("fake msg", 'foo off', ['foo on', 'bla'], 3), ("", '', [], 3), (None, None, [], 0), ] ) def test_statustimeout_str( self, msg, actual_status, desired_statuses, status_timeout): """All tests for StatusTimeout.__str__().""" exc = StatusTimeout( msg, actual_status, desired_statuses, status_timeout) exp_str = str(exc.args[0]) # Execute the code to be tested str_str = str(exc) assert str_str == exp_str @pytest.mark.parametrize( "msg, actual_status, desired_statuses, status_timeout", [ ("fake msg", 'foo off', ['foo on', 'bla'], 3), ("", '', [], 3), (None, None, [], 0), ] ) def test_statustimeout_str_def( self, msg, actual_status, desired_statuses, status_timeout): """All tests for StatusTimeout.str_def().""" exc = StatusTimeout( msg, actual_status, desired_statuses, status_timeout) classname = exc.__class__.__name__ # Execute the code to be tested str_def = exc.str_def() str_def = ' ' + str_def assert str_def.find(' classname={!r};'.format(classname)) >= 0 assert str_def.find(' message={!r};'.format(msg)) >= 0 assert str_def.find(' actual_status={!r};'. format(actual_status)) >= 0 assert str_def.find(' desired_statuses={!r};'. format(desired_statuses)) >= 0 assert str_def.find(' status_timeout={!r};'. format(status_timeout)) >= 0 class TestNoUniqueMatch(object): """All tests for exception class NoUniqueMatch.""" def setup_method(self): self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.faked_cpc = self.session.hmc.cpcs.add({ 'object-id': 'faked-cpc1', 'parent': None, 'class': 'cpc', 'name': 'cpc_1', 'description': 'CPC #1', 'status': 'active', 'dpm-enabled': True, 'is-ensemble-member': False, 'iml-mode': 'dpm', }) self.faked_osa1 = self.faked_cpc.adapters.add({ 'object-id': 'fake-osa1', 'parent': self.faked_cpc.uri, 'class': 'adapter', 'name': 'osa 1', 'description': 'OSA #1', 'status': 'active', 'type': 'osd', }) self.faked_osa2 = self.faked_cpc.adapters.add({ 'object-id': 'fake-osa2', 'parent': self.faked_cpc.uri, 'class': 'adapter', 'name': 'osa 2', 'description': 'OSA #2', 'status': 'active', 'type': 'osd', }) self.client = Client(self.session) @pytest.mark.parametrize( # Input and expected arguments. "args", [ # args: (filter_args,) - manager, resources are added dynamically ({'type': 'osa', 'status': 'active'},), ({},), (None,), ] ) @pytest.mark.parametrize( # Whether each input arg is passed as pos.arg (None) or keyword arg # (arg name), or is defaulted (omitted from right). "arg_names", [ (None, None, None), ('filter_args', 'manager', 'resources'), ] ) def test_nouniquematch_initial_attrs(self, arg_names, args): """Test initial attributes of NoUniqueMatch.""" filter_args = args[0] cpc = self.client.cpcs.find(name='cpc_1') manager = cpc.adapters resources = manager.list() resource_uris = [r.uri for r in resources] _args = list(args) _args.append(manager) _args.append(resources) posargs, kwargs = func_args(_args, arg_names) # Execute the code to be tested exc = NoUniqueMatch(*posargs, **kwargs) assert isinstance(exc, Error) assert len(exc.args) == 1 assert isinstance(exc.args[0], six.string_types) # auto-generated message, we don't expect a particular value assert exc.filter_args == filter_args assert exc.manager == manager assert exc.resources == resources assert exc.resource_uris == resource_uris @pytest.mark.parametrize( "filter_args", [ {'type': 'osa', 'status': 'active'}, ] ) def test_nouniquematch_repr(self, filter_args): """All tests for NoUniqueMatch.__repr__().""" cpc = self.client.cpcs.find(name='cpc_1') manager = cpc.adapters resources = manager.list() exc = NoUniqueMatch(filter_args, manager, resources) classname = exc.__class__.__name__ # Execute the code to be tested repr_str = repr(exc) # We check the one-lined string just roughly repr_str = repr_str.replace('\n', '\\n') assert re.match(r'^{}\s*\(.*\)$'.format(classname), repr_str) @pytest.mark.parametrize( "filter_args", [ {'type': 'osa', 'status': 'active'}, ] ) def test_nouniquematch_str(self, filter_args): """All tests for NoUniqueMatch.__str__().""" cpc = self.client.cpcs.find(name='cpc_1') manager = cpc.adapters resources = manager.list() exc = NoUniqueMatch(filter_args, manager, resources) exp_str = str(exc.args[0]) # Execute the code to be tested str_str = str(exc) assert str_str == exp_str @pytest.mark.parametrize( "filter_args", [ {'type': 'osa', 'status': 'active'}, ] ) def test_nouniquematch_str_def(self, filter_args): """All tests for NoUniqueMatch.str_def().""" cpc = self.client.cpcs.find(name='cpc_1') manager = cpc.adapters resources = manager.list() exc = NoUniqueMatch(filter_args, manager, resources) classname = exc.__class__.__name__ # Execute the code to be tested str_def = exc.str_def() str_def = ' ' + str_def assert str_def.find(' classname={!r};'.format(classname)) >= 0 assert str_def.find(' resource_classname={!r};'. format(manager.resource_class.__name__)) >= 0 assert str_def.find(' filter_args={!r};'.format(filter_args)) >= 0 assert str_def.find(' parent_classname={!r};'. format(manager.parent.__class__.__name__)) >= 0 assert str_def.find(' parent_name={!r};'. format(manager.parent.name)) >= 0 assert str_def.find(' message=') >= 0 class TestNotFound(object): """All tests for exception class NotFound.""" def setup_method(self): self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.faked_cpc = self.session.hmc.cpcs.add({ 'object-id': 'faked-cpc1', 'parent': None, 'class': 'cpc', 'name': 'cpc_1', 'description': 'CPC #1', 'status': 'active', 'dpm-enabled': True, 'is-ensemble-member': False, 'iml-mode': 'dpm', }) self.faked_osa1 = self.faked_cpc.adapters.add({ 'object-id': 'fake-osa1', 'parent': self.faked_cpc.uri, 'class': 'adapter', 'name': 'osa 1', 'description': 'OSA #1', 'status': 'inactive', 'type': 'osd', }) self.client = Client(self.session) @pytest.mark.parametrize( # Input and expected arguments. "args", [ # args: (filter_args,) - manager is added dynamically ({'type': 'osa', 'status': 'active'},), ({},), (None,), ] ) @pytest.mark.parametrize( # Whether each input arg is passed as pos.arg (None) or keyword arg # (arg name), or is defaulted (omitted from right). "arg_names", [ (None, None), ('filter_args', 'manager'), ] ) def test_notfound_initial_attrs(self, arg_names, args): """Test initial attributes of NotFound.""" filter_args = args[0] cpc = self.client.cpcs.find(name='cpc_1') manager = cpc.adapters _args = list(args) _args.append(manager) posargs, kwargs = func_args(_args, arg_names) # Execute the code to be tested exc = NotFound(*posargs, **kwargs) assert isinstance(exc, Error) assert len(exc.args) == 1 assert isinstance(exc.args[0], six.string_types) # auto-generated message, we don't expect a particular value assert exc.filter_args == filter_args assert exc.manager == manager @pytest.mark.parametrize( "filter_args", [ {'type': 'osa', 'status': 'active'}, ] ) def test_notfound_repr(self, filter_args): """All tests for NotFound.__repr__().""" cpc = self.client.cpcs.find(name='cpc_1') manager = cpc.adapters exc = NotFound(filter_args, manager) classname = exc.__class__.__name__ # Execute the code to be tested repr_str = repr(exc) # We check the one-lined string just roughly repr_str = repr_str.replace('\n', '\\n') assert re.match(r'^{}\s*\(.*\)$'.format(classname), repr_str) @pytest.mark.parametrize( "filter_args", [ {'type': 'osa', 'status': 'active'}, ] ) def test_notfound_str(self, filter_args): """All tests for NotFound.__str__().""" cpc = self.client.cpcs.find(name='cpc_1') manager = cpc.adapters exc = NotFound(filter_args, manager) exp_str = str(exc.args[0]) # Execute the code to be tested str_str = str(exc) assert str_str == exp_str @pytest.mark.parametrize( "filter_args", [ {'type': 'osa', 'status': 'active'}, ] ) def test_notfound_str_def(self, filter_args): """All tests for NotFound.str_def().""" cpc = self.client.cpcs.find(name='cpc_1') manager = cpc.adapters exc = NotFound(filter_args, manager) classname = exc.__class__.__name__ # Execute the code to be tested str_def = exc.str_def() str_def = ' ' + str_def assert str_def.find(' classname={!r};'.format(classname)) >= 0 assert str_def.find(' resource_classname={!r};'. format(manager.resource_class.__name__)) >= 0 assert str_def.find(' filter_args={!r};'.format(filter_args)) >= 0 assert str_def.find(' parent_classname={!r};'. format(manager.parent.__class__.__name__)) >= 0 assert str_def.find(' parent_name={!r};'. format(manager.parent.name)) >= 0 assert str_def.find(' message=') >= 0 zhmcclient-0.22.0/tests/unit/zhmcclient/__init__.py0000644000076500000240000000000013364325033022676 0ustar maierastaff00000000000000zhmcclient-0.22.0/tests/unit/zhmcclient/test_timestats.py0000644000076500000240000002343313364325033024232 0ustar maierastaff00000000000000# 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. """ Unit tests for _timestats module. """ from __future__ import absolute_import, print_function import time import pytest from zhmcclient import TimeStatsKeeper, TimeStats PRINT_HEADER = \ "Time statistics (times in seconds):\n" \ "Count Average Minimum Maximum Operation name" PRINT_HEADER_DISABLED = \ "Time statistics (times in seconds):\n" \ "Disabled." def time_abs_delta(t1, t2): """ Return the positive difference between two float values. """ return abs(t1 - t2) def measure(stats, duration): """ Return the measured duration of one begin/end cycle of a TimeStats object of a given intended duration. The runtime behavior may be that the actual duration is larger than the intended duration, therefore we need to measure the actual duration. """ begin = time.time() stats.begin() time.sleep(duration) stats.end() end = time.time() return end - begin class TestTimeStats(object): """All tests for TimeStatsKeeper and TimeStats.""" def test_enabling(self): """Test enabling and disabling.""" keeper = TimeStatsKeeper() assert not keeper.enabled, \ "Verify that initial state is disabled" keeper.disable() assert not keeper.enabled, \ "Verify that disabling a disabled keeper works" keeper.enable() assert keeper.enabled, \ "Verify that enabling a disabled keeper works" keeper.enable() assert keeper.enabled, \ "Verify that enabling an enabled keeper works" keeper.disable() assert not keeper.enabled, \ "Verify that disabling an enabled keeper works" def test_get(self): """Test getting time statistics.""" keeper = TimeStatsKeeper() snapshot_length = len(keeper.snapshot()) assert snapshot_length == 0, \ "Verify that initial state has no time statistics. " \ "Actual number = %d" % snapshot_length stats = keeper.get_stats('foo') snapshot_length = len(keeper.snapshot()) assert snapshot_length == 0, \ "Verify that getting a new stats with a disabled keeper results " \ "in no time statistics. Actual number = %d" % snapshot_length assert stats.keeper == keeper assert stats.name == "disabled" # stats for disabled keeper assert stats.count == 0 assert stats.avg_time == 0 assert stats.min_time == float('inf') assert stats.max_time == 0 keeper.enable() stats = keeper.get_stats('foo') snapshot_length = len(keeper.snapshot()) assert snapshot_length == 1, \ "Verify that getting a new stats with an enabled keeper results " \ "in one time statistics. Actual number = %d" % snapshot_length assert stats.keeper == keeper assert stats.name == 'foo' assert stats.count == 0 assert stats.avg_time == 0 assert stats.min_time == float('inf') assert stats.max_time == 0 keeper.get_stats('foo') snapshot_length = len(keeper.snapshot()) assert snapshot_length == 1, \ "Verify that getting an existing stats with an enabled keeper " \ "results in the same number of time statistics. " \ "Actual number = %d" % snapshot_length def test_measure_enabled(self): """Test measuring time with enabled keeper.""" keeper = TimeStatsKeeper() keeper.enable() # TimeStatsKeeper on Windows has only a precision of 1/60 sec duration = 1.6 delta = duration / 10.0 stats = keeper.get_stats('foo') dur = measure(stats, duration) stats_dict = keeper.snapshot() for op_name in stats_dict: stats = stats_dict[op_name] assert stats.count == 1 assert time_abs_delta(stats.avg_time, dur) < delta, \ "avg time: actual: %f, expected: %f, delta: %f" % \ (stats.avg_time, dur, delta) assert time_abs_delta(stats.min_time, dur) < delta, \ "min time: actual: %f, expected: %f, delta: %f" % \ (stats.min_time, dur, delta) assert time_abs_delta(stats.max_time, dur) < delta, \ "max time: actual: %f, expected: %f, delta: %f" % \ (stats.max_time, dur, delta) stats.reset() assert stats.count == 0 assert stats.avg_time == 0 assert stats.min_time == float('inf') assert stats.max_time == 0 def test_measure_disabled(self): """Test measuring time with disabled keeper.""" keeper = TimeStatsKeeper() duration = 0.2 stats = keeper.get_stats('foo') assert stats.name == 'disabled' stats.begin() time.sleep(duration) stats.end() stats_dict = keeper.snapshot() for op_name in stats_dict: stats = stats_dict[op_name] assert stats.count == 0 assert stats.avg_time == 0 assert stats.min_time == float('inf') assert stats.max_time == 0 def test_snapshot(self): """Test that snapshot() takes a stable snapshot.""" keeper = TimeStatsKeeper() keeper.enable() duration = 0.2 stats = keeper.get_stats('foo') # produce a first data item stats.begin() time.sleep(duration) stats.end() # take the snapshot snap_stats_dict = keeper.snapshot() # produce a second data item stats.begin() time.sleep(duration) stats.end() # verify that only the first data item is in the snapshot for op_name in snap_stats_dict: snap_stats = snap_stats_dict[op_name] assert snap_stats.count == 1 # verify that both data items are in the original stats object assert stats.count == 2 def test_measure_avg_min_max(self): """Test measuring avg min max values.""" keeper = TimeStatsKeeper() keeper.enable() # TimeStatsKeeper on Windows has only a precision of 1/60 sec durations = (0.6, 1.2, 1.5) delta = 0.08 count = len(durations) stats = keeper.get_stats('foo') m_durations = [] for duration in durations: m_durations.append(measure(stats, duration)) min_dur = min(m_durations) max_dur = max(m_durations) avg_dur = sum(m_durations) / float(count) stats_dict = keeper.snapshot() for op_name in stats_dict: stats = stats_dict[op_name] assert stats.count == 3 assert time_abs_delta(stats.avg_time, avg_dur) < delta, \ "avg time: actual: %f, expected: %f, delta: %f" % \ (stats.avg_time, avg_dur, delta) assert time_abs_delta(stats.min_time, min_dur) < delta, \ "min time: actual: %f, expected: %f, delta: %f" % \ (stats.min_time, min_dur, delta) assert time_abs_delta(stats.max_time, max_dur) < delta, \ "max time: actual: %f, expected: %f, delta: %f" % \ (stats.max_time, max_dur, delta) def test_only_end(self): """Test that invoking end() before begin() has ever been called raises a RuntimeError exception.""" keeper = TimeStatsKeeper() keeper.enable() stats = keeper.get_stats('foo') with pytest.raises(RuntimeError): stats.end() def test_end_after_end(self): """Test that invoking end() after a begin/end sequence raises a RuntimeError exception.""" keeper = TimeStatsKeeper() keeper.enable() stats = keeper.get_stats('foo') stats.begin() time.sleep(0.01) stats.end() with pytest.raises(RuntimeError): stats.end() def test_str_empty(self): """Test TimestatsKeeper.__str__() for an empty enabled keeper.""" keeper = TimeStatsKeeper() keeper.enable() s = str(keeper) assert s == PRINT_HEADER def test_str_disabled(self): """Test TimestatsKeeper.__str__() for a disabled keeper.""" keeper = TimeStatsKeeper() s = str(keeper) assert s == PRINT_HEADER_DISABLED def test_str_one(self): """Test TimestatsKeeper.__str__() for an enabled keeper with one data item.""" keeper = TimeStatsKeeper() keeper.enable() duration = 0.1 stats = keeper.get_stats('foo') # produce a data item stats.begin() time.sleep(duration) stats.end() s = str(keeper) assert s.startswith(PRINT_HEADER), \ "Unexpected str(keeper): %r" % s num_lines = len(s.split('\n')) assert num_lines == 3, \ "Unexpected str(keeper): %r" % s def test_ts_str(self): """Test Timestats.__str__().""" keeper = TimeStatsKeeper() timestats = TimeStats(keeper, "foo") s = str(timestats) assert s.startswith("TimeStats:"), \ "Unexpected str(timestats): %r" % s num_lines = len(s.split('\n')) assert num_lines == 1, \ "Unexpected str(timestats): %r" % s zhmcclient-0.22.0/tests/unit/zhmcclient/test_adapter.py0000644000076500000240000005147413364325033023643 0ustar maierastaff00000000000000# 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. """ Unit tests for _adapter module. """ from __future__ import absolute_import, print_function import pytest import copy import re from zhmcclient import Client, Adapter, NotFound, HTTPError from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources # Object IDs and names of our faked adapters: OSA1_OID = 'osa1-oid' OSA1_NAME = 'osa 1' HS2_OID = 'hs2-oid' HS2_NAME = 'hs 2' class TestAdapter(object): """All tests for the Adapter and AdapterManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked CPC in DPM mode without any child resources. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) self.faked_cpc = self.session.hmc.cpcs.add({ 'object-id': 'fake-cpc1-oid', # object-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': 'fake-cpc1-name', 'description': 'CPC #1 (DPM mode)', 'status': 'active', 'dpm-enabled': True, 'is-ensemble-member': False, 'iml-mode': 'dpm', 'machine-type': '2964', # z13 }) self.cpc = self.client.cpcs.find(name='fake-cpc1-name') def add_standard_osa(self): """Add a standard OSA adapter with one port to the faked HMC.""" # Adapter properties that will be auto-set: # - object-uri # - adapter-family # - network-port-uris (to empty array) faked_osa1 = self.faked_cpc.adapters.add({ 'object-id': OSA1_OID, 'parent': self.faked_cpc.uri, 'class': 'adapter', 'name': OSA1_NAME, 'description': 'OSA #1', 'status': 'active', 'type': 'osd', 'adapter-id': '123', 'detected-card-type': 'osa-express-5s-10gb', 'card-location': '1234-5678-J.01', 'port-count': 1, 'network-port-uris': [], 'state': 'online', 'configured-capacity': 80, 'used-capacity': 0, 'allowed-capacity': 80, 'maximum-total-capacity': 80, 'physical-channel-status': 'operating', }) # Port properties that will be auto-set: # - element-uri # Properties in parent adapter that will be auto-set: # - network-port-uris faked_osa1.ports.add({ 'element-id': 'fake-port11-oid', 'parent': faked_osa1.uri, 'class': 'network-port', 'index': 0, 'name': 'fake-port11-name', 'description': 'OSA #1 Port #1', }) return faked_osa1 def add_standard_hipersocket(self): """Add a standard Hipersocket adapter with one port to the faked HMC.""" # Adapter properties that will be auto-set: # - object-uri # - adapter-family # - network-port-uris (to empty array) faked_hs2 = self.faked_cpc.adapters.add({ 'object-id': HS2_OID, 'parent': self.faked_cpc.uri, 'class': 'adapter', 'name': HS2_NAME, 'description': 'Hipersocket #2', 'status': 'active', 'type': 'hipersockets', 'adapter-id': '123', 'detected-card-type': 'hipersockets', 'port-count': 1, 'network-port-uris': [], 'state': 'online', 'configured-capacity': 32, 'used-capacity': 0, 'allowed-capacity': 32, 'maximum-total-capacity': 32, 'physical-channel-status': 'operating', 'maximum-transmission-unit-size': 56, }) # Port properties that will be auto-set: # - element-uri # Properties in parent adapter that will be auto-set: # - network-port-uris faked_hs2.ports.add({ 'element-id': 'fake-port21-oid', 'parent': faked_hs2.uri, 'class': 'network-port', 'index': 0, 'name': 'fake-port21-name', 'description': 'Hipersocket #2 Port #1', }) return faked_hs2 def add_crypto_ce5s(self, faked_cpc): """Add a Crypto Express 5S adapter to a faked CPC.""" # Adapter properties that will be auto-set: # - object-uri # - adapter-family faked_adapter = faked_cpc.adapters.add({ 'object-id': 'fake-ce5s-oid', 'parent': faked_cpc.uri, 'class': 'adapter', 'name': 'fake-ce5s-name', 'description': 'Crypto Express 5S #1', 'status': 'active', 'type': 'crypto', 'adapter-id': '123', 'detected-card-type': 'crypto-express-5s', 'card-location': 'vvvv-wwww', 'state': 'online', 'physical-channel-status': 'operating', 'crypto-number': 7, 'crypto-type': 'ep11-coprocessor', 'udx-loaded': False, 'tke-commands-enabled': False, }) return faked_adapter def add_ficon_fe6sp(self, faked_cpc): """Add a not-configured FICON Express 6S+ adapter to a faked CPC.""" # Adapter properties that will be auto-set: # - object-uri # - storage-port-uris faked_ficon_adapter = faked_cpc.adapters.add({ 'object-id': 'fake-ficon6s-oid', 'parent': faked_cpc.uri, 'class': 'adapter', 'name': 'fake-ficon6s-name', 'description': 'FICON Express 6S+ #1', 'status': 'active', 'type': 'not-configured', 'adapter-id': '124', 'adapter-family': 'ficon', 'detected-card-type': 'ficon-express-16s-plus', 'card-location': 'vvvv-wwww', 'port-count': 1, 'state': 'online', 'configured-capacity': 254, 'used-capacity': 0, 'allowed-capacity': 254, 'maximum-total-capacity': 254, 'channel-path-id': None, 'physical-channel-status': 'not-defined', }) # Port properties that will be auto-set: # - element-uri # Properties in parent adapter that will be auto-set: # - storage-port-uris faked_ficon_adapter.ports.add({ 'element-id': 'fake-port11-oid', 'parent': faked_ficon_adapter.uri, 'class': 'storage-port', 'index': 0, 'name': 'fake-port11-name', 'description': 'FICON #1 Port #1', }) return faked_ficon_adapter def add_cpc_z13s(self): """Add a CPC #2 of type z13s to the faked HMC.""" # CPC properties that will be auto-set: # - object-uri faked_cpc = self.session.hmc.cpcs.add({ 'object-id': 'fake-cpc-2-oid', 'parent': None, 'class': 'cpc', 'name': 'fake-cpc-2-name', 'description': 'CPC z13s #2', 'status': 'active', 'dpm-enabled': True, 'is-ensemble-member': False, 'iml-mode': 'dpm', 'machine-type': '2965', # z13s }) return faked_cpc def test_adaptermanager_initial_attrs(self): """Test initial attributes of AdapterManager.""" adapter_mgr = self.cpc.adapters # Verify all public properties of the manager object assert adapter_mgr.resource_class == Adapter assert adapter_mgr.session == self.session assert adapter_mgr.parent == self.cpc assert adapter_mgr.cpc == self.cpc # TODO: Test for AdapterManager.__repr__() @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(), ['object-uri', 'name', 'status']), (dict(full_properties=False), ['object-uri', 'name', 'status']), (dict(full_properties=True), None), ] ) def test_adaptermanager_list_full_properties( self, full_properties_kwargs, prop_names): """Test AdapterManager.list() with full_properties.""" # Add two faked adapters faked_osa1 = self.add_standard_osa() faked_hs2 = self.add_standard_hipersocket() exp_faked_adapters = [faked_osa1, faked_hs2] adapter_mgr = self.cpc.adapters # Execute the code to be tested adapters = adapter_mgr.list(**full_properties_kwargs) assert_resources(adapters, exp_faked_adapters, prop_names) @pytest.mark.parametrize( "filter_args, exp_names", [ ({'object-id': OSA1_OID}, [OSA1_NAME]), ({'object-id': HS2_OID}, [HS2_NAME]), ({'object-id': [OSA1_OID, HS2_OID]}, [OSA1_NAME, HS2_NAME]), ({'object-id': [OSA1_OID, OSA1_OID]}, [OSA1_NAME]), ({'object-id': OSA1_OID + 'foo'}, []), ({'object-id': [OSA1_OID, HS2_OID + 'foo']}, [OSA1_NAME]), ({'object-id': [HS2_OID + 'foo', OSA1_OID]}, [OSA1_NAME]), ({'name': OSA1_NAME}, [OSA1_NAME]), ({'name': HS2_NAME}, [HS2_NAME]), ({'name': [OSA1_NAME, HS2_NAME]}, [OSA1_NAME, HS2_NAME]), ({'name': OSA1_NAME + 'foo'}, []), ({'name': [OSA1_NAME, HS2_NAME + 'foo']}, [OSA1_NAME]), ({'name': [HS2_NAME + 'foo', OSA1_NAME]}, [OSA1_NAME]), ({'name': [OSA1_NAME, OSA1_NAME]}, [OSA1_NAME]), ({'name': '.*osa 1'}, [OSA1_NAME]), ({'name': 'osa 1.*'}, [OSA1_NAME]), ({'name': 'osa .'}, [OSA1_NAME]), ({'name': '.sa 1'}, [OSA1_NAME]), ({'name': '.+'}, [OSA1_NAME, HS2_NAME]), ({'name': 'osa 1.+'}, []), ({'name': '.+osa 1'}, []), ({'name': OSA1_NAME, 'object-id': OSA1_OID}, [OSA1_NAME]), ({'name': OSA1_NAME, 'object-id': OSA1_OID + 'foo'}, []), ({'name': OSA1_NAME + 'foo', 'object-id': OSA1_OID}, []), ({'name': OSA1_NAME + 'foo', 'object-id': OSA1_OID + 'foo'}, []), ] ) def test_adaptermanager_list_filter_args(self, filter_args, exp_names): """Test AdapterManager.list() with filter_args.""" # Add two faked adapters self.add_standard_osa() self.add_standard_hipersocket() adapter_mgr = self.cpc.adapters # Execute the code to be tested adapters = adapter_mgr.list(filter_args=filter_args) assert len(adapters) == len(exp_names) if exp_names: names = [ad.properties['name'] for ad in adapters] assert set(names) == set(exp_names) def test_adaptermanager_create_hipersocket(self): """Test AdapterManager.create_hipersocket().""" hs_properties = { 'name': 'hs 3', 'description': 'Hipersocket #3', 'port-description': 'Hipersocket #3 Port', 'maximum-transmission-unit-size': 56, } adapter_mgr = self.cpc.adapters # Execute the code to be tested adapter = adapter_mgr.create_hipersocket(properties=hs_properties) assert isinstance(adapter, Adapter) assert adapter.name == adapter.properties['name'] assert adapter.uri == adapter.properties['object-uri'] # We expect the input properties to be in the resource object assert adapter.properties['name'] == 'hs 3' assert adapter.properties['description'] == 'Hipersocket #3' assert adapter.properties['port-description'] == 'Hipersocket #3 Port' assert adapter.properties['maximum-transmission-unit-size'] == 56 # TODO: Test for initial Adapter attributes (ports, port_uris_prop, # port_uri_segment) def test_adapter_repr(self): """Test Adapter.__repr__().""" # Add a faked adapter faked_osa = self.add_standard_osa() adapter_mgr = self.cpc.adapters adapter = adapter_mgr.find(name=faked_osa.name) # Execute the code to be tested repr_str = repr(adapter) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=adapter.__class__.__name__, id=id(adapter)), repr_str) def test_max_crypto_domains(self): """Test Adapter.maximum_crypto_domains on z13 and z13s.""" faked_cpc = self.faked_cpc faked_crypto = self.add_crypto_ce5s(faked_cpc) self._one_test_max_crypto_domains(faked_cpc, faked_crypto, 85) faked_cpc = self.add_cpc_z13s() faked_crypto = self.add_crypto_ce5s(faked_cpc) self._one_test_max_crypto_domains(faked_cpc, faked_crypto, 40) def _one_test_max_crypto_domains(self, faked_cpc, faked_adapter, exp_max_domains): cpc = self.client.cpcs.find(name=faked_cpc.name) adapter = cpc.adapters.find(name=faked_adapter.name) # Exercise code to be tested max_domains = adapter.maximum_crypto_domains assert max_domains == exp_max_domains def test_adapter_delete(self): """Test Adapter.delete() for Hipersocket adapter.""" # Add two faked adapters self.add_standard_osa() faked_hs = self.add_standard_hipersocket() adapter_mgr = self.cpc.adapters hs_adapter = adapter_mgr.find(name=faked_hs.name) # Execute the code to be tested hs_adapter.delete() with pytest.raises(NotFound): hs_adapter = adapter_mgr.find(type='hipersockets') with pytest.raises(NotFound): hs_adapter = adapter_mgr.find(name=faked_hs.name) adapters = adapter_mgr.list() assert len(adapters) == 1 with pytest.raises(HTTPError) as exc_info: hs_adapter.pull_full_properties() exc = exc_info.value assert exc.http_status == 404 assert exc.reason == 1 @pytest.mark.parametrize( "input_props", [ {}, {'description': 'New adapter description'}, {'channel-path-id': '1A', 'description': 'New adapter description'}, ] ) def test_adapter_update_properties(self, input_props): """Test Adapter.update_properties().""" # Add a faked adapter faked_adapter = self.add_standard_osa() adapter_mgr = self.cpc.adapters adapter = adapter_mgr.find(name=faked_adapter.name) adapter.pull_full_properties() saved_properties = copy.deepcopy(adapter.properties) # Execute the code to be tested adapter.update_properties(properties=input_props) # Verify that the resource object already reflects the property # updates. for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in adapter.properties prop_value = adapter.properties[prop_name] assert prop_value == exp_prop_value # Refresh the resource object and verify that the resource object # still reflects the property updates. adapter.pull_full_properties() for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in adapter.properties prop_value = adapter.properties[prop_name] assert prop_value == exp_prop_value def test_adapter_update_name(self): """ Test Adapter.update_properties() with 'name' property. """ # Add a faked adapter faked_adapter = self.add_standard_osa() adapter_name = faked_adapter.name adapter_mgr = self.cpc.adapters adapter = adapter_mgr.find(name=adapter_name) new_adapter_name = "new-" + adapter_name # Execute the code to be tested adapter.update_properties(properties={'name': new_adapter_name}) # Verify that the resource is no longer found by its old name, using # list() (this does not use the name-to-URI cache). adapters_list = adapter_mgr.list(filter_args=dict(name=adapter_name)) assert len(adapters_list) == 0 # Verify that the resource is no longer found by its old name, using # find() (this uses the name-to-URI cache). with pytest.raises(NotFound): adapter_mgr.find(name=adapter_name) # Verify that the resource object already reflects the update, even # though it has not been refreshed yet. assert adapter.properties['name'] == new_adapter_name # Refresh the resource object and verify that it still reflects the # update. adapter.pull_full_properties() assert adapter.properties['name'] == new_adapter_name # Verify that the resource can be found by its new name, using find() new_adapter_find = adapter_mgr.find(name=new_adapter_name) assert new_adapter_find.properties['name'] == new_adapter_name # Verify that the resource can be found by its new name, using list() new_adapters_list = adapter_mgr.list( filter_args=dict(name=new_adapter_name)) assert len(new_adapters_list) == 1 new_adapter_list = new_adapters_list[0] assert new_adapter_list.properties['name'] == new_adapter_name # TODO: Test for Adapter.change_crypto_type() @pytest.mark.parametrize( "init_type", ['not-configured', 'fc', 'fcp'] ) @pytest.mark.parametrize( "new_type", ['not-configured', 'fc', 'fcp'] ) def test_change_adapter_type_success(self, init_type, new_type): """Test Adapter.change_adapter_type() on ficon adapter with success.""" faked_cpc = self.faked_cpc faked_adapter = self.add_ficon_fe6sp(faked_cpc) # Set the desired initial adapter type for the test faked_adapter.properties['type'] = init_type adapter_mgr = self.cpc.adapters adapter = adapter_mgr.find(name=faked_adapter.name) if new_type == init_type: with pytest.raises(HTTPError) as exc_info: # Execute the code to be tested adapter.change_adapter_type(new_type) exc = exc_info.value assert exc.http_status == 400 assert exc.reason == 8 else: # Execute the code to be tested. adapter.change_adapter_type(new_type) act_type = adapter.get_property('type') assert act_type == new_type @pytest.mark.parametrize( "desc, family, init_type, new_type, exp_exc", [ ( "Invalid adapter family: 'osa'", 'osa', 'osd', None, HTTPError({'http-status': 400, 'reason': 18}) ), ( "Invalid new type value: 'xxx'", 'ficon', 'fcp', 'xxx', HTTPError({'http-status': 400, 'reason': 8}) ), ] ) def test_change_adapter_type_error( self, desc, family, init_type, new_type, exp_exc): """Test Adapter.change_adapter_type().""" faked_cpc = self.faked_cpc if family == 'ficon': faked_adapter = self.add_ficon_fe6sp(faked_cpc) else: assert family == 'osa' faked_adapter = self.add_standard_osa() faked_adapter.properties['type'] == init_type adapter_mgr = self.cpc.adapters adapter = adapter_mgr.find(name=faked_adapter.name) with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested adapter.change_adapter_type(new_type) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason zhmcclient-0.22.0/tests/unit/zhmcclient/test_ldap_server_definition.py0000644000076500000240000002743413364325033026740 0ustar maierastaff00000000000000# 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. """ Unit tests for _ldap_srv_def module. """ from __future__ import absolute_import, print_function import pytest import re import copy from zhmcclient import Client, HTTPError, NotFound, LdapServerDefinition from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources class TestLdapServerDefinition(object): """All tests for the LdapServerDefinition and LdapServerDefinitionManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked Console without any child resources. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) self.faked_console = self.session.hmc.consoles.add({ 'object-id': None, # object-uri will be automatically set 'parent': None, 'class': 'console', 'name': 'fake-console1', 'description': 'Console #1', }) self.console = self.client.consoles.find(name=self.faked_console.name) def add_ldap_srv_def(self, name): faked_ldap_srv_def = self.faked_console.ldap_server_definitions.add({ 'element-id': 'oid-{}'.format(name), # element-uri will be automatically set 'parent': '/api/console', 'class': 'ldap-server-definition', 'name': name, 'description': 'LDAP Server Definition {}'.format(name), 'primary-hostname-ipaddr': 'host-{}'.format(name), }) return faked_ldap_srv_def def test_ldap_srv_def_manager_repr(self): """Test LdapServerDefinitionManager.__repr__().""" ldap_srv_def_mgr = self.console.ldap_server_definitions # Execute the code to be tested repr_str = repr(ldap_srv_def_mgr) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=ldap_srv_def_mgr.__class__.__name__, id=id(ldap_srv_def_mgr)), repr_str) def test_ldap_srv_def_manager_initial_attrs(self): """Test initial attributes of LdapServerDefinitionManager.""" ldap_srv_def_mgr = self.console.ldap_server_definitions # Verify all public properties of the manager object assert ldap_srv_def_mgr.resource_class == LdapServerDefinition assert ldap_srv_def_mgr.class_name == 'ldap-server-definition' assert ldap_srv_def_mgr.session is self.session assert ldap_srv_def_mgr.parent is self.console assert ldap_srv_def_mgr.console is self.console @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(full_properties=False), ['element-uri']), (dict(full_properties=True), ['element-uri', 'name']), (dict(), # test default for full_properties (True) ['element-uri', 'name']), ] ) @pytest.mark.parametrize( "filter_args, exp_names", [ (None, ['a', 'b']), ({}, ['a', 'b']), ({'name': 'a'}, ['a']), ] ) def test_ldap_srv_def_manager_list( self, filter_args, exp_names, full_properties_kwargs, prop_names): """Test LdapServerDefinitionManager.list().""" faked_ldap_srv_def1 = self.add_ldap_srv_def(name='a') faked_ldap_srv_def2 = self.add_ldap_srv_def(name='b') faked_ldap_srv_defs = [faked_ldap_srv_def1, faked_ldap_srv_def2] exp_faked_ldap_srv_defs = [u for u in faked_ldap_srv_defs if u.name in exp_names] ldap_srv_def_mgr = self.console.ldap_server_definitions # Execute the code to be tested ldap_srv_defs = ldap_srv_def_mgr.list(filter_args=filter_args, **full_properties_kwargs) assert_resources(ldap_srv_defs, exp_faked_ldap_srv_defs, prop_names) @pytest.mark.parametrize( "input_props, exp_prop_names, exp_exc", [ ({}, # props missing None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X'}, # props missing None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X', 'name': 'a'}, ['element-uri', 'name', 'description'], None), ] ) def test_ldap_srv_def_manager_create( self, input_props, exp_prop_names, exp_exc): """Test LdapServerDefinitionManager.create().""" ldap_srv_def_mgr = self.console.ldap_server_definitions if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested ldap_srv_def_mgr.create(properties=input_props) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason else: # Execute the code to be tested. ldap_srv_def = ldap_srv_def_mgr.create(properties=input_props) # Check the resource for consistency within itself assert isinstance(ldap_srv_def, LdapServerDefinition) ldap_srv_def_name = ldap_srv_def.name exp_ldap_srv_def_name = ldap_srv_def.properties['name'] assert ldap_srv_def_name == exp_ldap_srv_def_name ldap_srv_def_uri = ldap_srv_def.uri exp_ldap_srv_def_uri = ldap_srv_def.properties['element-uri'] assert ldap_srv_def_uri == exp_ldap_srv_def_uri # Check the properties against the expected names and values for prop_name in exp_prop_names: assert prop_name in ldap_srv_def.properties if prop_name in input_props: value = ldap_srv_def.properties[prop_name] exp_value = input_props[prop_name] assert value == exp_value def test_ldap_srv_def_repr(self): """Test LdapServerDefinition.__repr__().""" faked_ldap_srv_def1 = self.add_ldap_srv_def(name='a') ldap_srv_def1 = self.console.ldap_server_definitions.find( name=faked_ldap_srv_def1.name) # Execute the code to be tested repr_str = repr(ldap_srv_def1) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=ldap_srv_def1.__class__.__name__, id=id(ldap_srv_def1)), repr_str) @pytest.mark.parametrize( "input_props, exp_exc", [ ({'name': 'a'}, None), ({'name': 'b'}, None), ] ) def test_ldap_srv_def_delete(self, input_props, exp_exc): """Test LdapServerDefinition.delete().""" faked_ldap_srv_def = self.add_ldap_srv_def(name=input_props['name']) ldap_srv_def_mgr = self.console.ldap_server_definitions ldap_srv_def = ldap_srv_def_mgr.find(name=faked_ldap_srv_def.name) if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested ldap_srv_def.delete() exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the LDAP Server Definition still exists ldap_srv_def_mgr.find(name=faked_ldap_srv_def.name) else: # Execute the code to be tested. ldap_srv_def.delete() # Check that the LDAP Server Definition no longer exists with pytest.raises(NotFound) as exc_info: ldap_srv_def_mgr.find(name=faked_ldap_srv_def.name) def test_ldap_srv_def_delete_create_same_name(self): """Test LdapServerDefinition.delete() followed by create() with same name.""" ldap_srv_def_name = 'faked_a' # Add the LDAP Server Definition to be tested self.add_ldap_srv_def(name=ldap_srv_def_name) # Input properties for a LDAP Server Definition with the same name sn_ldap_srv_def_props = { 'name': ldap_srv_def_name, 'description': 'LDAP Server Definition with same name', 'type': 'user-defined', } ldap_srv_def_mgr = self.console.ldap_server_definitions ldap_srv_def = ldap_srv_def_mgr.find(name=ldap_srv_def_name) # Execute the deletion code to be tested ldap_srv_def.delete() # Check that the LDAP Server Definition no longer exists with pytest.raises(NotFound): ldap_srv_def_mgr.find(name=ldap_srv_def_name) # Execute the creation code to be tested. ldap_srv_def_mgr.create(sn_ldap_srv_def_props) # Check that the LDAP Server Definition exists again under that name sn_ldap_srv_def = ldap_srv_def_mgr.find(name=ldap_srv_def_name) description = sn_ldap_srv_def.get_property('description') assert description == sn_ldap_srv_def_props['description'] @pytest.mark.parametrize( "input_props", [ {}, {'description': 'New LDAP Server Definition description'}, ] ) def test_ldap_srv_def_update_properties(self, input_props): """Test LdapServerDefinition.update_properties().""" ldap_srv_def_name = 'faked_a' # Add the LDAP Server Definition to be tested self.add_ldap_srv_def(name=ldap_srv_def_name) ldap_srv_def_mgr = self.console.ldap_server_definitions ldap_srv_def = ldap_srv_def_mgr.find(name=ldap_srv_def_name) ldap_srv_def.pull_full_properties() saved_properties = copy.deepcopy(ldap_srv_def.properties) # Execute the code to be tested ldap_srv_def.update_properties(properties=input_props) # Verify that the resource object already reflects the property # updates. for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in ldap_srv_def.properties prop_value = ldap_srv_def.properties[prop_name] assert prop_value == exp_prop_value, \ "Unexpected value for property {!r}".format(prop_name) # Refresh the resource object and verify that the resource object # still reflects the property updates. ldap_srv_def.pull_full_properties() for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in ldap_srv_def.properties prop_value = ldap_srv_def.properties[prop_name] assert prop_value == exp_prop_value zhmcclient-0.22.0/tests/unit/zhmcclient/test_nic.py0000644000076500000240000005174013364325033022770 0ustar maierastaff00000000000000# 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. """ Unit tests for _nic module. """ from __future__ import absolute_import, print_function import pytest import re import copy from zhmcclient import Client, Nic, HTTPError, NotFound from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources # Object IDs and names of our faked NICs: NIC1_OID = 'nic 1-oid' NIC1_NAME = 'nic 1' NIC2_OID = 'nic 2-oid' NIC2_NAME = 'nic 2' # URIs and Object IDs of elements referenced in NIC properties: VSWITCH11_OID = 'fake-vswitch11-oid' VSWITCH11_URI = '/api/virtual-switches/{}'.format(VSWITCH11_OID) ROCE2_OID = 'fake-roce2-oid' PORT21_OID = 'fake-port21-oid' PORT21_URI = '/api/adapters/{}/network-ports/{}'.format(ROCE2_OID, PORT21_OID) class TestNic(object): """All tests for Nic and NicManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked CPC in DPM mode with one partition that has no NICs. Add one OSA adapter, port and vswitch, for tests with OSA-backed NICs. Add one ROSE adapter and port, for tests with ROCE-backed NICs. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) # Add a CPC in DPM mode self.faked_cpc = self.session.hmc.cpcs.add({ 'element-id': 'fake-cpc1-oid', # element-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': 'fake-cpc1-name', 'description': 'CPC #1 (DPM mode)', 'status': 'active', 'dpm-enabled': True, 'is-ensemble-member': False, 'iml-mode': 'dpm', }) self.cpc = self.client.cpcs.find(name='fake-cpc1-name') # Add a partition to the CPC self.faked_partition = self.faked_cpc.partitions.add({ 'element-id': 'fake-part1-oid', # element-uri will be automatically set 'parent': self.faked_cpc.uri, 'class': 'partition', 'name': 'fake-part1-name', 'description': 'Partition #1', 'status': 'active', 'initial-memory': 1024, 'maximum-memory': 2048, }) self.partition = self.cpc.partitions.find(name='fake-part1-name') # Add an OSA adapter, port and vswitch to the CPC self.faked_osa1 = self.faked_cpc.adapters.add({ 'object-id': 'osa1-oid', 'parent': self.faked_cpc.uri, 'class': 'adapter', 'name': 'osa1', 'description': 'OSA #1', 'status': 'active', 'type': 'osd', 'adapter-id': '123', 'detected-card-type': 'osa-express-5s-10gb', 'card-location': '1234-5678-J.01', 'port-count': 1, 'network-port-uris': [], 'state': 'online', 'configured-capacity': 80, 'used-capacity': 0, 'allowed-capacity': 80, 'maximum-total-capacity': 80, 'channel-path-id': '1D', 'physical-channel-status': 'operating', }) self.faked_port11 = self.faked_osa1.ports.add({ 'element-id': 'fake-port11-oid', 'parent': self.faked_osa1.uri, 'class': 'network-port', 'index': 0, 'name': 'fake-port11-name', 'description': 'OSA #1 Port #1', }) self.faked_vswitch11 = self.faked_cpc.virtual_switches.add({ 'object-id': VSWITCH11_OID, 'parent': self.faked_cpc.uri, 'class': 'virtual-switch', 'name': 'fake-vswitch11-name', 'description': 'Vswitch for OSA #1 Port #1', 'type': 'osa', 'backing-adapter-uri': self.faked_osa1.uri, 'port': self.faked_port11.properties['index'], 'connected-vnic-uris': [], }) assert VSWITCH11_URI == self.faked_vswitch11.uri # Add a ROCE adapter and port to the CPC self.faked_roce2 = self.faked_cpc.adapters.add({ 'object-id': ROCE2_OID, 'parent': self.faked_cpc.uri, 'class': 'adapter', 'name': 'roce2', 'description': 'ROCE #2', 'status': 'active', 'type': 'roce', 'adapter-id': '123', 'detected-card-type': '10gbe-roce-express', 'card-location': '1234-5678-J.01', 'port-count': 1, 'network-port-uris': [], 'state': 'online', 'configured-capacity': 80, 'used-capacity': 0, 'allowed-capacity': 80, 'maximum-total-capacity': 80, 'physical-channel-status': 'operating', }) self.faked_port21 = self.faked_roce2.ports.add({ 'element-id': PORT21_OID, 'parent': self.faked_roce2.uri, 'class': 'network-port', 'index': 1, 'name': 'fake-port21-name', 'description': 'ROCE #2 Port #1', }) assert PORT21_URI == self.faked_port21.uri def add_nic1(self): """Add a faked OSA NIC 1 to the faked partition.""" faked_nic = self.faked_partition.nics.add({ 'element-id': NIC1_OID, # element-uri will be automatically set 'parent': self.faked_partition.uri, 'class': 'nic', 'name': NIC1_NAME, 'description': 'NIC ' + NIC1_NAME, 'type': 'osd', 'virtual-switch-uri': VSWITCH11_URI, 'device-number': '1111', 'ssc-management-nic': False, }) return faked_nic def add_nic2(self): """Add a faked ROCE NIC 2 to the faked partition.""" faked_nic = self.faked_partition.nics.add({ 'element-id': NIC2_OID, # element-uri will be automatically set 'parent': self.faked_partition.uri, 'class': 'nic', 'name': NIC2_NAME, 'description': 'NIC ' + NIC2_NAME, 'type': 'roce', 'network-adapter-port-uri': PORT21_URI, 'device-number': '1112', 'ssc-management-nic': False, }) return faked_nic def test_nicmanager_initial_attrs(self): """Test initial attributes of NicManager.""" nic_mgr = self.partition.nics # Verify all public properties of the manager object assert nic_mgr.resource_class == Nic assert nic_mgr.session == self.session assert nic_mgr.parent == self.partition assert nic_mgr.partition == self.partition # TODO: Test for NicManager.__repr__() @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(), ['element-uri']), (dict(full_properties=False), ['element-uri']), (dict(full_properties=True), None), ] ) def test_nicmanager_list_full_properties( self, full_properties_kwargs, prop_names): """Test NicManager.list() with full_properties.""" # Add two faked NICs faked_nic1 = self.add_nic1() faked_nic2 = self.add_nic2() exp_faked_nics = [faked_nic1, faked_nic2] nic_mgr = self.partition.nics # Execute the code to be tested nics = nic_mgr.list(**full_properties_kwargs) assert_resources(nics, exp_faked_nics, prop_names) @pytest.mark.parametrize( "filter_args, exp_oids", [ ({'element-id': NIC1_OID}, [NIC1_OID]), ({'element-id': NIC2_OID}, [NIC2_OID]), ({'element-id': [NIC1_OID, NIC2_OID]}, [NIC1_OID, NIC2_OID]), ({'element-id': [NIC1_OID, NIC1_OID]}, [NIC1_OID]), ({'element-id': NIC1_OID + 'foo'}, []), ({'element-id': [NIC1_OID, NIC2_OID + 'foo']}, [NIC1_OID]), ({'element-id': [NIC2_OID + 'foo', NIC1_OID]}, [NIC1_OID]), ({'name': NIC1_NAME}, [NIC1_OID]), ({'name': NIC2_NAME}, [NIC2_OID]), ({'name': [NIC1_NAME, NIC2_NAME]}, [NIC1_OID, NIC2_OID]), ({'name': NIC1_NAME + 'foo'}, []), ({'name': [NIC1_NAME, NIC2_NAME + 'foo']}, [NIC1_OID]), ({'name': [NIC2_NAME + 'foo', NIC1_NAME]}, [NIC1_OID]), ({'name': [NIC1_NAME, NIC1_NAME]}, [NIC1_OID]), ({'name': '.*nic 1'}, [NIC1_OID]), ({'name': 'nic 1.*'}, [NIC1_OID]), ({'name': 'nic .'}, [NIC1_OID, NIC2_OID]), ({'name': '.ic 1'}, [NIC1_OID]), ({'name': '.+'}, [NIC1_OID, NIC2_OID]), ({'name': 'nic 1.+'}, []), ({'name': '.+nic 1'}, []), ({'name': NIC1_NAME, 'element-id': NIC1_OID}, [NIC1_OID]), ({'name': NIC1_NAME, 'element-id': NIC1_OID + 'foo'}, []), ({'name': NIC1_NAME + 'foo', 'element-id': NIC1_OID}, []), ({'name': NIC1_NAME + 'foo', 'element-id': NIC1_OID + 'foo'}, []), ] ) def test_nicmanager_list_filter_args(self, filter_args, exp_oids): """Test NicManager.list() with filter_args.""" # Add two faked NICs self.add_nic1() self.add_nic2() nic_mgr = self.partition.nics # Execute the code to be tested nics = nic_mgr.list(filter_args=filter_args) assert len(nics) == len(exp_oids) if exp_oids: oids = [nic.properties['element-id'] for nic in nics] assert set(oids) == set(exp_oids) @pytest.mark.parametrize( "initial_partition_status, exp_status_exc", [ ('stopped', None), ('terminated', None), ('starting', HTTPError({'http-status': 409, 'reason': 1})), ('active', None), ('stopping', HTTPError({'http-status': 409, 'reason': 1})), ('degraded', None), ('reservation-error', None), ('paused', None), ] ) @pytest.mark.parametrize( "input_props, exp_prop_names, exp_prop_exc", [ ({}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-nic-x'}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'network-adapter-port-uri': PORT21_URI}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'virtual-switch-uri': VSWITCH11_URI}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-nic-x', 'network-adapter-port-uri': PORT21_URI}, ['element-uri', 'name', 'network-adapter-port-uri'], None), ({'name': 'fake-nic-x', 'virtual-switch-uri': VSWITCH11_URI}, ['element-uri', 'name', 'virtual-switch-uri'], None), ] ) def test_nicmanager_create( self, input_props, exp_prop_names, exp_prop_exc, initial_partition_status, exp_status_exc): """Test NicManager.create().""" # Set the status of the faked partition self.faked_partition.properties['status'] = initial_partition_status nic_mgr = self.partition.nics if exp_status_exc: exp_exc = exp_status_exc elif exp_prop_exc: exp_exc = exp_prop_exc else: exp_exc = None if exp_exc: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested nic = nic_mgr.create(properties=input_props) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason else: # Execute the code to be tested. # Note: the Nic object returned by Nic.create() has # the input properties plus 'element-uri' plus 'element-id'. nic = nic_mgr.create(properties=input_props) # Check the resource for consistency within itself assert isinstance(nic, Nic) nic_name = nic.name exp_nic_name = nic.properties['name'] assert nic_name == exp_nic_name nic_uri = nic.uri exp_nic_uri = nic.properties['element-uri'] assert nic_uri == exp_nic_uri # Check the properties against the expected names and values for prop_name in exp_prop_names: assert prop_name in nic.properties if prop_name in input_props: value = nic.properties[prop_name] exp_value = input_props[prop_name] assert value == exp_value def test_nic_repr(self): """Test Nic.__repr__().""" # Add a faked nic faked_nic = self.add_nic1() nic_mgr = self.partition.nics nic = nic_mgr.find(name=faked_nic.name) # Execute the code to be tested repr_str = repr(nic) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=nic.__class__.__name__, id=id(nic)), repr_str) @pytest.mark.parametrize( "initial_partition_status, exp_exc", [ ('stopped', None), ('terminated', None), ('starting', HTTPError({'http-status': 409, 'reason': 1})), ('active', None), ('stopping', HTTPError({'http-status': 409, 'reason': 1})), ('degraded', None), ('reservation-error', None), ('paused', None), ] ) def test_nic_delete(self, initial_partition_status, exp_exc): """Test Nic.delete().""" # Add a faked NIC to be tested and another one faked_nic = self.add_nic1() self.add_nic2() # Set the status of the faked partition self.faked_partition.properties['status'] = initial_partition_status nic_mgr = self.partition.nics nic = nic_mgr.find(name=faked_nic.name) if exp_exc: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested nic.delete() exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the NIC still exists nic_mgr.find(name=faked_nic.name) else: # Execute the code to be tested. nic.delete() # Check that the NIC no longer exists with pytest.raises(NotFound) as exc_info: nic_mgr.find(name=faked_nic.name) def test_nic_delete_create_same_name(self): """Test Nic.delete() followed by Nic.create() with same name.""" # Add a faked NIC to be tested and another one faked_nic = self.add_nic1() nic_name = faked_nic.name self.add_nic2() # Construct the input properties for a third NIC with same name part3_props = copy.deepcopy(faked_nic.properties) part3_props['description'] = 'Third NIC' # Set the status of the faked partition self.faked_partition.properties['status'] = 'stopped' # deletable nic_mgr = self.partition.nics nic = nic_mgr.find(name=nic_name) # Execute the deletion code to be tested. nic.delete() # Check that the NIC no longer exists with pytest.raises(NotFound): nic_mgr.find(name=nic_name) # Execute the creation code to be tested. nic_mgr.create(part3_props) # Check that the NIC exists again under that name nic3 = nic_mgr.find(name=nic_name) description = nic3.get_property('description') assert description == 'Third NIC' @pytest.mark.parametrize( "input_props", [ {}, {'description': 'New NIC description'}, {'device-number': 'FEDC', 'description': 'New NIC description'}, ] ) def test_nic_update_properties(self, input_props): """Test Nic.update_properties().""" # Add a faked NIC faked_nic = self.add_nic1() # Set the status of the faked partition self.faked_partition.properties['status'] = 'stopped' # updatable nic_mgr = self.partition.nics nic = nic_mgr.find(name=faked_nic.name) nic.pull_full_properties() saved_properties = copy.deepcopy(nic.properties) # Execute the code to be tested nic.update_properties(properties=input_props) # Verify that the resource object already reflects the property # updates. for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in nic.properties prop_value = nic.properties[prop_name] assert prop_value == exp_prop_value # Refresh the resource object and verify that the resource object # still reflects the property updates. nic.pull_full_properties() for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in nic.properties prop_value = nic.properties[prop_name] assert prop_value == exp_prop_value def test_nic_update_name(self): """Test Nic.update_properties() with 'name' property.""" # Add a faked NIC faked_nic = self.add_nic1() nic_name = faked_nic.name # Set the status of the faked partition self.faked_partition.properties['status'] = 'stopped' # updatable nic_mgr = self.partition.nics nic = nic_mgr.find(name=nic_name) new_nic_name = "new-" + nic_name # Execute the code to be tested nic.update_properties(properties={'name': new_nic_name}) # Verify that the resource is no longer found by its old name, using # list() (this does not use the name-to-URI cache). nics_list = nic_mgr.list( filter_args=dict(name=nic_name)) assert len(nics_list) == 0 # Verify that the resource is no longer found by its old name, using # find() (this uses the name-to-URI cache). with pytest.raises(NotFound): nic_mgr.find(name=nic_name) # Verify that the resource object already reflects the update, even # though it has not been refreshed yet. assert nic.properties['name'] == new_nic_name # Refresh the resource object and verify that it still reflects the # update. nic.pull_full_properties() assert nic.properties['name'] == new_nic_name # Verify that the resource can be found by its new name, using find() new_nic_find = nic_mgr.find(name=new_nic_name) assert new_nic_find.properties['name'] == new_nic_name # Verify that the resource can be found by its new name, using list() new_nics_list = nic_mgr.list( filter_args=dict(name=new_nic_name)) assert len(new_nics_list) == 1 new_nic_list = new_nics_list[0] assert new_nic_list.properties['name'] == new_nic_name def test_nicmanager_resource_object(self): """ Test NicManager.resource_object(). This test exists for historical reasons, and by now is covered by the test for BaseManager.resource_object(). """ nic_mgr = self.partition.nics nic_oid = 'fake-nic-id0711' # Execute the code to be tested nic = nic_mgr.resource_object(nic_oid) nic_uri = self.partition.uri + "/nics/" + nic_oid assert isinstance(nic, Nic) assert nic.uri == nic_uri assert nic.properties['element-uri'] == nic_uri assert nic.properties['element-id'] == nic_oid assert nic.properties['class'] == 'nic' assert nic.properties['parent'] == self.partition.uri zhmcclient-0.22.0/tests/unit/zhmcclient/test_partition.py0000644000076500000240000007373213364325033024235 0ustar maierastaff00000000000000# 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. """ Unit tests for _partition module. """ from __future__ import absolute_import, print_function import pytest import re import copy from zhmcclient import Client, Partition, HTTPError, NotFound from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources # Object IDs and names of our faked partitions: PART1_OID = 'part1-oid' PART1_NAME = 'part 1' PART2_OID = 'part2-oid' PART2_NAME = 'part 2' PART3_OID = 'part3-oid' PART3_NAME = 'part 3' class TestPartition(object): """All tests for the Partition and PartitionManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked CPC in DPM mode without any child resources. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) self.faked_cpc = self.session.hmc.cpcs.add({ 'object-id': 'fake-cpc1-oid', # object-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': 'fake-cpc1-name', 'description': 'CPC #1 (DPM mode)', 'status': 'active', 'dpm-enabled': True, 'is-ensemble-member': False, 'iml-mode': 'dpm', }) self.cpc = self.client.cpcs.find(name='fake-cpc1-name') def add_partition1(self): """Add partition 1 (type linux).""" faked_partition = self.faked_cpc.partitions.add({ 'object-id': PART1_OID, # object-uri will be automatically set 'parent': self.faked_cpc.uri, 'class': 'partition', 'name': PART1_NAME, 'description': 'Partition #1', 'status': 'active', 'type': 'linux', 'initial-memory': 1024, 'maximum-memory': 2048, }) return faked_partition def add_partition2(self): """Add partition 2 (type ssc).""" faked_partition = self.faked_cpc.partitions.add({ 'object-id': PART2_OID, # object-uri will be automatically set 'parent': self.faked_cpc.uri, 'class': 'partition', 'name': PART2_NAME, 'description': 'Partition #2', 'status': 'active', 'type': 'ssc', 'initial-memory': 1024, 'maximum-memory': 2048, }) return faked_partition def add_partition3(self): """Add partition 3 (support for firmware features).""" faked_partition = self.faked_cpc.partitions.add({ 'object-id': PART3_OID, # object-uri will be automatically set 'parent': self.faked_cpc.uri, 'class': 'partition', 'name': PART3_NAME, 'description': 'Partition #3', 'status': 'active', 'type': 'linux', 'initial-memory': 1024, 'maximum-memory': 2048, 'available-features-list': [], }) return faked_partition def add_partition(self, part_name): """Add a partition (using one of the known names).""" if part_name == PART1_NAME: faked_partition = self.add_partition1() elif part_name == PART2_NAME: faked_partition = self.add_partition2() elif part_name == PART3_NAME: faked_partition = self.add_partition3() return faked_partition def test_partitionmanager_initial_attrs(self): """Test initial attributes of PartitionManager.""" partition_mgr = self.cpc.partitions # Verify all public properties of the manager object assert partition_mgr.resource_class == Partition assert partition_mgr.session == self.session assert partition_mgr.parent == self.cpc assert partition_mgr.cpc == self.cpc # TODO: Test for PartitionManager.__repr__() @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(), ['object-uri', 'name', 'status']), (dict(full_properties=False), ['object-uri', 'name', 'status']), (dict(full_properties=True), None), ] ) def test_partitionmanager_list_full_properties( self, full_properties_kwargs, prop_names): """Test PartitionManager.list() with full_properties.""" # Add two faked partitions faked_partition1 = self.add_partition1() faked_partition2 = self.add_partition2() exp_faked_partitions = [faked_partition1, faked_partition2] partition_mgr = self.cpc.partitions # Execute the code to be tested partitions = partition_mgr.list(**full_properties_kwargs) assert_resources(partitions, exp_faked_partitions, prop_names) @pytest.mark.parametrize( "filter_args, exp_names", [ ({'object-id': PART1_OID}, [PART1_NAME]), ({'object-id': PART2_OID}, [PART2_NAME]), ({'object-id': [PART1_OID, PART2_OID]}, [PART1_NAME, PART2_NAME]), ({'object-id': [PART1_OID, PART1_OID]}, [PART1_NAME]), ({'object-id': PART1_OID + 'foo'}, []), ({'object-id': [PART1_OID, PART2_OID + 'foo']}, [PART1_NAME]), ({'object-id': [PART2_OID + 'foo', PART1_OID]}, [PART1_NAME]), ({'name': PART1_NAME}, [PART1_NAME]), ({'name': PART2_NAME}, [PART2_NAME]), ({'name': [PART1_NAME, PART2_NAME]}, [PART1_NAME, PART2_NAME]), ({'name': PART1_NAME + 'foo'}, []), ({'name': [PART1_NAME, PART2_NAME + 'foo']}, [PART1_NAME]), ({'name': [PART2_NAME + 'foo', PART1_NAME]}, [PART1_NAME]), ({'name': [PART1_NAME, PART1_NAME]}, [PART1_NAME]), ({'name': '.*part 1'}, [PART1_NAME]), ({'name': 'part 1.*'}, [PART1_NAME]), ({'name': 'part .'}, [PART1_NAME, PART2_NAME]), ({'name': '.art 1'}, [PART1_NAME]), ({'name': '.+'}, [PART1_NAME, PART2_NAME]), ({'name': 'part 1.+'}, []), ({'name': '.+part 1'}, []), ({'name': PART1_NAME, 'object-id': PART1_OID}, [PART1_NAME]), ({'name': PART1_NAME, 'object-id': PART1_OID + 'foo'}, []), ({'name': PART1_NAME + 'foo', 'object-id': PART1_OID}, []), ({'name': PART1_NAME + 'foo', 'object-id': PART1_OID + 'foo'}, []), ] ) def test_partitionmanager_list_filter_args(self, filter_args, exp_names): """Test PartitionManager.list() with filter_args.""" # Add two faked partitions self.add_partition1() self.add_partition2() partition_mgr = self.cpc.partitions # Execute the code to be tested partitions = partition_mgr.list(filter_args=filter_args) assert len(partitions) == len(exp_names) if exp_names: names = [p.properties['name'] for p in partitions] assert set(names) == set(exp_names) @pytest.mark.parametrize( "input_props, exp_prop_names, exp_exc", [ ({}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X'}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-part-x'}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-part-x', 'initial-memory': 1024}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-part-x', 'initial-memory': 1024, 'maximum-memory': 1024}, ['object-uri', 'name', 'initial-memory', 'maximum-memory'], None), ({'name': 'fake-part-x', 'initial-memory': 1024, 'maximum-memory': 1024, 'description': 'fake description X'}, ['object-uri', 'name', 'initial-memory', 'maximum-memory', 'description'], None), ] ) def test_partitionmanager_create(self, input_props, exp_prop_names, exp_exc): """Test PartitionManager.create().""" partition_mgr = self.cpc.partitions if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested partition = partition_mgr.create(properties=input_props) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason else: # Execute the code to be tested. # Note: the Partition object returned by Partition.create() has # the input properties plus 'object-uri'. partition = partition_mgr.create(properties=input_props) # Check the resource for consistency within itself assert isinstance(partition, Partition) partition_name = partition.name exp_partition_name = partition.properties['name'] assert partition_name == exp_partition_name partition_uri = partition.uri exp_partition_uri = partition.properties['object-uri'] assert partition_uri == exp_partition_uri # Check the properties against the expected names and values for prop_name in exp_prop_names: assert prop_name in partition.properties if prop_name in input_props: value = partition.properties[prop_name] exp_value = input_props[prop_name] assert value == exp_value def test_partitionmanager_resource_object(self): """ Test PartitionManager.resource_object(). This test exists for historical reasons, and by now is covered by the test for BaseManager.resource_object(). """ partition_mgr = self.cpc.partitions partition_oid = 'fake-partition-id42' # Execute the code to be tested partition = partition_mgr.resource_object(partition_oid) partition_uri = "/api/partitions/" + partition_oid assert isinstance(partition, Partition) assert partition.uri == partition_uri assert partition.properties['object-uri'] == partition_uri assert partition.properties['object-id'] == partition_oid assert partition.properties['class'] == 'partition' assert partition.properties['parent'] == self.cpc.uri # TODO: Test for initial Partition attributes (nics, hbas, # virtual_functions) def test_partition_repr(self): """Test Partition.__repr__().""" # Add a faked partition faked_partition = self.add_partition1() partition_mgr = self.cpc.partitions partition = partition_mgr.find(name=faked_partition.name) # Execute the code to be tested repr_str = repr(partition) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=partition.__class__.__name__, id=id(partition)), repr_str) @pytest.mark.parametrize( "initial_status, exp_exc", [ ('stopped', None), ('terminated', HTTPError({'http-status': 409, 'reason': 1})), ('starting', HTTPError({'http-status': 409, 'reason': 1})), ('active', HTTPError({'http-status': 409, 'reason': 1})), ('stopping', HTTPError({'http-status': 409, 'reason': 1})), ('degraded', HTTPError({'http-status': 409, 'reason': 1})), ('reservation-error', HTTPError({'http-status': 409, 'reason': 1})), ('paused', HTTPError({'http-status': 409, 'reason': 1})), ] ) def test_partition_delete(self, initial_status, exp_exc): """Test Partition.delete().""" # Add a faked partition to be tested and another one faked_partition = self.add_partition1() self.add_partition2() # Set the initial status of the faked partition faked_partition.properties['status'] = initial_status partition_mgr = self.cpc.partitions partition = partition_mgr.find(name=faked_partition.name) if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested partition.delete() exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the partition still exists partition_mgr.find(name=faked_partition.name) else: # Execute the code to be tested. partition.delete() # Check that the partition no longer exists with pytest.raises(NotFound) as exc_info: partition_mgr.find(name=faked_partition.name) def test_partition_delete_create_same_name(self): """Test Partition.delete() followed by create() with same name.""" # Add a faked partition to be tested and another one faked_partition = self.add_partition1() partition_name = faked_partition.name self.add_partition2() # Construct the input properties for a third partition part3_props = copy.deepcopy(faked_partition.properties) part3_props['description'] = 'Third partition' # Set the initial status of the faked partition faked_partition.properties['status'] = 'stopped' # deletable partition_mgr = self.cpc.partitions partition = partition_mgr.find(name=partition_name) # Execute the deletion code to be tested. partition.delete() # Check that the partition no longer exists with pytest.raises(NotFound): partition_mgr.find(name=partition_name) # Execute the creation code to be tested. partition_mgr.create(part3_props) # Check that the partition exists again under that name partition3 = partition_mgr.find(name=partition_name) description = partition3.get_property('description') assert description == 'Third partition' @pytest.mark.parametrize( "desc, partition_name, available_features, feature_name, " "exp_feature_enabled, exp_exc", [ ( "No feature support on the CPC", PART1_NAME, None, 'fake-feature1', None, ValueError() ), ( "Feature not available on the partition (empty feature list)", PART3_NAME, [], 'fake-feature1', None, ValueError() ), ( "Feature not available on the part (one other feature avail)", PART3_NAME, [ dict(name='fake-feature-foo', state=True), ], 'fake-feature1', None, ValueError() ), ( "Feature disabled (the only feature available)", PART3_NAME, [ dict(name='fake-feature1', state=False), ], 'fake-feature1', False, None ), ( "Feature enabled (the only feature available)", PART3_NAME, [ dict(name='fake-feature1', state=True), ], 'fake-feature1', True, None ), ] ) def test_partition_feature_enabled( self, desc, partition_name, available_features, feature_name, exp_feature_enabled, exp_exc): """Test Partition.feature_enabled().""" # Add a faked Partition faked_partition = self.add_partition(partition_name) # Set up the firmware feature list if available_features is not None: faked_partition.properties['available-features-list'] = \ available_features partition_mgr = self.cpc.partitions partition = partition_mgr.find(name=partition_name) if exp_exc: with pytest.raises(exp_exc.__class__): # Execute the code to be tested partition.feature_enabled(feature_name) else: # Execute the code to be tested act_feature_enabled = partition.feature_enabled(feature_name) assert act_feature_enabled == exp_feature_enabled @pytest.mark.parametrize( "desc, partition_name, available_features, exp_exc", [ ( "No feature support on the CPC", PART1_NAME, None, ValueError() ), ( "Feature not available on the partition (empty feature list)", PART3_NAME, [], None ), ( "Feature not available on the part (one other feature avail)", PART3_NAME, [ dict(name='fake-feature-foo', state=True), ], None ), ( "Feature disabled (the only feature available)", PART3_NAME, [ dict(name='fake-feature1', state=False), ], None ), ( "Feature enabled (the only feature available)", PART3_NAME, [ dict(name='fake-feature1', state=True), ], None ), ] ) def test_partition_feature_info( self, desc, partition_name, available_features, exp_exc): """Test Partition.feature_info().""" # Add a faked Partition faked_partition = self.add_partition(partition_name) # Set up the firmware feature list if available_features is not None: faked_partition.properties['available-features-list'] = \ available_features partition_mgr = self.cpc.partitions partition = partition_mgr.find(name=partition_name) if exp_exc: with pytest.raises(exp_exc.__class__): # Execute the code to be tested partition.feature_info() else: # Execute the code to be tested act_features = partition.feature_info() assert act_features == available_features @pytest.mark.parametrize( "partition_name", [ PART1_NAME, PART2_NAME, ] ) @pytest.mark.parametrize( "input_props", [ {}, {'description': 'New partition description'}, {'initial-memory': 512, 'description': 'New partition description'}, {'autogenerate-partition-id': True, 'partition-id': None}, {'boot-device': 'none', 'boot-ftp-host': None, 'boot-ftp-username': None, 'boot-ftp-password': None, 'boot-ftp-insfile': None}, {'boot-device': 'none', 'boot-network-device': None}, {'boot-device': 'none', 'boot-removable-media': None, 'boot-removable-media-type': None}, {'boot-device': 'none', 'boot-storage-device': None, 'boot-logical-unit-number': None, 'boot-world-wide-port-name': None}, {'boot-device': 'none', 'boot-iso-image-name': None, 'boot-iso-insfile': None}, {'ssc-ipv4-gateway': None, 'ssc-ipv6-gateway': None, 'ssc-master-userid': None, 'ssc-master-pw': None}, ] ) def test_partition_update_properties(self, input_props, partition_name): """Test Partition.update_properties().""" # Add faked partitions self.add_partition1() self.add_partition2() partition_mgr = self.cpc.partitions partition = partition_mgr.find(name=partition_name) partition.pull_full_properties() saved_properties = copy.deepcopy(partition.properties) # Execute the code to be tested partition.update_properties(properties=input_props) # Verify that the resource object already reflects the property # updates. for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in partition.properties prop_value = partition.properties[prop_name] assert prop_value == exp_prop_value # Refresh the resource object and verify that the resource object # still reflects the property updates. partition.pull_full_properties() for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in partition.properties prop_value = partition.properties[prop_name] assert prop_value == exp_prop_value def test_partition_update_name(self): """ Test Partition.update_properties() with 'name' property. """ # Add a faked partition faked_partition = self.add_partition1() partition_name = faked_partition.name partition_mgr = self.cpc.partitions partition = partition_mgr.find(name=partition_name) new_partition_name = "new-" + partition_name # Execute the code to be tested partition.update_properties(properties={'name': new_partition_name}) # Verify that the resource is no longer found by its old name, using # list() (this does not use the name-to-URI cache). partitions_list = partition_mgr.list( filter_args=dict(name=partition_name)) assert len(partitions_list) == 0 # Verify that the resource is no longer found by its old name, using # find() (this uses the name-to-URI cache). with pytest.raises(NotFound): partition_mgr.find(name=partition_name) # Verify that the resource object already reflects the update, even # though it has not been refreshed yet. assert partition.properties['name'] == new_partition_name # Refresh the resource object and verify that it still reflects the # update. partition.pull_full_properties() assert partition.properties['name'] == new_partition_name # Verify that the resource can be found by its new name, using find() new_partition_find = partition_mgr.find(name=new_partition_name) assert new_partition_find.properties['name'] == new_partition_name # Verify that the resource can be found by its new name, using list() new_partitions_list = partition_mgr.list( filter_args=dict(name=new_partition_name)) assert len(new_partitions_list) == 1 new_partition_list = new_partitions_list[0] assert new_partition_list.properties['name'] == new_partition_name @pytest.mark.parametrize( "initial_status, exp_exc", [ ('stopped', None), ('terminated', HTTPError({'http-status': 409, 'reason': 1})), ('starting', HTTPError({'http-status': 409, 'reason': 1})), ('active', HTTPError({'http-status': 409, 'reason': 1})), ('stopping', HTTPError({'http-status': 409, 'reason': 1})), ('degraded', HTTPError({'http-status': 409, 'reason': 1})), ('reservation-error', HTTPError({'http-status': 409, 'reason': 1})), ('paused', HTTPError({'http-status': 409, 'reason': 1})), ] ) def test_partition_start(self, initial_status, exp_exc): """Test Partition.start().""" # Add a faked partition faked_partition = self.add_partition1() # Set the initial status of the faked partition faked_partition.properties['status'] = initial_status partition_mgr = self.cpc.partitions partition = partition_mgr.find(name=faked_partition.name) if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested partition.start() exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason else: # Execute the code to be tested. ret = partition.start() assert ret == {} partition.pull_full_properties() status = partition.get_property('status') assert status == 'active' @pytest.mark.parametrize( "initial_status, exp_exc", [ ('stopped', HTTPError({'http-status': 409, 'reason': 1})), ('terminated', None), ('starting', HTTPError({'http-status': 409, 'reason': 1})), ('active', None), ('stopping', HTTPError({'http-status': 409, 'reason': 1})), ('degraded', HTTPError({'http-status': 409, 'reason': 1})), ('reservation-error', HTTPError({'http-status': 409, 'reason': 1})), ('paused', None), ] ) def test_partition_stop(self, initial_status, exp_exc): """Test Partition.stop().""" # Add a faked partition faked_partition = self.add_partition1() # Set the initial status of the faked partition faked_partition.properties['status'] = initial_status partition_mgr = self.cpc.partitions partition = partition_mgr.find(name=faked_partition.name) if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested partition.stop() exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason else: # Execute the code to be tested. ret = partition.stop() assert ret == {} partition.pull_full_properties() status = partition.get_property('status') assert status == 'stopped' # TODO: Re-enable test_partition_dump_partition() once supported in hdlr def xtest_partition_dump_partition(self): """Test Partition.dump_partition().""" # Add a faked partition faked_partition = self.add_partition1() partition_mgr = self.cpc.partitions partition = partition_mgr.find(name=faked_partition.name) parameters = { 'dump-load-hba-uri': 'fake-hba-uri', 'dump-world-wide-port-name': 'fake-wwpn', 'dump-logical-unit-number': 'fake-lun', } # Execute the code to be tested. ret = partition.dump_partition(parameters=parameters) assert ret == {} # TODO: Re-enable test_partition_psw_restart() once supported in hdlr def xtest_partition_psw_restart(self): """Test Partition.psw_restart().""" # Add a faked partition faked_partition = self.add_partition1() partition_mgr = self.cpc.partitions partition = partition_mgr.find(name=faked_partition.name) # Execute the code to be tested. ret = partition.psw_restart() assert ret == {} # TODO: Re-enable test_partition_mount_iso_image() once supported in hdlr def xtest_partition_mount_iso_image(self): """Test Partition.mount_iso_image().""" # Add a faked partition faked_partition = self.add_partition1() partition_mgr = self.cpc.partitions partition = partition_mgr.find(name=faked_partition.name) image = b'fake-image-data' image_name = 'fake-image-name' ins_file_name = 'fake-ins-file-name' # Execute the code to be tested. ret = partition.mount_iso_image(image=image, image_name=image_name, ins_file_name=ins_file_name) assert ret is None # TODO: Re-enable test_partition_unmount_iso_image() once supported in hdlr def xtest_partition_unmount_iso_image(self): """Test Partition.unmount_iso_image().""" # Add a faked partition faked_partition = self.add_partition1() partition_mgr = self.cpc.partitions partition = partition_mgr.find(name=faked_partition.name) # Execute the code to be tested. ret = partition.unmount_iso_image() assert ret is None # TODO: Test for Partition.send_os_command() # TODO: Test for Partition.wait_for_status() # TODO: Test for Partition.increase_crypto_config() # TODO: Test for Partition.decrease_crypto_config() # TODO: Test for Partition.change_crypto_domain_config() zhmcclient-0.22.0/tests/unit/zhmcclient/test_notification.py0000644000076500000240000001303713364325033024702 0ustar maierastaff00000000000000# 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. """ Unit tests for _notification module. The test strategy is to mock the STOMP messages of the HMC using the requests_mock package. """ from __future__ import absolute_import, print_function import json import threading from mock import patch from zhmcclient._notification import NotificationReceiver class MockedStompConnection(object): """ A class that replaces stomp.Connection for the usage scope in the zhmcclient._notification module, and that adds the ability to queue STOMP messages. """ def __init__(self, *args, **kwargs): """We ignore the args: [(self._host, self._port)], use_ssl="SSL") """ self._state_connected = False self._listener = None self._connect_userid = None self._connect_password = None self._connect_wait = None self._subscribe_destination = None self._subscribe_id = None self._subscribe_ack = None self._queued_messages = [] # items: tuple(headers, message_str) self._sender_thread = None def set_listener(self, name, listener): """Mocks the same-named method of stomp.Connection.""" assert not self._state_connected self._listener = listener def start(self): """Mocks the same-named method of stomp.Connection.""" assert not self._state_connected def connect(self, userid, password, wait): """Mocks the same-named method of stomp.Connection.""" assert not self._state_connected self._state_connected = True self._connect_userid = userid self._connect_password = password self._connect_wait = wait def subscribe(self, destination, id, ack): """Mocks the same-named method of stomp.Connection.""" assert self._state_connected self._subscribe_destination = destination self._subscribe_id = id self._subscribe_ack = ack def disconnect(self): """Mocks the same-named method of stomp.Connection.""" assert self._state_connected self._sender_thread.join() self._sender_thread = None self._state_connected = False def mock_add_message(self, headers, message_obj): """Adds a STOMP message to the queue.""" assert self._sender_thread is None message_str = json.dumps(message_obj) self._queued_messages.append((headers, message_str)) def mock_start(self): """Start the STOMP message sender thread.""" assert self._state_connected self._sender_thread = threading.Thread(target=self.mock_sender_run) self._sender_thread.start() def mock_sender_run(self): """Simulates the HMC sending STOMP messages. This method runs in a separate thread and processes the queued STOMP messages and sends them to the notification listener set up by the NotificationReceiver class.""" for msg_item in self._queued_messages: # The following method blocks until it can deliver a message headers, message_str = msg_item self._listener.on_message(headers, message_str) self._listener.on_disconnected() def receiver_run(receiver, msg_items): for headers, message in receiver.notifications(): msg_items.append((headers, message)) return msg_items def receive_notifications(receiver): msg_items = [] receiver_thread = threading.Thread(target=receiver_run, args=(receiver, msg_items)) receiver_thread.start() receiver.close() receiver_thread.join(1.0) if receiver_thread.is_alive(): raise AssertionError("receiver_thread is still alive") return msg_items class TestNotification(object): def setup_method(self): self.topic = 'fake-topic' self.hmc = 'fake-hmc' self.userid = 'fake-userid' self.password = 'fake-password' self.std_headers = { 'notification-type': 'fake-type' } @patch(target='stomp.Connection', new=MockedStompConnection) def test_no_messages(self): receiver = NotificationReceiver(self.topic, self.hmc, self.userid, self.password) conn = receiver._conn # We do not add any STOMP messages conn.mock_start() msg_items = receive_notifications(receiver) assert msg_items == [] @patch(target='stomp.Connection', new=MockedStompConnection) def test_one_message(self): receiver = NotificationReceiver(self.topic, self.hmc, self.userid, self.password) conn = receiver._conn # Add one STOMP message to be sent message_obj = dict(a=1, b=2) conn.mock_add_message(self.std_headers, message_obj) conn.mock_start() msg_items = receive_notifications(receiver) assert len(msg_items) == 1 msg0 = msg_items[0] assert msg0[0] == self.std_headers assert msg0[1] == message_obj zhmcclient-0.22.0/tests/unit/zhmcclient/test_user_role.py0000644000076500000240000004117513364325033024217 0ustar maierastaff00000000000000# 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. """ Unit tests for _user_role module. """ from __future__ import absolute_import, print_function import pytest import re import copy import six from zhmcclient import Client, HTTPError, NotFound, BaseResource, UserRole from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources class TestUserRole(object): """All tests for the UserRole and UserRoleManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked Console without any child resources. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) self.faked_console = self.session.hmc.consoles.add({ 'object-id': None, # object-uri will be automatically set 'parent': None, 'class': 'console', 'name': 'fake-console1', 'description': 'Console #1', }) self.console = self.client.consoles.find(name=self.faked_console.name) def add_user_role(self, name, type_): faked_user_role = self.faked_console.user_roles.add({ 'object-id': 'oid-{}'.format(name), # object-uri will be automatically set 'parent': '/api/console', 'class': 'user-role', 'name': name, 'description': 'User Role {}'.format(name), 'type': type_, }) return faked_user_role def test_user_role_manager_repr(self): """Test UserRoleManager.__repr__().""" user_role_mgr = self.console.user_roles # Execute the code to be tested repr_str = repr(user_role_mgr) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=user_role_mgr.__class__.__name__, id=id(user_role_mgr)), repr_str) def test_user_role_manager_initial_attrs(self): """Test initial attributes of UserRoleManager.""" user_role_mgr = self.console.user_roles # Verify all public properties of the manager object assert user_role_mgr.resource_class == UserRole assert user_role_mgr.class_name == 'user-role' assert user_role_mgr.session is self.session assert user_role_mgr.parent is self.console assert user_role_mgr.console is self.console @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(full_properties=False), ['object-uri']), (dict(full_properties=True), ['object-uri', 'name']), (dict(), # test default for full_properties (True) ['object-uri', 'name']), ] ) @pytest.mark.parametrize( "filter_args, exp_names", [ (None, ['a', 'b']), ({}, ['a', 'b']), ({'name': 'a'}, ['a']), ] ) def test_user_role_manager_list( self, filter_args, exp_names, full_properties_kwargs, prop_names): """Test UserRoleManager.list().""" faked_user_role1 = self.add_user_role(name='a', type_='user-defined') faked_user_role2 = self.add_user_role(name='b', type_='user-defined') faked_user_roles = [faked_user_role1, faked_user_role2] exp_faked_user_roles = [u for u in faked_user_roles if u.name in exp_names] user_role_mgr = self.console.user_roles # Execute the code to be tested user_roles = user_role_mgr.list(filter_args=filter_args, **full_properties_kwargs) assert_resources(user_roles, exp_faked_user_roles, prop_names) @pytest.mark.parametrize( "input_props, exp_prop_names, exp_exc", [ ({}, # name missing None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X'}, # name missing None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-name-x', 'type': 'user-defined'}, # type not allowed None, HTTPError({'http-status': 400, 'reason': 6})), ({'name': 'fake-name-x', 'type': 'system-defined'}, # type not allowed None, HTTPError({'http-status': 400, 'reason': 6})), ({'name': 'fake-name-x'}, ['object-uri', 'name'], None), ({'name': 'fake-name-x', 'description': 'fake description X'}, ['object-uri', 'name', 'description'], None), ] ) def test_user_role_manager_create(self, input_props, exp_prop_names, exp_exc): """Test UserRoleManager.create().""" user_role_mgr = self.console.user_roles if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested user_role_mgr.create(properties=input_props) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason else: # Execute the code to be tested. user_role = user_role_mgr.create(properties=input_props) # Check the resource for consistency within itself assert isinstance(user_role, UserRole) user_role_name = user_role.name exp_user_role_name = user_role.properties['name'] assert user_role_name == exp_user_role_name user_role_uri = user_role.uri exp_user_role_uri = user_role.properties['object-uri'] assert user_role_uri == exp_user_role_uri # Check the properties against the expected names and values for prop_name in exp_prop_names: assert prop_name in user_role.properties if prop_name in input_props: value = user_role.properties[prop_name] exp_value = input_props[prop_name] assert value == exp_value def test_user_role_repr(self): """Test UserRole.__repr__().""" faked_user_role1 = self.add_user_role(name='a', type_='user-defined') user_role1 = self.console.user_roles.find(name=faked_user_role1.name) # Execute the code to be tested repr_str = repr(user_role1) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=user_role1.__class__.__name__, id=id(user_role1)), repr_str) @pytest.mark.parametrize( "input_name, input_type, exp_exc", [ ('a', 'user-defined', None), # ('b', 'system-defined', # HTTPError({'http-status': 400, 'reason': 312})), # TODO: Re-enable once rejection for system-defined roles supported ] ) def test_user_role_delete(self, input_name, input_type, exp_exc): """Test UserRole.delete().""" faked_user_role = self.add_user_role(name=input_name, type_=input_type) user_role_mgr = self.console.user_roles user_role = user_role_mgr.find(name=faked_user_role.name) if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested user_role.delete() exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the user role still exists user_role_mgr.find(name=faked_user_role.name) else: # Execute the code to be tested. user_role.delete() # Check that the user role no longer exists with pytest.raises(NotFound) as exc_info: user_role_mgr.find(name=faked_user_role.name) def test_user_role_delete_create_same_name(self): """Test UserRole.delete() followed by create() with same name.""" user_role_name = 'faked_a' # Add the user role to be tested self.add_user_role(name=user_role_name, type_='user-defined') # Input properties for a user role with the same name sn_user_role_props = { 'name': user_role_name, 'description': 'User Role with same name', } user_role_mgr = self.console.user_roles user_role = user_role_mgr.find(name=user_role_name) # Execute the deletion code to be tested user_role.delete() # Check that the user role no longer exists with pytest.raises(NotFound): user_role_mgr.find(name=user_role_name) # Execute the creation code to be tested. user_role_mgr.create(sn_user_role_props) # Check that the user role exists again under that name sn_user_role = user_role_mgr.find(name=user_role_name) description = sn_user_role.get_property('description') assert description == sn_user_role_props['description'] @pytest.mark.parametrize( "input_props", [ {}, {'description': 'New user role description'}, ] ) def test_user_role_update_properties(self, input_props): """Test UserRole.update_properties().""" # Add the user role to be tested faked_user_role = self.add_user_role(name='a', type_='user-defined') user_role_mgr = self.console.user_roles user_role = user_role_mgr.find(name=faked_user_role.name) user_role.pull_full_properties() saved_properties = copy.deepcopy(user_role.properties) # Execute the code to be tested user_role.update_properties(properties=input_props) # Verify that the resource object already reflects the property # updates. for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in user_role.properties prop_value = user_role.properties[prop_name] assert prop_value == exp_prop_value, \ "Unexpected value for property {!r}".format(prop_name) # Refresh the resource object and verify that the resource object # still reflects the property updates. user_role.pull_full_properties() for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in user_role.properties prop_value = user_role.properties[prop_name] assert prop_value == exp_prop_value @pytest.mark.parametrize( "role_name, role_type, exp_exc", [ ('ra', 'user-defined', None), ('rb', 'system-defined', HTTPError({'http-status': 400, 'reason': 314})), ] ) @pytest.mark.parametrize( "perm_args", [ {'permitted_object': 'cpc', 'include_members': True, 'view_only': False}, ] ) def test_user_role_add_permission( self, perm_args, role_name, role_type, exp_exc): """Test UserRole.add_permission().""" faked_user_role = self.add_user_role(name=role_name, type_=role_type) user_role_mgr = self.console.user_roles user_role = user_role_mgr.find(name=faked_user_role.name) permitted_object = perm_args['permitted_object'] if isinstance(permitted_object, BaseResource): perm_obj = permitted_object.uri perm_type = 'object' else: assert isinstance(permitted_object, six.string_types) perm_obj = permitted_object perm_type = 'object-class' permission_parms = { 'permitted-object': perm_obj, 'permitted-object-type': perm_type, 'include-members': perm_args['include_members'], 'view-only-mode': perm_args['view_only'], } if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested user_role.add_permission(**perm_args) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the user role still does not have that permission user_role.pull_full_properties() if 'permissions' in user_role.properties: permissions = user_role.properties['permissions'] assert permission_parms not in permissions else: # Execute the code to be tested. ret = user_role.add_permission(**perm_args) assert ret is None # Check that the user role now has that permission user_role.pull_full_properties() permissions = user_role.properties['permissions'] assert permission_parms in permissions @pytest.mark.parametrize( "role_name, role_type, exp_exc", [ ('ra', 'user-defined', None), ('rb', 'system-defined', HTTPError({'http-status': 400, 'reason': 314})), ] ) @pytest.mark.parametrize( "perm_args", [ {'permitted_object': 'cpc', 'include_members': True, 'view_only': False}, ] ) def test_user_role_remove_permission( self, perm_args, role_name, role_type, exp_exc): """Test UserRole.remove_permission().""" faked_user_role = self.add_user_role(name=role_name, type_=role_type) user_role_mgr = self.console.user_roles user_role = user_role_mgr.find(name=faked_user_role.name) permitted_object = perm_args['permitted_object'] if isinstance(permitted_object, BaseResource): perm_obj = permitted_object.uri perm_type = 'object' else: assert isinstance(permitted_object, six.string_types) perm_obj = permitted_object perm_type = 'object-class' permission_parms = { 'permitted-object': perm_obj, 'permitted-object-type': perm_type, 'include-members': perm_args['include_members'], 'view-only-mode': perm_args['view_only'], } # Prepare the user role with the initial permission if 'permissions' not in faked_user_role.properties: faked_user_role.properties['permissions'] = [] faked_user_role.properties['permissions'].append(permission_parms) if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested user_role.remove_permission(**perm_args) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the user role still has that permission user_role.pull_full_properties() permissions = user_role.properties['permissions'] assert permission_parms in permissions else: # Execute the code to be tested. ret = user_role.remove_permission(**perm_args) assert ret is None # Check that the user role no longer has that permission user_role.pull_full_properties() if 'permissions' in user_role.properties: permissions = user_role.properties['permissions'] assert permission_parms not in permissions zhmcclient-0.22.0/tests/unit/zhmcclient/test_activation_profile.py0000644000076500000240000002675713364325033026112 0ustar maierastaff00000000000000# 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. """ Unit tests for _activation_profile module. """ from __future__ import absolute_import, print_function import pytest import copy import re from zhmcclient import Client, ActivationProfile from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources class TestActivationProfile(object): """ All tests for the ActivationProfile and ActivationProfileManager classes. """ def setup_method(self): """ Set up a faked session, and add a faked CPC in classic mode, and add two faked activation profiles of each type. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) self.faked_cpc = self.session.hmc.cpcs.add({ 'object-id': 'fake-cpc1-oid', # object-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': 'fake-cpc1-name', 'description': 'CPC #1 (classic mode)', 'status': 'active', 'dpm-enabled': False, 'is-ensemble-member': False, 'iml-mode': 'lpar', }) self.cpc = self.client.cpcs.find(name='fake-cpc1-name') self.faked_reset_ap_1 = self.faked_cpc.reset_activation_profiles.add({ # element-uri is set up automatically 'name': 'rap_1', 'parent': self.faked_cpc.uri, 'class': 'reset-activation-profile', 'description': 'RAP #1', }) self.faked_reset_ap_2 = self.faked_cpc.reset_activation_profiles.add({ # element-uri is set up automatically 'name': 'rap_2', 'parent': self.faked_cpc.uri, 'class': 'reset-activation-profile', 'description': 'RAP #2', }) self.faked_image_ap_1 = self.faked_cpc.image_activation_profiles.add({ # element-uri is set up automatically 'name': 'iap_1', 'parent': self.faked_cpc.uri, 'class': 'image-activation-profile', 'description': 'IAP #1', }) self.faked_image_ap_2 = self.faked_cpc.image_activation_profiles.add({ # element-uri is set up automatically 'name': 'iap_2', 'parent': self.faked_cpc.uri, 'class': 'image-activation-profile', 'description': 'IAP #2', }) self.faked_load_ap_1 = self.faked_cpc.load_activation_profiles.add({ # element-uri is set up automatically 'name': 'lap_1', 'parent': self.faked_cpc.uri, 'class': 'load-activation-profile', 'description': 'LAP #1', }) self.faked_load_ap_2 = self.faked_cpc.load_activation_profiles.add({ # element-uri is set up automatically 'name': 'lap_2', 'parent': self.faked_cpc.uri, 'class': 'load-activation-profile', 'description': 'LAP #2', }) @pytest.mark.parametrize( "profile_type", ['reset', 'image', 'load'] ) def test_profilemanager_initial_attrs(self, profile_type): """Test initial attributes of ActivationProfileManager.""" mgr_attr = profile_type + '_activation_profiles' profile_mgr = getattr(self.cpc, mgr_attr) # Verify all public properties of the manager object assert profile_mgr.resource_class == ActivationProfile assert profile_mgr.session == self.session assert profile_mgr.parent == self.cpc assert profile_mgr.cpc == self.cpc assert profile_mgr.profile_type == profile_type # TODO: Test for ActivationProfileManager.__repr__() @pytest.mark.parametrize( "profile_type", ['reset', 'image', 'load'] ) @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(), ['name', 'element-uri']), (dict(full_properties=False), ['name', 'element-uri']), (dict(full_properties=True), None), ] ) def test_profilemanager_list_full_properties( self, full_properties_kwargs, prop_names, profile_type): """Test ActivationProfileManager.list() with full_properties.""" mgr_attr = profile_type + '_activation_profiles' faked_profile_mgr = getattr(self.faked_cpc, mgr_attr) exp_faked_profiles = faked_profile_mgr.list() profile_mgr = getattr(self.cpc, mgr_attr) # Execute the code to be tested profiles = profile_mgr.list(**full_properties_kwargs) assert_resources(profiles, exp_faked_profiles, prop_names) @pytest.mark.parametrize( "profile_type, filter_args, exp_names", [ ('reset', {'name': 'rap_2'}, ['rap_2']), ('reset', {'name': '.*rap_1'}, ['rap_1']), ('reset', {'name': 'rap_1.*'}, ['rap_1']), ('reset', {'name': 'rap_.'}, ['rap_1', 'rap_2']), ('reset', {'name': '.ap_1'}, ['rap_1']), ('reset', {'name': '.+'}, ['rap_1', 'rap_2']), ('reset', {'name': 'rap_1.+'}, []), ('reset', {'name': '.+rap_1'}, []), ('image', {'name': 'iap_1'}, ['iap_1']), ('image', {'name': '.*iap_1'}, ['iap_1']), ('image', {'name': 'iap_1.*'}, ['iap_1']), ('image', {'name': 'iap_.'}, ['iap_1', 'iap_2']), ('image', {'name': '.ap_1'}, ['iap_1']), ('image', {'name': '.+'}, ['iap_1', 'iap_2']), ('image', {'name': 'iap_1.+'}, []), ('image', {'name': '.+iap_1'}, []), ('load', {'name': 'lap_2'}, ['lap_2']), ('load', {'name': '.*lap_1'}, ['lap_1']), ('load', {'name': 'lap_1.*'}, ['lap_1']), ('load', {'name': 'lap_.'}, ['lap_1', 'lap_2']), ('load', {'name': '.ap_1'}, ['lap_1']), ('load', {'name': '.+'}, ['lap_1', 'lap_2']), ('load', {'name': 'lap_1.+'}, []), ('load', {'name': '.+lap_1'}, []), ('reset', {'class': 'reset-activation-profile'}, ['rap_1', 'rap_2']), ('image', {'class': 'image-activation-profile'}, ['iap_1', 'iap_2']), ('load', {'class': 'load-activation-profile'}, ['lap_1', 'lap_2']), ('reset', {'class': 'reset-activation-profile', 'description': 'RAP #2'}, ['rap_2']), ('image', {'class': 'image-activation-profile', 'description': 'IAP #1'}, ['iap_1']), ('load', {'class': 'load-activation-profile', 'description': 'LAP #2'}, ['lap_2']), ('reset', {'description': 'RAP #1'}, ['rap_1']), ('image', {'description': 'IAP #2'}, ['iap_2']), ('load', {'description': 'LAP #1'}, ['lap_1']), ] ) def test_profilemanager_list_filter_args( self, profile_type, filter_args, exp_names): """Test ActivationProfileManager.list() with filter_args.""" mgr_attr = profile_type + '_activation_profiles' profile_mgr = getattr(self.cpc, mgr_attr) # Execute the code to be tested profiles = profile_mgr.list(filter_args=filter_args) assert len(profiles) == len(exp_names) if exp_names: names = [ap.properties['name'] for ap in profiles] assert set(names) == set(exp_names) # TODO: Test for initial ActivationProfile attributes def test_profile_repr(self): """Test ActivationProfile.__repr__().""" # We test __repr__() just for reset activation profiles, because the # ActivationProfile class is the same for all profile types and we know # that __repr__() does not depend on the profile type. profile_mgr = self.cpc.reset_activation_profiles reset_ap = profile_mgr.find(name='rap_1') # Execute the code to be tested repr_str = repr(reset_ap) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=reset_ap.__class__.__name__, id=id(reset_ap)), repr_str) @pytest.mark.parametrize( "profile_type", ['reset', 'image', 'load'] ) @pytest.mark.parametrize( "input_props", [ {}, {'description': 'New profile description'}, {'description': ''}, {'ssc-network-info': { 'chpid': '1a', 'port': 0, 'ipaddr-type': 'dhcp', 'vlan-id': None, 'static-ip-info': None, }}, {'group-profile-uri': None}, {'zaware-gateway-info': None}, ] ) def test_profile_update_properties(self, input_props, profile_type): """Test ActivationProfile.update_properties().""" mgr_attr = profile_type + '_activation_profiles' profile_mgr = getattr(self.cpc, mgr_attr) profile = profile_mgr.list()[0] profile.pull_full_properties() saved_properties = copy.deepcopy(profile.properties) # Execute the code to be tested profile.update_properties(properties=input_props) # Verify that the resource object already reflects the property # updates. for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in profile.properties prop_value = profile.properties[prop_name] assert prop_value == exp_prop_value # Refresh the resource object and verify that the resource object # still reflects the property updates. profile.pull_full_properties() for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in profile.properties prop_value = profile.properties[prop_name] assert prop_value == exp_prop_value zhmcclient-0.22.0/tests/unit/zhmcclient/test_manager.py0000644000076500000240000007155313364325033023635 0ustar maierastaff00000000000000# 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. """ Unit tests for _manager module. """ from __future__ import absolute_import, print_function from datetime import datetime import time import re import warnings import pytest from zhmcclient import BaseResource, BaseManager, Session, NotFound, \ NoUniqueMatch from zhmcclient._manager import _NameUriCache class MyResource(BaseResource): """ A derived resource for testing purposes. It is only needed because BaseManager needs it; it is not subject of test in this unit test module. """ # This init method is not part of the external API, so this testcase may # need to be updated if the API changes. def __init__(self, manager, uri, name=None, properties=None): super(MyResource, self).__init__(manager, uri, name, properties) class MyManager(BaseManager): """ A derived resource manager for testing the (abstract) BaseManager class. """ # This init method is not part of the external API, so this testcase may # need to be updated if the API changes. def __init__(self, session): super(MyManager, self).__init__( resource_class=MyResource, class_name='myresource', session=session, parent=None, # a top-level resource base_uri='/api/myresources/', oid_prop='fake_object_id', uri_prop='fake_uri_prop', name_prop='fake_name_prop', query_props=[]) self._list_resources = [] # resources to return in list() self._list_called = 0 # number of calls to list() def list(self, full_properties=False, filter_args=None): # This mocked implementation does its work based upon the # _list_resources instance variable, and then applies client-side # filtering on top of it. result_list = [] for res in self._list_resources: if not filter_args or self._matches_filters(res, filter_args): result_list.append(res) self._list_called += 1 return result_list class TestManager0(object): """ Tests for the BaseManager class with no initial resources. """ def setup_method(self): self.session = Session(host='fake-host', userid='fake-user', password='fake-pw') def test_resource_object_oid(self): """ Test BaseManager.resource_object() with oid input. """ res_mgr = MyManager(self.session) res_oid = 'fake-res-id0711' # Execute the code to be tested res = res_mgr.resource_object(res_oid) res_uri = res_mgr._base_uri + '/' + res_oid assert isinstance(res, res_mgr.resource_class) assert res.uri == res_uri assert res.properties[res_mgr._uri_prop] == res_uri assert res.properties[res_mgr._oid_prop] == res_oid assert res.properties['class'] == res_mgr.class_name exp_parent = res_mgr.parent.uri if res_mgr.parent is not None else None assert res.properties['parent'] == exp_parent def test_resource_object_uri(self): """ Test BaseManager.resource_object() with uri input. """ res_mgr = MyManager(self.session) res_oid = 'fake-res-id0711' res_uri = '/api/myresources/' + res_oid # Execute the code to be tested res = res_mgr.resource_object(res_uri) assert isinstance(res, res_mgr.resource_class) assert res.uri == res_uri assert res.properties[res_mgr._uri_prop] == res_uri assert res.properties[res_mgr._oid_prop] == res_oid assert res.properties['class'] == res_mgr.class_name exp_parent = res_mgr.parent.uri if res_mgr.parent is not None else None assert res.properties['parent'] == exp_parent def test_resource_object_props(self): """ Test BaseManager.resource_object() with additional props. """ res_mgr = MyManager(self.session) res_oid = 'fake-res-id0711' res_uri = '/api/myresources/' + res_oid add_props = { 'name': 'abc', 'prop1': 123, } # Execute the code to be tested res = res_mgr.resource_object(res_uri, add_props) assert res.properties['name'] == add_props['name'] assert res.properties['prop1'] == add_props['prop1'] class TestManager1(object): """ Tests for the BaseManager class with one resource. """ def setup_method(self): self.session = Session(host='fake-host', userid='fake-user', password='fake-pw') self.manager = MyManager(self.session) self.resource_uri = "/api/fake-uri-1" self.resource_name = "fake-name-1" self.resource = MyResource( self.manager, uri=self.resource_uri, properties={ self.manager._name_prop: self.resource_name, "other": "fake-other-1", }) self.manager._list_resources = [self.resource] def test_repr(self): """Test BaseManager.__repr__().""" manager = self.manager repr_str = repr(manager) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=manager.__class__.__name__, id=id(manager)), repr_str) def test_init_properties(self): """Test BaseManager properties after initialization.""" assert self.manager.resource_class == MyResource assert self.manager.session == self.session assert self.manager.parent is None def test_invalidate_cache(self): """Test invalidate_cache().""" filter_args = {self.manager._name_prop: self.resource_name} # Populate the cache by finding a resource by name. self.manager.find(**filter_args) assert self.manager._list_called == 1 # Check that on the second find by name, list() is not called again. self.manager.find(**filter_args) assert self.manager._list_called == 1 # Invalidate the cache via invalidate_cache(). self.manager.invalidate_cache() # Check that on the third find by name, list() is called again, because # the cache had been invalidated. self.manager.find(**filter_args) assert self.manager._list_called == 2 def test_flush(self): """Test flush() and verify that it raises a DeprecationWarning.""" filter_args = {self.manager._name_prop: self.resource_name} # Populate the cache by finding a resource by name. self.manager.find(**filter_args) assert self.manager._list_called == 1 # Check that on the second find by name, list() is not called again. self.manager.find(**filter_args) assert self.manager._list_called == 1 # Invalidate the cache via flush(). with warnings.catch_warnings(record=True) as wngs: warnings.simplefilter("always") self.manager.flush() assert len(wngs) == 1 wng = wngs[0] assert issubclass(wng.category, DeprecationWarning), \ "Unexpected warnings class: %s" % wng.category # Check that on the third find by name, list() is called again, because # the cache had been invalidated. self.manager.find(**filter_args) assert self.manager._list_called == 2 def test_list_not_implemented(self): """Test that BaseManager.list() raises NotImplementedError.""" manager = BaseManager( resource_class=MyResource, class_name='myresource', session=self.session, parent=None, # a top-level resource base_uri='/api/myresources/', oid_prop='fake_object_id', uri_prop='fake_uri_prop', name_prop='fake_name_prop', query_props=[]) with pytest.raises(NotImplementedError): manager.list() class TestManager2(object): """ Tests for the BaseManager class with two resources. """ def setup_method(self): self.session = Session(host='fake-host', userid='fake-user', password='fake-pw') self.manager = MyManager(self.session) self.resource1 = MyResource( self.manager, uri="/api/fake-uri-1", properties={ self.manager._name_prop: "fake-name-1", "other": "fake-other-1", "same": "fake-same", "int_other": 23, "int_same": 42, }) self.resource2 = MyResource( self.manager, uri="/api/fake-uri-2", properties={ self.manager._name_prop: "fake-name-2", "other": "fake-other-2", "same": "fake-same", "int_other": 24, "int_same": 42, }) self.manager._list_resources = [self.resource1, self.resource2] def test_findall_name_none(self): """Test BaseManager.findall() with no resource matching by the name resource property.""" filter_args = {self.manager._name_prop: "not-exists"} resources = self.manager.findall(**filter_args) assert len(resources) == 0 def test_findall_name_one(self): """Test BaseManager.findall() with one resource matching by the name resource property.""" filter_args = {self.manager._name_prop: self.resource2.name} resources = self.manager.findall(**filter_args) assert len(resources) == 1 assert resources[0].uri == self.resource2.uri assert resources[0].name == self.resource2.name def test_findall_str_none(self): """Test BaseManager.findall() with no resource matching by a string-typed (non-name) resource property.""" resources = self.manager.findall(other="not-exists") assert len(resources) == 0 def test_findall_str_one(self): """Test BaseManager.findall() with one resource matching by a string-typed (non-name) resource property.""" resources = self.manager.findall(other="fake-other-2") assert len(resources) == 1 assert resources[0].uri == self.resource2.uri assert resources[0].name == self.resource2.name def test_findall_str_one_and(self): """Test BaseManager.findall() with one resource matching by two string-typed (non-name) resource properties (which get ANDed).""" resources = self.manager.findall(same="fake-same", other="fake-other-2") assert len(resources) == 1 assert resources[0].uri == self.resource2.uri assert resources[0].name == self.resource2.name def test_findall_str_two(self): """Test BaseManager.findall() with two resources matching by a string-typed (non-name) resource property.""" resources = self.manager.findall(same="fake-same") assert len(resources) == 2 assert set([res.uri for res in resources]) == \ set([self.resource1.uri, self.resource2.uri]) def test_findall_str_two_or(self): """Test BaseManager.findall() with two resources matching by a list of string-typed (non-name) resource properties (which get ORed).""" resources = self.manager.findall(other=["fake-other-1", "fake-other-2"]) assert len(resources) == 2 assert set([res.uri for res in resources]) == \ set([self.resource1.uri, self.resource2.uri]) def test_findall_int_none(self): """Test BaseManager.findall() with no resource matching by a integer-typed resource property.""" resources = self.manager.findall(int_other=815) assert len(resources) == 0 def test_findall_int_one(self): """Test BaseManager.findall() with one resource matching by a integer-typed resource property.""" resources = self.manager.findall(int_other=24) assert len(resources) == 1 assert resources[0].uri == self.resource2.uri assert resources[0].name == self.resource2.name def test_findall_int_two(self): """Test BaseManager.findall() with two resources matching by a integer-typed resource property.""" resources = self.manager.findall(int_same=42) assert len(resources) == 2 assert set([res.uri for res in resources]) == \ set([self.resource1.uri, self.resource2.uri]) def test_find_name_none(self): """Test BaseManager.find() with no resource matching by the name resource property.""" filter_args = {self.manager._name_prop: "not-exists"} with pytest.raises(NotFound): self.manager.find(**filter_args) def test_find_name_one(self): """Test BaseManager.find() with one resource matching by the name resource property.""" filter_args = {self.manager._name_prop: self.resource2.name} resource = self.manager.find(**filter_args) assert resource.uri == self.resource2.uri assert resource.name == self.resource2.name def test_find_str_none(self): """Test BaseManager.find() with no resource matching by a string-typed (non-name) resource property.""" with pytest.raises(NotFound): self.manager.find(other="not-exists") def test_find_str_one(self): """Test BaseManager.find() with one resource matching by a string-typed (non-name) resource property.""" resource = self.manager.find(other="fake-other-2") assert resource.uri == self.resource2.uri assert resource.name == self.resource2.name def test_find_str_two(self): """Test BaseManager.find() with two resources matching by a string-typed (non-name) resource property.""" with pytest.raises(NoUniqueMatch): self.manager.find(same="fake-same") def test_find_int_none(self): """Test BaseManager.find() with no resource matching by a string-typed (non-name) resource property.""" with pytest.raises(NotFound): self.manager.find(int_other=815) def test_find_int_one(self): """Test BaseManager.find() with one resource matching by a string-typed (non-name) resource property.""" resource = self.manager.find(int_other=24) assert resource.uri == self.resource2.uri assert resource.name == self.resource2.name def test_find_int_two(self): """Test BaseManager.find() with two resources matching by a string-typed (non-name) resource property.""" with pytest.raises(NoUniqueMatch): self.manager.find(int_same=42) def test_find_by_name_none(self): """Test BaseManager.find_by_name() with no resource matching by the name resource property.""" with pytest.raises(NotFound): self.manager.find_by_name("not-exists") def test_find_by_name_one(self): """Test BaseManager.find_by_name() with one resource matching by the name resource property.""" resource = self.manager.find_by_name(self.resource2.name) assert resource.uri == self.resource2.uri assert resource.name == self.resource2.name class TestNameUriCache(object): """All tests for the _NameUriCache class.""" def assert_datetime_near(self, dt1, dt2, max_delta=0.1): delta = abs(dt2 - dt1).total_seconds() assert delta <= max_delta, \ "Datetime values are %s s apart, maximum is %s s" % \ (delta, max_delta) def setup_method(self): self.session = Session(host='fake-host', userid='fake-user', password='fake-pw') self.manager = MyManager(self.session) self.resource1_uri = "/api/fake-uri-1" self.resource1_name = "fake-name-1" self.resource1 = MyResource( self.manager, uri=self.resource1_uri, properties={ self.manager._name_prop: self.resource1_name, "other": "fake-other-1" }) self.resource2_uri = "/api/fake-uri-2" self.resource2_name = "fake-name-2" self.resource2 = MyResource( self.manager, uri=self.resource2_uri, properties={ self.manager._name_prop: self.resource2_name, "other": "fake-other-2" }) self.all_names = {self.resource1_name, self.resource2_name} self.manager._list_resources = [self.resource1, self.resource2] self.timetolive = 1.0 # seconds self.cache = _NameUriCache(self.manager, self.timetolive) self.created = datetime.now() def test_initial(self): """Test initial cache state.""" assert self.cache._manager == self.manager assert self.cache._timetolive == self.timetolive assert self.cache._uris == {} self.assert_datetime_near(self.cache._invalidated, self.created) def test_get_no_invalidate(self): """Tests for get() without auto-invalidating the cache.""" # Check that accessing an existing resource name that is not yet in the # cache brings all resources into the cache and causes list() to be # called once. resource1_uri = self.cache.get(self.resource1_name) assert resource1_uri == self.resource1.uri assert set(self.cache._uris.keys()) == self.all_names assert self.manager._list_called == 1 # Check that on the second access of the same name, list() is not # called again. resource1_uri = self.cache.get(self.resource1_name) assert self.manager._list_called == 1 def test_get_non_existing(self): """Tests for get() of a non-existing entry.""" # Check that accessing a non-existing resource name raises an # exception, but has populated the cache. with pytest.raises(NotFound): self.cache.get('non-existing') assert set(self.cache._uris.keys()) == self.all_names assert self.manager._list_called == 1 def test_get_auto_invalidate(self): """Tests for get() with auto-invalidating the cache.""" # Populate the cache. self.cache.get(self.resource1_name) assert self.manager._list_called == 1 self.assert_datetime_near(self.cache._invalidated, self.created) # Check that on the second access of the same name, list() is not # called again. self.cache.get(self.resource1_name) assert self.manager._list_called == 1 # Wait until the time-to-live has safely passed. time.sleep(self.timetolive + 0.2) # Check that on the third access of the same name, list() is called # again, because the cache now has auto-invalidated. self.cache.get(self.resource1_name) invalidated = datetime.now() assert self.manager._list_called == 2 self.assert_datetime_near(self.cache._invalidated, invalidated) def test_get_manual_invalidate(self): """Tests for get() and manual invalidate().""" # Populate the cache. self.cache.get(self.resource1_name) assert self.manager._list_called == 1 self.assert_datetime_near(self.cache._invalidated, self.created) # Check that on the second access of the same name, list() is not # called again. self.cache.get(self.resource1_name) assert self.manager._list_called == 1 # Manually invalidate the cache. self.cache.invalidate() invalidated = datetime.now() self.assert_datetime_near(self.cache._invalidated, invalidated) assert self.cache._uris == {} # Check that on the third access of the same name, list() is called # again, because the cache has been invalidated. self.cache.get(self.resource1_name) assert self.manager._list_called == 2 assert set(self.cache._uris.keys()) == self.all_names def test_refresh_empty(self): """Test refresh() on an empty cache.""" # Refresh the cache and check that this invalidates it and # re-populates it. self.cache.refresh() refreshed = datetime.now() self.assert_datetime_near(self.cache._invalidated, refreshed) assert self.manager._list_called == 1 assert set(self.cache._uris.keys()) == self.all_names def test_refresh_populated(self): """Test refresh() on a fully populated cache.""" # Populate the cache. self.cache.get(self.resource1_name) assert self.manager._list_called == 1 self.assert_datetime_near(self.cache._invalidated, self.created) # Refresh the cache and check that this invalidates it and # re-populates it. self.cache.refresh() refreshed = datetime.now() self.assert_datetime_near(self.cache._invalidated, refreshed) assert self.manager._list_called == 2 assert set(self.cache._uris.keys()) == self.all_names def test_delete_existing(self): """Test delete() of an existing cache entry, and re-accessing it.""" # Populate the cache. self.cache.get(self.resource1_name) assert self.manager._list_called == 1 self.assert_datetime_near(self.cache._invalidated, self.created) # Delete an existing cache entry and check that the entry is now gone. self.cache.delete(self.resource1_name) assert set(self.cache._uris.keys()) == {self.resource2_name} # Re-access the deleted entry, and check that list() is called again # to get that entry into the cache. self.cache.get(self.resource1_name) assert self.manager._list_called == 2 assert set(self.cache._uris.keys()) == self.all_names def test_delete_non_existing(self): """Test delete() of a non-existing cache entry.""" # Populate the cache. self.cache.get(self.resource1_name) assert self.manager._list_called == 1 self.assert_datetime_near(self.cache._invalidated, self.created) # Delete a non-existing cache entry and check that no exception is # raised and that the cache still contains the same entries. self.cache.delete('non-existing') assert self.manager._list_called == 1 assert set(self.cache._uris.keys()) == self.all_names def test_delete_none(self): """Test delete() of `None`.""" # Populate the cache. self.cache.get(self.resource1_name) assert self.manager._list_called == 1 self.assert_datetime_near(self.cache._invalidated, self.created) # Delete `None` and check that no exception is raised and that the # cache still contains the same entries. self.cache.delete(None) assert self.manager._list_called == 1 assert set(self.cache._uris.keys()) == self.all_names def test_update_from_empty(self): """Test update_from() on an empty cache.""" resource3_uri = "/api/fake-uri-3" resource3_name = "fake-name-3" resource3 = MyResource( self.manager, uri=resource3_uri, properties={ self.manager._name_prop: resource3_name, }) resource4_uri = "/api/fake-uri-4" resource4_name = "fake-name-4" resource4 = MyResource( self.manager, uri=resource4_uri, properties={ self.manager._name_prop: resource4_name, }) # Update the cache from these two resources check that they are now in # the cache (and that list() has not been called) self.cache.update_from([resource3, resource4]) assert self.manager._list_called == 0 assert set(self.cache._uris.keys()) == {resource3_name, resource4_name} def test_update_from_populated_modify_name(self): """Test update_from() on a populated cache and modify the URI of one existing entry.""" resource3_uri = "/api/fake-uri-3" resource3_name = "fake-name-3" resource3 = MyResource( self.manager, uri=resource3_uri, properties={ self.manager._name_prop: resource3_name, }) resource2_new_uri = "/api/fake-new-uri-2" resource2_new = MyResource( self.manager, uri=resource2_new_uri, properties={ self.manager._name_prop: self.resource2_name, }) # Populate the cache. self.cache.get(self.resource1_name) assert self.manager._list_called == 1 assert set(self.cache._uris.keys()) == \ {self.resource1_name, self.resource2_name} # Update the cache from these two resources check that they are now in # the cache (and that list() has not been called again). self.cache.update_from([resource3, resource2_new]) assert self.manager._list_called == 1 assert set(self.cache._uris.keys()) == \ {self.resource1_name, self.resource2_name, resource3_name} # Access the modified entry, and check that the entry has changed # (and that list() has not been called again). resource2_uri = self.cache.get(self.resource2_name) assert self.manager._list_called == 1 assert resource2_uri == resource2_new_uri def test_update_empty(self): """Test update() on an empty cache.""" resource3_uri = "/api/fake-uri-3" resource3_name = "fake-name-3" # Update the cache, to get the entry added. self.cache.update(resource3_name, resource3_uri) assert self.manager._list_called == 0 # Access the new entry, and check the entry (and that list() has not # been called). act_resource3_uri = self.cache.get(resource3_name) assert self.manager._list_called == 0 assert act_resource3_uri == resource3_uri def test_update_empty_empty(self): """Test update() on an empty cache with an empty resource name.""" resource3_uri = "/api/fake-uri-3" resource3_name = "" # Update the cache with the empty resource name, and check that no # exception is raised and that the cache is still empty. self.cache.update(resource3_name, resource3_uri) assert self.cache._uris == {} assert self.manager._list_called == 0 def test_update_empty_none(self): """Test update() on an empty cache with a `None` resource name.""" resource3_uri = "/api/fake-uri-3" resource3_name = None # Update the cache with the empty resource name, and check that no # exception is raised and that the cache is still empty. self.cache.update(resource3_name, resource3_uri) assert self.cache._uris == {} assert self.manager._list_called == 0 def test_update_populated_new(self): """Test update() on a populated cache with a new entry.""" resource3_uri = "/api/fake-uri-3" resource3_name = "fake-name-3" # Populate the cache. self.cache.get(self.resource1_name) assert self.manager._list_called == 1 assert set(self.cache._uris.keys()) == \ {self.resource1_name, self.resource2_name} # Update the cache, to get the new entry added. self.cache.update(resource3_name, resource3_uri) assert self.manager._list_called == 1 # Access the new entry, and check the entry (and that list() has not # been called). act_resource3_uri = self.cache.get(resource3_name) assert self.manager._list_called == 1 assert act_resource3_uri == resource3_uri def test_update_populated_modify(self): """Test update() on a populated cache by modifying an existing entry.""" resource2_new_uri = "/api/fake-new-uri-2" # Populate the cache. self.cache.get(self.resource1_name) assert self.manager._list_called == 1 assert set(self.cache._uris.keys()) == \ {self.resource1_name, self.resource2_name} # Update the cache, to get the existing entry modified. self.cache.update(self.resource2_name, resource2_new_uri) assert self.manager._list_called == 1 # Access the new entry, and check the entry (and that list() has not # been called again). act_resource2_uri = self.cache.get(self.resource2_name) assert self.manager._list_called == 1 assert act_resource2_uri == resource2_new_uri zhmcclient-0.22.0/tests/unit/zhmcclient/test_client.py0000644000076500000240000001043613364325033023472 0ustar maierastaff00000000000000# 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. """ Unit tests for _client module. """ from __future__ import absolute_import, print_function import pytest from zhmcclient import Client, CpcManager, MetricsContextManager from zhmcclient_mock import FakedSession class TestClient(object): """All tests for Client classes.""" @pytest.mark.parametrize( "hmc_name, hmc_version, api_version", [ ('fake-hmc', '2.13.1', '1.8'), ] ) def test_client_initial_attrs(self, hmc_name, hmc_version, api_version): """Test initial attributes of Client.""" session = FakedSession('fake-host', hmc_name, hmc_version, api_version) # Execute the code to be tested client = Client(session) assert client.session is session assert isinstance(client.cpcs, CpcManager) assert client.cpcs.session is session assert isinstance(client.metrics_contexts, MetricsContextManager) assert client.metrics_contexts.session is session assert client.metrics_contexts.client is client @pytest.mark.parametrize( "hmc_name, hmc_version, api_version", [ ('fake-hmc1', '2.13.1', '1.8'), ('fake-hmc2', '2.14.0', '2.20'), ] ) def test_version_info(self, hmc_name, hmc_version, api_version): """All tests for Client.version_info().""" session = FakedSession('fake-host', hmc_name, hmc_version, api_version) # Client object under test client = Client(session) # Execute the code to be tested version_info = client.version_info() exp_version_info = tuple([int(v) for v in api_version.split('.')]) assert version_info == exp_version_info @pytest.mark.parametrize( "hmc_name, hmc_version, api_version", [ ('fake-hmc1', '2.13.1', '1.8'), ('fake-hmc2', '2.14.0', '2.20'), ] ) def test_query_api_version(self, hmc_name, hmc_version, api_version): """All tests for Client.query_api_version().""" session = FakedSession('fake-host', hmc_name, hmc_version, api_version) # Client object under test client = Client(session) # Execute the code to be tested api_version_info = client.query_api_version() api_major_version = int(api_version.split('.')[0]) api_minor_version = int(api_version.split('.')[1]) exp_api_version_info = { 'api-major-version': api_major_version, 'api-minor-version': api_minor_version, 'hmc-version': hmc_version, 'hmc-name': hmc_name, } assert api_version_info == exp_api_version_info @pytest.mark.parametrize( "resources, exp_exc, exp_inventory", [ (None, Exception, None ), ([], None, {} # TODO: Add expected inventory ), (['partition'], None, {} # TODO: Add expected inventory ), ] ) def xtest_get_inventory(self, resources, exp_exc, exp_inventory): """All tests for Client.get_inventory().""" # TODO: Enable once mock support for Client.get_inventory() is there session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') # Client object under test client = Client(session) # TODO: Set up inventory from expected inventory if exp_exc: try: # Execute the code to be tested client.get_inventory(resources) except exp_exc: pass else: # Execute the code to be tested inventory = client.get_inventory(resources) assert inventory == exp_inventory zhmcclient-0.22.0/tests/unit/zhmcclient/test_hba.py0000644000076500000240000005320313364325033022745 0ustar maierastaff00000000000000# 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. """ Unit tests for _hba module. """ from __future__ import absolute_import, print_function import pytest import re import copy from zhmcclient import Client, Hba, HTTPError, NotFound from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources # Object IDs and names of our faked HBAs: HBA1_OID = 'hba 1-oid' HBA1_NAME = 'hba 1' HBA2_OID = 'hba 2-oid' HBA2_NAME = 'hba 2' # URIs and Object IDs of elements referenced in HBA properties: FCP1_OID = 'fake-fcp1-oid' PORT11_OID = 'fake-port11-oid' PORT11_URI = '/api/adapters/{}/storage-ports/{}'.format(FCP1_OID, PORT11_OID) class TestHba(object): """All tests for Hba and HbaManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked CPC in DPM mode with one partition that has no HBAs. Add one FCP adapter and port. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) # Add a CPC in DPM mode self.faked_cpc = self.session.hmc.cpcs.add({ 'element-id': 'fake-cpc1-oid', # element-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': 'fake-cpc1-name', 'description': 'CPC #1 (DPM mode)', 'status': 'active', 'dpm-enabled': True, 'is-ensemble-member': False, 'iml-mode': 'dpm', }) self.cpc = self.client.cpcs.find(name='fake-cpc1-name') # Add a partition to the CPC self.faked_partition = self.faked_cpc.partitions.add({ 'element-id': 'fake-part1-oid', # element-uri will be automatically set 'parent': self.faked_cpc.uri, 'class': 'partition', 'name': 'fake-part1-name', 'description': 'Partition #1', 'status': 'active', 'initial-memory': 1024, 'maximum-memory': 2048, }) self.partition = self.cpc.partitions.find(name='fake-part1-name') # Add an FCP adapter and port to the CPC self.faked_fcp1 = self.faked_cpc.adapters.add({ 'object-id': FCP1_OID, 'parent': self.faked_cpc.uri, 'class': 'adapter', 'name': 'fcp1', 'description': 'FCP #1', 'status': 'active', 'type': 'fcp', 'adapter-id': '123', 'detected-card-type': '10gbe-roce-express', 'card-location': '1234-5678-J.01', 'port-count': 1, 'network-port-uris': [], 'state': 'online', 'configured-capacity': 80, 'used-capacity': 0, 'allowed-capacity': 80, 'maximum-total-capacity': 80, 'physical-channel-status': 'operating', }) self.faked_port11 = self.faked_fcp1.ports.add({ 'element-id': PORT11_OID, 'parent': self.faked_fcp1.uri, 'class': 'storage-port', 'index': 1, 'name': 'fake-port11-name', 'description': 'FCP #1 Port #1', }) assert PORT11_URI == self.faked_port11.uri def add_hba1(self): """Add a faked HBA 1 to the faked partition.""" faked_hba = self.faked_partition.hbas.add({ 'element-id': HBA1_OID, # element-uri will be automatically set 'parent': self.faked_partition.uri, 'class': 'hba', 'name': HBA1_NAME, 'description': 'HBA ' + HBA1_NAME, 'adapter-port-uri': PORT11_URI, 'wwpn': 'AABBCCDDEEFF0011', 'device-number': '1111', }) return faked_hba def add_hba2(self): """Add a faked HBA 2 to the faked partition.""" faked_hba = self.faked_partition.hbas.add({ 'element-id': HBA2_OID, # element-uri will be automatically set 'parent': self.faked_partition.uri, 'class': 'hba', 'name': HBA2_NAME, 'description': 'HBA ' + HBA2_NAME, 'adapter-port-uri': PORT11_URI, 'wwpn': 'AABBCCDDEEFF0012', 'device-number': '1112', }) return faked_hba def test_hbamanager_initial_attrs(self): """Test initial attributes of HbaManager.""" hba_mgr = self.partition.hbas # Verify all public properties of the manager object assert hba_mgr.resource_class == Hba assert hba_mgr.session == self.session assert hba_mgr.parent == self.partition assert hba_mgr.partition == self.partition # TODO: Test for HbaManager.__repr__() @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(), ['element-uri']), (dict(full_properties=False), ['element-uri']), (dict(full_properties=True), None), ] ) def test_hbamanager_list_full_properties( self, full_properties_kwargs, prop_names): """Test HbaManager.list() with full_properties.""" # Add two faked HBAs faked_hba1 = self.add_hba1() faked_hba2 = self.add_hba2() exp_faked_hbas = [faked_hba1, faked_hba2] hba_mgr = self.partition.hbas # Execute the code to be tested hbas = hba_mgr.list(**full_properties_kwargs) assert_resources(hbas, exp_faked_hbas, prop_names) @pytest.mark.parametrize( "filter_args, exp_oids", [ ({'element-id': HBA1_OID}, [HBA1_OID]), ({'element-id': HBA2_OID}, [HBA2_OID]), ({'element-id': [HBA1_OID, HBA2_OID]}, [HBA1_OID, HBA2_OID]), ({'element-id': [HBA1_OID, HBA1_OID]}, [HBA1_OID]), ({'element-id': HBA1_OID + 'foo'}, []), ({'element-id': [HBA1_OID, HBA2_OID + 'foo']}, [HBA1_OID]), ({'element-id': [HBA2_OID + 'foo', HBA1_OID]}, [HBA1_OID]), ({'name': HBA1_NAME}, [HBA1_OID]), ({'name': HBA2_NAME}, [HBA2_OID]), ({'name': [HBA1_NAME, HBA2_NAME]}, [HBA1_OID, HBA2_OID]), ({'name': HBA1_NAME + 'foo'}, []), ({'name': [HBA1_NAME, HBA2_NAME + 'foo']}, [HBA1_OID]), ({'name': [HBA2_NAME + 'foo', HBA1_NAME]}, [HBA1_OID]), ({'name': [HBA1_NAME, HBA1_NAME]}, [HBA1_OID]), ({'name': '.*hba 1'}, [HBA1_OID]), ({'name': 'hba 1.*'}, [HBA1_OID]), ({'name': 'hba .'}, [HBA1_OID, HBA2_OID]), ({'name': '.ba 1'}, [HBA1_OID]), ({'name': '.+'}, [HBA1_OID, HBA2_OID]), ({'name': 'hba 1.+'}, []), ({'name': '.+hba 1'}, []), ({'name': HBA1_NAME, 'element-id': HBA1_OID}, [HBA1_OID]), ({'name': HBA1_NAME, 'element-id': HBA1_OID + 'foo'}, []), ({'name': HBA1_NAME + 'foo', 'element-id': HBA1_OID}, []), ({'name': HBA1_NAME + 'foo', 'element-id': HBA1_OID + 'foo'}, []), ] ) def test_hbamanager_list_filter_args(self, filter_args, exp_oids): """Test HbaManager.list() with filter_args.""" # Add two faked HBAs self.add_hba1() self.add_hba2() hba_mgr = self.partition.hbas # Execute the code to be tested hbas = hba_mgr.list(filter_args=filter_args) assert len(hbas) == len(exp_oids) if exp_oids: oids = [hba.properties['element-id'] for hba in hbas] assert set(oids) == set(exp_oids) @pytest.mark.parametrize( "initial_partition_status, exp_status_exc", [ ('stopped', None), ('terminated', None), ('starting', HTTPError({'http-status': 409, 'reason': 1})), ('active', None), ('stopping', HTTPError({'http-status': 409, 'reason': 1})), ('degraded', None), ('reservation-error', None), ('paused', None), ] ) @pytest.mark.parametrize( "input_props, exp_prop_names, exp_prop_exc", [ ({}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-hba-x'}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'adapter-port-uri': PORT11_URI}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-hba-x', 'adapter-port-uri': PORT11_URI}, ['element-uri', 'name', 'adapter-port-uri'], None), ] ) def test_hbamanager_create( self, input_props, exp_prop_names, exp_prop_exc, initial_partition_status, exp_status_exc): """Test HbaManager.create().""" # Set the status of the faked partition self.faked_partition.properties['status'] = initial_partition_status hba_mgr = self.partition.hbas if exp_status_exc: exp_exc = exp_status_exc elif exp_prop_exc: exp_exc = exp_prop_exc else: exp_exc = None if exp_exc: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested hba = hba_mgr.create(properties=input_props) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason else: # Execute the code to be tested. # Note: the Hba object returned by Hba.create() has # the input properties plus 'element-uri' plus 'element-id'. hba = hba_mgr.create(properties=input_props) # Check the resource for consistency within itself assert isinstance(hba, Hba) hba_name = hba.name exp_hba_name = hba.properties['name'] assert hba_name == exp_hba_name hba_uri = hba.uri exp_hba_uri = hba.properties['element-uri'] assert hba_uri == exp_hba_uri # Check the properties against the expected names and values for prop_name in exp_prop_names: assert prop_name in hba.properties if prop_name in input_props: value = hba.properties[prop_name] exp_value = input_props[prop_name] assert value == exp_value def test_hba_repr(self): """Test Hba.__repr__().""" # Add a faked hba faked_hba = self.add_hba1() hba_mgr = self.partition.hbas hba = hba_mgr.find(name=faked_hba.name) # Execute the code to be tested repr_str = repr(hba) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=hba.__class__.__name__, id=id(hba)), repr_str) @pytest.mark.parametrize( "initial_partition_status, exp_exc", [ ('stopped', None), ('terminated', None), ('starting', HTTPError({'http-status': 409, 'reason': 1})), ('active', None), ('stopping', HTTPError({'http-status': 409, 'reason': 1})), ('degraded', None), ('reservation-error', None), ('paused', None), ] ) def test_hba_delete(self, initial_partition_status, exp_exc): """Test Hba.delete().""" # Add a faked HBA to be tested and another one faked_hba = self.add_hba1() self.add_hba2() # Set the status of the faked partition self.faked_partition.properties['status'] = initial_partition_status hba_mgr = self.partition.hbas hba = hba_mgr.find(name=faked_hba.name) if exp_exc: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested hba.delete() exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the HBA still exists hba_mgr.find(name=faked_hba.name) else: # Execute the code to be tested. hba.delete() # Check that the HBA no longer exists with pytest.raises(NotFound) as exc_info: hba_mgr.find(name=faked_hba.name) def test_hba_delete_create_same_name(self): """Test Hba.delete() followed by Hba.create() with same name.""" # Add a faked HBA to be tested and another one faked_hba = self.add_hba1() hba_name = faked_hba.name self.add_hba2() # Construct the input properties for a third HBA with same name part3_props = copy.deepcopy(faked_hba.properties) part3_props['description'] = 'Third HBA' # Set the status of the faked partition self.faked_partition.properties['status'] = 'stopped' # deletable hba_mgr = self.partition.hbas hba = hba_mgr.find(name=hba_name) # Execute the deletion code to be tested. hba.delete() # Check that the HBA no longer exists with pytest.raises(NotFound): hba_mgr.find(name=hba_name) # Execute the creation code to be tested. hba_mgr.create(part3_props) # Check that the HBA exists again under that name hba3 = hba_mgr.find(name=hba_name) description = hba3.get_property('description') assert description == 'Third HBA' @pytest.mark.parametrize( "input_props", [ {}, {'description': 'New HBA description'}, {'device-number': 'FEDC', 'description': 'New HBA description'}, ] ) def test_hba_update_properties(self, input_props): """Test Hba.update_properties().""" # Add a faked HBA faked_hba = self.add_hba1() # Set the status of the faked partition self.faked_partition.properties['status'] = 'stopped' # updatable hba_mgr = self.partition.hbas hba = hba_mgr.find(name=faked_hba.name) hba.pull_full_properties() saved_properties = copy.deepcopy(hba.properties) # Execute the code to be tested hba.update_properties(properties=input_props) # Verify that the resource object already reflects the property # updates. for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in hba.properties prop_value = hba.properties[prop_name] assert prop_value == exp_prop_value # Refresh the resource object and verify that the resource object # still reflects the property updates. hba.pull_full_properties() for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in hba.properties prop_value = hba.properties[prop_name] assert prop_value == exp_prop_value def test_hba_update_name(self): """Test Hba.update_properties() with 'name' property.""" # Add a faked HBA faked_hba = self.add_hba1() hba_name = faked_hba.name # Set the status of the faked partition self.faked_partition.properties['status'] = 'stopped' # updatable hba_mgr = self.partition.hbas hba = hba_mgr.find(name=hba_name) new_hba_name = "new-" + hba_name # Execute the code to be tested hba.update_properties(properties={'name': new_hba_name}) # Verify that the resource is no longer found by its old name, using # list() (this does not use the name-to-URI cache). hbas_list = hba_mgr.list( filter_args=dict(name=hba_name)) assert len(hbas_list) == 0 # Verify that the resource is no longer found by its old name, using # find() (this uses the name-to-URI cache). with pytest.raises(NotFound): hba_mgr.find(name=hba_name) # Verify that the resource object already reflects the update, even # though it has not been refreshed yet. assert hba.properties['name'] == new_hba_name # Refresh the resource object and verify that it still reflects the # update. hba.pull_full_properties() assert hba.properties['name'] == new_hba_name # Verify that the resource can be found by its new name, using find() new_hba_find = hba_mgr.find(name=new_hba_name) assert new_hba_find.properties['name'] == new_hba_name # Verify that the resource can be found by its new name, using list() new_hbas_list = hba_mgr.list( filter_args=dict(name=new_hba_name)) assert len(new_hbas_list) == 1 new_hba_list = new_hbas_list[0] assert new_hba_list.properties['name'] == new_hba_name @pytest.mark.parametrize( "initial_partition_status, exp_exc", [ ('stopped', None), ('terminated', None), ('starting', HTTPError({'http-status': 409, 'reason': 1})), ('active', None), ('stopping', HTTPError({'http-status': 409, 'reason': 1})), ('degraded', None), ('reservation-error', None), ('paused', None), ] ) def test_hba_reassign_port(self, initial_partition_status, exp_exc): """Test Hba.reassign_port().""" # Add a faked HBA to be tested. # Its port points to a faked URI. faked_hba = self.add_hba1() # Add a faked FCP with one port that the HBA will be reassigned to faked_adapter = self.faked_cpc.adapters.add({ 'object-id': 'fake-fcp1-oid', # object-uri is auto-set based upon object-id 'parent': self.faked_cpc.uri, 'class': 'adapter', 'name': 'fake-fcp1', 'description': 'FCP #1', 'status': 'active', 'type': 'fcp', # adapter-family is auto-set based upon type 'adapter-id': '123', 'detected-card-type': 'ficon-express-16s', 'card-location': '1234-5678-J.01', 'port-count': 1, 'storage-port-uris': [], 'state': 'online', 'configured-capacity': 80, 'used-capacity': 0, 'allowed-capacity': 80, 'maximum-total-capacity': 80, 'channel-path-id': '1B', 'physical-channel-status': 'operating', }) adapter = self.cpc.adapters.find(name='fake-fcp1') faked_adapter.ports.add({ 'element-id': 'fake-port1-oid', # element-uri is auto-set based upon object-id 'parent': faked_adapter.uri, 'class': 'storage-port', 'name': 'fake-port1', 'description': 'FCP #1 Port 1', 'index': 0, 'fabric-id': None, }) port = adapter.ports.find(name='fake-port1') # Set the status of the faked partition self.faked_partition.properties['status'] = initial_partition_status # The HBA object we will perform the test on hba = self.partition.hbas.find(name=faked_hba.name) # Save the HBA properties for later comparison hba.pull_full_properties() saved_properties = copy.deepcopy(hba.properties) if exp_exc: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested hba.reassign_port(port) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason # Check that the port of the HBA is unchanged ... prop_name = 'adapter-port-uri' # ... in the resource object: assert hba.properties[prop_name] == saved_properties[prop_name] # ... and again when refreshed from the mock state: hba.pull_full_properties() assert hba.properties[prop_name] == saved_properties[prop_name] else: # Execute the code to be tested. hba.reassign_port(port) # Check that the port of the HBA has been set ... # ... in the resource object: prop_name = 'adapter-port-uri' assert hba.properties[prop_name] == port.uri # ... and again when refreshed from the mock state: hba.pull_full_properties() assert hba.properties[prop_name] == port.uri zhmcclient-0.22.0/tests/unit/zhmcclient/test_virtual_switch.py0000644000076500000240000002121513364325033025260 0ustar maierastaff00000000000000# 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. """ Unit tests for _virtual_switch module. """ from __future__ import absolute_import, print_function import re # FIXME: Migrate requests_mock to zhmcclient_mock. import requests_mock from zhmcclient import Session, Client, Nic class TestVirtualSwitch(object): """All tests for VirtualSwitch and VirtualSwitchManager classes.""" def setup_method(self): self.session = Session('vswitch-dpm-host', 'vswitch-user', 'vswitch-pwd') self.client = Client(self.session) with requests_mock.mock() as m: # Because logon is deferred until needed, we perform it # explicitly in order to keep mocking in the actual test simple. m.post('/api/sessions', json={'api-session': 'vswitch-session-id'}) self.session.logon() self.cpc_mgr = self.client.cpcs with requests_mock.mock() as m: result = { 'cpcs': [ { 'object-uri': '/api/cpcs/vswitch-cpc-id-1', 'name': 'CPC', 'status': 'service-required', } ] } m.get('/api/cpcs', json=result) cpcs = self.cpc_mgr.list() self.cpc = cpcs[0] def teardown_method(self): with requests_mock.mock() as m: m.delete('/api/sessions/this-session', status_code=204) self.session.logoff() def test_init(self): """Test __init__() on VirtualSwitchManager instance in CPC.""" vswitch_mgr = self.cpc.virtual_switches assert vswitch_mgr.cpc == self.cpc def test_list_short_ok(self): """ Test successful list() with short set of properties on VirtualSwitchManager instance in CPC. """ vswitch_mgr = self.cpc.virtual_switches with requests_mock.mock() as m: result = { 'virtual-switches': [ { 'name': 'VSWITCH1', 'object-uri': '/api/virtual-switches/fake-vswitch-id1', 'type': 'osd' }, { 'name': 'VSWITCH2', 'object-uri': '/api/virtual-switches/fake-vswitch-id2', 'type': 'hpersockets' } ] } m.get('/api/cpcs/vswitch-cpc-id-1/virtual-switches', json=result) vswitches = vswitch_mgr.list(full_properties=False) assert len(vswitches) == len(result['virtual-switches']) for idx, vswitch in enumerate(vswitches): assert vswitch.properties == \ result['virtual-switches'][idx] assert vswitch.uri == \ result['virtual-switches'][idx]['object-uri'] assert not vswitch.full_properties assert vswitch.manager == vswitch_mgr def test_list_full_ok(self): """ Test successful list() with full set of properties on VirtualSwitchManager instance in CPC. """ vswitch_mgr = self.cpc.virtual_switches with requests_mock.mock() as m: result = { 'virtual-switches': [ { 'name': 'VSWITCH1', 'object-uri': '/api/virtual-switches/fake-vswitch-id1', 'type': 'osd' }, { 'name': 'VSWITCH2', 'object-uri': '/api/virtual-switches/fake-vswitch-id2', 'type': 'hipersockets' } ] } m.get('/api/cpcs/vswitch-cpc-id-1/virtual-switches', json=result) mock_result_virtual_switch1 = { 'name': 'VSWITCH1', 'object-uri': '/api/virtual-switches/fake-vswitch-id1', 'type': 'osd', 'class': 'virtual-switch', 'description': 'Test VirtualSwitch', 'more_properties': 'bliblablub' } m.get('/api/virtual-switches/fake-vswitch-id1', json=mock_result_virtual_switch1) mock_result_virtual_switch2 = { 'name': 'VSWITCH2', 'object-uri': '/api/virtual-switches/fake-vswitch-id2', 'type': 'hipersockets', 'class': 'virtual-switch', 'description': 'Test VirtualSwitch', 'more_properties': 'bliblablub' } m.get('/api/virtual-switches/fake-vswitch-id2', json=mock_result_virtual_switch2) vswitches = vswitch_mgr.list(full_properties=True) assert len(vswitches) == len(result['virtual-switches']) for idx, vswitch in enumerate(vswitches): assert vswitch.properties['name'] == \ result['virtual-switches'][idx]['name'] assert vswitch.uri == \ result['virtual-switches'][idx]['object-uri'] assert vswitch.full_properties assert vswitch.manager == vswitch_mgr def test_update_properties(self): """ This tests the 'Update VirtualSwitch Properties' operation. """ vswitch_mgr = self.cpc.virtual_switches with requests_mock.mock() as m: result = { 'virtual-switches': [ { 'name': 'VSWITCH1', 'object-uri': '/api/virtual-switches/fake-vswitch-id1', 'type': 'osd' }, { 'name': 'VSWITCH2', 'object-uri': '/api/virtual-switches/fake-vswitch-id2', 'type': 'hpersockets' } ] } m.get('/api/cpcs/vswitch-cpc-id-1/virtual-switches', json=result) vswitches = vswitch_mgr.list(full_properties=False) vswitch = vswitches[0] m.post("/api/virtual-switches/fake-vswitch-id1", status_code=204) vswitch.update_properties(properties={}) def test_get_connected_nics(self): """ This tests the `get_connected_nics()` method. """ vswitch_mgr = self.cpc.virtual_switches with requests_mock.mock() as m: result = { 'virtual-switches': [ { 'name': 'VSWITCH1', 'object-uri': '/api/virtual-switches/fake-vswitch-id1', 'type': 'osd' }, { 'name': 'VSWITCH2', 'object-uri': '/api/virtual-switches/fake-vswitch-id2', 'type': 'hpersockets' } ] } m.get('/api/cpcs/vswitch-cpc-id-1/virtual-switches', json=result) vswitches = vswitch_mgr.list(full_properties=False) vswitch = vswitches[0] result = { 'connected-vnic-uris': [ '/api/partitions/fake-part-id1/nics/fake-nic-id1', '/api/partitions/fake-part-id1/nics/fake-nic-id2', '/api/partitions/fake-part-id1/nics/fake-nic-id3' ] } m.get( "/api/virtual-switches/fake-vswitch-id1/" "operations/get-connected-vnics", json=result) nics = vswitch.get_connected_nics() assert isinstance(nics, list) for i, nic in enumerate(nics): assert isinstance(nic, Nic) nic_uri = result['connected-vnic-uris'][i] assert nic.uri == nic_uri assert nic.properties['element-uri'] == nic_uri m = re.match(r"^/api/partitions/([^/]+)/nics/([^/]+)/?$", nic_uri) nic_id = m.group(2) assert nic.properties['element-id'] == nic_id zhmcclient-0.22.0/tests/unit/zhmcclient/test_storage_group.py0000644000076500000240000005127413364325033025101 0ustar maierastaff00000000000000# 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. """ Unit tests for _storage_group module. """ from __future__ import absolute_import, print_function import pytest import re import copy from zhmcclient import Client, Cpc, StorageGroup, StorageGroupManager, \ StorageVolumeManager, VirtualStorageResourceManager, HTTPError, \ NotFound from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources # Object IDs and names of our faked storage groups: CPC_OID = 'fake-cpc1-oid' CPC_URI = '/api/cpcs/%s' % CPC_OID SG1_OID = 'sg1-oid' SG1_NAME = 'sg 1' SG2_OID = 'sg2-oid' SG2_NAME = 'sg 2' class TestStorageGroup(object): """All tests for the StorageGroup and StorageGroupManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked CPC in DPM mode without any child resources. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.14.1', '1.9') self.client = Client(self.session) self.faked_cpc = self.session.hmc.cpcs.add({ 'object-id': CPC_OID, # object-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': 'fake-cpc1-name', 'description': 'CPC #1 (DPM mode, storage mgmt feature enabled)', 'status': 'active', 'dpm-enabled': True, 'is-ensemble-member': False, 'iml-mode': 'dpm', 'available-features-list': [ dict(name='dpm-storage-management', state=True), ], }) assert self.faked_cpc.uri == CPC_URI self.cpc = self.client.cpcs.find(name='fake-cpc1-name') self.faked_console = self.session.hmc.consoles.add({ # object-id is set up automatically # object-uri is set up automatically # parent will be automatically set # class will be automatically set 'name': 'fake-console-name', 'description': 'The HMC', }) self.console = self.client.consoles.console def add_storage_group1(self): """Add storage group 1 (type fcp).""" faked_storage_group = self.faked_console.storage_groups.add({ 'object-id': SG1_OID, # object-uri will be automatically set # parent will be automatically set # class will be automatically set 'cpc-uri': CPC_URI, 'name': SG1_NAME, 'description': 'Storage Group #1', 'type': 'fcp', 'shared': False, 'fulfillment-state': 'complete', 'connectivity': 4, }) return faked_storage_group def add_storage_group2(self): """Add storage group 2 (type fc).""" faked_storage_group = self.faked_console.storage_groups.add({ 'object-id': SG2_OID, # object-uri will be automatically set # parent will be automatically set # class will be automatically set 'cpc-uri': CPC_URI, 'name': SG2_NAME, 'description': 'Storage Group #2', 'type': 'fc', 'shared': False, 'fulfillment-state': 'complete', 'connectivity': 4, }) return faked_storage_group def test_storagegroupmanager_initial_attrs(self): """Test initial attributes of StorageGroupManager.""" storage_group_mgr = self.console.storage_groups assert isinstance(storage_group_mgr, StorageGroupManager) # Verify all public properties of the manager object assert storage_group_mgr.resource_class == StorageGroup assert storage_group_mgr.session == self.session assert storage_group_mgr.parent == self.console assert storage_group_mgr.console == self.console # TODO: Test for StorageGroupManager.__repr__() testcases_storagegroupmanager_list_full_properties = ( "full_properties_kwargs, prop_names", [ (dict(), ['object-uri', 'cpc-uri', 'name', 'fulfillment-state', 'type']), (dict(full_properties=False), ['object-uri', 'cpc-uri', 'name', 'fulfillment-state', 'type']), (dict(full_properties=True), None), ] ) @pytest.mark.parametrize( *testcases_storagegroupmanager_list_full_properties ) def test_storagegroupmanager_list_full_properties( self, full_properties_kwargs, prop_names): """Test StorageGroupManager.list() with full_properties.""" # Add two faked storage groups faked_storage_group1 = self.add_storage_group1() faked_storage_group2 = self.add_storage_group2() exp_faked_storage_groups = [faked_storage_group1, faked_storage_group2] storage_group_mgr = self.console.storage_groups # Execute the code to be tested storage_groups = storage_group_mgr.list(**full_properties_kwargs) assert_resources(storage_groups, exp_faked_storage_groups, prop_names) testcases_storagegroupmanager_list_filter_args = ( "filter_args, exp_names", [ ({'object-id': SG1_OID}, [SG1_NAME]), ({'object-id': SG2_OID}, [SG2_NAME]), ({'object-id': [SG1_OID, SG2_OID]}, [SG1_NAME, SG2_NAME]), ({'object-id': [SG1_OID, SG1_OID]}, [SG1_NAME]), ({'object-id': SG1_OID + 'foo'}, []), ({'object-id': [SG1_OID, SG2_OID + 'foo']}, [SG1_NAME]), ({'object-id': [SG2_OID + 'foo', SG1_OID]}, [SG1_NAME]), ({'name': SG1_NAME}, [SG1_NAME]), ({'name': SG2_NAME}, [SG2_NAME]), ({'name': [SG1_NAME, SG2_NAME]}, [SG1_NAME, SG2_NAME]), ({'name': SG1_NAME + 'foo'}, []), ({'name': [SG1_NAME, SG2_NAME + 'foo']}, [SG1_NAME]), ({'name': [SG2_NAME + 'foo', SG1_NAME]}, [SG1_NAME]), ({'name': [SG1_NAME, SG1_NAME]}, [SG1_NAME]), ({'name': '.*sg 1'}, [SG1_NAME]), ({'name': 'sg 1.*'}, [SG1_NAME]), ({'name': 'sg .'}, [SG1_NAME, SG2_NAME]), ({'name': '.g 1'}, [SG1_NAME]), ({'name': '.+'}, [SG1_NAME, SG2_NAME]), ({'name': 'sg 1.+'}, []), ({'name': '.+sg 1'}, []), ({'name': SG1_NAME, 'object-id': SG1_OID}, [SG1_NAME]), ({'name': SG1_NAME, 'object-id': SG1_OID + 'foo'}, []), ({'name': SG1_NAME + 'foo', 'object-id': SG1_OID}, []), ({'name': SG1_NAME + 'foo', 'object-id': SG1_OID + 'foo'}, []), ] ) @pytest.mark.parametrize( *testcases_storagegroupmanager_list_filter_args ) def test_storagegroupmanager_list_filter_args( self, filter_args, exp_names): """Test StorageGroupManager.list() with filter_args.""" # Add two faked storage_groups self.add_storage_group1() self.add_storage_group2() storage_group_mgr = self.console.storage_groups # Execute the code to be tested storage_groups = storage_group_mgr.list(filter_args=filter_args) assert len(storage_groups) == len(exp_names) if exp_names: names = [p.properties['name'] for p in storage_groups] assert set(names) == set(exp_names) testcases_storagegroupmanager_create_no_volumes = ( "input_props, exp_prop_names, exp_exc", [ ({}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'description': 'fake description X'}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-sg-x'}, None, HTTPError({'http-status': 400, 'reason': 5})), ({'name': 'fake-sg-x', 'cpc-uri': CPC_URI, 'type': 'fcp'}, ['object-uri', 'name', 'cpc-uri'], None), ] ) @pytest.mark.parametrize( *testcases_storagegroupmanager_create_no_volumes ) def test_storagegroupmanager_create( self, input_props, exp_prop_names, exp_exc): """Test StorageGroupManager.create().""" # TODO: Add logic and test cases for creating initial storage volumes storage_group_mgr = self.console.storage_groups if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested storage_group = storage_group_mgr.create( properties=input_props) exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason else: # Execute the code to be tested. # Note: the StorageGroup object returned by StorageGroup.create() # has the input properties plus 'object-uri'. storage_group = storage_group_mgr.create(properties=input_props) # Check the resource for consistency within itself assert isinstance(storage_group, StorageGroup) storage_group_name = storage_group.name exp_storage_group_name = storage_group.properties['name'] assert storage_group_name == exp_storage_group_name storage_group_uri = storage_group.uri exp_storage_group_uri = storage_group.properties['object-uri'] assert storage_group_uri == exp_storage_group_uri # Check the properties against the expected names and values for prop_name in exp_prop_names: assert prop_name in storage_group.properties if prop_name in input_props: value = storage_group.properties[prop_name] exp_value = input_props[prop_name] assert value == exp_value def test_storagegroupmanager_resource_object(self): """ Test StorageGroupManager.resource_object(). This test exists for historical reasons, and by now is covered by the test for BaseManager.resource_object(). """ # Add a faked storage_group faked_storage_group = self.add_storage_group1() storage_group_oid = faked_storage_group.oid storage_group_mgr = self.console.storage_groups # Execute the code to be tested storage_group = storage_group_mgr.resource_object(storage_group_oid) storage_group_uri = "/api/storage-groups/" + storage_group_oid sv_mgr = storage_group.storage_volumes vsr_mgr = storage_group.virtual_storage_resources assert isinstance(storage_group, StorageGroup) assert isinstance(sv_mgr, StorageVolumeManager) assert isinstance(vsr_mgr, VirtualStorageResourceManager) sg_cpc = storage_group.cpc assert isinstance(sg_cpc, Cpc) assert sg_cpc.uri == storage_group.properties['cpc-uri'] # Note: Properties inherited from BaseResource are tested there, # but we test them again: assert storage_group.properties['object-uri'] == storage_group_uri assert storage_group.properties['object-id'] == storage_group_oid assert storage_group.properties['class'] == 'storage-group' assert storage_group.properties['parent'] == self.console.uri def test_storagegroup_repr(self): """Test StorageGroup.__repr__().""" # Add a faked storage_group faked_storage_group = self.add_storage_group1() storage_group_mgr = self.console.storage_groups storage_group = storage_group_mgr.find(name=faked_storage_group.name) # Execute the code to be tested repr_str = repr(storage_group) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=storage_group.__class__.__name__, id=id(storage_group)), repr_str) def test_storagegroup_delete_non_associated(self): """Test StorageGroup.delete() of non-associated storage group.""" # Add a faked storage group to be tested and another one faked_storage_group = self.add_storage_group1() self.add_storage_group2() storage_group_mgr = self.console.storage_groups storage_group = storage_group_mgr.find(name=faked_storage_group.name) # Execute the code to be tested. storage_group.delete() # Check that the storage group no longer exists with pytest.raises(NotFound): storage_group_mgr.find(name=faked_storage_group.name) def test_storagegroup_delete_create_same_name(self): """Test StorageGroup.delete() followed by create() with same name.""" # Add a faked storage_group to be tested and another one faked_storage_group = self.add_storage_group1() storage_group_name = faked_storage_group.name self.add_storage_group2() # Construct the input properties for a third storage_group sg3_props = copy.deepcopy(faked_storage_group.properties) sg3_props['description'] = 'Third storage_group' storage_group_mgr = self.console.storage_groups storage_group = storage_group_mgr.find(name=storage_group_name) # Execute the deletion code to be tested. storage_group.delete() # Check that the storage_group no longer exists with pytest.raises(NotFound): storage_group_mgr.find(name=storage_group_name) # Execute the creation code to be tested. storage_group_mgr.create(sg3_props) # Check that the storage_group exists again under that name storage_group3 = storage_group_mgr.find(name=storage_group_name) description = storage_group3.get_property('description') assert description == 'Third storage_group' testcases_storagegroup_update_properties_sgs = ( "storage_group_name", [ SG1_NAME, SG2_NAME, ] ) testcases_storagegroup_update_properties_props = ( "input_props", [ {}, {'description': 'New storage_group description'}, {'shared': True}, {'connectivity': 8}, ] ) @pytest.mark.parametrize( *testcases_storagegroup_update_properties_sgs ) @pytest.mark.parametrize( *testcases_storagegroup_update_properties_props ) def test_storagegroup_update_properties( self, input_props, storage_group_name): """Test StorageGroup.update_properties().""" # Add faked storage_groups self.add_storage_group1() self.add_storage_group2() storage_group_mgr = self.console.storage_groups storage_group = storage_group_mgr.find(name=storage_group_name) storage_group.pull_full_properties() saved_properties = copy.deepcopy(storage_group.properties) # Execute the code to be tested storage_group.update_properties(properties=input_props) # Verify that the resource object already reflects the property # updates. for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in storage_group.properties prop_value = storage_group.properties[prop_name] assert prop_value == exp_prop_value # Refresh the resource object and verify that the resource object # still reflects the property updates. storage_group.pull_full_properties() for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in storage_group.properties prop_value = storage_group.properties[prop_name] assert prop_value == exp_prop_value def test_storagegroup_update_name(self): """ Test StorageGroup.update_properties() with 'name' property. """ # Add a faked storage_group faked_storage_group = self.add_storage_group1() storage_group_name = faked_storage_group.name storage_group_mgr = self.console.storage_groups storage_group = storage_group_mgr.find(name=storage_group_name) new_storage_group_name = "new-" + storage_group_name # Execute the code to be tested storage_group.update_properties( properties={'name': new_storage_group_name}) # Verify that the resource is no longer found by its old name, using # list() (this does not use the name-to-URI cache). storage_groups_list = storage_group_mgr.list( filter_args=dict(name=storage_group_name)) assert len(storage_groups_list) == 0 # Verify that the resource is no longer found by its old name, using # find() (this uses the name-to-URI cache). with pytest.raises(NotFound): storage_group_mgr.find(name=storage_group_name) # Verify that the resource object already reflects the update, even # though it has not been refreshed yet. assert storage_group.properties['name'] == new_storage_group_name # Refresh the resource object and verify that it still reflects the # update. storage_group.pull_full_properties() assert storage_group.properties['name'] == new_storage_group_name # Verify that the resource can be found by its new name, using find() new_storage_group_find = storage_group_mgr.find( name=new_storage_group_name) assert new_storage_group_find.properties['name'] == \ new_storage_group_name # Verify that the resource can be found by its new name, using list() new_storage_groups_list = storage_group_mgr.list( filter_args=dict(name=new_storage_group_name)) assert len(new_storage_groups_list) == 1 new_storage_group_list = new_storage_groups_list[0] assert new_storage_group_list.properties['name'] == \ new_storage_group_name # TODO: Adjust to invoke a SG method @pytest.mark.parametrize( "initial_status, exp_exc", [ ('stopped', None), ('terminated', HTTPError({'http-status': 409, 'reason': 1})), ('starting', HTTPError({'http-status': 409, 'reason': 1})), ('active', HTTPError({'http-status': 409, 'reason': 1})), ('stopping', HTTPError({'http-status': 409, 'reason': 1})), ('degraded', HTTPError({'http-status': 409, 'reason': 1})), ('reservation-error', HTTPError({'http-status': 409, 'reason': 1})), ('paused', HTTPError({'http-status': 409, 'reason': 1})), ] ) def xtest_storagegroup_start(self, initial_status, exp_exc): """Test StorageGroup.start().""" # Add a faked storage_group faked_storage_group = self.add_storage_group1() # Set the initial status of the faked storage_group faked_storage_group.properties['status'] = initial_status storage_group_mgr = self.console.storage_groups storage_group = storage_group_mgr.find(name=faked_storage_group.name) if exp_exc is not None: with pytest.raises(exp_exc.__class__) as exc_info: # Execute the code to be tested storage_group.start() exc = exc_info.value if isinstance(exp_exc, HTTPError): assert exc.http_status == exp_exc.http_status assert exc.reason == exp_exc.reason else: # Execute the code to be tested. ret = storage_group.start() assert ret == {} storage_group.pull_full_properties() status = storage_group.get_property('status') assert status == 'active' zhmcclient-0.22.0/tests/unit/zhmcclient/test_lpar.py0000644000076500000240000005476213364325033023164 0ustar maierastaff00000000000000# 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. """ Unit tests for _lpar module. """ from __future__ import absolute_import, print_function import pytest import re import copy import mock from zhmcclient import Client, Lpar, HTTPError, StatusTimeout from zhmcclient_mock import FakedSession, LparActivateHandler, \ LparDeactivateHandler, LparLoadHandler from tests.common.utils import assert_resources # Object IDs and names of our faked LPARs: LPAR1_OID = 'lpar1-oid' LPAR1_NAME = 'lpar 1' LPAR2_OID = 'lpar2-oid' LPAR2_NAME = 'lpar 2' class TestLpar(object): """All tests for Lpar and LparManager classes.""" def setup_method(self): """ Set up a faked session, and add a faked CPC in classic mode without any child resources. """ self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.session.retry_timeout_config.status_timeout = 1 self.client = Client(self.session) self.faked_cpc = self.session.hmc.cpcs.add({ 'object-id': 'fake-cpc1-oid', # object-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': 'fake-cpc1-name', 'description': 'CPC #1 (classic mode)', 'status': 'active', 'dpm-enabled': False, 'is-ensemble-member': False, 'iml-mode': 'lpar', }) self.cpc = self.client.cpcs.find(name='fake-cpc1-name') def add_lpar1(self): """Add lpar 1 (type linux).""" faked_lpar = self.faked_cpc.lpars.add({ 'object-id': LPAR1_OID, # object-uri will be automatically set 'parent': self.faked_cpc.uri, 'class': 'logical-partition', 'name': LPAR1_NAME, 'description': 'LPAR #1 (Linux)', 'status': 'operating', 'activation-mode': 'linux', }) return faked_lpar def add_lpar2(self): """Add lpar 2 (type ssc).""" faked_lpar = self.faked_cpc.lpars.add({ 'object-id': LPAR2_OID, # object-uri will be automatically set 'parent': self.faked_cpc.uri, 'class': 'logical-partition', 'name': LPAR2_NAME, 'description': 'LPAR #2 (SSC)', 'status': 'operating', 'activation-mode': 'ssc', }) return faked_lpar def test_lparmanager_initial_attrs(self): """Test initial attributes of LparManager.""" lpar_mgr = self.cpc.lpars # Verify all public properties of the manager object assert lpar_mgr.resource_class == Lpar assert lpar_mgr.session == self.session assert lpar_mgr.parent == self.cpc assert lpar_mgr.cpc == self.cpc # TODO: Test for LparManager.__repr__() @pytest.mark.parametrize( "full_properties_kwargs, prop_names", [ (dict(), ['object-uri', 'name', 'status']), (dict(full_properties=False), ['object-uri', 'name', 'status']), (dict(full_properties=True), None), ] ) def test_lparmanager_list_full_properties( self, full_properties_kwargs, prop_names): """Test LparManager.list() with full_properties.""" # Add two faked LPARs faked_lpar1 = self.add_lpar1() faked_lpar2 = self.add_lpar2() exp_faked_lpars = [faked_lpar1, faked_lpar2] lpar_mgr = self.cpc.lpars # Execute the code to be tested lpars = lpar_mgr.list(**full_properties_kwargs) assert_resources(lpars, exp_faked_lpars, prop_names) @pytest.mark.parametrize( "filter_args, exp_names", [ ({'object-id': LPAR1_OID}, [LPAR1_NAME]), ({'object-id': LPAR2_OID}, [LPAR2_NAME]), ({'object-id': [LPAR1_OID, LPAR2_OID]}, [LPAR1_NAME, LPAR2_NAME]), ({'object-id': [LPAR1_OID, LPAR1_OID]}, [LPAR1_NAME]), ({'object-id': LPAR1_OID + 'foo'}, []), ({'object-id': [LPAR1_OID, LPAR2_OID + 'foo']}, [LPAR1_NAME]), ({'object-id': [LPAR2_OID + 'foo', LPAR1_OID]}, [LPAR1_NAME]), ({'name': LPAR1_NAME}, [LPAR1_NAME]), ({'name': LPAR2_NAME}, [LPAR2_NAME]), ({'name': [LPAR1_NAME, LPAR2_NAME]}, [LPAR1_NAME, LPAR2_NAME]), ({'name': LPAR1_NAME + 'foo'}, []), ({'name': [LPAR1_NAME, LPAR2_NAME + 'foo']}, [LPAR1_NAME]), ({'name': [LPAR2_NAME + 'foo', LPAR1_NAME]}, [LPAR1_NAME]), ({'name': [LPAR1_NAME, LPAR1_NAME]}, [LPAR1_NAME]), ({'name': '.*lpar 1'}, [LPAR1_NAME]), ({'name': 'lpar 1.*'}, [LPAR1_NAME]), ({'name': 'lpar .'}, [LPAR1_NAME, LPAR2_NAME]), ({'name': '.par 1'}, [LPAR1_NAME]), ({'name': '.+'}, [LPAR1_NAME, LPAR2_NAME]), ({'name': 'lpar 1.+'}, []), ({'name': '.+lpar 1'}, []), ({'name': LPAR1_NAME, 'object-id': LPAR1_OID}, [LPAR1_NAME]), ({'name': LPAR1_NAME, 'object-id': LPAR1_OID + 'foo'}, []), ({'name': LPAR1_NAME + 'foo', 'object-id': LPAR1_OID}, []), ({'name': LPAR1_NAME + 'foo', 'object-id': LPAR1_OID + 'foo'}, []), ] ) def test_lparmanager_list_filter_args(self, filter_args, exp_names): """Test LparManager.list() with filter_args.""" # Add two faked LPARs self.add_lpar1() self.add_lpar2() lpar_mgr = self.cpc.lpars # Execute the code to be tested lpars = lpar_mgr.list(filter_args=filter_args) assert len(lpars) == len(exp_names) if exp_names: names = [p.properties['name'] for p in lpars] assert set(names) == set(exp_names) def test_lpar_repr(self): """Test Lpar.__repr__().""" # Add a faked LPAR faked_lpar = self.add_lpar1() lpar_mgr = self.cpc.lpars lpar = lpar_mgr.find(name=faked_lpar.name) # Execute the code to be tested repr_str = repr(lpar) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=lpar.__class__.__name__, id=id(lpar)), repr_str) @pytest.mark.parametrize( "lpar_name", [ LPAR1_NAME, LPAR2_NAME, ] ) @pytest.mark.parametrize( "input_props", [ {}, {'description': 'New lpar description'}, {'acceptable-status': ['operating', 'not-operating'], 'description': 'New lpar description'}, {'ssc-master-userid': None, 'ssc-master-pw': None}, ] ) def test_lpar_update_properties(self, input_props, lpar_name): """Test Lpar.update_properties().""" # Add faked lpars self.add_lpar1() self.add_lpar2() lpar_mgr = self.cpc.lpars lpar = lpar_mgr.find(name=lpar_name) lpar.pull_full_properties() saved_properties = copy.deepcopy(lpar.properties) # Execute the code to be tested lpar.update_properties(properties=input_props) # Verify that the resource object already reflects the property # updates. for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in lpar.properties prop_value = lpar.properties[prop_name] assert prop_value == exp_prop_value # Refresh the resource object and verify that the resource object # still reflects the property updates. lpar.pull_full_properties() for prop_name in saved_properties: if prop_name in input_props: exp_prop_value = input_props[prop_name] else: exp_prop_value = saved_properties[prop_name] assert prop_name in lpar.properties prop_value = lpar.properties[prop_name] assert prop_value == exp_prop_value @pytest.mark.parametrize( "initial_profile, profile_kwargs, exp_profile, exp_profile_exc", [ ('', dict(), None, HTTPError({'http-status': 500, 'reason': 263})), (LPAR1_NAME, dict(), LPAR1_NAME, None), (LPAR2_NAME, dict(), None, HTTPError({'http-status': 500, 'reason': 263})), ('', dict(activation_profile_name=LPAR1_NAME), LPAR1_NAME, None), (LPAR1_NAME, dict(activation_profile_name=LPAR1_NAME), LPAR1_NAME, None), (LPAR2_NAME, dict(activation_profile_name=LPAR1_NAME), LPAR1_NAME, None), ('', dict(activation_profile_name=LPAR2_NAME), None, HTTPError({'http-status': 500, 'reason': 263})), (LPAR1_NAME, dict(activation_profile_name=LPAR2_NAME), None, HTTPError({'http-status': 500, 'reason': 263})), (LPAR2_NAME, dict(activation_profile_name=LPAR2_NAME), None, HTTPError({'http-status': 500, 'reason': 263})), ] ) @pytest.mark.parametrize( "initial_status, status_kwargs, act_exp_status, exp_status_exc", [ ('not-activated', dict(), # Verify that force has a default 'not-operating', None), ('not-activated', dict(force=False), 'not-operating', None), ('not-activated', dict(force=True), 'not-operating', None), ('not-operating', dict(force=False), 'not-operating', None), ('not-operating', dict(force=True), 'not-operating', None), ('operating', dict(), # Verify that force default is False 'not-operating', HTTPError({'http-status': 500, 'reason': 263})), ('operating', dict(force=False), 'not-operating', HTTPError({'http-status': 500, 'reason': 263})), ('operating', dict(force=True), 'not-operating', None), ('exceptions', dict(force=False), 'not-operating', None), ('exceptions', dict(force=True), 'not-operating', None), ('not-activated', dict(), 'exceptions', StatusTimeout(None, None, None, None)), ('not-activated', dict(allow_status_exceptions=False), 'exceptions', StatusTimeout(None, None, None, None)), ('not-activated', dict(allow_status_exceptions=True), 'exceptions', None), ] ) @mock.patch.object(LparActivateHandler, 'get_status') def test_lpar_activate( self, get_status_mock, initial_status, status_kwargs, act_exp_status, exp_status_exc, initial_profile, profile_kwargs, exp_profile, exp_profile_exc): """Test Lpar.activate().""" # Add a faked LPAR faked_lpar = self.add_lpar1() faked_lpar.properties['status'] = initial_status faked_lpar.properties['next-activation-profile-name'] = initial_profile lpar_mgr = self.cpc.lpars lpar = lpar_mgr.find(name=faked_lpar.name) input_kwargs = dict(status_kwargs, **profile_kwargs) exp_excs = [] if exp_status_exc: exp_excs.append(exp_status_exc) if exp_profile_exc: exp_excs.append(exp_profile_exc) get_status_mock.return_value = act_exp_status if exp_excs: with pytest.raises(Exception) as exc_info: # Execute the code to be tested lpar.activate(**input_kwargs) exc = exc_info.value exp_exc_classes = [e.__class__ for e in exp_excs] assert isinstance(exc, tuple(exp_exc_classes)) if isinstance(exc, HTTPError): exp_httperror = [e for e in exp_excs if isinstance(e, HTTPError)][0] assert exc.http_status == exp_httperror.http_status assert exc.reason == exp_httperror.reason else: # Execute the code to be tested. ret = lpar.activate(**input_kwargs) assert ret is None lpar.pull_full_properties() status = lpar.get_property('status') assert status == act_exp_status last_profile_name = lpar.get_property( 'last-used-activation-profile') assert last_profile_name == exp_profile @pytest.mark.parametrize( "initial_status, input_kwargs, act_exp_status, exp_status_exc", [ ('not-activated', dict(), # Verify that force has a default 'not-activated', HTTPError({'http-status': 500, 'reason': 263})), ('not-activated', dict(force=False), 'not-activated', HTTPError({'http-status': 500, 'reason': 263})), ('not-activated', dict(force=True), 'not-activated', None), ('not-operating', dict(force=False), 'not-activated', None), ('not-operating', dict(force=True), 'not-activated', None), ('operating', dict(), # Verify that force default is False 'not-activated', HTTPError({'http-status': 500, 'reason': 263})), ('operating', dict(force=False), 'not-activated', HTTPError({'http-status': 500, 'reason': 263})), ('operating', dict(force=True), 'not-activated', None), ('exceptions', dict(force=False), 'not-activated', None), ('exceptions', dict(force=True), 'not-activated', None), ('not-operating', dict(), 'exceptions', StatusTimeout(None, None, None, None)), ('not-operating', dict(allow_status_exceptions=False), 'exceptions', StatusTimeout(None, None, None, None)), ('not-operating', dict(allow_status_exceptions=True), 'exceptions', None), ] ) @mock.patch.object(LparDeactivateHandler, 'get_status') def test_lpar_deactivate( self, get_status_mock, initial_status, input_kwargs, act_exp_status, exp_status_exc): """Test Lpar.deactivate().""" # Add a faked LPAR faked_lpar = self.add_lpar1() faked_lpar.properties['status'] = initial_status lpar_mgr = self.cpc.lpars lpar = lpar_mgr.find(name=faked_lpar.name) get_status_mock.return_value = act_exp_status exp_excs = [] if exp_status_exc: exp_excs.append(exp_status_exc) if exp_excs: with pytest.raises(Exception) as exc_info: # Execute the code to be tested lpar.deactivate(**input_kwargs) exc = exc_info.value exp_exc_classes = [e.__class__ for e in exp_excs] assert isinstance(exc, tuple(exp_exc_classes)) if isinstance(exc, HTTPError): exp_httperror = [e for e in exp_excs if isinstance(e, HTTPError)][0] assert exc.http_status == exp_httperror.http_status assert exc.reason == exp_httperror.reason else: # Execute the code to be tested. ret = lpar.deactivate(**input_kwargs) assert ret is None lpar.pull_full_properties() status = lpar.get_property('status') assert status == act_exp_status @pytest.mark.parametrize( "initial_loadparm, loadparm_kwargs, exp_loadparm, exp_loadparm_exc", [ (None, dict(), '', None), (None, dict(load_parameter='abcd'), 'abcd', None), ('abcd', dict(), 'abcd', None), ('fooo', dict(load_parameter='abcd'), 'abcd', None), ] ) @pytest.mark.parametrize( "initial_loadaddr, loadaddr_kwargs, exp_loadaddr, exp_loadaddr_exc", [ (None, dict(), None, HTTPError({'http-status': 400, 'reason': 5})), (None, dict(load_address='5176'), '5176', None), ('5176', dict(), '5176', None), ('1234', dict(load_address='5176'), '5176', None), ] ) @pytest.mark.parametrize( "initial_status, status_kwargs, act_exp_status, exp_status_exc" ", initial_stored_status, exp_stored_status, exp_store_status_exc", [ ('not-activated', dict(), 'operating', HTTPError({'http-status': 409, 'reason': 0}), None, None, None), ('not-activated', dict(force=False), 'operating', HTTPError({'http-status': 409, 'reason': 0}), None, None, None), ('not-activated', dict(force=True), 'operating', HTTPError({'http-status': 409, 'reason': 0}), None, None, None), ('not-operating', dict(force=False), 'operating', None, None, None, None), ('not-operating', dict(force=True), 'operating', None, None, None, None), ('operating', dict(), 'operating', HTTPError({'http-status': 500, 'reason': 263}), None, None, None), ('operating', dict(force=False), 'operating', HTTPError({'http-status': 500, 'reason': 263}), None, None, None), ('operating', dict(force=True), 'operating', None, None, None, None), ('exceptions', dict(force=False), 'operating', None, None, None, None), ('exceptions', dict(force=True), 'operating', None, None, None, None), ('not-operating', dict(), 'exceptions', StatusTimeout(None, None, None, None), None, None, None), ('not-operating', dict(allow_status_exceptions=False), 'exceptions', StatusTimeout(None, None, None, None), None, None, None), ('not-operating', dict(allow_status_exceptions=True), 'exceptions', None, None, None, None), ('not-operating', dict(store_status_indicator=False), 'operating', None, None, None, None), ('not-operating', dict(store_status_indicator=True), 'operating', None, None, 'not-operating', None), ] ) @pytest.mark.parametrize( "initial_memory, memory_kwargs, exp_memory, exp_memory_exc", [ ('foobar', dict(), '', None), ('foobar', dict(clear_indicator=False), 'foobar', None), ('foobar', dict(clear_indicator=True), '', None), ] ) @mock.patch.object(LparLoadHandler, 'get_status') def test_lpar_load( self, get_status_mock, initial_status, status_kwargs, act_exp_status, exp_status_exc, initial_loadaddr, loadaddr_kwargs, exp_loadaddr, exp_loadaddr_exc, initial_loadparm, loadparm_kwargs, exp_loadparm, exp_loadparm_exc, initial_memory, memory_kwargs, exp_memory, exp_memory_exc, initial_stored_status, exp_stored_status, exp_store_status_exc): """Test Lpar.load().""" # Add a faked LPAR faked_lpar = self.add_lpar1() faked_lpar.properties['status'] = initial_status faked_lpar.properties['last-used-load-address'] = initial_loadaddr faked_lpar.properties['last-used-load-parameter'] = initial_loadparm faked_lpar.properties['memory'] = initial_memory lpar_mgr = self.cpc.lpars lpar = lpar_mgr.find(name=faked_lpar.name) input_kwargs = dict(status_kwargs, **loadaddr_kwargs) input_kwargs.update(**loadparm_kwargs) input_kwargs.update(**memory_kwargs) exp_excs = [] if exp_status_exc: exp_excs.append(exp_status_exc) if exp_loadaddr_exc: exp_excs.append(exp_loadaddr_exc) if exp_loadparm_exc: exp_excs.append(exp_loadparm_exc) if exp_memory_exc: exp_excs.append(exp_memory_exc) if exp_store_status_exc: exp_excs.append(exp_store_status_exc) get_status_mock.return_value = act_exp_status if exp_excs: with pytest.raises(Exception) as exc_info: # Execute the code to be tested lpar.load(**input_kwargs) exc = exc_info.value exp_exc_classes = [e.__class__ for e in exp_excs] assert isinstance(exc, tuple(exp_exc_classes)) if isinstance(exc, HTTPError): exp_httperror = [e for e in exp_excs if isinstance(e, HTTPError)][0] assert exc.http_status == exp_httperror.http_status assert exc.reason == exp_httperror.reason else: # Execute the code to be tested. ret = lpar.load(**input_kwargs) assert ret is None lpar.pull_full_properties() status = lpar.get_property('status') assert status == act_exp_status last_loadaddr = lpar.get_property('last-used-load-address') assert last_loadaddr == exp_loadaddr last_loadparm = lpar.get_property('last-used-load-parameter') assert last_loadparm == exp_loadparm last_memory = lpar.get_property('memory') assert last_memory == exp_memory stored_status = lpar.get_property('stored-status') assert stored_status == exp_stored_status # TODO: Test for Lpar.scsi_load() # TODO: Test for Lpar.stop() # TODO: Test for Lpar.reset_clear() zhmcclient-0.22.0/tests/unit/zhmcclient_mock/0000755000076500000240000000000013414661056021614 5ustar maierastaff00000000000000zhmcclient-0.22.0/tests/unit/zhmcclient_mock/test_urihandler.py0000644000076500000240000050372713364325033025374 0ustar maierastaff00000000000000# 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. """ Unit tests for _urihandler module of the zhmcclient_mock package. """ from __future__ import absolute_import, print_function import requests.packages.urllib3 from datetime import datetime # FIXME: Migrate mock to zhmcclient_mock from mock import MagicMock import pytest from zhmcclient_mock._hmc import FakedHmc, FakedMetricGroupDefinition, \ FakedMetricObjectValues from zhmcclient_mock._urihandler import HTTPError, InvalidResourceError, \ InvalidMethodError, CpcNotInDpmError, CpcInDpmError, BadRequestError, \ ConflictError, ConnectionError, \ parse_query_parms, UriHandler, \ GenericGetPropertiesHandler, GenericUpdatePropertiesHandler, \ GenericDeleteHandler, \ VersionHandler, \ ConsoleHandler, ConsoleRestartHandler, ConsoleShutdownHandler, \ ConsoleMakePrimaryHandler, ConsoleReorderUserPatternsHandler, \ ConsoleGetAuditLogHandler, ConsoleGetSecurityLogHandler, \ ConsoleListUnmanagedCpcsHandler, \ UsersHandler, UserHandler, UserAddUserRoleHandler, \ UserRemoveUserRoleHandler, \ UserRolesHandler, UserRoleHandler, UserRoleAddPermissionHandler, \ UserRoleRemovePermissionHandler, \ TasksHandler, TaskHandler, \ UserPatternsHandler, UserPatternHandler, \ PasswordRulesHandler, PasswordRuleHandler, \ LdapServerDefinitionsHandler, LdapServerDefinitionHandler, \ CpcsHandler, CpcHandler, CpcSetPowerSaveHandler, \ CpcSetPowerCappingHandler, CpcGetEnergyManagementDataHandler, \ CpcStartHandler, CpcStopHandler, \ CpcImportProfilesHandler, CpcExportProfilesHandler, \ CpcExportPortNamesListHandler, \ MetricsContextsHandler, MetricsContextHandler, \ PartitionsHandler, PartitionHandler, PartitionStartHandler, \ PartitionStopHandler, PartitionScsiDumpHandler, \ PartitionPswRestartHandler, PartitionMountIsoImageHandler, \ PartitionUnmountIsoImageHandler, PartitionIncreaseCryptoConfigHandler, \ PartitionDecreaseCryptoConfigHandler, PartitionChangeCryptoConfigHandler, \ HbasHandler, HbaHandler, HbaReassignPortHandler, \ NicsHandler, NicHandler, \ VirtualFunctionsHandler, VirtualFunctionHandler, \ AdaptersHandler, AdapterHandler, AdapterChangeCryptoTypeHandler, \ AdapterChangeAdapterTypeHandler, \ NetworkPortHandler, \ StoragePortHandler, \ VirtualSwitchesHandler, VirtualSwitchHandler, \ VirtualSwitchGetVnicsHandler, \ LparsHandler, LparHandler, LparActivateHandler, LparDeactivateHandler, \ LparLoadHandler, \ ResetActProfilesHandler, ResetActProfileHandler, \ ImageActProfilesHandler, ImageActProfileHandler, \ LoadActProfilesHandler, LoadActProfileHandler requests.packages.urllib3.disable_warnings() class TestHTTPError(object): """All tests for class HTTPError.""" def test_attributes(self): method = 'GET' uri = '/api/cpcs' http_status = 500 reason = 42 message = "fake message" exc = HTTPError(method, uri, http_status, reason, message) assert exc.method == method assert exc.uri == uri assert exc.http_status == http_status assert exc.reason == reason assert exc.message == message def test_response(self): method = 'GET' uri = '/api/cpcs' http_status = 500 reason = 42 message = "fake message" expected_response = { 'request-method': method, 'request-uri': uri, 'http-status': http_status, 'reason': reason, 'message': message, } exc = HTTPError(method, uri, http_status, reason, message) response = exc.response() assert response == expected_response class TestConnectionError(object): """All tests for class ConnectionError.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.cpc1 = self.hmc.cpcs.add({'name': 'cpc1'}) def test_attributes(self): msg = "fake error message" exc = ConnectionError(msg) assert exc.message == msg class DummyHandler1(object): pass class DummyHandler2(object): pass class DummyHandler3(object): pass class TestInvalidResourceError(object): """All tests for class InvalidResourceError.""" def test_attributes_with_handler(self): method = 'GET' uri = '/api/cpcs' exp_http_status = 404 exp_reason = 1 exc = InvalidResourceError(method, uri, DummyHandler1) assert exc.method == method assert exc.uri == uri assert exc.http_status == exp_http_status assert exc.reason == exp_reason assert uri in exc.message # next test case exp_reason = 2 exc = InvalidResourceError(method, uri, DummyHandler1, reason=exp_reason) assert exc.reason == exp_reason # next test case exp_resource_uri = '/api/resource' exc = InvalidResourceError(method, uri, DummyHandler1, resource_uri=exp_resource_uri) assert exp_resource_uri in exc.message def test_attributes_no_handler(self): method = 'GET' uri = '/api/cpcs' exp_http_status = 404 exp_reason = 1 exc = InvalidResourceError(method, uri, None) assert exc.method == method assert exc.uri == uri assert exc.http_status == exp_http_status assert exc.reason == exp_reason class TestInvalidMethodError(object): """All tests for class InvalidMethodError.""" def test_attributes_with_handler(self): method = 'DELETE' uri = '/api/cpcs' exp_http_status = 404 exp_reason = 1 exc = InvalidMethodError(method, uri, DummyHandler1) assert exc.method == method assert exc.uri == uri assert exc.http_status == exp_http_status assert exc.reason == exp_reason def test_attributes_no_handler(self): method = 'DELETE' uri = '/api/cpcs' exp_http_status = 404 exp_reason = 1 exc = InvalidMethodError(method, uri, None) assert exc.method == method assert exc.uri == uri assert exc.http_status == exp_http_status assert exc.reason == exp_reason class TestCpcNotInDpmError(object): """All tests for class CpcNotInDpmError.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.cpc1 = self.hmc.cpcs.add({'name': 'cpc1'}) def test_attributes(self): method = 'GET' uri = '/api/cpcs/1/partitions' exp_http_status = 409 exp_reason = 5 exc = CpcNotInDpmError(method, uri, self.cpc1) assert exc.method == method assert exc.uri == uri assert exc.http_status == exp_http_status assert exc.reason == exp_reason class TestCpcInDpmError(object): """All tests for class CpcInDpmError.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.cpc1 = self.hmc.cpcs.add({'name': 'cpc1'}) def test_attributes(self): method = 'GET' uri = '/api/cpcs/1/logical-partitions' exp_http_status = 409 exp_reason = 4 exc = CpcInDpmError(method, uri, self.cpc1) assert exc.method == method assert exc.uri == uri assert exc.http_status == exp_http_status assert exc.reason == exp_reason class TestParseQueryParms(object): """All tests for parse_query_parms().""" def test_none(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', None) assert filter_args is None def test_empty(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', '') assert filter_args is None def test_one_normal(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', 'a=b') assert filter_args == {'a': 'b'} def test_two_normal(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', 'a=b&c=d') assert filter_args == {'a': 'b', 'c': 'd'} def test_one_trailing_amp(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', 'a=b&') assert filter_args == {'a': 'b'} def test_one_leading_amp(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', '&a=b') assert filter_args == {'a': 'b'} def test_one_missing_value(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', 'a=') assert filter_args == {'a': ''} def test_one_missing_name(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', '=b') assert filter_args == {'': 'b'} def test_two_same_normal(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', 'a=b&a=c') assert filter_args == {'a': ['b', 'c']} def test_two_same_one_normal(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', 'a=b&d=e&a=c') assert filter_args == {'a': ['b', 'c'], 'd': 'e'} def test_space_value_1(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', 'a=b%20c') assert filter_args == {'a': 'b c'} def test_space_value_2(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', 'a=%20c') assert filter_args == {'a': ' c'} def test_space_value_3(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', 'a=b%20') assert filter_args == {'a': 'b '} def test_space_value_4(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', 'a=%20') assert filter_args == {'a': ' '} def test_space_name_1(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', 'a%20b=c') assert filter_args == {'a b': 'c'} def test_space_name_2(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', '%20b=c') assert filter_args == {' b': 'c'} def test_space_name_3(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', 'a%20=c') assert filter_args == {'a ': 'c'} def test_space_name_4(self): filter_args = parse_query_parms('fake-meth', 'fake-uri', '%20=c') assert filter_args == {' ': 'c'} def test_invalid_format_1(self): with pytest.raises(HTTPError) as exc_info: parse_query_parms('fake-meth', 'fake-uri', 'a==b') exc = exc_info.value assert exc.http_status == 400 assert exc.reason == 1 def test_invalid_format_2(self): with pytest.raises(HTTPError) as exc_info: parse_query_parms('fake-meth', 'fake-uri', 'a=b=c') exc = exc_info.value assert exc.http_status == 400 assert exc.reason == 1 def test_invalid_format_3(self): with pytest.raises(HTTPError) as exc_info: parse_query_parms('fake-meth', 'fake-uri', 'a') exc = exc_info.value assert exc.http_status == 400 assert exc.reason == 1 class TestUriHandlerHandlerEmpty(object): """All tests for UriHandler.handler() with empty URIs.""" def setup_method(self): self.uris = () self.urihandler = UriHandler(self.uris) def test_uris_empty_1(self): with pytest.raises(InvalidResourceError): self.urihandler.handler('/api/cpcs', 'GET') def test_uris_empty_2(self): with pytest.raises(InvalidResourceError): self.urihandler.handler('', 'GET') class TestUriHandlerHandlerSimple(object): """All tests for UriHandler.handler() with a simple set of URIs.""" def setup_method(self): self.uris = ( (r'/api/cpcs', DummyHandler1), (r'/api/cpcs/([^/]+)', DummyHandler2), (r'/api/cpcs/([^/]+)/child', DummyHandler3), ) self.urihandler = UriHandler(self.uris) def test_ok1(self): handler_class, uri_parms = self.urihandler.handler( '/api/cpcs', 'GET') assert handler_class == DummyHandler1 assert len(uri_parms) == 0 def test_ok2(self): handler_class, uri_parms = self.urihandler.handler( '/api/cpcs/fake-id1', 'GET') assert handler_class == DummyHandler2 assert len(uri_parms) == 1 assert uri_parms[0] == 'fake-id1' def test_ok3(self): handler_class, uri_parms = self.urihandler.handler( '/api/cpcs/fake-id1/child', 'GET') assert handler_class == DummyHandler3 assert len(uri_parms) == 1 assert uri_parms[0] == 'fake-id1' def test_err_begin_missing(self): with pytest.raises(InvalidResourceError): self.urihandler.handler('api/cpcs', 'GET') def test_err_begin_extra(self): with pytest.raises(InvalidResourceError): self.urihandler.handler('x/api/cpcs', 'GET') def test_err_end_missing(self): with pytest.raises(InvalidResourceError): self.urihandler.handler('/api/cpc', 'GET') def test_err_end_extra(self): with pytest.raises(InvalidResourceError): self.urihandler.handler('/api/cpcs_x', 'GET') def test_err_end_slash(self): with pytest.raises(InvalidResourceError): self.urihandler.handler('/api/cpcs/', 'GET') def test_err_end2_slash(self): with pytest.raises(InvalidResourceError): self.urihandler.handler('/api/cpcs/fake-id1/', 'GET') def test_err_end2_missing(self): with pytest.raises(InvalidResourceError): self.urihandler.handler('/api/cpcs/fake-id1/chil', 'GET') def test_err_end2_extra(self): with pytest.raises(InvalidResourceError): self.urihandler.handler('/api/cpcs/fake-id1/child_x', 'GET') class TestUriHandlerMethod(object): """All tests for get(), post(), delete() methods of class UriHandler.""" def setup_method(self): self.uris = ( (r'/api/cpcs', DummyHandler1), (r'/api/cpcs/([^/]+)', DummyHandler2), ) self.cpc1 = { 'object-id': '1', 'object-uri': '/api/cpcs/1', 'name': 'cpc1', } self.cpc2 = { 'object-id': '2', 'object-uri': '/api/cpcs/2', 'name': 'cpc2', } self.new_cpc = { 'object-id': '3', 'object-uri': '/api/cpcs/3', 'name': 'cpc3', } self.cpcs = { 'cpcs': [self.cpc1, self.cpc2] } DummyHandler1.get = staticmethod(MagicMock( return_value=self.cpcs)) DummyHandler1.post = staticmethod(MagicMock( return_value=self.new_cpc)) DummyHandler2.get = staticmethod(MagicMock( return_value=self.cpc1)) DummyHandler2.delete = staticmethod(MagicMock( return_value=None)) self.urihandler = UriHandler(self.uris) self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') def teardown_method(self): delattr(DummyHandler1, 'get') delattr(DummyHandler1, 'post') delattr(DummyHandler2, 'get') delattr(DummyHandler2, 'delete') def test_get_cpcs(self): # the function to be tested result = self.urihandler.get(self.hmc, '/api/cpcs', True) assert result == self.cpcs DummyHandler1.get.assert_called_with( 'GET', self.hmc, '/api/cpcs', tuple(), True) assert DummyHandler1.post.called == 0 assert DummyHandler2.get.called == 0 assert DummyHandler2.delete.called == 0 def test_get_cpc1(self): # the function to be tested result = self.urihandler.get(self.hmc, '/api/cpcs/1', True) assert result == self.cpc1 assert DummyHandler1.get.called == 0 assert DummyHandler1.post.called == 0 DummyHandler2.get.assert_called_with( 'GET', self.hmc, '/api/cpcs/1', tuple('1'), True) assert DummyHandler2.delete.called == 0 def test_post_cpcs(self): # the function to be tested result = self.urihandler.post(self.hmc, '/api/cpcs', {}, True, True) assert result == self.new_cpc assert DummyHandler1.get.called == 0 DummyHandler1.post.assert_called_with( 'POST', self.hmc, '/api/cpcs', tuple(), {}, True, True) assert DummyHandler2.get.called == 0 assert DummyHandler2.delete.called == 0 def test_delete_cpc2(self): # the function to be tested self.urihandler.delete(self.hmc, '/api/cpcs/2', True) assert DummyHandler1.get.called == 0 assert DummyHandler1.post.called == 0 assert DummyHandler2.get.called == 0 DummyHandler2.delete.assert_called_with( 'DELETE', self.hmc, '/api/cpcs/2', tuple('2'), True) def standard_test_hmc(): """ Return a FakedHmc object that is prepared with a few standard resources for testing. """ hmc_resources = { 'consoles': [ { 'properties': { 'name': 'fake_console_name', }, 'users': [ { 'properties': { 'object-id': 'fake-user-oid-1', 'name': 'fake_user_name_1', 'description': 'User #1', 'type': 'system-defined', }, }, ], 'user_roles': [ { 'properties': { 'object-id': 'fake-user-role-oid-1', 'name': 'fake_user_role_name_1', 'description': 'User Role #1', 'type': 'system-defined', }, }, ], 'user_patterns': [ { 'properties': { 'element-id': 'fake-user-pattern-oid-1', 'name': 'fake_user_pattern_name_1', 'description': 'User Pattern #1', 'pattern': 'fake_user_name_*', 'type': 'glob-like', 'retention-time': 0, 'user-template-uri': '/api/users/fake-user-oid-1', }, }, ], 'password_rules': [ { 'properties': { 'element-id': 'fake-password-rule-oid-1', 'name': 'fake_password_rule_name_1', 'description': 'Password Rule #1', 'type': 'system-defined', }, }, ], 'tasks': [ { 'properties': { 'element-id': 'fake-task-oid-1', 'name': 'fake_task_name_1', 'description': 'Task #1', }, }, { 'properties': { 'element-id': 'fake-task-oid-2', 'name': 'fake_task_name_2', 'description': 'Task #2', }, }, ], 'ldap_server_definitions': [ { 'properties': { 'element-id': 'fake-ldap-srv-def-oid-1', 'name': 'fake_ldap_srv_def_name_1', 'description': 'LDAP Srv Def #1', 'primary-hostname-ipaddr': '10.11.12.13', }, }, ], } ], 'cpcs': [ { 'properties': { 'object-id': '1', 'name': 'cpc_1', 'dpm-enabled': False, 'description': 'CPC #1 (classic mode)', 'status': 'operating', }, 'lpars': [ { 'properties': { 'object-id': '1', 'name': 'lpar_1', 'description': 'LPAR #1 in CPC #1', 'status': 'not-activated', }, }, ], 'reset_activation_profiles': [ { 'properties': { 'name': 'r1', 'description': 'Reset profile #1 in CPC #1', }, }, ], 'image_activation_profiles': [ { 'properties': { 'name': 'i1', 'description': 'Image profile #1 in CPC #1', }, }, ], 'load_activation_profiles': [ { 'properties': { 'name': 'L1', 'description': 'Load profile #1 in CPC #1', }, }, ], }, { 'properties': { 'object-id': '2', 'name': 'cpc_2', 'dpm-enabled': True, 'description': 'CPC #2 (DPM mode)', 'status': 'active', }, 'partitions': [ { 'properties': { 'object-id': '1', 'name': 'partition_1', 'description': 'Partition #1 in CPC #2', 'status': 'stopped', 'hba-uris': [], # updated automatically 'nic-uris': [], # updated automatically 'virtual-function-uris': [], # updated autom. }, 'hbas': [ { 'properties': { 'element-id': '1', 'name': 'hba_1', 'description': 'HBA #1 in Partition #1', 'adapter-port-uri': '/api/adapters/2/storage-ports/1', 'wwpn': 'CFFEAFFE00008001', 'device-number': '1001', }, }, ], 'nics': [ { 'properties': { 'element-id': '1', 'name': 'nic_1', 'description': 'NIC #1 in Partition #1', 'network-adapter-port-uri': '/api/adapters/3/network-ports/1', 'device-number': '2001', }, }, ], 'virtual_functions': [ { 'properties': { 'element-id': '1', 'name': 'vf_1', 'description': 'VF #1 in Partition #1', 'device-number': '3001', }, }, ], }, ], 'adapters': [ { 'properties': { 'object-id': '1', 'name': 'osa_1', 'description': 'OSA #1 in CPC #2', 'adapter-family': 'osa', 'network-port-uris': [], # updated automatically 'status': 'active', 'adapter-id': 'BEF', }, 'ports': [ { 'properties': { 'element-id': '1', 'name': 'osa_1_port_1', 'description': 'Port #1 of OSA #1', }, }, ], }, { 'properties': { 'object-id': '2', 'name': 'fcp_2', 'description': 'FCP #2 in CPC #2', 'adapter-family': 'ficon', 'storage-port-uris': [], # updated automatically 'adapter-id': 'CEF', }, 'ports': [ { 'properties': { 'element-id': '1', 'name': 'fcp_2_port_1', 'description': 'Port #1 of FCP #2', }, }, ], }, { 'properties': { 'object-id': '2a', 'name': 'fcp_2a', 'description': 'FCP #2a in CPC #2', 'adapter-family': 'ficon', 'storage-port-uris': [], # updated automatically 'adapter-id': 'CEE', }, 'ports': [ { 'properties': { 'element-id': '1', 'name': 'fcp_2a_port_1', 'description': 'Port #1 of FCP #2a', }, }, ], }, { 'properties': { 'object-id': '3', 'name': 'roce_3', 'description': 'ROCE #3 in CPC #2', 'adapter-family': 'roce', 'network-port-uris': [], # updated automatically 'adapter-id': 'DEF', }, 'ports': [ { 'properties': { 'element-id': '1', 'name': 'roce_3_port_1', 'description': 'Port #1 of ROCE #3', }, }, ], }, { 'properties': { 'object-id': '4', 'name': 'crypto_4', 'description': 'Crypto #4 in CPC #2', 'adapter-family': 'crypto', 'adapter-id': 'EEF', 'detected-card-type': 'crypto-express-5s', 'crypto-number': 7, 'crypto-type': 'accelerator', }, }, ], 'virtual_switches': [ { 'properties': { 'object-id': '1', 'name': 'vswitch_osa_1', 'description': 'Vswitch for OSA #1 in CPC #2', }, }, ], }, ], } hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') hmc.add_resources(hmc_resources) return hmc, hmc_resources class TestGenericGetPropertiesHandler(object): """All tests for class GenericGetPropertiesHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)', GenericGetPropertiesHandler), ) self.urihandler = UriHandler(self.uris) def test_get(self): # the function to be tested: cpc1 = self.urihandler.get(self.hmc, '/api/cpcs/1', True) exp_cpc1 = { 'object-id': '1', 'object-uri': '/api/cpcs/1', 'class': 'cpc', 'parent': None, 'name': 'cpc_1', 'dpm-enabled': False, 'is-ensemble-member': False, 'description': 'CPC #1 (classic mode)', 'status': 'operating', } assert cpc1 == exp_cpc1 def test_get_error_offline(self): self.hmc.disable() with pytest.raises(ConnectionError): # the function to be tested: self.urihandler.get(self.hmc, '/api/cpcs/1', True) class _GenericGetUpdatePropertiesHandler(GenericGetPropertiesHandler, GenericUpdatePropertiesHandler): pass class TestGenericUpdatePropertiesHandler(object): """All tests for class GenericUpdatePropertiesHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)', _GenericGetUpdatePropertiesHandler), ) self.urihandler = UriHandler(self.uris) def test_update_verify(self): update_cpc1 = { 'description': 'CPC #1 (updated)', } # the function to be tested: resp = self.urihandler.post(self.hmc, '/api/cpcs/1', update_cpc1, True, True) assert resp is None cpc1 = self.urihandler.get(self.hmc, '/api/cpcs/1', True) assert cpc1['description'] == 'CPC #1 (updated)' def test_post_error_offline(self): self.hmc.disable() update_cpc1 = { 'description': 'CPC #1 (updated)', } with pytest.raises(ConnectionError): # the function to be tested: self.urihandler.post(self.hmc, '/api/cpcs/1', update_cpc1, True, True) class TestGenericDeleteHandler(object): """All tests for class GenericDeleteHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console/ldap-server-definitions/([^/]+)', GenericDeleteHandler), ) self.urihandler = UriHandler(self.uris) def test_delete(self): uri = '/api/console/ldap-server-definitions/fake-ldap-srv-def-oid-1' # the function to be tested: ret = self.urihandler.delete(self.hmc, uri, True) assert ret is None # Verify it no longer exists: with pytest.raises(KeyError): self.hmc.lookup_by_uri(uri) def test_delete_error_offline(self): self.hmc.disable() uri = '/api/console/ldap-server-definitions/fake-ldap-srv-def-oid-1' with pytest.raises(ConnectionError): # the function to be tested: self.urihandler.delete(self.hmc, uri, True) class TestVersionHandler(object): """All tests for class VersionHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/version', VersionHandler), ) self.urihandler = UriHandler(self.uris) def test_get_version(self): # the function to be tested: resp = self.urihandler.get(self.hmc, '/api/version', True) api_major, api_minor = self.hmc.api_version.split('.') exp_resp = { 'hmc-name': self.hmc.hmc_name, 'hmc-version': self.hmc.hmc_version, 'api-major-version': int(api_major), 'api-minor-version': int(api_minor), } assert resp == exp_resp class TestConsoleHandler(object): """All tests for class ConsoleHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console', ConsoleHandler), ) self.urihandler = UriHandler(self.uris) # Note: There is no test_list() function because there is no List # operation for Console resources. def test_get(self): # the function to be tested: console = self.urihandler.get(self.hmc, '/api/console', True) exp_console = { 'object-uri': '/api/console', 'name': 'fake_console_name', 'class': 'console', 'parent': None, } assert console == exp_console class TestConsoleRestartHandler(object): """All tests for class ConsoleRestartHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console', ConsoleHandler), (r'/api/console/operations/restart', ConsoleRestartHandler), ) self.urihandler = UriHandler(self.uris) def test_restart_success(self): body = { 'force': False, } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/console/operations/restart', body, True, True) assert self.hmc.enabled assert resp is None def test_restart_error_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) body = { 'force': False, } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post( self.hmc, '/api/console/operations/restart', body, True, True) exc = exc_info.value assert exc.reason == 1 class TestConsoleShutdownHandler(object): """All tests for class ConsoleShutdownHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console', ConsoleHandler), (r'/api/console/operations/shutdown', ConsoleShutdownHandler), ) self.urihandler = UriHandler(self.uris) def test_shutdown_success(self): body = { 'force': False, } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/console/operations/shutdown', body, True, True) assert not self.hmc.enabled assert resp is None def test_shutdown_error_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) body = { 'force': False, } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post( self.hmc, '/api/console/operations/shutdown', body, True, True) exc = exc_info.value assert exc.reason == 1 class TestConsoleMakePrimaryHandler(object): """All tests for class ConsoleMakePrimaryHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console', ConsoleHandler), (r'/api/console/operations/make-primary', ConsoleMakePrimaryHandler), ) self.urihandler = UriHandler(self.uris) def test_make_primary_success(self): # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/console/operations/make-primary', None, True, True) assert self.hmc.enabled assert resp is None def test_make_primary_error_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) body = { 'force': False, } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post( self.hmc, '/api/console/operations/make-primary', body, True, True) exc = exc_info.value assert exc.reason == 1 class TestConsoleReorderUserPatternsHandler(object): """All tests for class ConsoleReorderUserPatternsHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() # Remove the standard User Pattern objects for this test console = self.hmc.lookup_by_uri('/api/console') user_pattern_objs = console.user_patterns.list() for obj in user_pattern_objs: console.user_patterns.remove(obj.oid) self.uris = ( (r'/api/console', ConsoleHandler), (r'/api/console/user-patterns(?:\?(.*))?', UserPatternsHandler), (r'/api/console/user-patterns/([^/]+)', UserPatternHandler), (r'/api/console/operations/reorder-user-patterns', ConsoleReorderUserPatternsHandler), ) self.urihandler = UriHandler(self.uris) def test_reorder_all(self): testcases = [ # (initial_order, new_order) (['a', 'b'], ['a', 'b']), (['a', 'b'], ['b', 'a']), (['a', 'b', 'c'], ['a', 'c', 'b']), (['a', 'b', 'c'], ['c', 'b', 'a']), ] for initial_order, new_order in testcases: self._test_reorder_one(initial_order, new_order) def _test_reorder_one(self, initial_order, new_order): # Create User Pattern objects in the initial order and build # name-to-URI mapping uri_by_name = {} for name in initial_order: user_pattern = { 'element-id': name + '-oid', 'name': name, 'pattern': name + '*', 'type': 'glob-like', 'retention-time': 0, 'user-template-uri': 'fake-uri', } resp = self.urihandler.post(self.hmc, '/api/console/user-patterns', user_pattern, True, True) uri = resp['element-uri'] uri_by_name[name] = uri new_uris = [uri_by_name[name] for name in new_order] body = { 'user-pattern-uris': new_uris } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/console/operations/reorder-user-patterns', body, True, True) # Retrieve the actual order of User Pattern objects resp = self.urihandler.get(self.hmc, '/api/console/user-patterns', True) act_user_patterns = resp['user-patterns'] act_uris = [up['element-uri'] for up in act_user_patterns] # Verify that the actual order is the new (expected) order: assert act_uris == new_uris def test_reorder_error_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) body = { 'user-pattern-uris': [] } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post( self.hmc, '/api/console/operations/reorder-user-patterns', body, True, True) exc = exc_info.value assert exc.reason == 1 class TestConsoleGetAuditLogHandler(object): """All tests for class ConsoleGetAuditLogHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console', ConsoleHandler), (r'/api/console/operations/get-audit-log', ConsoleGetAuditLogHandler), ) self.urihandler = UriHandler(self.uris) def test_get_audit_log_success(self): # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/console/operations/get-audit-log', None, True, True) assert resp == [] # TODO: Add testcases with non-empty audit log (once supported in mock) def test_get_audit_log_error_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post( self.hmc, '/api/console/operations/get-audit-log', None, True, True) exc = exc_info.value assert exc.reason == 1 class TestConsoleGetSecurityLogHandler(object): """All tests for class ConsoleGetSecurityLogHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console', ConsoleHandler), (r'/api/console/operations/get-security-log', ConsoleGetSecurityLogHandler), ) self.urihandler = UriHandler(self.uris) def test_get_security_log_success_empty(self): # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/console/operations/get-security-log', None, True, True) assert resp == [] # TODO: Add testcases with non-empty security log (once supported in mock) def test_get_security_log_error_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post( self.hmc, '/api/console/operations/get-security-log', None, True, True) exc = exc_info.value assert exc.reason == 1 class TestConsoleListUnmanagedCpcsHandler(object): """All tests for class ConsoleListUnmanagedCpcsHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console', ConsoleHandler), (r'/api/console/operations/list-unmanaged-cpcs(?:\?(.*))?', ConsoleListUnmanagedCpcsHandler), ) self.urihandler = UriHandler(self.uris) def test_list_success_empty(self): # the function to be tested: resp = self.urihandler.get( self.hmc, '/api/console/operations/list-unmanaged-cpcs', True) cpcs = resp['cpcs'] assert cpcs == [] # TODO: Add testcases for non-empty list of unmanaged CPCs def test_list_error_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.get( self.hmc, '/api/console/operations/list-unmanaged-cpcs', True) exc = exc_info.value assert exc.reason == 1 class TestUserHandlers(object): """All tests for classes UsersHandler and UserHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console/users(?:\?(.*))?', UsersHandler), (r'/api/users/([^/]+)', UserHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: users = self.urihandler.get(self.hmc, '/api/console/users', True) exp_users = { # properties reduced to those returned by List 'users': [ { 'object-uri': '/api/users/fake-user-oid-1', 'name': 'fake_user_name_1', 'type': 'system-defined', }, ] } assert users == exp_users def test_list_error_console_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.get(self.hmc, '/api/console/users', True) exc = exc_info.value assert exc.reason == 1 def test_get(self): # the function to be tested: user1 = self.urihandler.get(self.hmc, '/api/users/fake-user-oid-1', True) exp_user1 = { # properties reduced to those in standard test HMC 'object-id': 'fake-user-oid-1', 'object-uri': '/api/users/fake-user-oid-1', 'class': 'user', 'parent': '/api/console', 'name': 'fake_user_name_1', 'description': 'User #1', 'type': 'system-defined', } assert user1 == exp_user1 def test_create_verify(self): new_user2 = { 'object-id': '2', 'name': 'user_2', 'description': 'User #2', 'type': 'standard', 'authentication-type': 'local', } # the function to be tested: resp = self.urihandler.post(self.hmc, '/api/console/users', new_user2, True, True) assert len(resp) == 1 assert 'object-uri' in resp new_user2_uri = resp['object-uri'] assert new_user2_uri == '/api/users/2' exp_user2 = { 'object-id': '2', 'object-uri': '/api/users/2', 'class': 'user', 'parent': '/api/console', 'name': 'user_2', 'description': 'User #2', 'type': 'standard', 'authentication-type': 'local', } # the function to be tested: user2 = self.urihandler.get(self.hmc, '/api/users/2', True) assert user2 == exp_user2 def test_create_error_console_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) new_user2 = { 'object-id': '2', 'name': 'user_2', 'description': 'User #2', 'type': 'standard', 'authentication-type': 'local', } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, '/api/console/users', new_user2, True, True) exc = exc_info.value assert exc.reason == 1 def test_update_verify(self): update_user1 = { 'description': 'updated user #1', } # the function to be tested: self.urihandler.post(self.hmc, '/api/users/fake-user-oid-1', update_user1, True, True) user1 = self.urihandler.get(self.hmc, '/api/users/fake-user-oid-1', True) assert user1['description'] == 'updated user #1' def test_delete_verify_all(self): testcases = [ # (user_props, exp_exc_tuple) ({ 'object-id': '2', 'name': 'user_2', 'description': 'User #2', 'type': 'standard', 'authentication-type': 'local'}, None), ({ 'object-id': '3', 'name': 'user_3', 'description': 'User #3', 'type': 'template', 'authentication-type': 'local'}, None), ({ 'object-id': '4', 'name': 'user_4', 'description': 'User #4', 'type': 'pattern-based', 'authentication-type': 'local'}, (400, 312)), ] for user_props, exp_exc_tuple in testcases: self._test_delete_verify_one(user_props, exp_exc_tuple) def _test_delete_verify_one(self, user_props, exp_exc_tuple): user_oid = user_props['object-id'] user_uri = '/api/users/{}'.format(user_oid) # Create the user self.urihandler.post(self.hmc, '/api/console/users', user_props, True, True) # Verify that it exists self.urihandler.get(self.hmc, user_uri, True) if exp_exc_tuple is not None: with pytest.raises(HTTPError) as exc_info: # Execute the code to be tested self.urihandler.delete(self.hmc, user_uri, True) exc = exc_info.value assert exc.http_status == exp_exc_tuple[0] assert exc.reason == exp_exc_tuple[1] # Verify that it still exists self.urihandler.get(self.hmc, user_uri, True) else: # Execute the code to be tested self.urihandler.delete(self.hmc, user_uri, True) # Verify that it has been deleted with pytest.raises(InvalidResourceError): self.urihandler.get(self.hmc, user_uri, True) class TestUserAddUserRoleHandler(object): """All tests for class UserAddUserRoleHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() # Has a system-defined User (oid=fake-user-oid-1) # Has a system-defined User Role (oid=fake-user-role-oid-1) self.uris = ( (r'/api/console/users(?:\?(.*))?', UsersHandler), (r'/api/users/([^/]+)', UserHandler), (r'/api/users/([^/]+)/operations/add-user-role', UserAddUserRoleHandler), (r'/api/console/user-roles(?:\?(.*))?', UserRolesHandler), (r'/api/user-roles/([^/]+)', UserRoleHandler), ) self.urihandler = UriHandler(self.uris) def test_add_success(self): """Test successful addition of a user role to a user.""" # Add a user-defined User for our tests user2 = { 'name': 'user2', 'description': 'User #2', 'type': 'standard', 'authentication-type': 'local', } resp = self.urihandler.post( self.hmc, '/api/console/users', user2, True, True) self.user2_uri = resp['object-uri'] self.user2_props = self.urihandler.get( self.hmc, self.user2_uri, True) # Add a user-defined User Role for our tests user_role2 = { 'name': 'user_role_2', 'description': 'User Role #2', } resp = self.urihandler.post( self.hmc, '/api/console/user-roles', user_role2, True, True) self.user_role2_uri = resp['object-uri'] self.user_role2_props = self.urihandler.get( self.hmc, self.user_role2_uri, True) uri = self.user2_uri + '/operations/add-user-role' input_parms = { 'user-role-uri': self.user_role2_uri } # the function to be tested: resp = self.urihandler.post(self.hmc, uri, input_parms, True, True) assert resp is None user2_props = self.urihandler.get(self.hmc, self.user2_uri, True) assert 'user-roles' in user2_props user_roles = user2_props['user-roles'] assert len(user_roles) == 1 user_role_uri = user_roles[0] assert user_role_uri == self.user_role2_uri def test_add_error_bad_user(self): """Test failed addition of a user role to a bad user.""" # Add a user-defined User Role for our tests user_role2 = { 'name': 'user_role_2', 'description': 'User Role #2', } resp = self.urihandler.post( self.hmc, '/api/console/user-roles', user_role2, True, True) self.user_role2_uri = resp['object-uri'] self.user_role2_props = self.urihandler.get( self.hmc, self.user_role2_uri, True) bad_user_uri = '/api/users/not-found-oid' uri = bad_user_uri + '/operations/add-user-role' input_parms = { 'user-role-uri': self.user_role2_uri } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, uri, input_parms, True, True) exc = exc_info.value assert exc.reason == 1 # TODO: Add testcase for adding to system-defined or pattern-based user def test_add_error_bad_user_role(self): """Test failed addition of a bad user role to a user.""" # Add a user-defined User for our tests user2 = { 'name': 'user2', 'description': 'User #2', 'type': 'standard', 'authentication-type': 'local', } resp = self.urihandler.post( self.hmc, '/api/console/users', user2, True, True) self.user2_uri = resp['object-uri'] self.user2_props = self.urihandler.get( self.hmc, self.user2_uri, True) bad_user_role_uri = '/api/user-roles/not-found-oid' uri = self.user2_uri + '/operations/add-user-role' input_parms = { 'user-role-uri': bad_user_role_uri } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, uri, input_parms, True, True) exc = exc_info.value assert exc.reason == 2 class TestUserRemoveUserRoleHandler(object): """All tests for class UserRemoveUserRoleHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() # Has a system-defined User (oid=fake-user-oid-1) # Has a system-defined User Role (oid=fake-user-role-oid-1) self.uris = ( (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), ) self.urihandler = UriHandler(self.uris) def test_remove_success(self): """Test successful removal of a user role from a user.""" # Add a user-defined User for our tests user2 = { 'name': 'user2', 'description': 'User #2', 'type': 'standard', 'authentication-type': 'local', } resp = self.urihandler.post( self.hmc, '/api/console/users', user2, True, True) self.user2_uri = resp['object-uri'] self.user2_props = self.urihandler.get( self.hmc, self.user2_uri, True) # Add a user-defined User Role for our tests user_role2 = { 'name': 'user_role_2', 'description': 'User Role #2', } resp = self.urihandler.post( self.hmc, '/api/console/user-roles', user_role2, True, True) self.user_role2_uri = resp['object-uri'] self.user_role2_props = self.urihandler.get( self.hmc, self.user_role2_uri, True) # Add the user role to the user uri = self.user2_uri + '/operations/add-user-role' input_parms = { 'user-role-uri': self.user_role2_uri } self.urihandler.post(self.hmc, uri, input_parms, True, True) uri = self.user2_uri + '/operations/remove-user-role' input_parms = { 'user-role-uri': self.user_role2_uri } # the function to be tested: resp = self.urihandler.post(self.hmc, uri, input_parms, True, True) assert resp is None user2_props = self.urihandler.get(self.hmc, self.user2_uri, True) assert 'user-roles' in user2_props user_roles = user2_props['user-roles'] assert len(user_roles) == 0 def test_remove_error_bad_user(self): """Test failed removal of a user role from a bad user.""" # Add a user-defined User Role for our tests user_role2 = { 'name': 'user_role_2', 'description': 'User Role #2', } resp = self.urihandler.post( self.hmc, '/api/console/user-roles', user_role2, True, True) self.user_role2_uri = resp['object-uri'] self.user_role2_props = self.urihandler.get( self.hmc, self.user_role2_uri, True) bad_user_uri = '/api/users/not-found-oid' uri = bad_user_uri + '/operations/remove-user-role' input_parms = { 'user-role-uri': self.user_role2_uri } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, uri, input_parms, True, True) exc = exc_info.value assert exc.reason == 1 # TODO: Add testcase for removing from system-defined or pattern-based user def test_remove_error_bad_user_role(self): """Test failed removal of a bad user role from a user.""" # Add a user-defined User for our tests user2 = { 'name': 'user2', 'description': 'User #2', 'type': 'standard', 'authentication-type': 'local', } resp = self.urihandler.post( self.hmc, '/api/console/users', user2, True, True) self.user2_uri = resp['object-uri'] self.user2_props = self.urihandler.get( self.hmc, self.user2_uri, True) bad_user_role_uri = '/api/user-roles/not-found-oid' uri = self.user2_uri + '/operations/remove-user-role' input_parms = { 'user-role-uri': bad_user_role_uri } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, uri, input_parms, True, True) exc = exc_info.value assert exc.reason == 2 def test_remove_error_no_user_role(self): """Test failed removal of a user role that a user does not have.""" # Add a user-defined User for our tests user2 = { 'name': 'user2', 'description': 'User #2', 'type': 'standard', 'authentication-type': 'local', } resp = self.urihandler.post( self.hmc, '/api/console/users', user2, True, True) self.user2_uri = resp['object-uri'] self.user2_props = self.urihandler.get( self.hmc, self.user2_uri, True) # Add a user-defined User Role for our tests user_role2 = { 'name': 'user_role_2', 'description': 'User Role #2', } resp = self.urihandler.post( self.hmc, '/api/console/user-roles', user_role2, True, True) self.user_role2_uri = resp['object-uri'] self.user_role2_props = self.urihandler.get( self.hmc, self.user_role2_uri, True) # Do not(!) add the user role to the user uri = self.user2_uri + '/operations/remove-user-role' input_parms = { 'user-role-uri': self.user_role2_uri } with pytest.raises(ConflictError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, uri, input_parms, True, True) exc = exc_info.value assert exc.reason == 316 class TestUserRoleHandlers(object): """All tests for classes UserRolesHandler and UserRoleHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console/user-roles(?:\?(.*))?', UserRolesHandler), (r'/api/user-roles/([^/]+)', UserRoleHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: user_roles = self.urihandler.get(self.hmc, '/api/console/user-roles', True) exp_user_roles = { # properties reduced to those returned by List 'user-roles': [ { 'object-uri': '/api/user-roles/fake-user-role-oid-1', 'name': 'fake_user_role_name_1', 'type': 'system-defined', }, ] } assert user_roles == exp_user_roles def test_list_error_console_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.get(self.hmc, '/api/console/user-roles', True) exc = exc_info.value assert exc.reason == 1 def test_get(self): # the function to be tested: user_role1 = self.urihandler.get( self.hmc, '/api/user-roles/fake-user-role-oid-1', True) exp_user_role1 = { # properties reduced to those in standard test HMC 'object-id': 'fake-user-role-oid-1', 'object-uri': '/api/user-roles/fake-user-role-oid-1', 'class': 'user-role', 'parent': '/api/console', 'name': 'fake_user_role_name_1', 'description': 'User Role #1', 'type': 'system-defined', } assert user_role1 == exp_user_role1 def test_create_verify(self): new_user_role2 = { 'name': 'user_role_2', 'description': 'User Role #2', } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/console/user-roles', new_user_role2, True, True) assert len(resp) == 1 assert 'object-uri' in resp new_user_role2_uri = resp['object-uri'] # the function to be tested: user_role2 = self.urihandler.get(self.hmc, new_user_role2_uri, True) assert user_role2['type'] == 'user-defined' def test_create_error_console_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) new_user_role2 = { 'name': 'user_role_2', 'description': 'User Role #2', } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, '/api/console/user-roles', new_user_role2, True, True) exc = exc_info.value assert exc.reason == 1 def test_create_error_type(self): new_user_role2 = { 'name': 'user_role_2', 'description': 'User Role #2', 'type': 'user-defined', # error: type is implied } with pytest.raises(BadRequestError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, '/api/console/user-roles', new_user_role2, True, True) exc = exc_info.value assert exc.reason == 6 def test_update_verify(self): update_user_role1 = { 'description': 'updated user #1', } # the function to be tested: self.urihandler.post( self.hmc, '/api/user-roles/fake-user-role-oid-1', update_user_role1, True, True) user_role1 = self.urihandler.get( self.hmc, '/api/user-roles/fake-user-role-oid-1', True) assert user_role1['description'] == 'updated user #1' def test_delete_verify(self): new_user_role2 = { 'name': 'user_role_2', 'description': 'User Role #2', } # Create the user role resp = self.urihandler.post( self.hmc, '/api/console/user-roles', new_user_role2, True, True) new_user_role2_uri = resp['object-uri'] # Verify that it exists self.urihandler.get(self.hmc, new_user_role2_uri, True) # the function to be tested: self.urihandler.delete(self.hmc, new_user_role2_uri, True) # Verify that it has been deleted with pytest.raises(InvalidResourceError): self.urihandler.get(self.hmc, new_user_role2_uri, True) class TestUserRoleAddPermissionHandler(object): """All tests for class UserRoleAddPermissionHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() # Has a system-defined User Role (oid=fake-user-role-oid-1) self.uris = ( (r'/api/console/user-roles(?:\?(.*))?', UserRolesHandler), (r'/api/user-roles/([^/]+)', UserRoleHandler), (r'/api/user-roles/([^/]+)/operations/add-permission', UserRoleAddPermissionHandler), ) self.urihandler = UriHandler(self.uris) def test_add_all(self): """All tests for adding permissions to a User Role.""" testcases = [ # (input_permission, exp_permission) ( { 'permitted-object': 'partition', 'permitted-object-type': 'object-class', 'include-members': True, 'view-only-mode': False, }, { 'permitted-object': 'partition', 'permitted-object-type': 'object-class', 'include-members': True, 'view-only-mode': False, }, ), ( { 'permitted-object': 'adapter', 'permitted-object-type': 'object-class', }, { 'permitted-object': 'adapter', 'permitted-object-type': 'object-class', 'include-members': False, 'view-only-mode': True, }, ), ] for input_permission, exp_permission in testcases: self._test_add_one(input_permission, exp_permission) def _test_add_one(self, input_permission, exp_permission): # Add a user-defined User Role for our tests user_role2 = { 'name': 'user_role_2', 'description': 'User Role #2', } resp = self.urihandler.post( self.hmc, '/api/console/user-roles', user_role2, True, True) self.user_role2_uri = resp['object-uri'] self.user_role2_props = self.urihandler.get( self.hmc, self.user_role2_uri, True) uri = self.user_role2_uri + '/operations/add-permission' # the function to be tested: resp = self.urihandler.post( self.hmc, uri, input_permission, True, True) assert resp is None props = self.urihandler.get(self.hmc, self.user_role2_uri, True) assert 'permissions' in props permissions = props['permissions'] assert len(permissions) == 1 perm = permissions[0] assert perm == exp_permission def test_add_error_bad_user_role(self): """Test failed addition of a permission to a bad User Role.""" bad_user_role_uri = '/api/user-roles/not-found-oid' uri = bad_user_role_uri + '/operations/add-permission' input_parms = { 'permitted-object': 'partition', 'permitted-object-type': 'object-class', 'include-members': True, 'view-only-mode': False, } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, uri, input_parms, True, True) exc = exc_info.value assert exc.reason == 1 def test_add_error_system_user_role(self): """Test failed addition of a permission to a system-defined User Role.""" system_user_role_uri = '/api/user-roles/fake-user-role-oid-1' uri = system_user_role_uri + '/operations/add-permission' input_parms = { 'permitted-object': 'partition', 'permitted-object-type': 'object-class', 'include-members': True, 'view-only-mode': False, } with pytest.raises(BadRequestError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, uri, input_parms, True, True) exc = exc_info.value assert exc.reason == 314 class TestUserRoleRemovePermissionHandler(object): """All tests for class UserRoleRemovePermissionHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() # Has a system-defined User Role (oid=fake-user-role-oid-1) self.uris = ( (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), ) self.urihandler = UriHandler(self.uris) def test_remove_all(self): """All tests for removing permissions from a User Role.""" testcases = [ # (input_permission, removed_permission) ( { 'permitted-object': 'partition', 'permitted-object-type': 'object-class', 'include-members': True, 'view-only-mode': False, }, { 'permitted-object': 'partition', 'permitted-object-type': 'object-class', 'include-members': True, 'view-only-mode': False, }, ), ( { 'permitted-object': 'adapter', 'permitted-object-type': 'object-class', }, { 'permitted-object': 'adapter', 'permitted-object-type': 'object-class', 'include-members': False, 'view-only-mode': True, }, ), ] for input_permission, removed_permission in testcases: self._test_remove_one(input_permission, removed_permission) def _test_remove_one(self, input_permission, removed_permission): # Add a user-defined User Role for our tests user_role2 = { 'name': 'user_role_2', 'description': 'User Role #2', } resp = self.urihandler.post( self.hmc, '/api/console/user-roles', user_role2, True, True) self.user_role2_uri = resp['object-uri'] self.user_role2_props = self.urihandler.get( self.hmc, self.user_role2_uri, True) # Add the permission to be removed to the User Role: uri = self.user_role2_uri + '/operations/add-permission' resp = self.urihandler.post( self.hmc, uri, removed_permission, True, True) uri = self.user_role2_uri + '/operations/remove-permission' # the function to be tested: resp = self.urihandler.post( self.hmc, uri, input_permission, True, True) assert resp is None props = self.urihandler.get(self.hmc, self.user_role2_uri, True) assert 'permissions' in props permissions = props['permissions'] assert len(permissions) == 0 def test_remove_error_bad_user_role(self): """Test failed removal of a permission from a bad User Role.""" bad_user_role_uri = '/api/user-roles/not-found-oid' uri = bad_user_role_uri + '/operations/remove-permission' input_parms = { 'permitted-object': 'partition', 'permitted-object-type': 'object-class', 'include-members': True, 'view-only-mode': False, } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, uri, input_parms, True, True) exc = exc_info.value assert exc.reason == 1 def test_remove_error_system_user_role(self): """Test failed removal of a permission from a system-defined User Role.""" system_user_role_uri = '/api/user-roles/fake-user-role-oid-1' uri = system_user_role_uri + '/operations/remove-permission' input_parms = { 'permitted-object': 'partition', 'permitted-object-type': 'object-class', 'include-members': True, 'view-only-mode': False, } with pytest.raises(BadRequestError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, uri, input_parms, True, True) exc = exc_info.value assert exc.reason == 314 class TestTaskHandlers(object): """All tests for classes TasksHandler and TaskHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console/tasks(?:\?(.*))?', TasksHandler), (r'/api/console/tasks/([^/]+)', TaskHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: tasks = self.urihandler.get(self.hmc, '/api/console/tasks', True) exp_tasks = { # properties reduced to those returned by List 'tasks': [ { 'element-uri': '/api/console/tasks/fake-task-oid-1', 'name': 'fake_task_name_1', }, { 'element-uri': '/api/console/tasks/fake-task-oid-2', 'name': 'fake_task_name_2', }, ] } assert tasks == exp_tasks def test_list_error_console_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.get(self.hmc, '/api/console/tasks', True) exc = exc_info.value assert exc.reason == 1 def test_get(self): # the function to be tested: task1 = self.urihandler.get( self.hmc, '/api/console/tasks/fake-task-oid-1', True) exp_task1 = { # properties reduced to those in standard test HMC 'element-id': 'fake-task-oid-1', 'element-uri': '/api/console/tasks/fake-task-oid-1', 'class': 'task', 'parent': '/api/console', 'name': 'fake_task_name_1', 'description': 'Task #1', } assert task1 == exp_task1 class TestUserPatternHandlers(object): """All tests for classes UserPatternsHandler and UserPatternHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console/user-patterns(?:\?(.*))?', UserPatternsHandler), (r'/api/console/user-patterns/([^/]+)', UserPatternHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: user_patterns = self.urihandler.get( self.hmc, '/api/console/user-patterns', True) exp_user_patterns = { # properties reduced to those returned by List 'user-patterns': [ { 'element-uri': '/api/console/user-patterns/fake-user-pattern-oid-1', 'name': 'fake_user_pattern_name_1', 'type': 'glob-like', }, ] } assert user_patterns == exp_user_patterns def test_list_error_console_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.get(self.hmc, '/api/console/user-patterns', True) exc = exc_info.value assert exc.reason == 1 def test_get(self): # the function to be tested: user_pattern1 = self.urihandler.get( self.hmc, '/api/console/user-patterns/fake-user-pattern-oid-1', True) exp_user_pattern1 = { # properties reduced to those in std test HMC 'element-id': 'fake-user-pattern-oid-1', 'element-uri': '/api/console/user-patterns/fake-user-pattern-oid-1', 'class': 'user-pattern', 'parent': '/api/console', 'name': 'fake_user_pattern_name_1', 'description': 'User Pattern #1', 'pattern': 'fake_user_name_*', 'type': 'glob-like', 'retention-time': 0, 'user-template-uri': '/api/users/fake-user-oid-1', } assert user_pattern1 == exp_user_pattern1 def test_create_verify(self): new_user_pattern_input = { 'name': 'user_pattern_X', 'description': 'User Pattern #X', 'pattern': 'user*', 'type': 'glob-like', 'retention-time': 0, 'user-template-uri': '/api/users/fake-user-oid-1', } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/console/user-patterns', new_user_pattern_input, True, True) assert len(resp) == 1 assert 'element-uri' in resp new_user_pattern_uri = resp['element-uri'] # the function to be tested: new_user_pattern = self.urihandler.get( self.hmc, new_user_pattern_uri, True) new_name = new_user_pattern['name'] input_name = new_user_pattern_input['name'] assert new_name == input_name def test_create_error_console_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) new_user_pattern_input = { 'name': 'user_pattern_X', 'description': 'User Pattern #X', } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, '/api/console/user-patterns', new_user_pattern_input, True, True) exc = exc_info.value assert exc.reason == 1 def test_update_verify(self): update_user_pattern1 = { 'description': 'updated user pattern #1', } # the function to be tested: self.urihandler.post( self.hmc, '/api/console/user-patterns/fake-user-pattern-oid-1', update_user_pattern1, True, True) user_pattern1 = self.urihandler.get( self.hmc, '/api/console/user-patterns/fake-user-pattern-oid-1', True) assert user_pattern1['description'] == 'updated user pattern #1' def test_delete_verify(self): new_user_pattern_input = { 'name': 'user_pattern_x', 'description': 'User Pattern #X', 'pattern': 'user*', 'type': 'glob-like', 'retention-time': 0, 'user-template-uri': '/api/users/fake-user-oid-1', } # Create the User Pattern resp = self.urihandler.post( self.hmc, '/api/console/user-patterns', new_user_pattern_input, True, True) new_user_pattern_uri = resp['element-uri'] # Verify that it exists self.urihandler.get(self.hmc, new_user_pattern_uri, True) # the function to be tested: self.urihandler.delete(self.hmc, new_user_pattern_uri, True) # Verify that it has been deleted with pytest.raises(InvalidResourceError): self.urihandler.get(self.hmc, new_user_pattern_uri, True) class TestPasswordRuleHandlers(object): """All tests for classes PasswordRulesHandler and PasswordRuleHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console/password-rules(?:\?(.*))?', PasswordRulesHandler), (r'/api/console/password-rules/([^/]+)', PasswordRuleHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: password_rules = self.urihandler.get( self.hmc, '/api/console/password-rules', True) exp_password_rules = { # properties reduced to those returned by List 'password-rules': [ { 'element-uri': '/api/console/password-rules/fake-password-rule-oid-1', 'name': 'fake_password_rule_name_1', 'type': 'system-defined', }, ] } assert password_rules == exp_password_rules def test_list_error_console_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.get(self.hmc, '/api/console/password-rules', True) exc = exc_info.value assert exc.reason == 1 def test_get(self): # the function to be tested: password_rule1 = self.urihandler.get( self.hmc, '/api/console/password-rules/fake-password-rule-oid-1', True) exp_password_rule1 = { # properties reduced to those in std test HMC 'element-id': 'fake-password-rule-oid-1', 'element-uri': '/api/console/password-rules/fake-password-rule-oid-1', 'class': 'password-rule', 'parent': '/api/console', 'name': 'fake_password_rule_name_1', 'description': 'Password Rule #1', 'type': 'system-defined', } assert password_rule1 == exp_password_rule1 def test_create_verify(self): new_password_rule_input = { 'name': 'password_rule_X', 'description': 'Password Rule #X', } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/console/password-rules', new_password_rule_input, True, True) assert len(resp) == 1 assert 'element-uri' in resp new_password_rule_uri = resp['element-uri'] # the function to be tested: new_password_rule = self.urihandler.get( self.hmc, new_password_rule_uri, True) new_name = new_password_rule['name'] input_name = new_password_rule_input['name'] assert new_name == input_name def test_create_error_console_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) new_password_rule_input = { 'name': 'password_rule_X', 'description': 'Password Rule #X', } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post(self.hmc, '/api/console/password-rules', new_password_rule_input, True, True) exc = exc_info.value assert exc.reason == 1 def test_update_verify(self): update_password_rule1 = { 'description': 'updated password rule #1', } # the function to be tested: self.urihandler.post( self.hmc, '/api/console/password-rules/fake-password-rule-oid-1', update_password_rule1, True, True) password_rule1 = self.urihandler.get( self.hmc, '/api/console/password-rules/fake-password-rule-oid-1', True) assert password_rule1['description'] == 'updated password rule #1' def test_delete_verify(self): new_password_rule_input = { 'name': 'password_rule_X', 'description': 'Password Rule #X', } # Create the Password Rule resp = self.urihandler.post( self.hmc, '/api/console/password-rules', new_password_rule_input, True, True) new_password_rule_uri = resp['element-uri'] # Verify that it exists self.urihandler.get(self.hmc, new_password_rule_uri, True) # the function to be tested: self.urihandler.delete(self.hmc, new_password_rule_uri, True) # Verify that it has been deleted with pytest.raises(InvalidResourceError): self.urihandler.get(self.hmc, new_password_rule_uri, True) class TestLdapServerDefinitionHandlers(object): """All tests for classes LdapServerDefinitionsHandler and LdapServerDefinitionHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/console/ldap-server-definitions(?:\?(.*))?', LdapServerDefinitionsHandler), (r'/api/console/ldap-server-definitions/([^/]+)', LdapServerDefinitionHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: ldap_srv_defs = self.urihandler.get( self.hmc, '/api/console/ldap-server-definitions', True) exp_ldap_srv_defs = { # properties reduced to those returned by List 'ldap-server-definitions': [ { 'element-uri': '/api/console/ldap-server-definitions/' 'fake-ldap-srv-def-oid-1', 'name': 'fake_ldap_srv_def_name_1', }, ] } assert ldap_srv_defs == exp_ldap_srv_defs def test_list_error_console_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.get( self.hmc, '/api/console/ldap-server-definitions', True) exc = exc_info.value assert exc.reason == 1 def test_get(self): # the function to be tested: ldap_srv_def1 = self.urihandler.get( self.hmc, '/api/console/ldap-server-definitions/fake-ldap-srv-def-oid-1', True) exp_ldap_srv_def1 = { # properties reduced to those in std test HMC 'element-id': 'fake-ldap-srv-def-oid-1', 'element-uri': '/api/console/ldap-server-definitions/' 'fake-ldap-srv-def-oid-1', 'class': 'ldap-server-definition', 'parent': '/api/console', 'name': 'fake_ldap_srv_def_name_1', 'description': 'LDAP Srv Def #1', 'primary-hostname-ipaddr': '10.11.12.13', } assert ldap_srv_def1 == exp_ldap_srv_def1 def test_create_verify(self): new_ldap_srv_def_input = { 'name': 'ldap_srv_def_X', 'description': 'LDAP Srv Def #X', } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/console/ldap-server-definitions', new_ldap_srv_def_input, True, True) assert len(resp) == 1 assert 'element-uri' in resp new_ldap_srv_def_uri = resp['element-uri'] # the function to be tested: new_ldap_srv_def = self.urihandler.get( self.hmc, new_ldap_srv_def_uri, True) new_name = new_ldap_srv_def['name'] input_name = new_ldap_srv_def_input['name'] assert new_name == input_name def test_create_error_console_not_found(self): # Remove the faked Console object self.hmc.consoles.remove(None) new_ldap_srv_def_input = { 'name': 'ldap_srv_def_X', 'description': 'LDAP Srv Def #X', } with pytest.raises(InvalidResourceError) as exc_info: # the function to be tested: self.urihandler.post( self.hmc, '/api/console/ldap-server-definitions', new_ldap_srv_def_input, True, True) exc = exc_info.value assert exc.reason == 1 def test_update_verify(self): update_ldap_srv_def1 = { 'description': 'updated LDAP Srv Def #1', } # the function to be tested: self.urihandler.post( self.hmc, '/api/console/ldap-server-definitions/fake-ldap-srv-def-oid-1', update_ldap_srv_def1, True, True) ldap_srv_def1 = self.urihandler.get( self.hmc, '/api/console/ldap-server-definitions/fake-ldap-srv-def-oid-1', True) assert ldap_srv_def1['description'] == 'updated LDAP Srv Def #1' def test_delete_verify(self): new_ldap_srv_def_input = { 'name': 'ldap_srv_def_X', 'description': 'LDAP Srv Def #X', } # Create the LDAP Srv Def resp = self.urihandler.post( self.hmc, '/api/console/ldap-server-definitions', new_ldap_srv_def_input, True, True) new_ldap_srv_def_uri = resp['element-uri'] # Verify that it exists self.urihandler.get(self.hmc, new_ldap_srv_def_uri, True) # the function to be tested: self.urihandler.delete(self.hmc, new_ldap_srv_def_uri, True) # Verify that it has been deleted with pytest.raises(InvalidResourceError): self.urihandler.get(self.hmc, new_ldap_srv_def_uri, True) class TestCpcHandlers(object): """All tests for classes CpcsHandler and CpcHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs(?:\?(.*))?', CpcsHandler), (r'/api/cpcs/([^/]+)', CpcHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: cpcs = self.urihandler.get(self.hmc, '/api/cpcs', True) exp_cpcs = { # properties reduced to those returned by List 'cpcs': [ { 'object-uri': '/api/cpcs/1', 'name': 'cpc_1', 'status': 'operating', }, { 'object-uri': '/api/cpcs/2', 'name': 'cpc_2', 'status': 'active', }, ] } assert cpcs == exp_cpcs def test_get(self): # the function to be tested: cpc1 = self.urihandler.get(self.hmc, '/api/cpcs/1', True) exp_cpc1 = { 'object-id': '1', 'object-uri': '/api/cpcs/1', 'class': 'cpc', 'parent': None, 'name': 'cpc_1', 'dpm-enabled': False, 'is-ensemble-member': False, 'description': 'CPC #1 (classic mode)', 'status': 'operating', } assert cpc1 == exp_cpc1 def test_update_verify(self): update_cpc1 = { 'description': 'updated cpc #1', } # the function to be tested: self.urihandler.post(self.hmc, '/api/cpcs/1', update_cpc1, True, True) cpc1 = self.urihandler.get(self.hmc, '/api/cpcs/1', True) assert cpc1['description'] == 'updated cpc #1' class TestCpcSetPowerSaveHandler(object): """All tests for class CpcSetPowerSaveHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)', CpcHandler), (r'/api/cpcs/([^/]+)/operations/set-cpc-power-save', CpcSetPowerSaveHandler), ) self.urihandler = UriHandler(self.uris) @pytest.mark.parametrize( "power_saving, exp_error", [ (None, (400, 7)), ('invalid_power_save', (400, 7)), ('high-performance', None), ('low-power', None), ('custom', None), ] ) def test_set_power_save(self, power_saving, exp_error): operation_body = { 'power-saving': power_saving, } if exp_error: with pytest.raises(HTTPError) as exc_info: # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/cpcs/1/operations/set-cpc-power-save', operation_body, True, True) exc = exc_info.value assert exc.http_status == exp_error[0] assert exc.reason == exp_error[1] else: # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/cpcs/1/operations/set-cpc-power-save', operation_body, True, True) assert resp is None cpc1 = self.urihandler.get(self.hmc, '/api/cpcs/1', True) assert cpc1['cpc-power-saving'] == power_saving assert cpc1['zcpc-power-saving'] == power_saving class TestCpcSetPowerCappingHandler(object): """All tests for class CpcSetPowerCappingHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)', CpcHandler), (r'/api/cpcs/([^/]+)/operations/set-cpc-power-capping', CpcSetPowerCappingHandler), ) self.urihandler = UriHandler(self.uris) @pytest.mark.parametrize( "power_capping_state, power_cap_current, exp_error", [ (None, None, (400, 7)), ('enabled', None, (400, 7)), ('enabled', 20000, None), ('disabled', None, None), ] ) def test_set_power_capping(self, power_capping_state, power_cap_current, exp_error): operation_body = { 'power-capping-state': power_capping_state, } if power_cap_current is not None: operation_body['power-cap-current'] = power_cap_current if exp_error: with pytest.raises(HTTPError) as exc_info: # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/cpcs/1/operations/set-cpc-power-capping', operation_body, True, True) exc = exc_info.value assert exc.http_status == exp_error[0] assert exc.reason == exp_error[1] else: # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/cpcs/1/operations/set-cpc-power-capping', operation_body, True, True) assert resp is None cpc1 = self.urihandler.get(self.hmc, '/api/cpcs/1', True) assert cpc1['cpc-power-capping-state'] == power_capping_state assert cpc1['cpc-power-cap-current'] == power_cap_current assert cpc1['zcpc-power-capping-state'] == power_capping_state assert cpc1['zcpc-power-cap-current'] == power_cap_current class TestCpcGetEnergyManagementDataHandler(object): """All tests for class CpcGetEnergyManagementDataHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)', CpcHandler), (r'/api/cpcs/([^/]+)/operations/energy-management-data', CpcGetEnergyManagementDataHandler), ) self.urihandler = UriHandler(self.uris) @pytest.mark.parametrize( "cpc_uri, energy_props", [ ('/api/cpcs/1', { 'cpc-power-consumption': 14423, 'cpc-power-rating': 28000, 'cpc-power-save-allowed': 'allowed', 'cpc-power-saving': 'high-performance', 'cpc-power-saving-state': 'high-performance', 'zcpc-ambient-temperature': 26.7, 'zcpc-dew-point': 8.4, 'zcpc-exhaust-temperature': 29.0, 'zcpc-heat-load': 49246, 'zcpc-heat-load-forced-air': 10370, 'zcpc-heat-load-water': 38877, 'zcpc-humidity': 31, 'zcpc-maximum-potential-heat-load': 57922, 'zcpc-maximum-potential-power': 16964, 'zcpc-power-consumption': 14423, 'zcpc-power-rating': 28000, 'zcpc-power-save-allowed': 'under-group-control', 'zcpc-power-saving': 'high-performance', 'zcpc-power-saving-state': 'high-performance', }), ] ) def test_get_energy_management_data(self, cpc_uri, energy_props): # Setup the energy properties of the CPC self.urihandler.post(self.hmc, cpc_uri, energy_props, True, True) # the function to be tested: resp = self.urihandler.get( self.hmc, cpc_uri + '/operations/energy-management-data', True) em_objs = resp['objects'] assert len(em_objs) == 1 cpc_data = em_objs[0] assert cpc_data['object-uri'] == cpc_uri assert cpc_data['object-id'] in cpc_uri assert cpc_data['class'] == 'cpcs' assert cpc_data['error-occurred'] is False act_energy_props = cpc_data['properties'] for p in energy_props: exp_value = energy_props[p] assert p in act_energy_props assert act_energy_props[p] == exp_value class TestCpcStartStopHandler(object): """All tests for classes CpcStartHandler and CpcStopHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)', CpcHandler), (r'/api/cpcs/([^/]+)/operations/start', CpcStartHandler), (r'/api/cpcs/([^/]+)/operations/stop', CpcStopHandler), ) self.urihandler = UriHandler(self.uris) def test_stop_classic(self): # CPC1 is in classic mode cpc1 = self.urihandler.get(self.hmc, '/api/cpcs/1', True) assert cpc1['status'] == 'operating' # the function to be tested: with pytest.raises(CpcNotInDpmError): self.urihandler.post(self.hmc, '/api/cpcs/1/operations/stop', None, True, True) cpc1 = self.urihandler.get(self.hmc, '/api/cpcs/1', True) assert cpc1['status'] == 'operating' def test_start_classic(self): # CPC1 is in classic mode cpc1 = self.urihandler.get(self.hmc, '/api/cpcs/1', True) assert cpc1['status'] == 'operating' # the function to be tested: with pytest.raises(CpcNotInDpmError): self.urihandler.post(self.hmc, '/api/cpcs/1/operations/start', None, True, True) cpc1 = self.urihandler.get(self.hmc, '/api/cpcs/1', True) assert cpc1['status'] == 'operating' def test_stop_start_dpm(self): # CPC2 is in DPM mode cpc2 = self.urihandler.get(self.hmc, '/api/cpcs/2', True) assert cpc2['status'] == 'active' # the function to be tested: self.urihandler.post(self.hmc, '/api/cpcs/2/operations/stop', None, True, True) cpc2 = self.urihandler.get(self.hmc, '/api/cpcs/2', True) assert cpc2['status'] == 'not-operating' # the function to be tested: self.urihandler.post(self.hmc, '/api/cpcs/2/operations/start', None, True, True) cpc2 = self.urihandler.get(self.hmc, '/api/cpcs/2', True) assert cpc2['status'] == 'active' class TestCpcExportPortNamesListHandler(object): """All tests for class CpcExportPortNamesListHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs(?:\?(.*))?', CpcsHandler), (r'/api/cpcs/([^/]+)', CpcHandler), (r'/api/cpcs/([^/]+)/operations/export-port-names-list', CpcExportPortNamesListHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_err_no_input(self): # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/cpcs/2/operations/export-port-names-list', None, True, True) def test_invoke_ok(self): operation_body = { 'partitions': [ '/api/partitions/1', ] } exp_wwpn_list = [ 'partition_1,CEF,1001,CFFEAFFE00008001', ] # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/cpcs/2/operations/export-port-names-list', operation_body, True, True) assert len(resp) == 1 assert 'wwpn-list' in resp wwpn_list = resp['wwpn-list'] assert wwpn_list == exp_wwpn_list class TestCpcImportProfilesHandler(object): """All tests for class CpcImportProfilesHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs(?:\?(.*))?', CpcsHandler), (r'/api/cpcs/([^/]+)', CpcHandler), (r'/api/cpcs/([^/]+)/operations/import-profiles', CpcImportProfilesHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_err_no_input(self): # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/cpcs/1/operations/import-profiles', None, True, True) def test_invoke_ok(self): operation_body = { 'profile-area': 2, } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/cpcs/1/operations/import-profiles', operation_body, True, True) assert resp is None class TestCpcExportProfilesHandler(object): """All tests for class CpcExportProfilesHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs(?:\?(.*))?', CpcsHandler), (r'/api/cpcs/([^/]+)', CpcHandler), (r'/api/cpcs/([^/]+)/operations/export-profiles', CpcExportProfilesHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_err_no_input(self): # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/cpcs/1/operations/export-profiles', None, True, True) def test_invoke_ok(self): operation_body = { 'profile-area': 2, } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/cpcs/1/operations/export-profiles', operation_body, True, True) assert resp is None class TestMetricsContextHandlers(object): """All tests for classes MetricsContextsHandler and MetricsContextHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/services/metrics/context', MetricsContextsHandler), (r'/api/services/metrics/context/([^/]+)', MetricsContextHandler), ) self.urihandler = UriHandler(self.uris) def test_create_get_delete_context(self): mc_mgr = self.hmc.metrics_contexts # Prepare faked metric group definitions mg_name = 'partition-usage' mg_def = FakedMetricGroupDefinition( name=mg_name, types=[ ('metric-1', 'string-metric'), ('metric-2', 'integer-metric'), ]) mg_info = { 'group-name': mg_name, 'metric-infos': [ { 'metric-name': 'metric-1', 'metric-type': 'string-metric', }, { 'metric-name': 'metric-2', 'metric-type': 'integer-metric', }, ], } mc_mgr.add_metric_group_definition(mg_def) mg_name2 = 'cpc-usage' mg_def2 = FakedMetricGroupDefinition( name=mg_name2, types=[ ('metric-3', 'string-metric'), ('metric-4', 'integer-metric'), ]) mg_info2 = { 'group-name': mg_name2, 'metric-infos': [ { 'metric-name': 'metric-3', 'metric-type': 'string-metric', }, { 'metric-name': 'metric-4', 'metric-type': 'integer-metric', }, ], } mc_mgr.add_metric_group_definition(mg_def2) # Prepare faked metric values mo_val1_input = FakedMetricObjectValues( group_name=mg_name, resource_uri='/api/partitions/fake-oid', timestamp=datetime(2017, 9, 5, 12, 13, 10, 0), values=[ ('metric-1', "a"), ('metric-2', 5), ]) mc_mgr.add_metric_values(mo_val1_input) mo_val2_input = FakedMetricObjectValues( group_name=mg_name, resource_uri='/api/partitions/fake-oid', timestamp=datetime(2017, 9, 5, 12, 13, 20, 0), values=[ ('metric-1', "b"), ('metric-2', -7), ]) mc_mgr.add_metric_values(mo_val2_input) mo_val3_input = FakedMetricObjectValues( group_name=mg_name2, resource_uri='/api/cpcs/fake-oid', timestamp=datetime(2017, 9, 5, 12, 13, 10, 0), values=[ ('metric-1', "c"), ('metric-2', 0), ]) mc_mgr.add_metric_values(mo_val3_input) body = { 'anticipated-frequency-seconds': '10', 'metric-groups': [mg_name, mg_name2], } # the create function to be tested: resp = self.urihandler.post(self.hmc, '/api/services/metrics/context', body, True, True) assert isinstance(resp, dict) assert 'metrics-context-uri' in resp uri = resp['metrics-context-uri'] assert uri.startswith('/api/services/metrics/context/') assert 'metric-group-infos' in resp mg_infos = resp['metric-group-infos'] assert mg_infos == [mg_info, mg_info2] # the get function to be tested: mv_resp = self.urihandler.get(self.hmc, uri, True) exp_mv_resp = '''"partition-usage" "/api/partitions/fake-oid" 1504613590000 "a",5 "/api/partitions/fake-oid" 1504613600000 "b",-7 "cpc-usage" "/api/cpcs/fake-oid" 1504613590000 "c",0 ''' assert mv_resp == exp_mv_resp, \ "Actual response string:\n{!r}\n" \ "Expected response string:\n{!r}\n". \ format(mv_resp, exp_mv_resp) # the delete function to be tested: self.urihandler.delete(self.hmc, uri, True) class TestAdapterHandlers(object): """All tests for classes AdaptersHandler and AdapterHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)/adapters(?:\?(.*))?', AdaptersHandler), (r'/api/adapters/([^/]+)', AdapterHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: adapters = self.urihandler.get(self.hmc, '/api/cpcs/2/adapters', True) exp_adapters = { # properties reduced to those returned by List 'adapters': [ { 'object-uri': '/api/adapters/1', 'name': 'osa_1', 'status': 'active', }, { 'object-uri': '/api/adapters/2', 'name': 'fcp_2', 'status': 'active', }, { 'object-uri': '/api/adapters/2a', 'name': 'fcp_2a', 'status': 'active', }, { 'object-uri': '/api/adapters/3', 'name': 'roce_3', 'status': 'active', }, { 'object-uri': '/api/adapters/4', 'name': 'crypto_4', 'status': 'active', }, ] } assert adapters == exp_adapters def test_get(self): # the function to be tested: adapter1 = self.urihandler.get(self.hmc, '/api/adapters/1', True) exp_adapter1 = { 'object-id': '1', 'object-uri': '/api/adapters/1', 'class': 'adapter', 'parent': '/api/cpcs/2', 'name': 'osa_1', 'description': 'OSA #1 in CPC #2', 'status': 'active', 'adapter-family': 'osa', 'network-port-uris': ['/api/adapters/1/network-ports/1'], 'adapter-id': 'BEF', } assert adapter1 == exp_adapter1 def test_update_verify(self): update_adapter1 = { 'description': 'updated adapter #1', } # the function to be tested: self.urihandler.post(self.hmc, '/api/adapters/1', update_adapter1, True, True) adapter1 = self.urihandler.get(self.hmc, '/api/adapters/1', True) assert adapter1['description'] == 'updated adapter #1' class TestAdapterChangeCryptoTypeHandler(object): """All tests for class AdapterChangeCryptoTypeHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)/adapters(?:\?(.*))?', AdaptersHandler), (r'/api/adapters/([^/]+)', AdapterHandler), (r'/api/adapters/([^/]+)/operations/change-crypto-type', AdapterChangeCryptoTypeHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_err_no_body(self): # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/adapters/4/operations/change-crypto-type', None, True, True) def test_invoke_err_no_crypto_type_field(self): operation_body = { # no 'crypto-type' field } # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/adapters/4/operations/change-crypto-type', operation_body, True, True) def test_invoke_ok(self): operation_body = { 'crypto-type': 'cca-coprocessor', } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/adapters/4/operations/change-crypto-type', operation_body, True, True) assert resp is None class TestAdapterChangeAdapterTypeHandler(object): """All tests for class AdapterChangeAdapterTypeHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)/adapters(?:\?(.*))?', AdaptersHandler), (r'/api/adapters/([^/]+)', AdapterHandler), (r'/api/adapters/([^/]+)/operations/change-adapter-type', AdapterChangeAdapterTypeHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_err_no_body(self): # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/adapters/2/operations/change-adapter-type', None, True, True) def test_invoke_err_no_type_field(self): operation_body = { # no 'type' field } # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/adapters/2/operations/change-adapter-type', operation_body, True, True) def test_invoke_ok(self): operation_body = { 'type': 'fcp', } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/adapters/2/operations/change-adapter-type', operation_body, True, True) assert resp is None class TestNetworkPortHandlers(object): """All tests for class NetworkPortHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/adapters/([^/]+)/network-ports/([^/]+)', NetworkPortHandler), ) self.urihandler = UriHandler(self.uris) def test_get(self): # the function to be tested: port1 = self.urihandler.get(self.hmc, '/api/adapters/1/network-ports/1', True) exp_port1 = { 'element-id': '1', 'element-uri': '/api/adapters/1/network-ports/1', 'class': 'network-port', 'parent': '/api/adapters/1', 'name': 'osa_1_port_1', 'description': 'Port #1 of OSA #1', } assert port1 == exp_port1 def test_update_verify(self): update_port1 = { 'description': 'updated port #1', } # the function to be tested: self.urihandler.post(self.hmc, '/api/adapters/1/network-ports/1', update_port1, True, True) port1 = self.urihandler.get(self.hmc, '/api/adapters/1/network-ports/1', True) assert port1['description'] == 'updated port #1' class TestStoragePortHandlers(object): """All tests for class StoragePortHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/adapters/([^/]+)/storage-ports/([^/]+)', StoragePortHandler), ) self.urihandler = UriHandler(self.uris) def test_get(self): # the function to be tested: port1 = self.urihandler.get(self.hmc, '/api/adapters/2/storage-ports/1', True) exp_port1 = { 'element-id': '1', 'element-uri': '/api/adapters/2/storage-ports/1', 'class': 'storage-port', 'parent': '/api/adapters/2', 'name': 'fcp_2_port_1', 'description': 'Port #1 of FCP #2', } assert port1 == exp_port1 def test_update_verify(self): update_port1 = { 'description': 'updated port #1', } # the function to be tested: self.urihandler.post(self.hmc, '/api/adapters/2/storage-ports/1', update_port1, True, True) port1 = self.urihandler.get(self.hmc, '/api/adapters/2/storage-ports/1', True) assert port1['description'] == 'updated port #1' class TestPartitionHandlers(object): """All tests for classes PartitionsHandler and PartitionHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)/partitions(?:\?(.*))?', PartitionsHandler), (r'/api/partitions/([^/]+)', PartitionHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: partitions = self.urihandler.get(self.hmc, '/api/cpcs/2/partitions', True) exp_partitions = { # properties reduced to those returned by List 'partitions': [ { 'object-uri': '/api/partitions/1', 'name': 'partition_1', 'status': 'stopped', }, ] } assert partitions == exp_partitions def test_get(self): # the function to be tested: partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) exp_partition1 = { 'object-id': '1', 'object-uri': '/api/partitions/1', 'class': 'partition', 'parent': '/api/cpcs/2', 'name': 'partition_1', 'description': 'Partition #1 in CPC #2', 'status': 'stopped', 'hba-uris': ['/api/partitions/1/hbas/1'], 'nic-uris': ['/api/partitions/1/nics/1'], 'virtual-function-uris': ['/api/partitions/1/virtual-functions/1'], } assert partition1 == exp_partition1 def test_create_verify(self): new_partition2 = { 'object-id': '2', 'name': 'partition_2', 'initial-memory': 1024, 'maximum-memory': 2048, } # the function to be tested: resp = self.urihandler.post(self.hmc, '/api/cpcs/2/partitions', new_partition2, True, True) assert len(resp) == 1 assert 'object-uri' in resp new_partition2_uri = resp['object-uri'] assert new_partition2_uri == '/api/partitions/2' exp_partition2 = { 'object-id': '2', 'object-uri': '/api/partitions/2', 'class': 'partition', 'parent': '/api/cpcs/2', 'name': 'partition_2', 'status': 'stopped', 'hba-uris': [], 'nic-uris': [], 'virtual-function-uris': [], 'initial-memory': 1024, 'maximum-memory': 2048, } # the function to be tested: partition2 = self.urihandler.get(self.hmc, '/api/partitions/2', True) assert partition2 == exp_partition2 def test_update_verify(self): update_partition1 = { 'description': 'updated partition #1', } # the function to be tested: self.urihandler.post(self.hmc, '/api/partitions/1', update_partition1, True, True) partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) assert partition1['description'] == 'updated partition #1' def test_delete_verify(self): self.urihandler.get(self.hmc, '/api/partitions/1', True) # the function to be tested: self.urihandler.delete(self.hmc, '/api/partitions/1', True) with pytest.raises(InvalidResourceError): self.urihandler.get(self.hmc, '/api/partitions/1', True) class TestPartitionStartStopHandler(object): """All tests for classes PartitionStartHandler and PartitionStopHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/partitions/([^/]+)', PartitionHandler), (r'/api/partitions/([^/]+)/operations/start', PartitionStartHandler), (r'/api/partitions/([^/]+)/operations/stop', PartitionStopHandler), ) self.urihandler = UriHandler(self.uris) def test_start_stop(self): # CPC2 is in DPM mode partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) assert partition1['status'] == 'stopped' # the start() function to be tested, with a valid initial status: self.urihandler.post(self.hmc, '/api/partitions/1/operations/start', None, True, True) partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) assert partition1['status'] == 'active' # the start() function to be tested, with an invalid initial status: with pytest.raises(HTTPError): self.urihandler.post(self.hmc, '/api/partitions/1/operations/start', None, True, True) # the stop() function to be tested, with a valid initial status: assert partition1['status'] == 'active' self.urihandler.post(self.hmc, '/api/partitions/1/operations/stop', None, True, True) partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) assert partition1['status'] == 'stopped' # the stop() function to be tested, with an invalid initial status: with pytest.raises(HTTPError): self.urihandler.post(self.hmc, '/api/partitions/1/operations/stop', None, True, True) class TestPartitionScsiDumpHandler(object): """All tests for class PartitionScsiDumpHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/partitions/([^/]+)', PartitionHandler), (r'/api/partitions/([^/]+)/operations/scsi-dump', PartitionScsiDumpHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_err_no_body(self): # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/scsi-dump', None, True, True) def test_invoke_err_missing_fields_1(self): operation_body = { # missing: 'dump-load-hba-uri' 'dump-world-wide-port-name': 'fake-wwpn', 'dump-logical-unit-number': 'fake-lun', } # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/scsi-dump', operation_body, True, True) def test_invoke_err_missing_fields_2(self): operation_body = { 'dump-load-hba-uri': 'fake-uri', # missing: 'dump-world-wide-port-name' 'dump-logical-unit-number': 'fake-lun', } # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/scsi-dump', operation_body, True, True) def test_invoke_err_missing_fields_3(self): operation_body = { 'dump-load-hba-uri': 'fake-uri', 'dump-world-wide-port-name': 'fake-wwpn', # missing: 'dump-logical-unit-number' } # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/scsi-dump', operation_body, True, True) def test_invoke_err_status_1(self): operation_body = { 'dump-load-hba-uri': 'fake-uri', 'dump-world-wide-port-name': 'fake-wwpn', 'dump-logical-unit-number': 'fake-lun', } # Set the partition status to an invalid status for this operation partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) partition1['status'] = 'stopped' # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/scsi-dump', operation_body, True, True) def test_invoke_ok(self): operation_body = { 'dump-load-hba-uri': 'fake-uri', 'dump-world-wide-port-name': 'fake-wwpn', 'dump-logical-unit-number': 'fake-lun', } # Set the partition status to a valid status for this operation partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) partition1['status'] = 'active' # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/partitions/1/operations/scsi-dump', operation_body, True, True) assert resp == {} class TestPartitionPswRestartHandler(object): """All tests for class PartitionPswRestartHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/partitions/([^/]+)', PartitionHandler), (r'/api/partitions/([^/]+)/operations/psw-restart', PartitionPswRestartHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_err_status_1(self): # Set the partition status to an invalid status for this operation partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) partition1['status'] = 'stopped' # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/psw-restart', None, True, True) def test_invoke_ok(self): # Set the partition status to a valid status for this operation partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) partition1['status'] = 'active' # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/partitions/1/operations/psw-restart', None, True, True) assert resp == {} class TestPartitionMountIsoImageHandler(object): """All tests for class PartitionMountIsoImageHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/partitions/([^/]+)', PartitionHandler), (r'/api/partitions/([^/]+)/operations/mount-iso-image(?:\?(.*))?', PartitionMountIsoImageHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_err_queryparm_1(self): # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/mount-iso-image?' 'image-namex=fake-image&ins-file-name=fake-ins', None, True, True) def test_invoke_err_queryparm_2(self): # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/mount-iso-image?' 'image-name=fake-image&ins-file-namex=fake-ins', None, True, True) def test_invoke_err_status_1(self): # Set the partition status to an invalid status for this operation partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) partition1['status'] = 'starting' # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/mount-iso-image?' 'image-name=fake-image&ins-file-name=fake-ins', None, True, True) def test_invoke_ok(self): # Set the partition status to a valid status for this operation partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) partition1['status'] = 'active' # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/partitions/1/operations/mount-iso-image?' 'image-name=fake-image&ins-file-name=fake-ins', None, True, True) assert resp == {} boot_iso_image_name = partition1['boot-iso-image-name'] assert boot_iso_image_name == 'fake-image' boot_iso_ins_file = partition1['boot-iso-ins-file'] assert boot_iso_ins_file == 'fake-ins' class TestPartitionUnmountIsoImageHandler(object): """All tests for class PartitionUnmountIsoImageHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/partitions/([^/]+)', PartitionHandler), (r'/api/partitions/([^/]+)/operations/unmount-iso-image', PartitionUnmountIsoImageHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_err_status_1(self): # Set the partition status to an invalid status for this operation partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) partition1['status'] = 'starting' # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/unmount-iso-image', None, True, True) def test_invoke_ok(self): # Set the partition status to a valid status for this operation partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) partition1['status'] = 'active' # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/partitions/1/operations/unmount-iso-image', None, True, True) assert resp == {} boot_iso_image_name = partition1['boot-iso-image-name'] assert boot_iso_image_name is None boot_iso_ins_file = partition1['boot-iso-ins-file'] assert boot_iso_ins_file is None class TestPartitionIncreaseCryptoConfigHandler(object): """All tests for class PartitionIncreaseCryptoConfigHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/partitions/([^/]+)', PartitionHandler), (r'/api/partitions/([^/]+)/operations/' 'increase-crypto-configuration', PartitionIncreaseCryptoConfigHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_err_missing_body(self): # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/increase-crypto-configuration', None, True, True) def test_invoke_err_status_1(self): # Set the partition status to an invalid status for this operation partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) partition1['status'] = 'starting' # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/increase-crypto-configuration', {}, True, True) def test_invoke_ok(self): testcases = [ # (input_adapter_uris, input_domain_configs) # TODO: Change testcases to allow for different initial states (None, None), (None, [{'domain-index': 17, 'access-mode': 'control-usage'}, {'domain-index': 18, 'access-mode': 'control-usage'}]), (['fake-uri1', 'fake-uri2'], None), ([], []), ([], [{'domain-index': 17, 'access-mode': 'control-usage'}, {'domain-index': 18, 'access-mode': 'control-usage'}]), (['fake-uri1', 'fake-uri2'], []), (['fake-uri1', 'fake-uri2'], [{'domain-index': 17, 'access-mode': 'control-usage'}, {'domain-index': 18, 'access-mode': 'control-usage'}]), ] for tc in testcases: input_adapter_uris = tc[0] input_domain_configs = tc[1] operation_body = {} if input_adapter_uris is not None: operation_body['crypto-adapter-uris'] = input_adapter_uris if input_domain_configs is not None: operation_body['crypto-domain-configurations'] = \ input_domain_configs # Set the partition status to a valid status for this operation partition1 = self.urihandler.get( self.hmc, '/api/partitions/1', True) partition1['status'] = 'active' # Set up the initial partition config partition1['crypto-configuration'] = None # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/partitions/1/operations/increase-crypto-configuration', operation_body, True, True) assert resp is None crypto_config = partition1['crypto-configuration'] assert isinstance(crypto_config, dict) adapter_uris = crypto_config['crypto-adapter-uris'] assert isinstance(adapter_uris, list) exp_adapter_uris = input_adapter_uris \ if input_adapter_uris is not None else [] assert adapter_uris == exp_adapter_uris domain_configs = crypto_config['crypto-domain-configurations'] assert isinstance(domain_configs, list) exp_domain_configs = input_domain_configs \ if input_domain_configs is not None else [] assert domain_configs == exp_domain_configs class TestPartitionDecreaseCryptoConfigHandler(object): """All tests for class PartitionDecreaseCryptoConfigHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/partitions/([^/]+)', PartitionHandler), (r'/api/partitions/([^/]+)/operations/' 'decrease-crypto-configuration', PartitionDecreaseCryptoConfigHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_err_missing_body(self): # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/decrease-crypto-configuration', None, True, True) def test_invoke_err_status_1(self): # Set the partition status to an invalid status for this operation partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) partition1['status'] = 'starting' # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/decrease-crypto-configuration', {}, True, True) def test_invoke_ok(self): testcases = [ # (input_adapter_uris, input_domain_indexes) # TODO: Change testcases to allow for different initial states # TODO: Change testcases to allow for expected results (None, None), (None, [17, 18]), (['fake-uri1', 'fake-uri2'], None), ([], []), ([], [17, 18]), (['fake-uri1', 'fake-uri2'], []), (['fake-uri1', 'fake-uri2'], [17, 18]), ] for tc in testcases: input_adapter_uris = tc[0] input_domain_indexes = tc[1] operation_body = {} if input_adapter_uris is not None: operation_body['crypto-adapter-uris'] = input_adapter_uris if input_domain_indexes is not None: operation_body['crypto-domain-indexes'] = \ input_domain_indexes # Set the partition status to a valid status for this operation partition1 = self.urihandler.get( self.hmc, '/api/partitions/1', True) partition1['status'] = 'active' # Set up the initial partition config partition1['crypto-configuration'] = { 'crypto-adapter-uris': ['fake-uri1', 'fake-uri2'], 'crypto-domain-configurations': [ {'domain-index': 17, 'access-mode': 'control-usage'}, {'domain-index': 18, 'access-mode': 'control-usage'}, ] } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/partitions/1/operations/decrease-crypto-configuration', operation_body, True, True) assert resp is None crypto_config = partition1['crypto-configuration'] assert isinstance(crypto_config, dict) adapter_uris = crypto_config['crypto-adapter-uris'] assert isinstance(adapter_uris, list) domain_configs = crypto_config['crypto-domain-configurations'] assert isinstance(domain_configs, list) class TestPartitionChangeCryptoConfigHandler(object): """All tests for class PartitionChangeCryptoConfigHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/partitions/([^/]+)', PartitionHandler), (r'/api/partitions/([^/]+)/operations/' 'change-crypto-domain-configuration', PartitionChangeCryptoConfigHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_err_missing_body(self): # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/' 'change-crypto-domain-configuration', None, True, True) def test_invoke_err_missing_field_1(self): operation_body = { # missing 'domain-index' 'access-mode': 'control-usage', } # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/' 'change-crypto-domain-configuration', operation_body, True, True) def test_invoke_err_missing_field_2(self): operation_body = { 'domain-index': 17, # missing 'access-mode' } # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/' 'change-crypto-domain-configuration', operation_body, True, True) def test_invoke_err_status_1(self): # Set the partition status to an invalid status for this operation partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) partition1['status'] = 'starting' # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/operations/' 'change-crypto-domain-configuration', {}, True, True) def test_invoke_ok(self): testcases = [ # (input_domain_index, input_access_mode) # TODO: Change testcases to allow for different initial states # TODO: Change testcases to allow for expected results (17, 'control'), ] for tc in testcases: input_domain_index = tc[0] input_access_mode = tc[1] operation_body = {} if input_domain_index is not None: operation_body['domain-index'] = input_domain_index if input_access_mode is not None: operation_body['access-mode'] = input_access_mode # Set the partition status to a valid status for this operation partition1 = self.urihandler.get( self.hmc, '/api/partitions/1', True) partition1['status'] = 'active' # Set up the initial partition config partition1['crypto-configuration'] = { 'crypto-adapter-uris': ['fake-uri1', 'fake-uri2'], 'crypto-domain-configurations': [ {'domain-index': 17, 'access-mode': 'control-usage'}, {'domain-index': 18, 'access-mode': 'control-usage'}, ] } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/partitions/1/operations/' 'change-crypto-domain-configuration', operation_body, True, True) assert resp is None crypto_config = partition1['crypto-configuration'] assert isinstance(crypto_config, dict) adapter_uris = crypto_config['crypto-adapter-uris'] assert isinstance(adapter_uris, list) domain_configs = crypto_config['crypto-domain-configurations'] assert isinstance(domain_configs, list) class TestHbaHandler(object): """All tests for classes HbasHandler and HbaHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/partitions/([^/]+)', PartitionHandler), (r'/api/partitions/([^/]+)/hbas(?:\?(.*))?', HbasHandler), (r'/api/partitions/([^/]+)/hbas/([^/]+)', HbaHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) hba_uris = partition1.get('hba-uris', []) exp_hba_uris = [ '/api/partitions/1/hbas/1', ] assert hba_uris == exp_hba_uris def test_get(self): partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) hba1_uri = partition1.get('hba-uris', [])[0] # the function to be tested: hba1 = self.urihandler.get(self.hmc, hba1_uri, True) exp_hba1 = { 'element-id': '1', 'element-uri': '/api/partitions/1/hbas/1', 'class': 'hba', 'parent': '/api/partitions/1', 'name': 'hba_1', 'description': 'HBA #1 in Partition #1', 'adapter-port-uri': '/api/adapters/2/storage-ports/1', 'wwpn': 'CFFEAFFE00008001', 'device-number': '1001', } assert hba1 == exp_hba1 def test_create_verify(self): new_hba2 = { 'element-id': '2', 'name': 'hba_2', 'adapter-port-uri': '/api/adapters/2/storage-ports/1', } # the function to be tested: resp = self.urihandler.post(self.hmc, '/api/partitions/1/hbas', new_hba2, True, True) assert len(resp) == 1 assert 'element-uri' in resp new_hba2_uri = resp['element-uri'] assert new_hba2_uri == '/api/partitions/1/hbas/2' # the function to be tested: hba2 = self.urihandler.get(self.hmc, '/api/partitions/1/hbas/2', True) exp_hba2 = { 'element-id': '2', 'element-uri': '/api/partitions/1/hbas/2', 'class': 'hba', 'parent': '/api/partitions/1', 'name': 'hba_2', 'adapter-port-uri': '/api/adapters/2/storage-ports/1', 'device-number': hba2['device-number'], # auto-generated 'wwpn': hba2['wwpn'], # auto-generated } assert hba2 == exp_hba2 def test_update_verify(self): update_hba1 = { 'description': 'updated hba #1', } # the function to be tested: self.urihandler.post(self.hmc, '/api/partitions/1/hbas/1', update_hba1, True, True) hba1 = self.urihandler.get(self.hmc, '/api/partitions/1/hbas/1', True) assert hba1['description'] == 'updated hba #1' def test_delete_verify(self): self.urihandler.get(self.hmc, '/api/partitions/1/hbas/1', True) # the function to be tested: self.urihandler.delete(self.hmc, '/api/partitions/1/hbas/1', True) with pytest.raises(InvalidResourceError): self.urihandler.get(self.hmc, '/api/partitions/1/hbas/1', True) class TestHbaReassignPortHandler(object): """All tests for class HbaReassignPortHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/partitions/([^/]+)', PartitionHandler), (r'/api/partitions/([^/]+)/hbas/([^/]+)', HbaHandler), (r'/api/partitions/([^/]+)/hbas/([^/]+)' '/operations/reassign-storage-adapter-port', HbaReassignPortHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_err_missing_body(self): # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/hbas/1/operations/' 'reassign-storage-adapter-port', None, True, True) def test_invoke_err_missing_field_1(self): operation_body = { # missing 'adapter-port-uri' } # the function to be tested: with pytest.raises(HTTPError): self.urihandler.post( self.hmc, '/api/partitions/1/hbas/1/operations/' 'reassign-storage-adapter-port', operation_body, True, True) def test_invoke_ok(self): new_adapter_port_uri = '/api/adapters/2a/port/1' operation_body = { 'adapter-port-uri': new_adapter_port_uri, } # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/partitions/1/hbas/1/operations/' 'reassign-storage-adapter-port', operation_body, True, True) assert resp is None hba = self.urihandler.get(self.hmc, '/api/partitions/1/hbas/1', True) adapter_port_uri = hba['adapter-port-uri'] assert adapter_port_uri == new_adapter_port_uri class TestNicHandler(object): """All tests for classes NicsHandler and NicHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/partitions/([^/]+)', PartitionHandler), (r'/api/partitions/([^/]+)/nics(?:\?(.*))?', NicsHandler), (r'/api/partitions/([^/]+)/nics/([^/]+)', NicHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) nic_uris = partition1.get('nic-uris', []) exp_nic_uris = [ '/api/partitions/1/nics/1', ] assert nic_uris == exp_nic_uris def test_get(self): partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) nic1_uri = partition1.get('nic-uris', [])[0] # the function to be tested: nic1 = self.urihandler.get(self.hmc, nic1_uri, True) exp_nic1 = { 'element-id': '1', 'element-uri': '/api/partitions/1/nics/1', 'class': 'nic', 'parent': '/api/partitions/1', 'name': 'nic_1', 'description': 'NIC #1 in Partition #1', 'network-adapter-port-uri': '/api/adapters/3/network-ports/1', 'device-number': '2001', } assert nic1 == exp_nic1 def test_create_verify(self): new_nic2 = { 'element-id': '2', 'name': 'nic_2', 'network-adapter-port-uri': '/api/adapters/3/network-ports/1', } # the function to be tested: resp = self.urihandler.post(self.hmc, '/api/partitions/1/nics', new_nic2, True, True) assert len(resp) == 1 assert 'element-uri' in resp new_nic2_uri = resp['element-uri'] assert new_nic2_uri == '/api/partitions/1/nics/2' # the function to be tested: nic2 = self.urihandler.get(self.hmc, '/api/partitions/1/nics/2', True) exp_nic2 = { 'element-id': '2', 'element-uri': '/api/partitions/1/nics/2', 'class': 'nic', 'parent': '/api/partitions/1', 'name': 'nic_2', 'network-adapter-port-uri': '/api/adapters/3/network-ports/1', 'device-number': nic2['device-number'], # auto-generated } assert nic2 == exp_nic2 def test_update_verify(self): update_nic1 = { 'description': 'updated nic #1', } # the function to be tested: self.urihandler.post(self.hmc, '/api/partitions/1/nics/1', update_nic1, True, True) nic1 = self.urihandler.get(self.hmc, '/api/partitions/1/nics/1', True) assert nic1['description'] == 'updated nic #1' def test_delete_verify(self): self.urihandler.get(self.hmc, '/api/partitions/1/nics/1', True) # the function to be tested: self.urihandler.delete(self.hmc, '/api/partitions/1/nics/1', True) with pytest.raises(InvalidResourceError): self.urihandler.get(self.hmc, '/api/partitions/1/nics/1', True) class TestVirtualFunctionHandler(object): """All tests for classes VirtualFunctionsHandler and VirtualFunctionHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/partitions/([^/]+)', PartitionHandler), (r'/api/partitions/([^/]+)/virtual-functions(?:\?(.*))?', VirtualFunctionsHandler), (r'/api/partitions/([^/]+)/virtual-functions/([^/]+)', VirtualFunctionHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) vf_uris = partition1.get('virtual-function-uris', []) exp_vf_uris = [ '/api/partitions/1/virtual-functions/1', ] assert vf_uris == exp_vf_uris def test_get(self): partition1 = self.urihandler.get(self.hmc, '/api/partitions/1', True) vf1_uri = partition1.get('virtual-function-uris', [])[0] # the function to be tested: vf1 = self.urihandler.get(self.hmc, vf1_uri, True) exp_vf1 = { 'element-id': '1', 'element-uri': '/api/partitions/1/virtual-functions/1', 'class': 'virtual-function', 'parent': '/api/partitions/1', 'name': 'vf_1', 'description': 'VF #1 in Partition #1', 'device-number': '3001', } assert vf1 == exp_vf1 def test_create_verify(self): new_vf2 = { 'element-id': '2', 'name': 'vf_2', } # the function to be tested: resp = self.urihandler.post(self.hmc, '/api/partitions/1/virtual-functions', new_vf2, True, True) assert len(resp) == 1 assert 'element-uri' in resp new_vf2_uri = resp['element-uri'] assert new_vf2_uri == '/api/partitions/1/virtual-functions/2' # the function to be tested: vf2 = self.urihandler.get(self.hmc, '/api/partitions/1/virtual-functions/2', True) exp_vf2 = { 'element-id': '2', 'element-uri': '/api/partitions/1/virtual-functions/2', 'class': 'virtual-function', 'parent': '/api/partitions/1', 'name': 'vf_2', 'device-number': vf2['device-number'], # auto-generated } assert vf2 == exp_vf2 def test_update_verify(self): update_vf1 = { 'description': 'updated vf #1', } # the function to be tested: self.urihandler.post(self.hmc, '/api/partitions/1/virtual-functions/1', update_vf1, True, True) vf1 = self.urihandler.get(self.hmc, '/api/partitions/1/virtual-functions/1', True) assert vf1['description'] == 'updated vf #1' def test_delete_verify(self): self.urihandler.get(self.hmc, '/api/partitions/1/virtual-functions/1', True) # the function to be tested: self.urihandler.delete(self.hmc, '/api/partitions/1/virtual-functions/1', True) with pytest.raises(InvalidResourceError): self.urihandler.get(self.hmc, '/api/partitions/1/virtual-functions/1', True) class TestVirtualSwitchHandlers(object): """All tests for classes VirtualSwitchesHandler and VirtualSwitchHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)/virtual-switches(?:\?(.*))?', VirtualSwitchesHandler), (r'/api/virtual-switches/([^/]+)', VirtualSwitchHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: vswitches = self.urihandler.get(self.hmc, '/api/cpcs/2/virtual-switches', True) exp_vswitches = { # properties reduced to those returned by List 'virtual-switches': [ { 'object-uri': '/api/virtual-switches/1', 'name': 'vswitch_osa_1', # status not set in resource -> not in response }, ] } assert vswitches == exp_vswitches def test_get(self): # the function to be tested: vswitch1 = self.urihandler.get(self.hmc, '/api/virtual-switches/1', True) exp_vswitch1 = { 'object-id': '1', 'object-uri': '/api/virtual-switches/1', 'class': 'virtual-switch', 'parent': '/api/cpcs/2', 'name': 'vswitch_osa_1', 'description': 'Vswitch for OSA #1 in CPC #2', 'connected-vnic-uris': [], # auto-generated } assert vswitch1 == exp_vswitch1 class TestVirtualSwitchGetVnicsHandler(object): """All tests for class VirtualSwitchGetVnicsHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/virtual-switches/([^/]+)', VirtualSwitchHandler), (r'/api/virtual-switches/([^/]+)/operations/get-connected-vnics', VirtualSwitchGetVnicsHandler), ) self.urihandler = UriHandler(self.uris) def test_invoke_ok(self): connected_nic_uris = ['/api/adapters/1/ports/1'] # Set up the connected vNICs in the vswitch vswitch1 = self.urihandler.get(self.hmc, '/api/virtual-switches/1', True) vswitch1['connected-vnic-uris'] = connected_nic_uris # the function to be tested: resp = self.urihandler.post( self.hmc, '/api/virtual-switches/1/operations/get-connected-vnics', None, True, True) exp_resp = { 'connected-vnic-uris': connected_nic_uris, } assert resp == exp_resp class TestLparHandlers(object): """All tests for classes LparsHandler and LparHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)/logical-partitions(?:\?(.*))?', LparsHandler), (r'/api/logical-partitions/([^/]+)', LparHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: lpars = self.urihandler.get(self.hmc, '/api/cpcs/1/logical-partitions', True) exp_lpars = { # properties reduced to those returned by List 'logical-partitions': [ { 'object-uri': '/api/logical-partitions/1', 'name': 'lpar_1', 'status': 'not-activated', }, ] } assert lpars == exp_lpars def test_get(self): # the function to be tested: lpar1 = self.urihandler.get(self.hmc, '/api/logical-partitions/1', True) exp_lpar1 = { 'object-id': '1', 'object-uri': '/api/logical-partitions/1', 'class': 'logical-partition', 'parent': '/api/cpcs/1', 'name': 'lpar_1', 'status': 'not-activated', 'description': 'LPAR #1 in CPC #1', } assert lpar1 == exp_lpar1 class TestLparActLoadDeactHandler(object): """All tests for classes LparActivateHandler, LparLoadHandler, and LparDeactivateHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (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), ) self.urihandler = UriHandler(self.uris) def test_start_stop(self): # CPC1 is in classic mode lpar1 = self.urihandler.get(self.hmc, '/api/logical-partitions/1', True) assert lpar1['status'] == 'not-activated' lpar1_name = lpar1['name'] # the function to be tested: self.urihandler.post(self.hmc, '/api/logical-partitions/1/operations/activate', {'activation-profile-name': lpar1_name}, True, True) lpar1 = self.urihandler.get(self.hmc, '/api/logical-partitions/1', True) assert lpar1['status'] == 'not-operating' # the function to be tested: self.urihandler.post(self.hmc, '/api/logical-partitions/1/operations/load', {'load-address': '5176'}, True, True) lpar1 = self.urihandler.get(self.hmc, '/api/logical-partitions/1', True) assert lpar1['status'] == 'operating' # the function to be tested: self.urihandler.post(self.hmc, '/api/logical-partitions/1/operations/deactivate', {'force': True}, True, True) lpar1 = self.urihandler.get(self.hmc, '/api/logical-partitions/1', True) assert lpar1['status'] == 'not-activated' class TestResetActProfileHandlers(object): """All tests for classes ResetActProfilesHandler and ResetActProfileHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)/reset-activation-profiles(?:\?(.*))?', ResetActProfilesHandler), (r'/api/cpcs/([^/]+)/reset-activation-profiles/([^/]+)', ResetActProfileHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: raps = self.urihandler.get(self.hmc, '/api/cpcs/1/reset-activation-profiles', True) exp_raps = { # properties reduced to those returned by List 'reset-activation-profiles': [ { 'name': 'r1', 'element-uri': '/api/cpcs/1/reset-activation-profiles/r1', }, ] } assert raps == exp_raps def test_get(self): # the function to be tested: rap1 = self.urihandler.get(self.hmc, '/api/cpcs/1/reset-activation-profiles/r1', True) exp_rap1 = { 'name': 'r1', 'class': 'reset-activation-profile', 'parent': '/api/cpcs/1', 'element-uri': '/api/cpcs/1/reset-activation-profiles/r1', 'description': 'Reset profile #1 in CPC #1', } assert rap1 == exp_rap1 class TestImageActProfileHandlers(object): """All tests for classes ImageActProfilesHandler and ImageActProfileHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)/image-activation-profiles/([^/]+)', ImageActProfileHandler), (r'/api/cpcs/([^/]+)/image-activation-profiles(?:\?(.*))?', ImageActProfilesHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: iaps = self.urihandler.get(self.hmc, '/api/cpcs/1/image-activation-profiles', True) exp_iaps = { # properties reduced to those returned by List 'image-activation-profiles': [ { 'name': 'i1', 'element-uri': '/api/cpcs/1/image-activation-profiles/i1', }, ] } assert iaps == exp_iaps def test_get(self): # the function to be tested: iap1 = self.urihandler.get(self.hmc, '/api/cpcs/1/image-activation-profiles/i1', True) exp_iap1 = { 'name': 'i1', 'element-uri': '/api/cpcs/1/image-activation-profiles/i1', 'class': 'image-activation-profile', 'parent': '/api/cpcs/1', 'description': 'Image profile #1 in CPC #1', } assert iap1 == exp_iap1 class TestLoadActProfileHandlers(object): """All tests for classes LoadActProfilesHandler and LoadActProfileHandler.""" def setup_method(self): self.hmc, self.hmc_resources = standard_test_hmc() self.uris = ( (r'/api/cpcs/([^/]+)/load-activation-profiles/([^/]+)', LoadActProfileHandler), (r'/api/cpcs/([^/]+)/load-activation-profiles(?:\?(.*))?', LoadActProfilesHandler), ) self.urihandler = UriHandler(self.uris) def test_list(self): # the function to be tested: laps = self.urihandler.get(self.hmc, '/api/cpcs/1/load-activation-profiles', True) exp_laps = { # properties reduced to those returned by List 'load-activation-profiles': [ { 'name': 'L1', 'element-uri': '/api/cpcs/1/load-activation-profiles/L1', }, ] } assert laps == exp_laps def test_get(self): # the function to be tested: lap1 = self.urihandler.get(self.hmc, '/api/cpcs/1/load-activation-profiles/L1', True) exp_lap1 = { 'name': 'L1', 'element-uri': '/api/cpcs/1/load-activation-profiles/L1', 'class': 'load-activation-profile', 'parent': '/api/cpcs/1', 'description': 'Load profile #1 in CPC #1', } assert lap1 == exp_lap1 zhmcclient-0.22.0/tests/unit/zhmcclient_mock/test_idpool.py0000644000076500000240000001073613364325033024516 0ustar maierastaff00000000000000# 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. """ Unit tests for _idpool module of the zhmcclient_mock package. """ from __future__ import absolute_import, print_function import requests.packages.urllib3 import pytest from zhmcclient_mock._idpool import IdPool requests.packages.urllib3.disable_warnings() class TestIdPool(object): """All tests for class IdPool.""" def test_init_error_1(self): with pytest.raises(ValueError): IdPool(7, 6) def test_invalid_free_error_1(self): pool = IdPool(5, 5) with pytest.raises(ValueError): pool.free(4) # not in range with pytest.raises(ValueError): pool.free(5) # in range but not allocated with pytest.raises(ValueError): pool.free(6) # not in range def test_invalid_free_error_2(self): pool = IdPool(5, 5) pool.free_if_allocated(4) # not in range (= not allocated) pool.free_if_allocated(5) # in range but not allocated pool.free_if_allocated(6) # not in range (= not allocated) def _test_exhausting_for_lo_hi(self, lowest, highest): start = lowest end = highest + 1 pool = IdPool(lowest, highest) # Exhaust the pool id_list = [] for i in range(start, end): id = pool.alloc() id_list.append(id) # Verify uniqueness of the ID values id_set = set(id_list) assert len(id_set) == len(id_list) # Verify that the pool is exhausted with pytest.raises(ValueError): pool.alloc() def _test_free_for_lo_hi(self, lowest, highest): start = lowest end = highest + 1 pool = IdPool(lowest, highest) # Exhaust the pool id_list1 = [] for i in range(start, end): id = pool.alloc() id_list1.append(id) # Return everything to the pool for id in id_list1: pool.free(id) # Verify that nothing is used in the pool assert len(pool._used) == 0 # Exhaust the pool id_list2 = [] for i in range(start, end): id = pool.alloc() id_list2.append(id) # Verify that the same ID values came back as last time assert set(id_list1) == set(id_list2) # Verify that the pool is exhausted with pytest.raises(ValueError): pool.alloc() def _test_all_for_lo_hi(self, lowest, highest): self._test_exhausting_for_lo_hi(lowest, highest) self._test_free_for_lo_hi(lowest, highest) def test_all(self): # Knowing that the chunk size is 10, we focus on the sizes and range # boundaries around that self._test_all_for_lo_hi(0, 0) self._test_all_for_lo_hi(0, 1) self._test_all_for_lo_hi(0, 9) self._test_all_for_lo_hi(0, 10) self._test_all_for_lo_hi(0, 11) self._test_all_for_lo_hi(0, 19) self._test_all_for_lo_hi(0, 20) self._test_all_for_lo_hi(0, 21) self._test_all_for_lo_hi(3, 3) self._test_all_for_lo_hi(3, 4) self._test_all_for_lo_hi(3, 9) self._test_all_for_lo_hi(3, 10) self._test_all_for_lo_hi(3, 11) self._test_all_for_lo_hi(3, 12) self._test_all_for_lo_hi(3, 13) self._test_all_for_lo_hi(3, 14) self._test_all_for_lo_hi(9, 9) self._test_all_for_lo_hi(9, 10) self._test_all_for_lo_hi(9, 11) self._test_all_for_lo_hi(9, 18) self._test_all_for_lo_hi(9, 19) self._test_all_for_lo_hi(9, 20) self._test_all_for_lo_hi(10, 10) self._test_all_for_lo_hi(10, 11) self._test_all_for_lo_hi(10, 19) self._test_all_for_lo_hi(10, 20) self._test_all_for_lo_hi(10, 21) self._test_all_for_lo_hi(11, 11) self._test_all_for_lo_hi(11, 12) self._test_all_for_lo_hi(11, 20) self._test_all_for_lo_hi(11, 21) self._test_all_for_lo_hi(11, 22) zhmcclient-0.22.0/tests/unit/zhmcclient_mock/test_hmc.py0000644000076500000240000020762013364325033023777 0ustar maierastaff00000000000000# 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. """ Unit tests for _hmc module of the zhmcclient_mock package. """ from __future__ import absolute_import, print_function import re from datetime import datetime import pytest from zhmcclient_mock._hmc import FakedHmc, \ FakedBaseManager, FakedBaseResource, \ FakedActivationProfileManager, FakedActivationProfile, \ FakedAdapterManager, FakedAdapter, \ FakedCpcManager, FakedCpc, \ FakedHbaManager, FakedHba, \ FakedLparManager, FakedLpar, \ FakedNicManager, FakedNic, \ FakedPartitionManager, FakedPartition, \ FakedPortManager, FakedPort, \ FakedVirtualFunctionManager, FakedVirtualFunction, \ FakedVirtualSwitchManager, FakedVirtualSwitch, \ FakedMetricsContextManager, FakedMetricsContext, \ FakedMetricGroupDefinition, FakedMetricObjectValues class TestFakedHmc(object): """All tests for the zhmcclient_mock.FakedHmc class.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') def test_repr(self): """Test FakedHmc.__repr__().""" hmc = self.hmc repr_str = repr(hmc) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=hmc.__class__.__name__, id=id(hmc)), repr_str) def test_hmc(self): assert self.hmc.hmc_name == 'fake-hmc' assert self.hmc.hmc_version == '2.13.1' assert self.hmc.api_version == '1.8' assert isinstance(self.hmc.cpcs, FakedCpcManager) # the function to be tested: cpcs = self.hmc.cpcs.list() assert len(cpcs) == 0 def test_hmc_1_cpc(self): cpc1_in_props = {'name': 'cpc1'} # the function to be tested: cpc1 = self.hmc.cpcs.add(cpc1_in_props) cpc1_out_props = cpc1_in_props.copy() cpc1_out_props.update({ 'object-id': cpc1.oid, 'object-uri': cpc1.uri, 'class': 'cpc', 'parent': None, 'dpm-enabled': False, 'is-ensemble-member': False, 'status': 'operating', }) # the function to be tested: cpcs = self.hmc.cpcs.list() assert len(cpcs) == 1 assert cpcs[0] == cpc1 assert isinstance(cpc1, FakedCpc) assert cpc1.properties == cpc1_out_props assert cpc1.manager == self.hmc.cpcs def test_hmc_2_cpcs(self): cpc1_in_props = {'name': 'cpc1'} # the function to be tested: cpc1 = self.hmc.cpcs.add(cpc1_in_props) cpc1_out_props = cpc1_in_props.copy() cpc1_out_props.update({ 'object-id': cpc1.oid, 'object-uri': cpc1.uri, 'class': 'cpc', 'parent': None, 'dpm-enabled': False, 'is-ensemble-member': False, 'status': 'operating', }) cpc2_in_props = {'name': 'cpc2'} # the function to be tested: cpc2 = self.hmc.cpcs.add(cpc2_in_props) cpc2_out_props = cpc2_in_props.copy() cpc2_out_props.update({ 'object-id': cpc2.oid, 'object-uri': cpc2.uri, 'class': 'cpc', 'parent': None, 'dpm-enabled': False, 'is-ensemble-member': False, 'status': 'operating', }) # the function to be tested: cpcs = self.hmc.cpcs.list() assert len(cpcs) == 2 # We expect the order of addition to be maintained: assert cpcs[0] == cpc1 assert cpcs[1] == cpc2 assert isinstance(cpc1, FakedCpc) assert cpc1.properties == cpc1_out_props assert cpc1.manager == self.hmc.cpcs assert isinstance(cpc2, FakedCpc) assert cpc2.properties == cpc2_out_props assert cpc2.manager == self.hmc.cpcs def test_res_dict(self): cpc1_in_props = {'name': 'cpc1'} adapter1_in_props = {'name': 'osa1', 'adapter-family': 'hipersockets'} port1_in_props = {'name': 'osa1_1'} rd = { 'cpcs': [ { 'properties': cpc1_in_props, 'adapters': [ { 'properties': adapter1_in_props, 'ports': [ {'properties': port1_in_props}, ], }, ], }, ] } # the function to be tested: self.hmc.add_resources(rd) cpcs = self.hmc.cpcs.list() assert len(cpcs) == 1 cpc1 = cpcs[0] cpc1_out_props = cpc1_in_props.copy() cpc1_out_props.update({ 'object-id': cpc1.oid, 'object-uri': cpc1.uri, 'class': 'cpc', 'parent': None, 'dpm-enabled': False, 'is-ensemble-member': False, 'status': 'operating', }) assert isinstance(cpc1, FakedCpc) assert cpc1.properties == cpc1_out_props assert cpc1.manager == self.hmc.cpcs cpc1_adapters = cpc1.adapters.list() assert len(cpc1_adapters) == 1 adapter1 = cpc1_adapters[0] adapter1_ports = adapter1.ports.list() assert len(adapter1_ports) == 1 port1 = adapter1_ports[0] adapter1_out_props = adapter1_in_props.copy() adapter1_out_props.update({ 'object-id': adapter1.oid, 'object-uri': adapter1.uri, 'class': 'adapter', 'parent': cpc1.uri, 'status': 'active', 'network-port-uris': [port1.uri], }) assert isinstance(adapter1, FakedAdapter) assert adapter1.properties == adapter1_out_props assert adapter1.manager == cpc1.adapters port1_out_props = port1_in_props.copy() port1_out_props.update({ 'element-id': port1.oid, 'element-uri': port1.uri, 'class': 'network-port', 'parent': adapter1.uri, }) assert isinstance(port1, FakedPort) assert port1.properties == port1_out_props assert port1.manager == adapter1.ports class TestFakedBase(object): """All tests for the FakedBaseManager and FakedBaseResource classes.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.cpc1_oid = '42-abc-543' self.cpc1_uri = '/api/cpcs/%s' % self.cpc1_oid self.cpc1_in_props = { # All properties that are otherwise defaulted (but with non-default # values), plus 'name'. 'object-id': self.cpc1_oid, 'object-uri': self.cpc1_uri, 'class': 'cpc', 'parent': None, 'dpm-enabled': True, 'is-ensemble-member': False, 'status': 'service', 'name': 'cpc1', } rd = { 'cpcs': [ { 'properties': self.cpc1_in_props, }, ] } self.hmc.add_resources(rd) self.cpc_manager = self.hmc.cpcs self.cpc_resource = self.hmc.cpcs.list()[0] self.cpc1_out_props = self.cpc1_in_props.copy() def test_resource_repr(self): """Test FakedBaseResource.__repr__().""" resource = self.cpc_resource repr_str = repr(resource) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=resource.__class__.__name__, id=id(resource)), repr_str) def test_manager_repr(self): """Test FakedBaseManager.__repr__().""" manager = self.cpc_manager repr_str = repr(manager) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=manager.__class__.__name__, id=id(manager)), repr_str) def test_manager_attr(self): """Test FakedBaseManager attributes.""" assert isinstance(self.cpc_manager, FakedBaseManager) assert self.cpc_manager.hmc == self.hmc assert self.cpc_manager.parent == self.hmc assert self.cpc_manager.resource_class == FakedCpc assert self.cpc_manager.base_uri == '/api/cpcs' assert self.cpc_manager.oid_prop == 'object-id' assert self.cpc_manager.uri_prop == 'object-uri' def test_resource_attr(self): """Test FakedBaseResource attributes.""" assert isinstance(self.cpc_resource, FakedBaseResource) assert self.cpc_resource.manager == self.cpc_manager assert self.cpc_resource.properties == self.cpc1_out_props assert self.cpc_resource.oid == self.cpc1_out_props['object-id'] assert self.cpc_resource.uri == self.cpc1_out_props['object-uri'] class TestFakedActivationProfile(object): """All tests for the FakedActivationProfileManager and FakedActivationProfile classes.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.cpc1_in_props = {'name': 'cpc1'} self.resetprofile1_in_props = {'name': 'resetprofile1'} self.imageprofile1_in_props = {'name': 'imageprofile1'} self.loadprofile1_in_props = {'name': 'loadprofile1'} rd = { 'cpcs': [ { 'properties': self.cpc1_in_props, 'reset_activation_profiles': [ {'properties': self.resetprofile1_in_props}, ], 'image_activation_profiles': [ {'properties': self.imageprofile1_in_props}, ], 'load_activation_profiles': [ {'properties': self.loadprofile1_in_props}, ], }, ] } # This already uses add() of FakedActivationProfileManager: self.hmc.add_resources(rd) def test_profiles_attr(self): """Test CPC '*_activation_profiles' attributes.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] # Test reset activation profiles assert isinstance(cpc1.reset_activation_profiles, FakedActivationProfileManager) assert cpc1.reset_activation_profiles.profile_type == 'reset' assert re.match(r'/api/cpcs/[^/]+/reset-activation-profiles', cpc1.reset_activation_profiles.base_uri) # Test image activation profiles assert isinstance(cpc1.image_activation_profiles, FakedActivationProfileManager) assert cpc1.image_activation_profiles.profile_type == 'image' assert re.match(r'/api/cpcs/[^/]+/image-activation-profiles', cpc1.image_activation_profiles.base_uri) # Test load activation profiles assert isinstance(cpc1.load_activation_profiles, FakedActivationProfileManager) assert cpc1.load_activation_profiles.profile_type == 'load' assert re.match(r'/api/cpcs/[^/]+/load-activation-profiles', cpc1.load_activation_profiles.base_uri) def test_profiles_list(self): """Test list() of FakedActivationProfileManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] # Test reset activation profiles resetprofiles = cpc1.reset_activation_profiles.list() assert len(resetprofiles) == 1 resetprofile1 = resetprofiles[0] resetprofile1_out_props = self.resetprofile1_in_props.copy() resetprofile1_out_props.update({ 'name': resetprofile1.oid, 'element-uri': resetprofile1.uri, 'class': 'reset-activation-profile', 'parent': cpc1.uri, }) assert isinstance(resetprofile1, FakedActivationProfile) assert resetprofile1.properties == resetprofile1_out_props assert resetprofile1.manager == cpc1.reset_activation_profiles # Test image activation profiles imageprofiles = cpc1.image_activation_profiles.list() assert len(imageprofiles) == 1 imageprofile1 = imageprofiles[0] imageprofile1_out_props = self.imageprofile1_in_props.copy() imageprofile1_out_props.update({ 'name': imageprofile1.oid, 'element-uri': imageprofile1.uri, 'class': 'image-activation-profile', 'parent': cpc1.uri, }) assert isinstance(imageprofile1, FakedActivationProfile) assert imageprofile1.properties == imageprofile1_out_props assert imageprofile1.manager == cpc1.image_activation_profiles # Test load activation profiles loadprofiles = cpc1.load_activation_profiles.list() assert len(loadprofiles) == 1 loadprofile1 = loadprofiles[0] loadprofile1_out_props = self.loadprofile1_in_props.copy() loadprofile1_out_props.update({ 'name': loadprofile1.oid, 'element-uri': loadprofile1.uri, 'class': 'load-activation-profile', 'parent': cpc1.uri, }) assert isinstance(loadprofile1, FakedActivationProfile) assert loadprofile1.properties == loadprofile1_out_props assert loadprofile1.manager == cpc1.load_activation_profiles def test_profiles_add(self): """Test add() of FakedActivationProfileManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] resetprofiles = cpc1.reset_activation_profiles.list() assert len(resetprofiles) == 1 resetprofile2_in_props = {'name': 'resetprofile2'} # the function to be tested: new_resetprofile = cpc1.reset_activation_profiles.add( resetprofile2_in_props) resetprofiles = cpc1.reset_activation_profiles.list() assert len(resetprofiles) == 2 resetprofile2 = [p for p in resetprofiles if p.properties['name'] == # noqa: W504 resetprofile2_in_props['name']][0] assert new_resetprofile.properties == resetprofile2.properties assert new_resetprofile.manager == resetprofile2.manager resetprofile2_out_props = resetprofile2_in_props.copy() resetprofile2_out_props.update({ 'name': resetprofile2.oid, 'element-uri': resetprofile2.uri, 'class': 'reset-activation-profile', 'parent': cpc1.uri, }) assert isinstance(resetprofile2, FakedActivationProfile) assert resetprofile2.properties == resetprofile2_out_props assert resetprofile2.manager == cpc1.reset_activation_profiles # Because we know that the image and load profile managers are of the # same class, we don't need to test them. def test_profiles_remove(self): """Test remove() of FakedActivationProfileManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] resetprofiles = cpc1.reset_activation_profiles.list() resetprofile1 = resetprofiles[0] assert len(resetprofiles) == 1 # the function to be tested: cpc1.reset_activation_profiles.remove(resetprofile1.oid) resetprofiles = cpc1.reset_activation_profiles.list() assert len(resetprofiles) == 0 # Because we know that the image and load profile managers are of the # same class, we don't need to test them. class TestFakedAdapter(object): """All tests for the FakedAdapterManager and FakedAdapter classes.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.cpc1_in_props = {'name': 'cpc1'} self.adapter1_in_props = {'name': 'adapter1', 'type': 'roce'} rd = { 'cpcs': [ { 'properties': self.cpc1_in_props, 'adapters': [ {'properties': self.adapter1_in_props}, ], }, ] } # This already uses add() of FakedAdapterManager: self.hmc.add_resources(rd) def test_adapter_repr(self): """Test FakedAdapter.__repr__().""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] adapters = cpc1.adapters.list() adapter = adapters[0] repr_str = repr(adapter) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=adapter.__class__.__name__, id=id(adapter)), repr_str) def test_adapters_attr(self): """Test CPC 'adapters' attribute.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] assert isinstance(cpc1.adapters, FakedAdapterManager) assert re.match(r'/api/adapters', cpc1.adapters.base_uri) def test_adapters_list(self): """Test list() of FakedAdapterManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] # the function to be tested: adapters = cpc1.adapters.list() assert len(adapters) == 1 adapter1 = adapters[0] adapter1_out_props = self.adapter1_in_props.copy() adapter1_out_props.update({ 'object-id': adapter1.oid, 'object-uri': adapter1.uri, 'class': 'adapter', 'parent': cpc1.uri, 'status': 'active', 'adapter-family': 'roce', 'network-port-uris': [], }) assert isinstance(adapter1, FakedAdapter) assert adapter1.properties == adapter1_out_props assert adapter1.manager == cpc1.adapters # Quick check of child resources: assert isinstance(adapter1.ports, FakedPortManager) def test_adapters_add(self): """Test add() of FakedAdapterManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] adapters = cpc1.adapters.list() assert len(adapters) == 1 adapter2_in_props = {'name': 'adapter2', 'adapter-family': 'ficon'} # the function to be tested: new_adapter = cpc1.adapters.add( adapter2_in_props) adapters = cpc1.adapters.list() assert len(adapters) == 2 adapter2 = [a for a in adapters if a.properties['name'] == adapter2_in_props['name']][0] assert new_adapter.properties == adapter2.properties assert new_adapter.manager == adapter2.manager adapter2_out_props = adapter2_in_props.copy() adapter2_out_props.update({ 'object-id': adapter2.oid, 'object-uri': adapter2.uri, 'class': 'adapter', 'parent': cpc1.uri, 'status': 'active', 'storage-port-uris': [], }) assert isinstance(adapter2, FakedAdapter) assert adapter2.properties == adapter2_out_props assert adapter2.manager == cpc1.adapters def test_adapters_remove(self): """Test remove() of FakedAdapterManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] adapters = cpc1.adapters.list() adapter1 = adapters[0] assert len(adapters) == 1 # the function to be tested: cpc1.adapters.remove(adapter1.oid) adapters = cpc1.adapters.list() assert len(adapters) == 0 class TestFakedCpc(object): """All tests for the FakedCpcManager and FakedCpc classes.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.cpc1_in_props = {'name': 'cpc1'} rd = { 'cpcs': [ { 'properties': self.cpc1_in_props, }, ] } # This already uses add() of FakedCpcManager: self.hmc.add_resources(rd) def test_cpc_repr(self): """Test FakedCpc.__repr__().""" cpcs = self.hmc.cpcs.list() cpc = cpcs[0] repr_str = repr(cpc) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=cpc.__class__.__name__, id=id(cpc)), repr_str) def test_cpcs_attr(self): """Test HMC 'cpcs' attribute.""" assert isinstance(self.hmc.cpcs, FakedCpcManager) assert re.match(r'/api/cpcs', self.hmc.cpcs.base_uri) def test_cpcs_list(self): """Test list() of FakedCpcManager.""" # the function to be tested: cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] cpc1_out_props = self.cpc1_in_props.copy() cpc1_out_props.update({ 'object-id': cpc1.oid, 'object-uri': cpc1.uri, 'class': 'cpc', 'parent': None, 'dpm-enabled': False, 'is-ensemble-member': False, 'status': 'operating', }) assert isinstance(cpc1, FakedCpc) assert cpc1.properties == cpc1_out_props assert cpc1.manager == self.hmc.cpcs # Quick check of child resources: assert isinstance(cpc1.lpars, FakedLparManager) assert isinstance(cpc1.partitions, FakedPartitionManager) assert isinstance(cpc1.adapters, FakedAdapterManager) assert isinstance(cpc1.virtual_switches, FakedVirtualSwitchManager) assert isinstance(cpc1.reset_activation_profiles, FakedActivationProfileManager) assert isinstance(cpc1.image_activation_profiles, FakedActivationProfileManager) assert isinstance(cpc1.load_activation_profiles, FakedActivationProfileManager) def test_cpcs_add(self): """Test add() of FakedCpcManager.""" cpcs = self.hmc.cpcs.list() assert len(cpcs) == 1 cpc2_in_props = {'name': 'cpc2'} # the function to be tested: new_cpc = self.hmc.cpcs.add(cpc2_in_props) cpcs = self.hmc.cpcs.list() assert len(cpcs) == 2 cpc2 = [cpc for cpc in cpcs if cpc.properties['name'] == cpc2_in_props['name']][0] assert new_cpc.properties == cpc2.properties assert new_cpc.manager == cpc2.manager cpc2_out_props = cpc2_in_props.copy() cpc2_out_props.update({ 'object-id': cpc2.oid, 'object-uri': cpc2.uri, 'class': 'cpc', 'parent': None, 'dpm-enabled': False, 'is-ensemble-member': False, 'status': 'operating', }) assert isinstance(cpc2, FakedCpc) assert cpc2.properties == cpc2_out_props assert cpc2.manager == self.hmc.cpcs def test_cpcs_remove(self): """Test remove() of FakedCpcManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] assert len(cpcs) == 1 # the function to be tested: self.hmc.cpcs.remove(cpc1.oid) cpcs = self.hmc.cpcs.list() assert len(cpcs) == 0 class TestFakedHba(object): """All tests for the FakedHbaManager and FakedHba classes.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.adapter1_oid = '747-abc-12345' self.adapter1_uri = '/api/adapters/%s' % self.adapter1_oid self.port1_oid = '23' self.port1_uri = '/api/adapters/%s/storage-ports/%s' % \ (self.adapter1_oid, self.port1_oid) self.hba1_oid = '999-123-xyz' self.cpc1_in_props = {'name': 'cpc1'} self.partition1_in_props = {'name': 'partition1'} self.adapter1_in_props = { 'object-id': self.adapter1_oid, 'name': 'fcp1', 'type': 'fcp', } self.port1_in_props = { 'element-id': self.port1_oid, 'name': 'port1', } self.hba1_in_props = { 'element-id': self.hba1_oid, 'name': 'hba1', 'adapter-port-uri': self.port1_uri, } rd = { 'cpcs': [ { 'properties': self.cpc1_in_props, 'partitions': [ { 'properties': self.partition1_in_props, 'hbas': [ {'properties': self.hba1_in_props}, ], }, ], 'adapters': [ { 'properties': self.adapter1_in_props, 'ports': [ {'properties': self.port1_in_props}, ], }, ], }, ] } # This already uses add() of FakedHbaManager: self.hmc.add_resources(rd) def test_hbas_attr(self): """Test Partition 'hbas' attribute.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition1 = partitions[0] assert isinstance(partition1.hbas, FakedHbaManager) assert re.match(r'/api/partitions/[^/]+/hbas', partition1.hbas.base_uri) def test_hbas_list(self): """Test list() of FakedHbaManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition1 = partitions[0] # the function to be tested: hbas = partition1.hbas.list() assert len(hbas) == 1 hba1 = hbas[0] hba1_out_props = self.hba1_in_props.copy() hba1_out_props.update({ 'element-id': self.hba1_oid, 'element-uri': hba1.uri, 'class': 'hba', 'parent': partition1.uri, 'device-number': hba1.properties['device-number'], 'wwpn': hba1.properties['wwpn'], }) assert isinstance(hba1, FakedHba) assert hba1.properties == hba1_out_props assert hba1.manager == partition1.hbas def test_hbas_add(self): """Test add() of FakedHbaManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition1 = partitions[0] hbas = partition1.hbas.list() assert len(hbas) == 1 hba2_oid = '22-55-xy' port_uri = '/api/adapters/abc-123/storage-ports/42' hba2_in_props = { 'element-id': hba2_oid, 'name': 'hba2', 'adapter-port-uri': port_uri, 'device-number': '8001', 'wwpn': 'AFFEAFFE00008001', } # the function to be tested: new_hba = partition1.hbas.add( hba2_in_props) hbas = partition1.hbas.list() assert len(hbas) == 2 hba2 = [hba for hba in hbas if hba.properties['name'] == hba2_in_props['name']][0] assert new_hba.properties == hba2.properties assert new_hba.manager == hba2.manager hba2_out_props = hba2_in_props.copy() hba2_out_props.update({ 'element-id': hba2_oid, 'element-uri': hba2.uri, 'class': 'hba', 'parent': partition1.uri, }) assert isinstance(hba2, FakedHba) assert hba2.properties == hba2_out_props assert hba2.manager == partition1.hbas def test_hbas_remove(self): """Test remove() of FakedHbaManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition1 = partitions[0] hbas = partition1.hbas.list() hba1 = hbas[0] assert len(hbas) == 1 # the function to be tested: partition1.hbas.remove(hba1.oid) hbas = partition1.hbas.list() assert len(hbas) == 0 # TODO: Add testcases for updating 'hba-uris' parent property class TestFakedLpar(object): """All tests for the FakedLparManager and FakedLpar classes.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.cpc1_in_props = {'name': 'cpc1'} self.lpar1_in_props = {'name': 'lpar1'} rd = { 'cpcs': [ { 'properties': self.cpc1_in_props, 'lpars': [ {'properties': self.lpar1_in_props}, ], }, ] } # This already uses add() of FakedLparManager: self.hmc.add_resources(rd) def test_lpars_attr(self): """Test CPC 'lpars' attribute.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] assert isinstance(cpc1.lpars, FakedLparManager) assert re.match(r'/api/logical-partitions', cpc1.lpars.base_uri) def test_lpars_list(self): """Test list() of FakedLparManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] # the function to be tested: lpars = cpc1.lpars.list() assert len(lpars) == 1 lpar1 = lpars[0] lpar1_out_props = self.lpar1_in_props.copy() lpar1_out_props.update({ 'object-id': lpar1.oid, 'object-uri': lpar1.uri, 'class': 'logical-partition', 'parent': cpc1.uri, 'status': 'not-activated', }) assert isinstance(lpar1, FakedLpar) assert lpar1.properties == lpar1_out_props assert lpar1.manager == cpc1.lpars def test_lpars_add(self): """Test add() of FakedLparManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] lpars = cpc1.lpars.list() assert len(lpars) == 1 lpar2_in_props = {'name': 'lpar2'} # the function to be tested: new_lpar = cpc1.lpars.add( lpar2_in_props) lpars = cpc1.lpars.list() assert len(lpars) == 2 lpar2 = [p for p in lpars if p.properties['name'] == lpar2_in_props['name']][0] assert new_lpar.properties == lpar2.properties assert new_lpar.manager == lpar2.manager lpar2_out_props = lpar2_in_props.copy() lpar2_out_props.update({ 'object-id': lpar2.oid, 'object-uri': lpar2.uri, 'class': 'logical-partition', 'parent': cpc1.uri, 'status': 'not-activated', }) assert isinstance(lpar2, FakedLpar) assert lpar2.properties == lpar2_out_props assert lpar2.manager == cpc1.lpars def test_lpars_remove(self): """Test remove() of FakedLparManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] lpars = cpc1.lpars.list() lpar1 = lpars[0] assert len(lpars) == 1 # the function to be tested: cpc1.lpars.remove(lpar1.oid) lpars = cpc1.lpars.list() assert len(lpars) == 0 class TestFakedNic(object): """All tests for the FakedNicManager and FakedNic classes.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.adapter1_oid = '380-xyz-12345' self.adapter1_uri = '/api/adapters/%s' % self.adapter1_oid self.port1_oid = '32' self.port1_uri = '/api/adapters/%s/network-ports/%s' % \ (self.adapter1_oid, self.port1_oid) self.nic1_oid = 'ddd-999-123' self.cpc1_in_props = {'name': 'cpc1'} self.partition1_in_props = {'name': 'partition1'} self.nic1_in_props = { 'element-id': self.nic1_oid, 'name': 'nic1', 'network-adapter-port-uri': self.port1_uri, } self.adapter1_in_props = { 'object-id': self.adapter1_oid, 'name': 'roce1', 'type': 'roce', } self.port1_in_props = { 'element-id': self.port1_oid, 'name': 'port1', } rd = { 'cpcs': [ { 'properties': self.cpc1_in_props, 'partitions': [ { 'properties': self.partition1_in_props, 'nics': [ {'properties': self.nic1_in_props}, ], }, ], 'adapters': [ { 'properties': self.adapter1_in_props, 'ports': [ {'properties': self.port1_in_props}, ], }, ], }, ] } # This already uses add() of FakedNicManager: self.hmc.add_resources(rd) def test_nics_attr(self): """Test Partition 'nics' attribute.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition1 = partitions[0] assert isinstance(partition1.nics, FakedNicManager) assert re.match(r'/api/partitions/[^/]+/nics', partition1.nics.base_uri) def test_nics_list(self): """Test list() of FakedNicManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition1 = partitions[0] # the function to be tested: nics = partition1.nics.list() assert len(nics) == 1 nic1 = nics[0] nic1_out_props = self.nic1_in_props.copy() nic1_out_props.update({ 'element-id': self.nic1_oid, 'element-uri': nic1.uri, 'class': 'nic', 'parent': partition1.uri, 'device-number': nic1.properties['device-number'], }) assert isinstance(nic1, FakedNic) assert nic1.properties == nic1_out_props assert nic1.manager == partition1.nics def test_nics_add(self): """Test add() of FakedNicManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition1 = partitions[0] nics = partition1.nics.list() assert len(nics) == 1 nic2_oid = '77-55-ab' port_uri = '/api/adapters/abc-123/network-ports/42' nic2_in_props = { 'element-id': nic2_oid, 'name': 'nic2', 'network-adapter-port-uri': port_uri, } # the function to be tested: new_nic = partition1.nics.add( nic2_in_props) nics = partition1.nics.list() assert len(nics) == 2 nic2 = [nic for nic in nics if nic.properties['name'] == nic2_in_props['name']][0] assert new_nic.properties == nic2.properties assert new_nic.manager == nic2.manager nic2_out_props = nic2_in_props.copy() nic2_out_props.update({ 'element-id': nic2_oid, 'element-uri': nic2.uri, 'class': 'nic', 'parent': partition1.uri, 'device-number': nic2.properties['device-number'], }) assert isinstance(nic2, FakedNic) assert nic2.properties == nic2_out_props assert nic2.manager == partition1.nics def test_nics_remove(self): """Test remove() of FakedNicManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition1 = partitions[0] nics = partition1.nics.list() nic1 = nics[0] assert len(nics) == 1 # the function to be tested: partition1.nics.remove(nic1.oid) nics = partition1.nics.list() assert len(nics) == 0 # TODO: Add testcases for updating 'nic-uris' parent property class TestFakedPartition(object): """All tests for the FakedPartitionManager and FakedPartition classes.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.cpc1_in_props = {'name': 'cpc1'} self.partition1_in_props = {'name': 'partition1'} rd = { 'cpcs': [ { 'properties': self.cpc1_in_props, 'partitions': [ {'properties': self.partition1_in_props}, ], }, ] } # This already uses add() of FakedPartitionManager: self.hmc.add_resources(rd) def test_partition_repr(self): """Test FakedPartition.__repr__().""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition = partitions[0] repr_str = repr(partition) repr_str = repr_str.replace('\n', '\\n') # We check just the begin of the string: assert re.match(r'^{classname}\s+at\s+0x{id:08x}\s+\(\\n.*'. format(classname=partition.__class__.__name__, id=id(partition)), repr_str) def test_partitions_attr(self): """Test CPC 'partitions' attribute.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] assert isinstance(cpc1.partitions, FakedPartitionManager) assert re.match(r'/api/partitions', cpc1.partitions.base_uri) def test_partitions_list(self): """Test list() of FakedPartitionManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] # the function to be tested: partitions = cpc1.partitions.list() assert len(partitions) == 1 partition1 = partitions[0] partition1_out_props = self.partition1_in_props.copy() partition1_out_props.update({ 'object-id': partition1.oid, 'object-uri': partition1.uri, 'class': 'partition', 'parent': cpc1.uri, 'status': 'stopped', 'hba-uris': [], 'nic-uris': [], 'virtual-function-uris': [], }) assert isinstance(partition1, FakedPartition) assert partition1.properties == partition1_out_props assert partition1.manager == cpc1.partitions def test_partitions_add(self): """Test add() of FakedPartitionManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() assert len(partitions) == 1 partition2_in_props = {'name': 'partition2'} # the function to be tested: new_partition = cpc1.partitions.add( partition2_in_props) partitions = cpc1.partitions.list() assert len(partitions) == 2 partition2 = [p for p in partitions if p.properties['name'] == # noqa: W504 partition2_in_props['name']][0] assert new_partition.properties == partition2.properties assert new_partition.manager == partition2.manager partition2_out_props = partition2_in_props.copy() partition2_out_props.update({ 'object-id': partition2.oid, 'object-uri': partition2.uri, 'class': 'partition', 'parent': cpc1.uri, 'status': 'stopped', 'hba-uris': [], 'nic-uris': [], 'virtual-function-uris': [], }) assert isinstance(partition2, FakedPartition) assert partition2.properties == partition2_out_props assert partition2.manager == cpc1.partitions def test_partitions_remove(self): """Test remove() of FakedPartitionManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition1 = partitions[0] assert len(partitions) == 1 # the function to be tested: cpc1.partitions.remove(partition1.oid) partitions = cpc1.partitions.list() assert len(partitions) == 0 class TestFakedPort(object): """All tests for the FakedPortManager and FakedPort classes.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.cpc1_in_props = {'name': 'cpc1'} self.adapter1_in_props = {'name': 'adapter1', 'adapter-family': 'osa'} self.port1_in_props = {'name': 'port1'} rd = { 'cpcs': [ { 'properties': self.cpc1_in_props, 'adapters': [ { 'properties': self.adapter1_in_props, 'ports': [ {'properties': self.port1_in_props}, ], }, ], }, ] } # This already uses add() of FakedPortManager: self.hmc.add_resources(rd) def test_ports_attr(self): """Test Adapter 'ports' attribute.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] adapters = cpc1.adapters.list() adapter1 = adapters[0] assert isinstance(adapter1.ports, FakedPortManager) assert re.match(r'/api/adapters/[^/]+/network-ports', adapter1.ports.base_uri) def test_ports_list(self): """Test list() of FakedPortManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] adapters = cpc1.adapters.list() adapter1 = adapters[0] # the function to be tested: ports = adapter1.ports.list() assert len(ports) == 1 port1 = ports[0] port1_out_props = self.port1_in_props.copy() port1_out_props.update({ 'element-id': port1.oid, 'element-uri': port1.uri, 'class': 'network-port', 'parent': adapter1.uri, }) assert isinstance(port1, FakedPort) assert port1.properties == port1_out_props assert port1.manager == adapter1.ports def test_ports_add(self): """Test add() of FakedPortManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] adapters = cpc1.adapters.list() adapter1 = adapters[0] ports = adapter1.ports.list() assert len(ports) == 1 port2_in_props = {'name': 'port2'} # the function to be tested: new_port = adapter1.ports.add( port2_in_props) ports = adapter1.ports.list() assert len(ports) == 2 port2 = [p for p in ports if p.properties['name'] == port2_in_props['name']][0] assert new_port.properties == port2.properties assert new_port.manager == port2.manager port2_out_props = port2_in_props.copy() port2_out_props.update({ 'element-id': port2.oid, 'element-uri': port2.uri, 'class': 'network-port', 'parent': adapter1.uri, }) assert isinstance(port2, FakedPort) assert port2.properties == port2_out_props assert port2.manager == adapter1.ports def test_ports_remove(self): """Test remove() of FakedPortManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] adapters = cpc1.adapters.list() adapter1 = adapters[0] ports = adapter1.ports.list() port1 = ports[0] assert len(ports) == 1 # the function to be tested: adapter1.ports.remove(port1.oid) ports = adapter1.ports.list() assert len(ports) == 0 # TODO: Add testcases for updating 'network-port-uris' and # 'storage-port-uris' parent properties class TestFakedVirtualFunction(object): """All tests for the FakedVirtualFunctionManager and FakedVirtualFunction classes.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.cpc1_in_props = {'name': 'cpc1'} self.partition1_in_props = {'name': 'partition1'} self.virtual_function1_in_props = {'name': 'virtual_function1'} rd = { 'cpcs': [ { 'properties': self.cpc1_in_props, 'partitions': [ { 'properties': self.partition1_in_props, 'virtual_functions': [ {'properties': self.virtual_function1_in_props}, ], }, ], }, ] } # This already uses add() of FakedVirtualFunctionManager: self.hmc.add_resources(rd) def test_virtual_functions_attr(self): """Test CPC 'virtual_functions' attribute.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition1 = partitions[0] assert isinstance(partition1.virtual_functions, FakedVirtualFunctionManager) assert re.match(r'/api/partitions/[^/]+/virtual-functions', partition1.virtual_functions.base_uri) def test_virtual_functions_list(self): """Test list() of FakedVirtualFunctionManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition1 = partitions[0] # the function to be tested: virtual_functions = partition1.virtual_functions.list() assert len(virtual_functions) == 1 virtual_function1 = virtual_functions[0] virtual_function1_out_props = self.virtual_function1_in_props.copy() virtual_function1_out_props.update({ 'element-id': virtual_function1.oid, 'element-uri': virtual_function1.uri, 'class': 'virtual-function', 'parent': partition1.uri, 'device-number': virtual_function1.properties['device-number'], }) assert isinstance(virtual_function1, FakedVirtualFunction) assert virtual_function1.properties == virtual_function1_out_props assert virtual_function1.manager == partition1.virtual_functions def test_virtual_functions_add(self): """Test add() of FakedVirtualFunctionManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition1 = partitions[0] virtual_functions = partition1.virtual_functions.list() assert len(virtual_functions) == 1 virtual_function2_in_props = {'name': 'virtual_function2'} # the function to be tested: new_virtual_function = partition1.virtual_functions.add( virtual_function2_in_props) virtual_functions = partition1.virtual_functions.list() assert len(virtual_functions) == 2 virtual_function2 = [vf for vf in virtual_functions if vf.properties['name'] == # noqa: W504 virtual_function2_in_props['name']][0] assert new_virtual_function.properties == virtual_function2.properties assert new_virtual_function.manager == virtual_function2.manager virtual_function2_out_props = virtual_function2_in_props.copy() virtual_function2_out_props.update({ 'element-id': virtual_function2.oid, 'element-uri': virtual_function2.uri, 'class': 'virtual-function', 'parent': partition1.uri, 'device-number': virtual_function2.properties['device-number'], }) assert isinstance(virtual_function2, FakedVirtualFunction) assert virtual_function2.properties == virtual_function2_out_props assert virtual_function2.manager == partition1.virtual_functions def test_virtual_functions_remove(self): """Test remove() of FakedVirtualFunctionManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] partitions = cpc1.partitions.list() partition1 = partitions[0] virtual_functions = partition1.virtual_functions.list() virtual_function1 = virtual_functions[0] assert len(virtual_functions) == 1 # the function to be tested: partition1.virtual_functions.remove(virtual_function1.oid) virtual_functions = partition1.virtual_functions.list() assert len(virtual_functions) == 0 # TODO: Add testcases for updating 'virtual-function-uris' parent property class TestFakedVirtualSwitch(object): """All tests for the FakedVirtualSwitchManager and FakedVirtualSwitch classes.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.cpc1_in_props = {'name': 'cpc1'} self.virtual_switch1_in_props = {'name': 'virtual_switch1'} rd = { 'cpcs': [ { 'properties': self.cpc1_in_props, 'virtual_switches': [ {'properties': self.virtual_switch1_in_props}, ], }, ] } # This already uses add() of FakedVirtualSwitchManager: self.hmc.add_resources(rd) def test_virtual_switches_attr(self): """Test CPC 'virtual_switches' attribute.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] assert isinstance(cpc1.virtual_switches, FakedVirtualSwitchManager) assert re.match(r'/api/virtual-switches', cpc1.virtual_switches.base_uri) def test_virtual_switches_list(self): """Test list() of FakedVirtualSwitchManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] # the function to be tested: virtual_switches = cpc1.virtual_switches.list() assert len(virtual_switches) == 1 virtual_switch1 = virtual_switches[0] virtual_switch1_out_props = self.virtual_switch1_in_props.copy() virtual_switch1_out_props.update({ 'object-id': virtual_switch1.oid, 'object-uri': virtual_switch1.uri, 'class': 'virtual-switch', 'parent': cpc1.uri, 'connected-vnic-uris': [], }) assert isinstance(virtual_switch1, FakedVirtualSwitch) assert virtual_switch1.properties == virtual_switch1_out_props assert virtual_switch1.manager == cpc1.virtual_switches def test_virtual_switches_add(self): """Test add() of FakedVirtualSwitchManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] virtual_switches = cpc1.virtual_switches.list() assert len(virtual_switches) == 1 virtual_switch2_in_props = {'name': 'virtual_switch2'} # the function to be tested: new_virtual_switch = cpc1.virtual_switches.add( virtual_switch2_in_props) virtual_switches = cpc1.virtual_switches.list() assert len(virtual_switches) == 2 virtual_switch2 = [p for p in virtual_switches if p.properties['name'] == # noqa: W504 virtual_switch2_in_props['name']][0] assert new_virtual_switch.properties == virtual_switch2.properties assert new_virtual_switch.manager == virtual_switch2.manager virtual_switch2_out_props = virtual_switch2_in_props.copy() virtual_switch2_out_props.update({ 'object-id': virtual_switch2.oid, 'object-uri': virtual_switch2.uri, 'class': 'virtual-switch', 'parent': cpc1.uri, 'connected-vnic-uris': [], }) assert isinstance(virtual_switch2, FakedVirtualSwitch) assert virtual_switch2.properties == virtual_switch2_out_props assert virtual_switch2.manager == cpc1.virtual_switches def test_virtual_switches_remove(self): """Test remove() of FakedVirtualSwitchManager.""" cpcs = self.hmc.cpcs.list() cpc1 = cpcs[0] virtual_switches = cpc1.virtual_switches.list() virtual_switch1 = virtual_switches[0] assert len(virtual_switches) == 1 # the function to be tested: cpc1.virtual_switches.remove(virtual_switch1.oid) virtual_switches = cpc1.virtual_switches.list() assert len(virtual_switches) == 0 class TestFakedMetricsContext(object): """All tests for the FakedMetricsContextManager and FakedMetricsContext classes.""" def setup_method(self): self.hmc = FakedHmc('fake-hmc', '2.13.1', '1.8') self.cpc1_in_props = {'name': 'cpc1'} self.partition1_in_props = {'name': 'partition1'} rd = { 'cpcs': [ { 'properties': self.cpc1_in_props, 'partitions': [ { 'properties': self.partition1_in_props, }, ], }, ] } self.hmc.add_resources(rd) def test_metrics_contexts_attr(self): """Test faked HMC 'metrics_contexts' attribute.""" faked_hmc = self.hmc assert isinstance(faked_hmc.metrics_contexts, FakedMetricsContextManager) assert re.match(r'/api/services/metrics/context', faked_hmc.metrics_contexts.base_uri) def test_metrics_contexts_add(self): """Test add() of FakedMetricsContextManager.""" faked_hmc = self.hmc mc_in_props = { 'anticipated-frequency-seconds': 1, 'metric-groups': ['partition-usage'], } # the function to be tested: mc = faked_hmc.metrics_contexts.add(mc_in_props) assert isinstance(mc, FakedMetricsContext) assert re.match(r'/api/services/metrics/context/[^/]+', mc.uri) assert mc.manager is faked_hmc.metrics_contexts mc_props = mc_in_props.copy() mc_props.update({ 'fake-id': mc.oid, 'fake-uri': mc.uri, 'parent': None, }) assert mc.properties == mc_props def test_metrics_contexts_add_get_mg_def(self): """Test add_metric_group_definition(), get_metric_group_definition(), and get_metric_group_definition_names() of FakedMetricsContextManager.""" faked_hmc = self.hmc mc_mgr = faked_hmc.metrics_contexts mg_name = 'partition-usage' mg_def_input = FakedMetricGroupDefinition( name=mg_name, types={ 'metric-1': 'string-metric', 'metric-2': 'integer-metric', }) # Verify the initial M.G.Def names mg_def_names = mc_mgr.get_metric_group_definition_names() assert list(mg_def_names) == [] # Verify that a M.G.Def can be added mc_mgr.add_metric_group_definition(mg_def_input) # Verify the M.G.Def names after having added one mg_def_names = mc_mgr.get_metric_group_definition_names() assert list(mg_def_names) == [mg_name] # Verify that it can be retrieved mg_def = mc_mgr.get_metric_group_definition(mg_name) assert mg_def == mg_def_input # Verify that retrieving a non-existing M.G.Def fails with pytest.raises(ValueError) as exc_info: mc_mgr.get_metric_group_definition('foo') exc = exc_info.value assert re.match(r"^A metric group definition with this name does " r"not exist:.*", str(exc)) # Verify that adding an M.G.Def with an existing name fails with pytest.raises(ValueError) as exc_info: mc_mgr.add_metric_group_definition(mg_def_input) exc = exc_info.value assert re.match(r"^A metric group definition with this name already " r"exists:.*", str(exc)) # Verify that the M.G.Def names have not changed in these fails mg_def_names = mc_mgr.get_metric_group_definition_names() assert list(mg_def_names) == [mg_name] def test_metrics_contexts_add_get_metric_values(self): """Test add_metric_values(), get_metric_values(), and get_metric_values_group_names() of FakedMetricsContextManager.""" faked_hmc = self.hmc mc_mgr = faked_hmc.metrics_contexts mg_name = 'partition-usage' mo_val_input = FakedMetricObjectValues( group_name=mg_name, resource_uri='/api/partitions/fake-oid', timestamp=datetime.now(), values=[ ('metric-1', "a"), ('metric-2', 5), ]) mo_val2_input = FakedMetricObjectValues( group_name=mg_name, resource_uri='/api/partitions/fake-oid2', timestamp=datetime.now(), values=[ ('metric-1', "b"), ('metric-2', 7), ]) # Verify the initial M.O.Val group names mo_val_group_names = mc_mgr.get_metric_values_group_names() assert list(mo_val_group_names) == [] # Verify that a first M.O.Val can be added mc_mgr.add_metric_values(mo_val_input) # Verify the M.O.Val group names after having added one mo_val_group_names = mc_mgr.get_metric_values_group_names() assert list(mo_val_group_names) == [mg_name] # Verify that the M.O.Vals can be retrieved and contain the first one mo_vals = mc_mgr.get_metric_values(mg_name) assert list(mo_vals) == [mo_val_input] # Verify that retrieving a non-existing M.O.Val fails with pytest.raises(ValueError) as exc_info: mc_mgr.get_metric_values('foo') exc = exc_info.value assert re.match(r"^Metric values for this group name do not " r"exist:.*", str(exc)) # Verify that a second M.O.Val can be added for the same group name mc_mgr.add_metric_values(mo_val2_input) # Verify the M.O.Val group names after having added a second M.O.Val # for the same group name -> still just one group name mo_val_group_names = mc_mgr.get_metric_values_group_names() assert list(mo_val_group_names) == [mg_name] # Verify that the M.O.Vals can be retrieved and contain both mo_vals = mc_mgr.get_metric_values(mg_name) assert list(mo_vals) == [mo_val_input, mo_val2_input] def test_metrics_context_get_mg_defs(self): """Test get_metric_group_definitions() of FakedMetricsContext.""" faked_hmc = self.hmc mc_mgr = faked_hmc.metrics_contexts mg_name = 'partition-usage' mg_def = FakedMetricGroupDefinition( name=mg_name, types=[ ('metric-1', 'string-metric'), ('metric-2', 'integer-metric'), ]) mc_mgr.add_metric_group_definition(mg_def) mg_name2 = 'cpc-usage' mg_def2 = FakedMetricGroupDefinition( name=mg_name2, types=[ ('metric-3', 'string-metric'), ('metric-4', 'integer-metric'), ]) mc_mgr.add_metric_group_definition(mg_def2) # Test case where only one M.G.Def is tested mc_in_props = { 'anticipated-frequency-seconds': 1, 'metric-groups': [mg_name], } mc = faked_hmc.metrics_contexts.add(mc_in_props) exp_mg_defs = [mg_def] # the function to be tested: mg_defs = mc.get_metric_group_definitions() # Verify the returned M.G.Defs assert list(mg_defs) == exp_mg_defs # Test case where the default for M.G.Defs is tested mc_in_props = { 'anticipated-frequency-seconds': 1, # 'metric-groups' not specified -> default: return all } mc = faked_hmc.metrics_contexts.add(mc_in_props) exp_mg_defs = [mg_def, mg_def2] # the function to be tested: mg_defs = mc.get_metric_group_definitions() # Verify the returned M.G.Defs assert list(mg_defs) == exp_mg_defs def test_metrics_context_get_mg_infos(self): """Test get_metric_group_infos() of FakedMetricsContext.""" faked_hmc = self.hmc mc_mgr = faked_hmc.metrics_contexts mg_name = 'partition-usage' mg_def = FakedMetricGroupDefinition( name=mg_name, types=[ ('metric-1', 'string-metric'), ('metric-2', 'integer-metric'), ]) mg_info = { 'group-name': mg_name, 'metric-infos': [ { 'metric-name': 'metric-1', 'metric-type': 'string-metric', }, { 'metric-name': 'metric-2', 'metric-type': 'integer-metric', }, ], } mc_mgr.add_metric_group_definition(mg_def) mg_name2 = 'cpc-usage' mg_def2 = FakedMetricGroupDefinition( name=mg_name2, types=[ ('metric-3', 'string-metric'), ('metric-4', 'integer-metric'), ]) mg_info2 = { 'group-name': mg_name2, 'metric-infos': [ { 'metric-name': 'metric-3', 'metric-type': 'string-metric', }, { 'metric-name': 'metric-4', 'metric-type': 'integer-metric', }, ], } mc_mgr.add_metric_group_definition(mg_def2) # Test case where only one M.G.Def is tested mc_in_props = { 'anticipated-frequency-seconds': 1, 'metric-groups': [mg_name], } mc = faked_hmc.metrics_contexts.add(mc_in_props) exp_mg_infos = [mg_info] # the function to be tested: mg_infos = mc.get_metric_group_infos() # Verify the returned M.G.Defs assert list(mg_infos) == exp_mg_infos # Test case where the default for M.G.Defs is tested mc_in_props = { 'anticipated-frequency-seconds': 1, # 'metric-groups' not specified -> default: return all } mc = faked_hmc.metrics_contexts.add(mc_in_props) exp_mg_infos = [mg_info, mg_info2] # the function to be tested: mg_infos = mc.get_metric_group_infos() # Verify the returned M.G.Defs assert list(mg_infos) == exp_mg_infos def test_metrics_context_get_m_values(self): """Test get_metric_values() of FakedMetricsContext.""" faked_hmc = self.hmc mc_mgr = faked_hmc.metrics_contexts mg_name = 'partition-usage' mg_def = FakedMetricGroupDefinition( name=mg_name, types=[ ('metric-1', 'string-metric'), ('metric-2', 'integer-metric'), ]) mc_mgr.add_metric_group_definition(mg_def) mo_val_input = FakedMetricObjectValues( group_name=mg_name, resource_uri='/api/partitions/fake-oid', timestamp=datetime.now(), values=[ ('metric-1', "a"), ('metric-2', 5), ]) mc_mgr.add_metric_values(mo_val_input) mo_val2_input = FakedMetricObjectValues( group_name=mg_name, resource_uri='/api/partitions/fake-oid2', timestamp=datetime.now(), values=[ ('metric-1', "b"), ('metric-2', 7), ]) mc_mgr.add_metric_values(mo_val2_input) exp_mo_vals = mc_mgr.get_metric_values(mg_name) # Test case where only one M.G.Def is tested mc_in_props = { 'anticipated-frequency-seconds': 1, 'metric-groups': [mg_name], } mc = mc_mgr.add(mc_in_props) # the function to be tested: mv_list = mc.get_metric_values() assert len(mv_list) == 1 mv = mv_list[0] assert mv[0] == mg_name assert mv[1] == exp_mo_vals def test_metrics_context_get_m_values_response(self): """Test get_metric_values_response() of FakedMetricsContext.""" faked_hmc = self.hmc mc_mgr = faked_hmc.metrics_contexts mg_name = 'partition-usage' mg_def = FakedMetricGroupDefinition( name=mg_name, types=[ ('metric-1', 'string-metric'), ('metric-2', 'integer-metric'), ('metric-3', 'double-metric'), ]) mc_mgr.add_metric_group_definition(mg_def) mo_val_input = FakedMetricObjectValues( group_name=mg_name, resource_uri='/api/partitions/fake-oid', timestamp=datetime(2017, 9, 5, 12, 13, 10, 0), values=[ ('metric-1', "a"), ('metric-2', -5), ('metric-3', 3.1), ]) mc_mgr.add_metric_values(mo_val_input) mo_val2_input = FakedMetricObjectValues( group_name=mg_name, resource_uri='/api/partitions/fake-oid', timestamp=datetime(2017, 9, 5, 12, 13, 20, 0), values=[ ('metric-1', "0"), ('metric-2', 7), ('metric-3', -4.2), ]) mc_mgr.add_metric_values(mo_val2_input) mg_name2 = 'cpc-usage' mg_def2 = FakedMetricGroupDefinition( name=mg_name2, types=[ ('metric-4', 'double-metric'), ]) mc_mgr.add_metric_group_definition(mg_def2) mo_val3_input = FakedMetricObjectValues( group_name=mg_name2, resource_uri='/api/cpcs/fake-oid', timestamp=datetime(2017, 9, 5, 12, 13, 10, 0), values=[ ('metric-4', 7.0), ]) mc_mgr.add_metric_values(mo_val3_input) exp_mv_resp = '''"partition-usage" "/api/partitions/fake-oid" 1504613590000 "a",-5,3.1 "/api/partitions/fake-oid" 1504613600000 "0",7,-4.2 "cpc-usage" "/api/cpcs/fake-oid" 1504613590000 7.0 ''' # Test case where only one M.G.Def is tested mc_in_props = { 'anticipated-frequency-seconds': 1, 'metric-groups': [mg_name, mg_name2], } mc = mc_mgr.add(mc_in_props) # the function to be tested: mv_resp = mc.get_metric_values_response() assert mv_resp == exp_mv_resp, \ "Actual response string:\n{!r}\n" \ "Expected response string:\n{!r}\n". \ format(mv_resp, exp_mv_resp) class TestFakedMetricGroupDefinition(object): """All tests for the FakedMetricGroupDefinition class.""" def test_metric_group_definition_attr(self): """Test attributes of a FakedMetricGroupDefinition object.""" in_kwargs = { 'name': 'partition-usage', 'types': [ ('metric-1', 'string-metric'), ('metric-2', 'integer-metric'), ] } # the function to be tested: new_mgd = FakedMetricGroupDefinition(**in_kwargs) assert new_mgd.name == in_kwargs['name'] assert new_mgd.types == in_kwargs['types'] assert new_mgd.types is not in_kwargs['types'] # was copied class TestFakedMetricObjectValues(object): """All tests for the FakedMetricObjectValues class.""" def test_metric_object_values_attr(self): """Test attributes of a FakedMetricObjectValues object.""" in_kwargs = { 'group_name': 'partition-usage', 'resource_uri': '/api/partitions/fake-oid', 'timestamp': datetime.now(), 'values': [ ('metric-1', "a"), ('metric-2', 5), ] } # the function to be tested: new_mov = FakedMetricObjectValues(**in_kwargs) assert new_mov.group_name == in_kwargs['group_name'] assert new_mov.resource_uri == in_kwargs['resource_uri'] assert new_mov.timestamp == in_kwargs['timestamp'] assert new_mov.values == in_kwargs['values'] assert new_mov.values is not in_kwargs['values'] # was copied zhmcclient-0.22.0/tests/unit/tests/0000755000076500000240000000000013414661056017605 5ustar maierastaff00000000000000zhmcclient-0.22.0/tests/unit/tests/__init__.py0000644000076500000240000000000013173353641021704 0ustar maierastaff00000000000000zhmcclient-0.22.0/tests/unit/tests/common/0000755000076500000240000000000013414661056021075 5ustar maierastaff00000000000000zhmcclient-0.22.0/tests/unit/tests/common/test_utils.py0000644000076500000240000001224113364325033023642 0ustar maierastaff00000000000000# 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. """ Unit test cases for the tests/common/utils.py module. """ from __future__ import absolute_import, print_function import pytest from zhmcclient import Client from zhmcclient_mock import FakedSession from tests.common.utils import assert_resources class TestUtilsAssertResources(object): """All tests for utils.assert_resources().""" def setup_method(self): self.session = FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') self.client = Client(self.session) def add_cpcs(self): faked_cpc1 = self.session.hmc.cpcs.add({ 'object-id': 'fake-cpc1-oid', # object-uri is auto-generated 'parent': None, 'class': 'cpc', 'name': 'fake-cpc1-name', 'description': 'CPC #1', 'status': 'active', 'dpm-enabled': True, 'is-ensemble-member': False, 'iml-mode': 'dpm', }) faked_cpc2 = self.session.hmc.cpcs.add({ 'object-id': 'fake-cpc2-oid', # object-uri is auto-generated 'parent': None, 'class': 'cpc', 'name': 'fake-cpc2-name', 'description': 'CPC #2', 'status': 'active', 'dpm-enabled': False, 'is-ensemble-member': False, 'iml-mode': 'lpar', }) return [faked_cpc1, faked_cpc2] @pytest.mark.parametrize( "reverse", [False, True] ) @pytest.mark.parametrize( "props", ['all', 'some', 'empty', 'none'] ) def test_assert_resources_success(self, props, reverse): """Test assert_resources() with successful parameters.""" faked_cpcs = self.add_cpcs() cpcs = self.client.cpcs.list(full_properties=True) resources = cpcs exp_resources = faked_cpcs if reverse: exp_resources = list(reversed(exp_resources)) if props == 'all': prop_names = exp_resources[0].properties.keys() if props == 'some': prop_names = ['name', 'status'] # We change a property that is not being checked: exp_resources[0].properties['description'] = 'changed description' elif props == 'empty': prop_names = [] # No properties are checked, we change a property: exp_resources[0].properties['description'] = 'changed description' elif props == 'none': # All properties are checked. prop_names = None # Execute the code to be tested assert_resources(resources, exp_resources, prop_names) # If it comes back, our test is successful. @pytest.mark.parametrize( "reverse", [False, True] ) @pytest.mark.parametrize( "props", ['all', 'some', 'none'] ) def test_assert_resources_error_props(self, props, reverse): """Test assert_resources() with failing property checks.""" faked_cpcs = self.add_cpcs() cpcs = self.client.cpcs.list(full_properties=True) resources = cpcs exp_resources = faked_cpcs if reverse: exp_resources = list(reversed(exp_resources)) if props == 'all': prop_names = exp_resources[0].properties.keys() # We change a property that is being checked: exp_resources[0].properties['description'] = 'changed description' if props == 'some': prop_names = ['name', 'status'] # We change a property that is being checked: exp_resources[0].properties['status'] = 'not-operating' elif props == 'none': # All properties are checked. prop_names = None # We change a property that is being checked: exp_resources[0].properties['description'] = 'changed description' # Execute the code to be tested with pytest.raises(AssertionError): assert_resources(resources, exp_resources, prop_names) @pytest.mark.parametrize( "reverse", [False, True] ) def test_assert_resources_error_res(self, reverse): """Test assert_resources() with failing resource list checks.""" faked_cpcs = self.add_cpcs() cpcs = self.client.cpcs.list(full_properties=True) resources = cpcs[0:1] # trigger non-matching resource list exp_resources = faked_cpcs if reverse: exp_resources = list(reversed(exp_resources)) prop_names = exp_resources[0].properties.keys() # Execute the code to be tested with pytest.raises(AssertionError): assert_resources(resources, exp_resources, prop_names) zhmcclient-0.22.0/tests/unit/tests/common/__init__.py0000644000076500000240000000000013364325033023170 0ustar maierastaff00000000000000zhmcclient-0.22.0/tests/unit/__init__.py0000644000076500000240000000000013033142515020531 0ustar maierastaff00000000000000zhmcclient-0.22.0/tests/unit/test_example.py0000644000076500000240000001543513364325033021513 0ustar maierastaff00000000000000# 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. """ Example unit test for a user of the zhmcclient package. """ from __future__ import absolute_import, print_function import requests.packages.urllib3 import zhmcclient import zhmcclient_mock requests.packages.urllib3.disable_warnings() class TestMy(object): @staticmethod def create_session_1(): """ Demonstrate how to populate a faked session with resources defined in a resource dictionary. """ session = zhmcclient_mock.FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') session.hmc.add_resources({ 'cpcs': [ { 'properties': { # object-id is auto-generated # object-uri is auto-generated 'name': 'cpc_1', 'dpm-enabled': False, 'description': 'CPC #1', }, 'lpars': [ { 'properties': { # object-id is auto-generated # object-uri is auto-generated 'name': 'lpar_1', 'description': 'LPAR #1 in CPC #1', }, }, ], }, { 'properties': { # object-id is auto-generated # object-uri is auto-generated 'name': 'cpc_2', 'dpm-enabled': True, 'description': 'CPC #2', }, 'partitions': [ { 'properties': { # object-id is auto-generated # object-uri is auto-generated 'name': 'partition_1', 'description': 'Partition #1 in CPC #2', }, }, ], 'adapters': [ { 'properties': { # object-id is auto-generated # object-uri is auto-generated 'name': 'osa_1', 'description': 'OSA #1 in CPC #2', 'type': 'osd', }, 'ports': [ { 'properties': { # element-id is auto-generated # element-uri is auto-generated 'name': 'osa_1_port_1', 'description': 'Port #1 of OSA #1', }, }, ], }, ], }, ], }) return session @staticmethod def create_session_2(): """ Demonstrate how to populate a faked session with resources one by one. """ session = zhmcclient_mock.FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') cpc1 = session.hmc.cpcs.add({ # object-id is auto-generated # object-uri is auto-generated 'name': 'cpc_1', 'dpm-enabled': False, 'description': 'CPC #1', }) cpc1.lpars.add({ # object-id is auto-generated # object-uri is auto-generated 'name': 'lpar_1', 'description': 'LPAR #1 in CPC #1', }) cpc2 = session.hmc.cpcs.add({ # object-id is auto-generated # object-uri is auto-generated 'name': 'cpc_2', 'dpm-enabled': True, 'description': 'CPC #2', }) cpc2.partitions.add({ # object-id is auto-generated # object-uri is auto-generated 'name': 'partition_1', 'description': 'Partition #1 in CPC #2', }) adapter1 = cpc2.adapters.add({ # object-id is auto-generated # object-uri is auto-generated 'name': 'osa_1', 'description': 'OSA #1 in CPC #2', 'type': 'osd', }) adapter1.ports.add({ # element-id is auto-generated # element-uri is auto-generated 'name': 'osa_1_port_1', 'description': 'Port #1 of OSA #1', }) return session def check(self): """ Check the faked session and its faked HMC. """ assert self.session.host == 'fake-host' assert self.client.version_info() == (1, 8) cpcs = self.client.cpcs.list() assert len(cpcs) == 2 cpc1 = cpcs[0] # a CPC in classic mode assert cpc1.get_property('name') == 'cpc_1' assert not cpc1.dpm_enabled lpars = cpc1.lpars.list() assert len(lpars) == 1 lpar1 = lpars[0] assert lpar1.get_property('name') == 'lpar_1' cpc2 = cpcs[1] # a CPC in DPM mode assert cpc2.get_property('name') == 'cpc_2' assert cpc2.dpm_enabled partitions = cpc2.partitions.list() assert len(partitions) == 1 partition1 = partitions[0] assert partition1.get_property('name') == 'partition_1' adapters = cpc2.adapters.list() assert len(adapters) == 1 adapter1 = adapters[0] assert adapter1.get_property('name') == 'osa_1' ports = adapter1.ports.list() assert len(ports) == 1 port1 = ports[0] assert port1.get_property('name') == 'osa_1_port_1' def test_session_1(self): self.session = self.create_session_1() self.client = zhmcclient.Client(self.session) self.check() def test_session_2(self): self.session = self.create_session_2() self.client = zhmcclient.Client(self.session) self.check() zhmcclient-0.22.0/tests/__init__.py0000644000076500000240000000000012770240535017562 0ustar maierastaff00000000000000zhmcclient-0.22.0/tests/common/0000755000076500000240000000000013414661056016754 5ustar maierastaff00000000000000zhmcclient-0.22.0/tests/common/__init__.py0000644000076500000240000000000013364325033021047 0ustar maierastaff00000000000000zhmcclient-0.22.0/tests/common/utils.py0000644000076500000240000003660413364325033020473 0ustar maierastaff00000000000000# 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. """ Utility functions for tests. """ import sys import os import yaml import logging import zhmcclient import zhmcclient_mock # Logger names by log component LOGGER_NAMES = { 'all': '', # root logger 'api': zhmcclient.API_LOGGER_NAME, 'hmc': zhmcclient.HMC_LOGGER_NAME, } DEFAULT_LOG = 'all=warning' DEFAULT_RT_CONFIG = zhmcclient.RetryTimeoutConfig( connect_timeout=10, connect_retries=1, operation_timeout=300, status_timeout=60, ) def assert_resources(resources, exp_resources, prop_names): """ Assert that a list of resource objects is equal to an expected list of resource objects (or faked resource objects). This is done by comparing: - The resource URIs, making sure that the two lists have matching URIs. - The specified list of property names. Parameters: resources (list): List of BaseResource objects to be checked. exp_resources (list): List of BaseResource or FakedResource objects defining the expected number of objects and property values. prop_names (list): List of property names to be checked. """ # Assert the resource URIs uris = set([res.uri for res in resources]) exp_uris = set([res.uri for res in exp_resources]) assert uris == exp_uris for res in resources: # Search for the corresponding expected profile for exp_res in exp_resources: if exp_res.uri == res.uri: break # Assert the specified property names if prop_names is None: _prop_names = exp_res.properties.keys() else: _prop_names = prop_names for prop_name in _prop_names: prop_value = res.properties[prop_name] exp_prop_value = exp_res.properties[prop_name] assert prop_value == exp_prop_value def info(capsys, format_str, format_args=None): """ Print an information message during test execution that is not being captured by py.test (i.e. it is shown even when py.test is not invoked with '-s'). Parameters: capsys: The pytest 'capsys' fixture the testcase function must specify as an argument and pass to this function here. format_str (string): Percent-based format string. format_args: Argument(s) for the format string. """ if format_args is not None: msg = (format_str % format_args) else: msg = format_str with capsys.disabled(): print("Info: " + msg) class HmcCredentials(object): """ Utility class to encapsulate the data in an HMC credentials file (for the purpose of zhmcclient function tests). The HMC credentials file must be in YAML and must have entries for each CPC specify certain data about the HMC managing it, as in the following example:: cpcs: "CPC1": description: "z13 test system" contact: "Amy" hmc_host: "10.10.10.11" # required hmc_userid: "myuser1" # required hmc_password: "mypassword1" # required "CPC2": description: "z14 development system" contact: "Bob" hmc_host: "10.10.10.12" hmc_userid: "myuser2" hmc_password: "mypassword2" In the example above, any words in double quotes are data and can change, and any words without double quotes are considered keywords and must be specified as shown. "CPC1" and "CPC2" are CPC names that are used to select an entry in the file. The entry for a CPC contains data about the HMC managing that CPC, with its host, userid and password. If two CPCs are managed by the same HMC, there would be two CPC entries with the same HMC data. """ default_filepath = 'examples/hmccreds.yaml' def __init__(self, filepath=None): if filepath is None: filepath = self.default_filepath self._filepath = filepath @property def filepath(self): """ File path of this HMC credentials file. """ return self._filepath def get_cpc_items(self): """ Return a list of CPC data items from this HMC credentials file. Returns: list of dict: If the HMC credentials file could be opened and successfully be processed, returns a list of data items for all CPCs in the HMC credentials file, where each data item is a dictionary with with the following keys: * description: Short description of the CPC (optional) * contact: Contact info for the CPC (optional) * hmc_host: Hostname or IP address of HMC managing the CPC (req) * hmc_userid: Userid to log on to that HMC (required) * hmc_password: Password to log on to that HMC (required) If the HMC credentials file did not exist or could not be opened for reading, returns `None`. Raises: yaml.parser.ParserError: YAML parsing error in HMC credentials file. KeyError: Required 'cpcs' key not found in HMC credentials file. """ try: with open(self.filepath, 'r') as fp: hmccreds_data = yaml.load(fp) except IOError: return None cpc_items = hmccreds_data['cpcs'] return cpc_items def get_cpc_item(self, cpc_name): """ Return the CPC data item for a given CPC from this HMC credentials file. Parameters: cpc_name (string): CPC name used to select the corresponding HMC entry in the HMC credentials file. Returns: dict: If the HMC credentials file could be opened and successfully be processed, returns a CPC data item for the specified CPC, with the following keys: * description: Short description of the CPC (optional) * contact: Contact info for the CPC (optional) * hmc_host: Hostname or IP address of HMC managing the CPC (req) * hmc_userid: Userid to log on to that HMC (required) * hmc_password: Password to log on to that HMC (required) If the HMC credentials file did not exist or could not be opened for reading, returns `None`. Raises: yaml.parser.ParserError: YAML parsing error in HMC credentials file. KeyError: Required 'cpcs' key not found in HMC credentials file. ValueError: CPC item not found or required keys missing in CPC item. """ cpc_items = self.get_cpc_items() if cpc_items is None: return None cpc_item = cpc_items.get(cpc_name, None) if cpc_item is None: raise ValueError( "No item found for CPC {!r} in HMC credentials file {!r}". format(cpc_name, self.filepath)) # Required keys in a CPC data item in the HMC credentials file required_keys = ['hmc_host', 'hmc_userid', 'hmc_password'] # Check required keys in CPC data item: for key in required_keys: if key not in cpc_item or not cpc_item[key]: raise ValueError( "Required key {!r} missing in item for CPC {!r} in " "HMC credentials file {!r}". format(key, cpc_name, self.filepath)) return cpc_item def get_test_cpc(): """ Return the CPC name of the CPC to be tested. This is taken from the value of the 'TESTCPC' environment variable. If that variable is not set or empty, `None` is returned. Returns: string: Name of CPC to be tested, or `None` if no CPC has been defined. """ cpc_name = os.environ.get('TESTCPC', None) return cpc_name def setup_cpc(capsys, hmc_creds, fake_data, rt_config=None): """ Set up and return some objects for the CPC that is to be used for testing. This function uses the get_test_cpc() function to determine the CPC to be used for testing. If no CPC has been defined, this function sets up a faked CPC using the zhmcclient mock support. Parameters: capsys: The pytest 'capsys' fixture the testcase function must specify as an argument and pass to this functionhere. hmc_creds (HmcCredentials): HMC credentials with CPC data. fake_data (dict): Input data in case a mock environment needs to be set up. The dict has the following keys: * hmc_host (string): Hostname or IP address of the faked HMC. * hmc_name (string): HMC name of the faked HMC. * hmc_version (string): HMC version of the faked HMC. * api_version (string): API version of the faked HMC. * cpc_properties (dict): Properties for the faked CPC. 'name' must be set. rt_config (zhmcclient.RetryTimeoutConfig): Retry / timeout config to override the default values. The default values used by this function are the global defaults defined for the zhmcclient package, whereby connection retries, connection timeout, operation timeout, and status timeout have been shortened. The resulting default values should be good for most function testcases. Returns: tuple: Tuple with the objects thathave been set up: * cpc_name (string): Name of the CPC to be used (some fake name or the real name that has been set up). * session (zhmcclient.Session or zhmcclient_mock-FakedSession): zhmcclient session object (faked or real) to be used for accessing the HMC managing that CPC. * client (zhmcclient.Client): zhmcclient Client object to be used for accessing the HMC managing that CPC. * cpc (zhmcclient.Cpc): Cpc resource object representing the CPC to be used (faked or real). * faked_cpc (zhmcclient_mock.FakedCpc): FakedCpc object in case a mock environment was set up (so the caller can add resources to it), or otherwise `None`. """ cpc_name = get_test_cpc() if cpc_name is None: # No test CPC defined in the environment -> use mock support and # add a faked CPC. cpc_properties = fake_data['cpc_properties'] cpc_name = cpc_properties['name'] info(capsys, "Testing with faked CPC %r", cpc_name) session = zhmcclient_mock.FakedSession( fake_data['hmc_host'], fake_data['hmc_name'], fake_data['hmc_version'], fake_data['api_version']) faked_cpc = session.hmc.cpcs.add(cpc_properties) else: # A test CPC is defined in the environment -> use it! info(capsys, "Testing with CPC %r", cpc_name) eff_rt_config = DEFAULT_RT_CONFIG if rt_config: eff_rt_config.override_with(rt_config) cpc_item = hmc_creds.get_cpc_item(cpc_name) assert cpc_item, "HMC credentials file not found: {!r}".\ format(hmc_creds.filepath) session = zhmcclient.Session( cpc_item['hmc_host'], cpc_item['hmc_userid'], cpc_item['hmc_password'], retry_timeout_config=eff_rt_config) faked_cpc = None client = zhmcclient.Client(session) cpc = client.cpcs.find(name=cpc_name) return cpc_name, session, client, cpc, faked_cpc def print_logging(): """ Debug function that prints the relevant settings of all Python loggers that are relevant for zhmcclient. """ logger_names = LOGGER_NAMES.values() for logger_name in logger_names: logger = logging.getLogger(logger_name) print_logger(logger) def print_logger(logger): """ Debug function that prints the relevant settings of a Python logger. """ print("Debug: Logger %r:" % logger.name) print("Debug: logger level: %s (%s)" % (logger.level, logging.getLevelName(logger.level))) if not logger.handlers: print("Debug: No handlers") for handler in logger.handlers: print("Debug: Handler %s:" % type(handler)) print("Debug: handler level: %s (%s)" % (handler.level, logging.getLevelName(handler.level))) format = getattr(handler.formatter, '_fmt', None) print("Debug: handler format: %r" % format) def setup_logging(): """ Set up logging for the zhmcclient, based on the value of the ZHMC_LOG env. variable with the following value:: COMP=LEVEL[,COMP=LEVEL[,...]] Where: * ``COMP`` is one of: ``all``, ``api``, ``hmc``. * ``LEVEL`` is one of: ``error``, ``warning``, ``info``, ``debug``. If the variable is not set, this defaults to:: all=warning """ log = os.environ.get('ZHMC_LOG', None) if log is None: log = DEFAULT_LOG log_components = LOGGER_NAMES.keys() for lc in log_components: reset_logger(lc) handler = logging.StreamHandler(stream=sys.stderr) fs = '%(levelname)s %(name)s: %(message)s' handler.setFormatter(logging.Formatter(fs)) log_specs = log.split(',') for log_spec in log_specs: # ignore extra ',' at begin, end or in between if log_spec == '': continue try: log_comp, log_level = log_spec.split('=', 1) except ValueError: raise ValueError("Missing '=' in COMP=LEVEL specification " "in ZHMC_LOG variable: {}".format(log_spec)) level = getattr(logging, log_level.upper(), None) if level is None: raise ValueError("Invalid level in COMP=LEVEL specification " "in ZHMC_LOG variable: {}".format(log_spec)) if log_comp not in log_components: raise ValueError("Invalid component in COMP=LEVEL specification " "in ZHMC_LOG variable: {}".format(log_spec)) setup_logger(log_comp, handler, level) def reset_logger(log_comp): """ Reset the logger for the specified log component (unless it is the root logger) to add a NullHandler if it does not have any handlers. Having a handler prevents a log request to be propagated to the parent logger. """ name = LOGGER_NAMES[log_comp] logger = logging.getLogger(name) if name != '' and not logger.handlers: logger.addHandler(logging.NullHandler()) def setup_logger(log_comp, handler, level): """ Setup the logger for the specified log component to add the specified handler (removing a possibly present NullHandler) and to set it to the specified log level. The handler is also set to the specified log level because the default level of a handler is 0 which causes it to process all levels. """ name = LOGGER_NAMES[log_comp] logger = logging.getLogger(name) for h in logger.handlers: if isinstance(h, logging.NullHandler): logger.removeHandler(h) handler.setLevel(level) logger.addHandler(handler) logger.setLevel(level) zhmcclient-0.22.0/tests/end2end/0000755000076500000240000000000013414661056017003 5ustar maierastaff00000000000000zhmcclient-0.22.0/tests/end2end/__init__.py0000644000076500000240000000000013364325033021076 0ustar maierastaff00000000000000zhmcclient-0.22.0/tests/end2end/test_hmc_credentials_file.py0000644000076500000240000000575213364325033024544 0ustar maierastaff00000000000000# 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. """ Function tests for HMC credentials file. """ from __future__ import absolute_import, print_function import requests.packages.urllib3 import pytest import zhmcclient from tests.common.utils import HmcCredentials, info requests.packages.urllib3.disable_warnings() class TestHMCCredentialsFile(object): """ Test your HMC credentials file, if you have one at the default location. """ def setup_method(self): self.hmc_creds = HmcCredentials() def test_1_format(self, capsys): """Test the format of the HMC credentials file.""" cpc_items = self.hmc_creds.get_cpc_items() if cpc_items is None: pytest.skip("HMC credentials file not found: %r" % self.hmc_creds.filepath) return assert len(cpc_items) > 0 @pytest.mark.skip("Disabled contacting all HMCs in credentials file") def test_2_hmcs(self, capsys): """ Check out the HMCs specified in the HMC credentials file. Skip HMCs that cannot be contacted. """ cpc_items = self.hmc_creds.get_cpc_items() if cpc_items is None: pytest.skip("HMC credentials file not found: %r" % self.hmc_creds.filepath) return rt_config = zhmcclient.RetryTimeoutConfig( connect_timeout=10, connect_retries=1, ) # Check HMCs and their CPCs for cpc_name in cpc_items: cpc_item = cpc_items[cpc_name] hmc_host = cpc_item['hmc_host'] info(capsys, "Checking HMC %r for CPC %r", (hmc_host, cpc_name)) session = zhmcclient.Session( hmc_host, cpc_item['hmc_userid'], cpc_item['hmc_password'], retry_timeout_config=rt_config) client = zhmcclient.Client(session) try: session.logon() except zhmcclient.ConnectionError as exc: info(capsys, "Skipping HMC %r for CPC %r: %s", (hmc_host, cpc_name, exc)) continue cpcs = client.cpcs.list() cpc_names = [cpc.name for cpc in cpcs] if cpc_name not in cpc_names: raise AssertionError( "CPC {!r} not found in HMC {!r}.\n" "Existing CPCs: {!r}". format(cpc_name, hmc_host, cpc_names)) session.logoff() zhmcclient-0.22.0/tests/end2end/test_activation_profiles.py0000644000076500000240000001274213364325033024462 0ustar maierastaff00000000000000# 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. """ Function tests for activation profile handling. """ from __future__ import absolute_import, print_function import pytest import requests.packages.urllib3 import zhmcclient from tests.common.utils import HmcCredentials, setup_cpc, setup_logging requests.packages.urllib3.disable_warnings() class TestActivationProfiles(object): """Test activation profile handling.""" # Prefix for any names of HMC resources that are being created NAME_PREFIX = 'zhmcclient.TestActivationProfiles.' def setup_method(self): """ Set up HMC data, Session to that HMC, Client, and Cpc object. """ self.hmc_creds = HmcCredentials() self.fake_data = dict( hmc_host='fake-host', hmc_name='fake-hmc', hmc_version='2.13.1', api_version='1.8', cpc_properties={ 'object-id': 'fake-cpc1-oid', # object-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': 'CPC1', 'description': 'Fake CPC #1 (classic mode)', 'status': 'active', 'dpm-enabled': False, 'is-ensemble-member': False, 'iml-mode': 'lpar', }) self.faked_cpc_resources = { 'lpars': [ { 'properties': { 'partition-number': 0x41, 'partition-identifier': 0x41, 'name': 'LPAR1', 'status': 'operating', 'activation-mode': 'linux', 'next-activation-profile-name': 'LPAR1', 'last-used-activation-profile': 'LPAR1', }, }, { 'properties': { 'partition-number': 0x42, 'partition-identifier': 0x42, 'name': 'LPAR2', 'status': 'not-activated', 'activation-mode': 'not-set', 'next-activation-profile-name': 'LPAR2', 'last-used-activation-profile': 'LPAR2', }, }, ], 'reset_activation_profiles': [ { 'properties': { 'name': 'CPC1', 'iocds-name': 'ABC', }, }, ], 'load_activation_profiles': [ { 'properties': { 'name': 'LPAR1', 'ipl-type': 'ipltype-standard', 'ipl-address': '189AB', }, }, { 'properties': { 'name': 'LPAR2', 'ipl-type': 'ipltype-scsi', 'worldwide-port-name': '1234', 'logical-unit-number': '1234', 'boot-record-lba': '1234', 'disk-partition-id': 0, }, }, ], 'image_activation_profiles': [ { 'properties': { 'name': 'LPAR1', # TODO: Add more properties }, }, { 'properties': { 'name': 'LPAR2', # TODO: Add more properties }, }, ], } setup_logging() @pytest.mark.parametrize( "profile_type", ['reset', 'image', 'load'] ) def test_ap_lf(self, capsys, profile_type): """List and find activation profiles.""" cpc_name, session, client, cpc, faked_cpc = \ setup_cpc(capsys, self.hmc_creds, self.fake_data) if faked_cpc: faked_cpc.add_resources(self.faked_cpc_resources) ap_mgr_attr = profile_type + '_activation_profiles' ap_class = profile_type + '-activation-profile' ap_mgr = getattr(cpc, ap_mgr_attr) # Test listing activation profiles ap_list = ap_mgr.list() assert len(ap_list) >= 1 for ap in ap_list: assert isinstance(ap, zhmcclient.ActivationProfile) # Pick the last one returned ap = ap_list[-1] ap_name = ap.name # Test finding the activation profile based on its (cached) name ap_found = ap_mgr.find(name=ap_name) assert ap_found.name == ap_name # There are no other server-side filtered props besides name # Test finding the partition based on a client-side filtered prop aps_found = ap_mgr.findall(**{'class': ap_class}) assert ap_name in [ap.name for ap in aps_found] # noqa: F812 # Cleanup session.logoff() zhmcclient-0.22.0/tests/end2end/test_partition_lifecycle.py0000644000076500000240000001130413364325033024437 0ustar maierastaff00000000000000# 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. """ Function tests for partition lifecycle. """ from __future__ import absolute_import, print_function import pytest import requests.packages.urllib3 import zhmcclient from tests.common.utils import HmcCredentials, info, setup_cpc, setup_logging requests.packages.urllib3.disable_warnings() class TestPartitionLifecycle(object): """Test partition lifecycle.""" # Prefix for any names of HMC resources that are being created NAME_PREFIX = 'zhmcclient.TestPartitionLifecycle.' def setup_method(self): """ Set up HMC data, Session to that HMC, Client, and Cpc object. """ self.hmc_creds = HmcCredentials() self.fake_data = dict( hmc_host='fake-host', hmc_name='fake-hmc', hmc_version='2.13.1', api_version='1.8', cpc_properties={ 'object-id': 'fake-cpc1-oid', # object-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': 'fake-cpc1', 'description': 'Fake CPC #1 (DPM mode)', 'status': 'active', 'dpm-enabled': True, 'is-ensemble-member': False, 'iml-mode': 'dpm', }) setup_logging() def test_crud(self, capsys): """Create, read, update and delete a partition.""" cpc_name, session, client, cpc, faked_cpc = \ setup_cpc(capsys, self.hmc_creds, self.fake_data) part_name = self.NAME_PREFIX + 'test_crud.part1' # Ensure a clean starting point for this test try: part = cpc.partitions.find(name=part_name) except zhmcclient.NotFound: pass else: info(capsys, "Cleaning up partition from previous run: {!r}". format(part)) status = part.get_property('status') if status != 'stopped': part.stop() part.delete() # Test creating the partition part_input_props = { 'name': part_name, 'description': 'Dummy partition description.', 'ifl-processors': 2, 'initial-memory': 1024, 'maximum-memory': 2048, 'processor-mode': 'shared', # used for filtering 'type': 'linux', # used for filtering } part_auto_props = { 'status': 'stopped', } part = cpc.partitions.create(part_input_props) for pn in part_input_props: exp_value = part_input_props[pn] assert part.properties[pn] == exp_value, \ "Unexpected value for property {!r}".format(pn) part.pull_full_properties() for pn in part_input_props: exp_value = part_input_props[pn] assert part.properties[pn] == exp_value, \ "Unexpected value for property {!r}".format(pn) for pn in part_auto_props: exp_value = part_auto_props[pn] assert part.properties[pn] == exp_value, \ "Unexpected value for property {!r}".format(pn) # Test finding the partition based on its (cached) name p = cpc.partitions.find(name=part_name) assert p.name == part_name # Test finding the partition based on a server-side filtered prop parts = cpc.partitions.findall(type='linux') assert part_name in [p.name for p in parts] # noqa: F812 # Test finding the partition based on a client-side filtered prop parts = cpc.partitions.findall(**{'processor-mode': 'shared'}) assert part_name in [p.name for p in parts] # noqa: F812 # Test updating a property of the partition new_desc = "Updated partition description." part.update_properties(dict(description=new_desc)) assert part.properties['description'] == new_desc part.pull_full_properties() assert part.properties['description'] == new_desc # Test deleting the partition part.delete() with pytest.raises(zhmcclient.NotFound): cpc.partitions.find(name=part_name) # Cleanup session.logoff() zhmcclient-0.22.0/tests/end2end/test_storage_group.py0000644000076500000240000002570713364325033023303 0ustar maierastaff00000000000000# 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. """ Function tests for storage groups and their child resources. """ from __future__ import absolute_import, print_function import pytest import requests.packages.urllib3 import zhmcclient from tests.common.utils import HmcCredentials, info, setup_cpc requests.packages.urllib3.disable_warnings() @pytest.mark.skip('TODO: Enable and fix issues') class TestStorageGroups(object): """Test storage groups and their child resources.""" # Prefix for any names of HMC resources that are being created NAME_PREFIX = 'zhmcclient.TestStorageGroups.' def setup_method(self): """ Set up HMC data, Session to that HMC, Client, and Cpc object. """ self.hmc_creds = HmcCredentials() self.fake_data = dict( hmc_host='fake-host', hmc_name='fake-hmc', hmc_version='2.13.1', api_version='1.8', cpc_properties={ 'object-id': 'fake-cpc1-oid', # object-uri is set up automatically 'parent': None, 'class': 'cpc', 'name': 'fake-cpc1', 'description': 'Fake CPC #1 (DPM mode)', 'status': 'active', 'dpm-enabled': True, 'is-ensemble-member': False, 'iml-mode': 'dpm', 'available-features-list': [ dict(name='dpm-storage-management', state=True), ], }) # setup_logging() @classmethod def dpm_storage_management_enabled(self, cpc): """ Return boolean indicating whether the "DPM Storage Management" feature is enabled for the specified CPC. If the machine is not even aware of firmware features, it is considered disabled. """ try: dpm_sm = cpc.feature_enabled('dpm-storage-management') except ValueError: dpm_sm = False return dpm_sm def test_stogrp_crud(self, capsys): """Create, read, update and delete a storage group.""" cpc_name, session, client, cpc, faked_cpc = \ setup_cpc(capsys, self.hmc_creds, self.fake_data) if not self.dpm_storage_management_enabled(cpc): info(capsys, "DPM Storage feature not enabled or not supported; " "Skipping test_stogrp_crud() test case") return console = client.consoles.console stogrp_name = self.NAME_PREFIX + 'test_stogrp_crud.stogrp1' # Ensure clean starting point try: stogrp = console.storage_groups.find(name=stogrp_name) except zhmcclient.NotFound: pass else: info(capsys, "Cleaning up storage group from previous run: {!r}". format(stogrp)) stogrp.delete() # Test creating the storage group stogrp_input_props = { 'name': stogrp_name, 'description': 'Dummy storage group description.', 'type': 'fcp', } stogrp_auto_props = { 'shared': False, 'active': False, 'fulfillment-state': 'creating', 'adapter-count': 1, } stogrp = console.storage_groups.create(stogrp_input_props) for pn in stogrp_input_props: exp_value = stogrp_input_props[pn] assert stogrp.properties[pn] == exp_value, \ "Unexpected value for property {!r} of storage group:\n" \ "{!r}".format(pn, sorted(stogrp.properties)) stogrp.pull_full_properties() for pn in stogrp_input_props: exp_value = stogrp_input_props[pn] assert stogrp.properties[pn] == exp_value, \ "Unexpected value for property {!r} of storage group:\n" \ "{!r}".format(pn, sorted(stogrp.properties)) if not faked_cpc: for pn in stogrp_auto_props: exp_value = stogrp_auto_props[pn] assert stogrp.properties[pn] == exp_value, \ "Unexpected value for property {!r} of storage group:\n" \ "{!r}".format(pn, sorted(stogrp.properties)) # Test finding the storage group based on its (cached) name sg = console.storage_groups.find(name=stogrp_name) assert sg.name == stogrp_name # Test finding the storage group based on a server-side filtered prop stogrps = console.storage_groups.findall(type='fcp') assert stogrp_name in [sg.name for sg in stogrps] # noqa: F812 # Test finding the storage group based on a client-side filtered prop stogrps = console.storage_groups.findall(active=False) assert stogrp_name in [sg.name for sg in stogrps] # Test updating a property of the storage group new_desc = "Updated storage group description." stogrp.update_properties(dict(description=new_desc)) assert stogrp.properties['description'] == new_desc stogrp.pull_full_properties() assert stogrp.properties['description'] == new_desc # Test deleting the storage group stogrp.delete() with pytest.raises(zhmcclient.NotFound): console.storage_groups.find(name=stogrp_name) # Cleanup session.logoff() def test_stovol_crud(self, capsys): """Create, read, update and delete a storage volume in a sto.grp.""" cpc_name, session, client, cpc, faked_cpc = \ setup_cpc(capsys, self.hmc_creds, self.fake_data) if not self.dpm_storage_management_enabled(cpc): info(capsys, "DPM Storage feature not enabled or not supported; " "Skipping test_stovol_crud() test case") return console = client.consoles.console stogrp_name = self.NAME_PREFIX + 'test_stovol_crud.stogrp1' stovol_name = self.NAME_PREFIX + 'test_stovol_crud.stovol1' # Ensure clean starting point try: stogrp = console.storage_groups.find(name=stogrp_name) except zhmcclient.NotFound: pass else: info(capsys, "Cleaning up storage group from previous run: {!r}". format(stogrp)) stogrp.delete() # Create a storage group for the volume stogrp = console.storage_groups.create( dict(name=stogrp_name, type='fcp')) # Test creating a volume stovol_input_props = { 'name': stovol_name, 'description': 'Dummy storage volume description.', 'size': 100, # MB } stovol_auto_props = { 'fulfillment-state': 'creating', 'usage': 'data', } # TODO: Remove this tempfix when fixed: if True: info(capsys, "Tempfix: Volume does not support 'cpc-uri' " "property; Omitting it in Create Volume.") else: stovol_input_props['cpc-uri'] = cpc.uri stovol = stogrp.storage_volumes.create(stovol_input_props) for pn in stovol_input_props: exp_value = stovol_input_props[pn] assert stovol.properties[pn] == exp_value, \ "Unexpected value for property {!r} of storage volume:\n" \ "{!r}".format(pn, sorted(stovol.properties)) stovol.pull_full_properties() for pn in stovol_input_props: # TODO: Remove this tempfix when fixed: if pn == 'name': info(capsys, "Tempfix: Create Volume does not honor name; " "Skipping assertion of name:\n" " provided name: %r\n" " created name: %r" % (stovol_input_props[pn], stovol.properties[pn])) continue exp_value = stovol_input_props[pn] assert stovol.properties[pn] == exp_value, \ "Unexpected value for property {!r} of storage volume:\n" \ "{!r}".format(pn, sorted(stovol.properties)) if not faked_cpc: for pn in stovol_auto_props: exp_value = stovol_auto_props[pn] assert stovol.properties[pn] == exp_value, \ "Unexpected value for property {!r} of storage volume:\n" \ "{!r}".format(pn, sorted(stovol.properties)) # Test finding the storage volume based on its (cached) name sv = stogrp.storage_volumes.find(name=stovol_name) assert sv.name == stovol_name # Test finding the storage volume based on a server-side filtered prop # TODO: Remove this tempfix when fixed: try: stovols = stogrp.storage_volumes.find(usage='data') except zhmcclient.HTTPError as exc: if exc.http_status == 500: info(capsys, "Tempfix: List Volumes filtered by usage raises " "%s,%s %r; Skipping this test." % (exc.http_status, exc.reason, exc.message)) else: assert stovol_name in [sv.name for sv in stovols] # noqa: F812 # Test finding the storage group based on a client-side filtered prop # TODO: Remove this tempfix when fixed: try: stovols = stogrp.storage_volumes.findall(active=False) except zhmcclient.HTTPError as exc: if exc.http_status == 500: info(capsys, "Tempfix: List Volumes raises " "%s,%s %r; Skipping this test." % (exc.http_status, exc.reason, exc.message)) else: assert stovol_name in [sv.name for sv in stovols] # Test updating a property of the storage volume new_desc = "Updated storage volume description." stovol.update_properties(dict(description=new_desc)) assert stovol.properties['description'] == new_desc stovol.pull_full_properties() assert stovol.properties['description'] == new_desc # Test deleting the storage volume # TODO: Remove this tempfix when fixed: try: stovol.delete() except zhmcclient.HTTPError as exc: if exc.http_status == 500: info(capsys, "Tempfix: Delete Volume raises " "%s,%s %r; Skipping this test." % (exc.http_status, exc.reason, exc.message)) else: with pytest.raises(zhmcclient.NotFound): stogrp.storage_volumes.find(name=stovol_name) # Cleanup stogrp.delete() session.logoff() zhmcclient-0.22.0/.coveragerc0000644000076500000240000000071012734726233016444 0ustar maierastaff00000000000000# Config file for Python "coverage" package. # # For documentation, see: # http://coverage.readthedocs.org/en/latest/config.html [run] # Controls whether branch coverage is measured, vs. just statement coverage. # TODO: Once statement coverage gets better, enable branch coverage. branch = False # The following files are omitted in the coverage. omit = [report] # Controls whether lines without coverage are shown in the report. show_missing = True zhmcclient-0.22.0/docs/0000755000076500000240000000000013414661056015252 5ustar maierastaff00000000000000zhmcclient-0.22.0/docs/index.rst0000644000076500000240000000202013364325033017101 0ustar maierastaff00000000000000.. 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 **************************************************************************** .. _GitHub project page: https://github.com/zhmcclient/python-zhmcclient .. toctree:: :maxdepth: 2 :numbered: intro.rst tutorial.rst concepts.rst general.rst resources.rst notifications.rst mocksupport.rst development.rst appendix.rst zhmcclient-0.22.0/docs/mocksupport.rst0000644000076500000240000002055213364325033020372 0ustar maierastaff00000000000000.. 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. .. .. _`Mock support`: Mock support ============ The zhmcclient PyPI package provides unit testing support for its users via its ``zhmcclient_mock`` Python package. That package allows users of zhmcclient to easily define a faked HMC that is populated with resources as needed by the test case. The faked HMC environment is set up by creating an instance of the :class:`zhmcclient_mock.FakedSession` class instead of the :class:`zhmcclient.Session` class: .. code-block:: python import zhmcclient import zhmcclient_mock session = zhmcclient_mock.FakedSession('fake-host', 'fake-hmc', '2.13.1', '1.8') client = zhmcclient.Client(session) cpcs = client.cpcs.list() . . . Other than using a different session class, the code operates against the same zhmcclient API as before. For example, you can see in the example above that the client object is set up from the same :class:`zhmcclient.Client` class as before, and that the CPCs can be listed through the API of the client object as before. The difference is that the faked session object contains a faked HMC and does not communicate at all with an actual HMC. The faked HMC of the faked session object can be accessed via the :attr:`~zhmcclient_mock.FakedSession.hmc` attribute of the faked session object in order to populate it with resources, for example to build up an initial resource environment for a test case. The following example of a unit test case shows how an initial set of resources that is defined as a dictionary and loaded into the faked HMC using the :meth:`~zhmcclient_mock.FakedHmc.add_resources` method: .. code-block:: python import unittest import zhmcclient import zhmcclient_mock class MyTests(unittest.TestCase): def setUp(self): self.session = zhmcclient_mock.FakedSession( 'fake-host', 'fake-hmc', '2.13.1', '1.8') self.session.hmc.add_resources({ 'cpcs': [ { 'properties': { 'name': 'cpc_1', 'description': 'CPC #1', }, 'adapters': [ { 'properties': { 'name': 'osa_1', 'description': 'OSA #1', 'adapter-family': 'osa', }, 'ports': [ { 'properties': { 'name': 'osa_1_1', 'description': 'OSA #1 Port #1', }, }, ] }, ] }, ] }) self.client = zhmcclient.Client(self.session) def test_list(self): cpcs = self.client.cpcs.list() self.assertEqual(len(cpcs), 1) self.assertEqual(cpcs[0].name, 'cpc_1') In this example, the ``test_list()`` method tests the CPC list method of the zhmcclient package, but the same approach is used for testing code that uses the zhmcclient package. As an alternative to bulk-loading resources via the input dictionary, it is also possible to add resources one by one using the ``add()`` methods of the faked resource manager classes, as shown in the following example: .. code-block:: python class MyTests(unittest.TestCase): def setUp(self): self.session = zhmcclient_mock.FakedSession( 'fake-host', 'fake-hmc', '2.13.1', '1.8') cpc1 = self.session.hmc.cpcs.add({ 'name': 'cpc_1', 'description': 'CPC #1', }) adapter1 = cpc1.adapters.add({ 'name': 'osa_1', 'description': 'OSA #1', 'adapter-family': 'osa', }) port1 = adapter1.ports.add({ 'name': 'osa_1_1', 'description': 'OSA #1 Port #1', }) self.client = zhmcclient.Client(self.session) As you can see, the resources need to be added from top to bottom in the resource tree, starting at the :attr:`~zhmcclient_mock.FakedSession.hmc` attribute of the faked session object. Section :ref:`Faked HMC` describes all faked resource and manager classes that you can use to add resources that way. Section :ref:`Faked session` describes the faked session class. .. _`Faked session`: Faked session ------------- .. automodule:: zhmcclient_mock._session .. autoclass:: zhmcclient_mock.FakedSession :members: .. _`Faked HMC`: Faked HMC --------- .. automodule:: zhmcclient_mock._hmc .. autoclass:: zhmcclient_mock.InputError :members: .. autoclass:: zhmcclient_mock.FakedHmc :members: .. autoclass:: zhmcclient_mock.FakedActivationProfileManager :members: .. autoclass:: zhmcclient_mock.FakedActivationProfile :members: .. autoclass:: zhmcclient_mock.FakedAdapterManager :members: .. autoclass:: zhmcclient_mock.FakedAdapter :members: .. autoclass:: zhmcclient_mock.FakedConsoleManager :members: .. autoclass:: zhmcclient_mock.FakedConsole :members: .. autoclass:: zhmcclient_mock.FakedCpcManager :members: .. autoclass:: zhmcclient_mock.FakedCpc :members: .. autoclass:: zhmcclient_mock.FakedHbaManager :members: .. autoclass:: zhmcclient_mock.FakedHba :members: .. autoclass:: zhmcclient_mock.FakedLdapServerDefinitionManager :members: .. autoclass:: zhmcclient_mock.FakedLdapServerDefinition :members: .. autoclass:: zhmcclient_mock.FakedLparManager :members: .. autoclass:: zhmcclient_mock.FakedLpar :members: .. autoclass:: zhmcclient_mock.FakedNicManager :members: .. autoclass:: zhmcclient_mock.FakedNic :members: .. autoclass:: zhmcclient_mock.FakedPartitionManager :members: .. autoclass:: zhmcclient_mock.FakedPartition :members: .. autoclass:: zhmcclient_mock.FakedPasswordRuleManager :members: .. autoclass:: zhmcclient_mock.FakedPasswordRule :members: .. autoclass:: zhmcclient_mock.FakedPortManager :members: .. autoclass:: zhmcclient_mock.FakedPort :members: .. autoclass:: zhmcclient_mock.FakedTaskManager :members: .. autoclass:: zhmcclient_mock.FakedTask :members: .. autoclass:: zhmcclient_mock.FakedUnmanagedCpcManager :members: .. autoclass:: zhmcclient_mock.FakedUnmanagedCpc :members: .. autoclass:: zhmcclient_mock.FakedUserManager :members: .. autoclass:: zhmcclient_mock.FakedUser :members: .. autoclass:: zhmcclient_mock.FakedUserPatternManager :members: .. autoclass:: zhmcclient_mock.FakedUserPattern :members: .. autoclass:: zhmcclient_mock.FakedUserRoleManager :members: .. autoclass:: zhmcclient_mock.FakedUserRole :members: .. autoclass:: zhmcclient_mock.FakedVirtualFunctionManager :members: .. autoclass:: zhmcclient_mock.FakedVirtualFunction :members: .. autoclass:: zhmcclient_mock.FakedVirtualSwitchManager :members: .. autoclass:: zhmcclient_mock.FakedVirtualSwitch :members: .. autoclass:: zhmcclient_mock.FakedMetricsContextManager :members: .. autoclass:: zhmcclient_mock.FakedMetricsContext :members: .. autoclass:: zhmcclient_mock.FakedBaseManager :members: .. autoclass:: zhmcclient_mock.FakedBaseResource :members: .. _`URI handler`: URI handler ----------- .. automodule:: zhmcclient_mock._urihandler .. autoclass:: zhmcclient_mock.LparActivateHandler :members: get_status, post .. autoclass:: zhmcclient_mock.LparDeactivateHandler :members: get_status, post .. autoclass:: zhmcclient_mock.LparLoadHandler :members: get_status, post zhmcclient-0.22.0/docs/concepts.rst0000644000076500000240000003651113364325033017624 0ustar maierastaff00000000000000.. 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. .. .. _`Concepts`: Concepts ======== This section presents some concepts that are helpful to understand when using the zhmcclient package. .. _`Topology`: Topology -------- The following figure shows the topology of Python applications using the zhmcclient package with an :term:`HMC` and the :term:`CPCs ` managed by that HMC: .. code-block:: text +----------------------------------------+ +--------------------+ | Node 1 | | Node 2 | | | | | | +----------------+ +--------------+ | | +--------------+ | | | Python app 1 | | Python app 2 | | | | Python app 3 | | | +----------------+ +--------------+ | | +--------------+ | | | zhmcclient | | zhmcclient | | | | zhmcclient | | | | S S NR | | S NR | | | | S | | | +-v------v---^---+ +----v--^------+ | | +-----v--------+ | +----|------|---|-----------|--|---------+ +--------|-----------+ | | | | | | REST| REST| |JMS REST| |JMS REST| | | | | | | +----v------v---^-----------v--^---------------------v-----------+ | | | HMC | | | | ... resources ... | | | +-------------+------------------------------------+-------------+ | | | | +-------------+------------+ +-------------+-------------+ | | | | | CPC 1 | | CPC 2 | | | | | | ... resources ... | | ... resources ... | | | | | +--------------------------+ +---------------------------+ The Python applications can be for example the ``zhmc`` CLI (provided in the :term:`zhmccli project`), your own Python scripts using the zhmcclient API, or long-lived services that perform some function. In any case, each Python application in the figure runs in the runtime of exactly one Python process. In that Python process, exactly one instance of the zhmcclient Python package is loaded. Performing HMC operations on a particular HMC requires a :class:`~zhmcclient.Session` object (shown as ``S`` in the figure). Receiving notifications from a particular HMC requires a :class:`~zhmcclient.NotificationReceiver` object (shown as ``NR`` in the figure). For example, Python app 1 in the figure has two sessions and one notification receiver. For simplicity, the two sessions go to the same HMC in this example, but they could also go to different HMCs. Similarly, a Python app could receive notifications from more than one HMC. .. _`Multi-threading considerations`: Multi-threading considerations ------------------------------ The zhmcclient package supports the use of multi-threading in Python processes, but each :class:`~zhmcclient.Session`, :class:`~zhmcclient.Client`, and :class:`~zhmcclient.NotificationReceiver` object can be used by only one thread at a time. However, this is not verified or enforced by the zhmcclient package, so ensuring this is a responsibility of the user of the zhmcclient package. If your Python app is multi-threaded, it is recommended that each thread with a need to perform HMC operations has its own :class:`~zhmcclient.Session` object and its own :class:`~zhmcclient.Client` object, and that each thread with a need to receive HMC notifications has its own :class:`~zhmcclient.NotificationReceiver` object. These different objects can very well target the same HMC. .. _`Resource model concepts`: Resource model concepts ----------------------- The zhmcclient package provides a resource model at its API that represents exactly the resource model described in the :term:`HMC API` book. Some of these resources are located on the HMC (for example HMC users), and some on the CPCs managed by the HMC (for example the CPC itself, or partitions on the CPC). The entry points for a user of the zhmcclient API are two objects that need to be created by the user: * a :class:`~zhmcclient.Session` object. A session object represents a REST session with exactly one HMC and handles all aspects of the session, such as the credentials for automatic logon and re-logon, the retry and timeout configuration, or the logging configuration. * a :class:`~zhmcclient.Client` object. A client object is the top of the resource tree and is initialized with a :class:`~zhmcclient.Session` object (if connecting to a real HMC) or with a :class:`~zhmcclient_mock.FakedSession` object (in unit tests that work against a mocked HMC). Despite its classname, a client object really represents the HMC (real or mocked). A session that is logged on is always in the context of the HMC userid that was used for the session. That HMC userid determines what the Python application using that session object can see and what it is allowed to do. See :ref:`Setting up the HMC` for a list of access rights that are needed in order to see all resources and to perform all tasks supported by the zhmcclient package. The :term:`HMC API` book details for each HMC operation which access rights are needed in order to perform the operation. A client object is the top of the resource tree exposed by an HMC. Resources located on the HMC (e.g. HMC userids) are direct or indirect children of the client object. The CPCs managed by the HMC are direct children of the client object, and the resources located on each CPC are direct or indirect children of the :class:`~zhmcclient.Cpc` object representing the CPC. There is a strict parent-child relationship in the resource model, so that the resource model is a strict tree without any shared children. For each actual managed resource on the HMC or its managed CPCs, the zhmcclient package may provide more than one Python object representing that resource. For example, the child resources of a resource can be listed by using the :meth:`~zhmcclient.BaseManager.list` method. Each time that method is invoked, it returns a new list of Python objects representing the state of the child resources at the time the call was made. This is an important principle in the design of the zhmcclient API: Whenever a Python object representing a resource (i.e. objects of subclasses of :class:`~zhmcclient.BaseResource`) is returned to the caller of the zhmcclient API, its state represents the state of the actual managed resource at the time the call was made, but the state of the Python resource object is not automatically being updated when the state of the actual managed resource changes. As a consequence, there are multiple Python resource objects for the same actual managed resource. All Python resource objects provided by the zhmcclient package can be asked to update their state to match the current state of the actual managed resource, via the :meth:`~zhmcclient.BaseResource.pull_full_properties` method. Alternatively, a new Python resource object with the current state of the actual managed resource can be retrieved using the :meth:`~zhmcclient.BaseManager.find` method using filters on name or object ID so that only the desired single resource is returned. See :ref:`Filtering` for details. With the exception of the :class:`~zhmcclient.Client` object, Python resource objects are never created by the user of the zhmcclient package. Instead, they are always returned back to the user. Most of the time, resource objects are returned from methods such as :meth:`~zhmcclient.BaseManager.list`, :meth:`~zhmcclient.BaseManager.find` or :meth:`~zhmcclient.BaseManager.findall`. They are methods on a manager object that handles the set of child resources of a particular type within a parent resource. For example, the :class:`~zhmcclient.Client` object has a :attr:`~zhmcclient.Client.cpcs` instance attribute of type :class:`~zhmcclient.CpcManager` which handles the CPCs managed by the HMC. Invoking :meth:`~zhmcclient.CpcManager.list` returns the CPCs managed by the HMC as :class:`~zhmcclient.Cpc` resource objects. Each :class:`~zhmcclient.Cpc` object has again instance attributes for its child resources, for example its :attr:`~zhmcclient.Cpc.partitions` instance attribute of type :class:`~zhmcclient.PartitionManager` handles the set of partitions of that CPC (but not the partitions of other CPCs managed by this HMC). See :ref:`Resources` for a description of the resource model supported by the zhmcclient package. .. _`Error handling`: Error handling -------------- Errors are returned to the user by raising exceptions. All exception classes defined in the zhmcclient package are derived from :class:`zhmcclient.Error`. Exceptions may be raised that are not derived from :class:`~zhmcclient.Error`. In all cases where this is possible, this is very likely caused by programming errors of the user (incorrect type passed in, invalid value passed in, etc.). Some HTTP status code / reason code combinations returned from the HMC are silently handled by the zhmcclient package: * GET, POST, or DELETE with status 403 and reason 5: This combination means that the HMC session token has expired. It is handled by re-logon, creating a new session token, and retrying the original HMC operation. * POST with status 202: This status code means that the operation is being performed asynchronously. There are two cases for that: * If there is a response body, an asynchronous job has been started on the HMC that performs the actual operation. If ``wait_for_completion`` is ``True`` in the method that invoked the HMC operation, the method waits for completion of the job (via polling with GET on the job URI), gathering success or failure from the job results. In case of success, the job results are returned from the method. In case of failure, an :class:`~zhmcclient.HTTPError` is raised based upon the error information in the job results. * If there is no response body, the operation is performed asynchronously on the HMC, but there is no job resource that can be used to poll for completion status. This is used only for operations such as restarting the HMC. The other HTTP status / reason code combinations are forwarded to the user by means of raising :class:`~zhmcclient.HTTPError`. That exception class is modeled after the error information described in section "Error response bodies" of the :term:`HMC API` book. The exception classes defined in the zhmcclient package are described in section :ref:`Exceptions`. .. _`Filtering`: Filtering --------- The resource lookup methods on manager objects support the concept of resource filtering. This concept allows narrowing the set of returned resources based upon the matching of filter arguments. The methods that support resource filtering, are: * :meth:`~zhmcclient.BaseManager.findall` * :meth:`~zhmcclient.BaseManager.find` * :meth:`~zhmcclient.BaseManager.list` A resource is included in the result only if it matches all filter arguments (i.e. this is a logical AND between the filter arguments). A filter argument specifies a property name and a match value. Any resource property may be specified in a filter argument. The zhmcclient implementation handles them 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. For the :meth:`~zhmcclient.BaseManager.findall` and :meth:`~zhmcclient.BaseManager.find` methods, an additional optimization is implemented: 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. The match value specifies how the corresponding resource property matches: * For resource properties of type String (as per the resource's data model in the :term:`HMC API`), the match value is interpreted as a regular expression that must match the actual resource property value. The regular expression syntax used is the same as that used by the Java programming language, as specified for the ``java.util.regex.Pattern`` class (see http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html). * For resource properties of type String Enum, the match value is interpreted as an exact string that must be equal to the actual resource property value. * For resource properties of other types, the match value is interpreted as an exact value that must be equal to the actual resource property value. * If the match value is a list or a tuple, a resource matches if any item in the list or tuple matches (i.e. this is a logical OR between the list items). If a property that is specified in filter arguments does not exist on all resources that are subject to be searched, those resources that do not have the property are treated as non-matching. An example for this situation is the "card-location" property of the Adapter resource which does not exist for Hipersocket adapters. Examples: * This example uses the :meth:`~zhmcclient.BaseManager.findall` method to return those OSA adapters in cage '1234' of a given CPC, whose state is 'stand-by', 'reserved', or 'unknown': .. code-block:: python filter_args = { 'adapter-family': 'osa', 'card-location': '1234-.*', 'state': ['stand-by', 'reserved', 'unknown'], } osa_adapters = cpc.adapters.findall(**filter_args) The returned resource objects will have only a minimal set of properties. * This example uses the :meth:`~zhmcclient.AdapterManager.list` method to return the same set of OSA adapters as the previous example, but the returned resource objects have the full set of properties: .. code-block:: python osa_adapters = cpc.adapters.list(full_properties=True, filter_args=filter_args) * This example uses the :meth:`~zhmcclient.BaseManager.find` method to return the adapter with a given adapter name: .. code-block:: python adapter1 = cpc.adapters.find(name='OSA-1') The returned resource object will have only a minimal set of properties. * This example uses the :meth:`~zhmcclient.BaseManager.find` method to return the adapter with a given object ID: .. code-block:: python oid = '12345-abc...-def-67890' adapter1 = cpc.adapters.find(**{'object-id':oid}) The returned resource object will have only a minimal set of properties. zhmcclient-0.22.0/docs/intro.rst0000644000076500000240000004166313364325033017145 0ustar maierastaff00000000000000.. 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. .. .. _`Introduction`: Introduction ============ .. _`What this package provides`: What this package provides -------------------------- The zhmcclient package (also known as python-zhmcclient) 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 is used for notification about changes in the system, or about completion of asynchronous tasks started using REST. .. _`zhmc CLI`: 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 :term:`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 :term:`zhmccli package` to its dependencies. .. _`Supported environments`: Supported environments ---------------------- The zhmcclient package is supported in these environments: * Operating systems: Linux, Windows, OS-X * Python versions: 2.7, 3.4, and higher 3.x * HMC versions: 2.11.1 and higher The following table shows for each HMC version the supported HMC API version and the supported IBM Z machine generations. The corresponding LinuxONE machine generations are listed in the notes below the table: =========== =============== ====================== ========================================= HMC version HMC API version HMC API book Machine generations =========== =============== ====================== ========================================= 2.11.1 1.1 - 1.2 :term:`HMC API 2.11.1` z196 and z114 2.12.0 1.3 :term:`HMC API 2.12.0` z196 to zEC12 and z114 2.12.1 1.4 - 1.5 :term:`HMC API 2.12.1` z196 to zEC12 and z114 to zBC12 2.13.0 1.6 :term:`HMC API 2.13.0` z196 to z13 (1) and z114 to zBC12 2.13.1 1.7, 2.1 - 2.2 :term:`HMC API 2.13.1` z196 to z13 (1) and z114 to z13s (2) 2.14.0 (5) 2.20 - 2.24 :term:`HMC API 2.14.0` z196 to z14 (3) and z114 to z14-ZR1 (4) =========== =============== ====================== ========================================= 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 (5) HMC version 2.14.0 supports both the first and second wave of machines; there is no 2.14.1 numbered HMC version anymore. .. _`Installation`: Installation ------------ .. _virtual Python environment: http://docs.python-guide.org/en/latest/dev/virtualenvs/ .. _Pypi: http://pypi.python.org/ The easiest way to install the zhmcclient package is by using Pip. Pip ensures that any dependent Python packages also get installed. With Pip, there are three options for where to install a Python package and its dependent packages: * Into a `virtual Python environment`_. This is done by having the virtual Python environment active, and running the Pip install commands as shown in the following sections. This option is recommended if you intend to develop programs using the zhmcclient API, because the packages you install do not interfere with other Python projects you may have. * Into the system Python, just for the current user. This is done by not having a virtual Python environment active, and by using the ``--user`` option on the Pip install commands shown in the following sections. This option is recommended if you intend to only use the zhmc CLI, or if you are not concerned about interfering with other Python projects you may have. * Into the system Python, for all users of the system. This is done by not having a virtual Python environment active, and by using ``sudo`` on the Pip install commands shown in the following sections. Be aware that this option will replace the content of existing Python packages, e.g. when a package version is updated. Such updated packages as well as any newly installed Python packages are not known by your operating system installer, so the knowledge of your operating system installer is now out of sync with the actual set of packages in the system Python. Therefore, this approach is not recommended and you should apply this approach only after you have thought about how you would maintain these Python packages in the future. Installation of latest released version ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following command installs the latest released version of the zhmcclient package from `Pypi`_ into the currently active Python environment: .. code-block:: text $ pip install zhmcclient Installation of latest development version ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you want to install the latest development level of the zhmcclient package instead for some reason, you can install directly from the ``master`` branch of its Git repository. The following command installs the latest development level of the zhmcclient package into the currently active Python environment: .. code-block:: text $ pip install git+https://github.com/zhmcclient/python-zhmcclient.git@master Installation on a system without Internet access ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In both cases described above, Internet access is needed to access these repositories. If you want to install the zhmcclient package on a system that does not have Internet access, you can do this by first downloading the zhmcclient package and its dependent packages on a download system that does have Internet access, transferring these packages to the target system, and installing them on the target system from the downloaded packages: 1. On a system with Internet access, download the zhmcclient package and its dependent packages: .. code-block:: text [download-system]$ mkdir packages [download-system]$ cd packages [download-system]$ pip download zhmcclient Collecting zhmcclient Using cached https://files.pythonhosted.org/packages/c3/29/7f0acab22b27ff29453ac87c92a2cbec2b16014b0d32c36fcce1ca285be7/zhmcclient-0.19.0-py2.py3-none-any.whl Saved ./zhmcclient-0.19.0-py2.py3-none-any.whl Collecting stomp.py>=4.1.15 (from zhmcclient) . . . Successfully downloaded zhmcclient stomp.py pytz requests decorator six pbr docopt idna urllib3 certifi chardet [download-system]$ ls -1 certifi-2018.4.16-py2.py3-none-any.whl chardet-3.0.4-py2.py3-none-any.whl decorator-4.3.0-py2.py3-none-any.whl docopt-0.6.2.tar.gz idna-2.6-py2.py3-none-any.whl pbr-4.0.2-py2.py3-none-any.whl pytz-2018.4-py2.py3-none-any.whl requests-2.18.4-py2.py3-none-any.whl six-1.11.0-py2.py3-none-any.whl stomp.py-4.1.20.tar.gz urllib3-1.22-py2.py3-none-any.whl zhmcclient-0.19.0-py2.py3-none-any.whl 2. Transfer all downloaded package files to the target system. Note that the package files are binary files. The actual files you see in your directory may not be the same ones shown in this section, because new package versions may have been released meanwhile, and new versions may even have different dependent packages. 3. On the target system, install the zhmcclient package in a way that causes Pip not to go out to the Pypi repository on the Internet, and instead resolves its dependencies by using the packages you transferred from the download system into the current directory: .. code-block:: text [target-system]$ ls -1 certifi-2018.4.16-py2.py3-none-any.whl chardet-3.0.4-py2.py3-none-any.whl decorator-4.3.0-py2.py3-none-any.whl docopt-0.6.2.tar.gz idna-2.6-py2.py3-none-any.whl pbr-4.0.2-py2.py3-none-any.whl pytz-2018.4-py2.py3-none-any.whl requests-2.18.4-py2.py3-none-any.whl six-1.11.0-py2.py3-none-any.whl stomp.py-4.1.20.tar.gz urllib3-1.22-py2.py3-none-any.whl zhmcclient-0.19.0-py2.py3-none-any.whl [target-system]$ pip install -f . --no-index --upgrade zhmcclient-*.whl Looking in links: . Processing ./zhmcclient-0.19.0-py2.py3-none-any.whl Collecting six>=1.10.0 (from zhmcclient==0.19.0) . . . Installing collected packages: six, pytz, idna, urllib3, certifi, chardet, requests, decorator, docopt, stomp.py, pbr, zhmcclient Successfully installed certifi-2018.4.16 chardet-3.0.4 decorator-4.3.0 docopt-0.6.2 idna-2.6 pbr-4.0.2 pytz-2018.4 requests-2.18.4 six-1.11.0 stomp.py-4.1.20 urllib3-1.22 zhmcclient-0.19.0 Verification of the installation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can verify that the zhmcclient package and its dependent packages are installed correctly by importing the package into Python: .. code-block:: text $ python -c "import zhmcclient; print('ok')" ok .. _`Setting up the HMC`: Setting up the HMC ------------------ Usage of the zhmcclient package requires that the HMC in question is prepared accordingly: 1. The Web Services API must be enabled on the HMC. 2. To use all functionality provided in the zhmcclient package, the HMC user ID that will be used by the zhmcclient must be authorized for the following tasks. The description of each method of the zhmcclient package will mention its specific authorization requirements. * "Remote Restart" must be enabled on the HMC * Use of the Web Services API * Shutdown/Restart * Manage Alternate HMC * Audit and Log Management * View Security Logs * Manage LDAP Server Definitions * Manage Password Rules * Manage Users * Manage User Patterns * Manage User Roles * Manage User Templates When using CPCs in DPM mode: * Start (a CPC in DPM mode) * Stop (a CPC in DPM mode) * New Partition * Delete Partition * Partition Details * Start Partition * Stop Partition * Dump Partition * PSW Restart (a Partition) * Create HiperSockets Adapter * Delete HiperSockets Adapter * Adapter Details * Manage Adapters * Export WWPNs When using CPCs in classic mode (or ensemble mode): * Activate (an LPAR) * Deactivate (an LPAR) * Load (an LPAR) * Customize/Delete Activation Profiles * CIM Actions ExportSettingsData 3. (Optional) If desired, the HMC user ID that will be used by the zhmcclient can be restricted to accessing only certain resources managed by the HMC. To establish such a restriction, create a custom HMC user role, limit resource access for that role accordingly, and associate the HMC user ID with that role. The zhmcclient needs object-access permission for the following resources: * CPCs to be accessed For CPCs in DPM mode: * Partitions to be accessed * Adapters to be accessed For CPCs in classic mode (or ensemble mode): * LPARs to be accessed For details, see the :term:`HMC Operations Guide`. A step-by-step description for a similar use case can be found in chapter 11, section "Enabling the System z HMC to work the Pacemaker STONITH Agent", in the :term:`KVM for IBM z Systems V1.1.2 System Administration` book. .. _`Examples`: Examples -------- 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) For more example code, see the Python scripts in the `examples directory`_ of the Git repository, or the :ref:`Tutorial` section of this documentation. .. _examples directory: https://github.com/zhmcclient/python-zhmcclient/tree/master/examples .. _`Versioning`: Versioning ---------- This documentation applies to version |release| of the zhmcclient package. You can also see that version in the top left corner of this page. The zhmcclient package uses the rules of `Semantic Versioning 2.0.0`_ for its version. .. _Semantic Versioning 2.0.0: http://semver.org/spec/v2.0.0.html The package version can be accessed by programs using the ``zhmcclient.__version__`` variable [#]_: .. autodata:: zhmcclient._version.__version__ This documentation may have been built from a development level of the package. You can recognize a development version of this package by the presence of a ".devD" suffix in the version string. Development versions are pre-versions of the next assumed version that is not yet released. For example, version 0.1.2.dev25 is development pre-version #25 of the next version to be released after 0.1.1. Version 1.1.2 is an `assumed` next version, because the `actually released` next version might be 0.2.0 or even 1.0.0. .. [#] For tooling reasons, that variable is shown as ``zhmcclient._version.__version__`` in this documentation, but it should be accessed as ``zhmcclient.__version__``. .. _`Compatibility`: Compatibility ------------- In this package, compatibility is always seen from the perspective of the user of the package. Thus, a backwards compatible new version of this package means that the user can safely upgrade to that new version without encountering compatibility issues. This package uses the rules of `Semantic Versioning 2.0.0`_ for compatibility between package versions, and for :ref:`deprecations `. The public API of this package that is subject to the semantic versioning rules (and specificically to its compatibility rules) is the API described in this documentation. Violations of these compatibility rules are described in section :ref:`Change log`. .. _`Deprecations`: Deprecations ------------ Deprecated functionality is marked accordingly in this documentation and in the :ref:`Change log`, and is made visible at runtime by issuing Python warnings of type :exc:`~py:exceptions.DeprecationWarning` (see :mod:`py:warnings` for details). Since Python 2.7, :exc:`~py:exceptions.DeprecationWarning` warnings are suppressed by default. They can be shown for example in any of these ways: * by specifying the Python command line option: ``-W default`` * by invoking Python with the environment variable: ``PYTHONWARNINGS=default`` * by issuing in your Python program: .. code-block:: python warnings.filterwarnings(action='default', category=DeprecationWarning) It is recommended that users of this package run their test code with :exc:`~py:exceptions.DeprecationWarning` warnings being shown, so they become aware of any use of deprecated functionality. It is even possible to raise an exception instead of issuing a warning message upon the use of deprecated functionality, by setting the action to ``'error'`` instead of ``'default'``. .. _`Reporting issues`: Reporting issues ---------------- If you encounter any problem with this package, or if you have questions of any kind related to this package (even when they are not about a problem), please open an issue in the `zhmcclient issue tracker`_. .. _zhmcclient issue tracker: https://github.com/zhmcclient/python-zhmcclient/issues .. _`License`: License ------- This package is licensed under the `Apache 2.0 License`_. .. _Apache 2.0 License: https://raw.githubusercontent.com/zhmcclient/python-zhmcclient/master/LICENSE zhmcclient-0.22.0/docs/conf.py0000644000076500000240000005477213364325033016564 0ustar maierastaff00000000000000# -*- coding: utf-8 -*- # # Configuration file for Sphinx builds for the zhmcclient project. # # Originally created by sphinx-quickstart, but then manually maintained. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os import re import inspect from pbr.version import VersionInfo # Imports used by code for autoautosummary from sphinx.ext.autosummary import Autosummary from sphinx.ext.autosummary import get_documenter from docutils.parsers.rst import directives from sphinx.util.inspect import safe_getattr from sphinx.util import logging # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.7' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx.ext.autosummary', 'sphinx.ext.intersphinx', 'sphinx.ext.extlinks', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', # disabed, raises anexception 'sphinx.ext.ifconfig', 'sphinx_git', # requires 'sphinx-git' Python package ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. source_encoding = 'utf-8' # The master toctree document. on_rtd = os.environ.get('READTHEDOCS', None) == 'True' if on_rtd: master_doc = 'index' else: master_doc = 'docs/index' # General information about the project. project = u'zhmcclient' copyright = u'IBM' author = u'IBM Z KVM OpenStack Team' # The short description of the package. _short_description = u'Client library for IBM Z Hardware Management Console Web Services API' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # RTD builds its versions as follows: # - latest: Is triggered by merges to the master branch that do not have a tag. # - stable: Is triggered by merges to any branch that do have a tag. # As a result of this trigger logic, the master branch does not build when # a version of the package is released, but only builds upon the next merge # afterwards. The latest version can be triggered manually if that is an issue. # The short X.Y version. # Note: We use the full version in both cases (e.g. 'M.N.U' or 'M.N.U.dev0'). version = VersionInfo('zhmcclient').release_string() # The full version, including alpha/beta/rc tags. release = version # Some prints, for extra information print("conf.py: pwd: %s" % os.getcwd()) print("conf.py: zhmcclient version according to pbr: %s" % version) print("conf.py: Last 5 commits:") sys.stdout.flush() os.system('git log --decorate --oneline |head -5') print("conf.py: End of commits") sys.stdout.flush() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["README.rst", "try", "design", "tests", ".tox", ".git", "attic", "dist", "build_doc", "zhmcclient.egg-info", ".eggs"] # The reST default role (used for this markup: `text`) to use for all # documents. None means it is rendered in italic, without a link. default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for Napoleon extension --------------------------------------- # Enable support for Google style docstrings. Defaults to True. napoleon_google_docstring = True # Enable support for NumPy style docstrings. Defaults to True. napoleon_numpy_docstring = False # Include private members (like _membername). False to fall back to Sphinx’s # default behavior. Defaults to False. napoleon_include_private_with_doc = False # Include special members (like __membername__). False to fall back to Sphinx’s # default behavior. Defaults to True. napoleon_include_special_with_doc = True # Use the .. admonition:: directive for the Example and Examples sections, # instead of the .. rubric:: directive. Defaults to False. napoleon_use_admonition_for_examples = False # Use the .. admonition:: directive for Notes sections, instead of the # .. rubric:: directive. Defaults to False. napoleon_use_admonition_for_notes = False # Use the .. admonition:: directive for References sections, instead of the # .. rubric:: directive. Defaults to False. napoleon_use_admonition_for_references = False # Use the :ivar: role for instance variables, instead of the .. attribute:: # directive. Defaults to False. napoleon_use_ivar = True # Use a :param: role for each function parameter, instead of a single # :parameters: role for all the parameters. Defaults to True. napoleon_use_param = True # Use the :rtype: role for the return type, instead of inlining it with the # description. Defaults to True. napoleon_use_rtype = True # -- Options for viewcode extension --------------------------------------- # Follow alias objects that are imported from another module such as functions, # classes and attributes. As side effects, this option ... ??? # If false, ... ???. # The default is True. viewcode_import = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. # See http://www.sphinx-doc.org/en/stable/theming.html for built-in themes. html_theme = "classic" # Theme options are theme-specific and customize the look and feel of a theme # further. # See http://www.sphinx-doc.org/en/stable/theming.html for the options # available for built-in themes. html_theme_options = { } # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If not defined, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = 'ld' # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. html_extra_path = ['_extra'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' #html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value #html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. #html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = project+'_doc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). 'papersize': 'a4paper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', # Latex figure (float) alignment #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, project+'.tex', _short_description, author, 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, project, _short_description, [author], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, project, _short_description, author, project, _short_description, 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False # -- Options for autodoc extension ---------------------------------------- # For documentation, see # http://www.sphinx-doc.org/en/stable/ext/autodoc.html # Selects what content will be inserted into a class description. # The possible values are: # "class" - Only the class’ docstring is inserted. This is the default. # "both" - Both the class’ and the __init__ method’s docstring are # concatenated and inserted. # "init" - Only the __init__ method’s docstring is inserted. # In all cases, the __init__ method is still independently rendered as a # special method, e.g. when the :special-members: option is set. autoclass_content = "both" # Selects if automatically documented members are sorted alphabetically # (value 'alphabetical'), by member type (value 'groupwise') or by source # order (value 'bysource'). The default is alphabetical. autodoc_member_order = "bysource" # This value is a list of autodoc directive flags that should be automatically # applied to all autodoc directives. The supported flags are: # 'members', 'undoc-members', 'private-members', 'special-members', # 'inherited-members' and 'show-inheritance'. # If you set one of these flags in this config value, you can use a negated # form, 'no-flag', in an autodoc directive, to disable it once. autodoc_default_flags = [] # Functions imported from C modules cannot be introspected, and therefore the # signature for such functions cannot be automatically determined. However, it # is an often-used convention to put the signature into the first line of the # function’s docstring. # If this boolean value is set to True (which is the default), autodoc will # look at the first line of the docstring for functions and methods, and if it # looks like a signature, use the line as the signature and remove it from the # docstring content. autodoc_docstring_signature = True # This value contains a list of modules to be mocked up. This is useful when # some external dependencies are not met at build time and break the building # process. autodoc_mock_imports = [] # -- Options for intersphinx extension ------------------------------------ # For documentation, see # http://www.sphinx-doc.org/en/stable/ext/intersphinx.html # Defines the prefixes for intersphinx links, and the targets they resolve to. # Example RST source for 'py2' prefix: # :func:`py2:platform.dist` # # Note: The URLs apparently cannot be the same for two different IDs; otherwise # the links for one of them are not being created. A small difference # such as adding a trailing backslash is already sufficient to work # around the problem. # # Note: This mapping does not control how links to datatypes of function # parameters are generated. # TODO: Find out how the targeted Python version for auto-generated links # to datatypes of function parameters can be controlled. # intersphinx_mapping = { 'py': ('https://docs.python.org/2/', None), # agnostic to Python version 'py2': ('https://docs.python.org/2', None), # specific to Python 2 'py3': ('https://docs.python.org/3', None), # specific to Python 3 } intersphinx_cache_limit = 5 # -- Options for extlinks extension --------------------------------------- # For documentation, see # http://www.sphinx-doc.org/en/stable/ext/extlinks.html # # Defines aliases for external links that can be used as role names. # # This config value must be a dictionary of external sites, mapping unique # short alias names to a base URL and a prefix: # * key: alias-name # * value: tuple of (base-url, prefix) # # Example for the config value: # # extlinks = { # 'issue': ('https://github.com/sphinx-doc/sphinx/issues/%s', 'Issue ') # } # # The alias-name can be used as a role in links. In the example, alias name # 'issue' is used in RST as follows: # :issue:`123`. # This then translates into a link: # https://github.com/sphinx-doc/sphinx/issues/123 # where the %s in the base-url was replaced with the value between back quotes. # # The prefix plays a role only for the link caption: # * If the prefix is None, the link caption is the full URL. # * If the prefix is the empty string, the link caption is the partial URL # given in the role content ("123" in this case.) # * If the prefix is a non-empty string, the link caption is the partial URL, # prepended by the prefix. In the above example, the link caption would be # "Issue 123". # # You can also use the usual "explicit title" syntax supported by other roles # that generate links to set the caption. In this case, the prefix is not # relevant. # For example, this RST: # :issue:`this issue <123>` # results in the link caption "this issue". extlinks = { 'nbview': ('http://nbviewer.jupyter.org/github/zhmcclient/python-zhmcclient/blob/master/docs/notebooks/%s', ''), 'nbdown': ('https://github.com/zhmcclient/python-zhmcclient/raw/master/docs/notebooks/%s', '') } # -- Support for autoautosummary ---------------------------------------------- # # Idea taken from https://stackoverflow.com/a/30783465/1424462 # class AutoAutoSummary(Autosummary): """ Sphinx extension that automatically generates a table of public methods or attributes of a class, using the AutoSummary extension. (i.e. each row in the table shows the method or attribute name with a link to the full description, and a one-line brief description). Usage in RST source:: .. autoclass:: path.to.class :: .. rubric:: Methods .. autoautosummary:: path.to.class :methods: .. rubric:: Attributes .. autoautosummary:: path.to.class :attributes: .. rubric:: Details """ option_spec = { 'methods': directives.unchanged, 'attributes': directives.unchanged } option_spec.update(Autosummary.option_spec) required_arguments = 1 # Fully qualified class name def __init__(self, *args, **kwargs): self._logger = logging.getLogger(__name__) # requires Sphinx 1.6.1 self._log_prefix = "conf.py/AutoAutoSummary" self._excluded_classes = ['BaseException'] super(AutoAutoSummary, self).__init__(*args, **kwargs) def _get_members(self, class_obj, member_type, include_in_public=None): """ Return class members of the specified type. class_obj: Class object. member_type: Member type ('method' or 'attribute'). include_in_public: set/list/tuple with member names that should be included in public members in addition to the public names (those starting without underscore). Returns: tuple(public_members, all_members): Names of the class members of the specified member type (public / all). """ try: app = self.state.document.settings.env.app except AttributeError: app = None if not include_in_public: include_in_public = [] all_members = [] for member_name in dir(class_obj): try: documenter = get_documenter( app, safe_getattr(class_obj, member_name), class_obj) except AttributeError: continue if documenter.objtype == member_type: all_members.append(member_name) public_members = [x for x in all_members if x in include_in_public or not x.startswith('_')] return public_members, all_members def _get_def_class(self, class_obj, member_name): """ Return the class object in MRO order that defines a member. class_obj: Class object that exposes (but not necessarily defines) the member. I.e. starting point of the search. member_name: Name of the member (method or attribute). Returns: Class object that defines the member. """ member_obj = getattr(class_obj, member_name) for def_class_obj in inspect.getmro(class_obj): if member_name in def_class_obj.__dict__: if def_class_obj.__name__ in self._excluded_classes: return class_obj # Fall back to input class return def_class_obj self._logger.warning( "%s: Definition class not found for member %s.%s, " "defaulting to class %s", self._log_prefix, class_obj.__name__, member_name, class_obj.__name__) return class_obj # Input class is better than nothing def run(self): try: full_class_name = str(self.arguments[0]) module_name, class_name = full_class_name.rsplit('.', 1) module_obj = __import__(module_name, globals(), locals(), [class_name]) class_obj = getattr(module_obj, class_name) if 'methods' in self.options: _, methods = self._get_members( class_obj, 'method', ['__init__']) self.content = [ "~%s.%s" % ( self._get_def_class(class_obj, method).__name__, method) for method in methods if not method.startswith('_') ] elif 'attributes' in self.options: _, attributes = self._get_members(class_obj, 'attribute') self.content = [ "~%s.%s" % ( self._get_def_class(class_obj, attrib).__name__, attrib) for attrib in attributes if not attrib.startswith('_') ] except Exception as exc: self._logger.error( "%s: Internal error: %s: %s", self._log_prefix, exc.__class__.__name__, exc) finally: return super(AutoAutoSummary, self).run() def setup(app): app.add_directive('autoautosummary', AutoAutoSummary) zhmcclient-0.22.0/docs/tutorial.rst0000644000076500000240000000600313364325033017642 0ustar maierastaff00000000000000.. 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. .. .. _`Tutorial`: .. _`Tutorials`: Tutorials ========= This section contains tutorials explaining the use of the zhmcclient package. Each tutorial is a `Jupyter Notebook `_ (formerly known as IPython Notebook). In order to view a tutorial, just click on a link in the table below. This will show the tutorial in the online `Jupyter Notebook Viewer `_. ================================== =========================================== Tutorial Short description ================================== =========================================== :nbview:`01_notebook_basics.ipynb` 1: Basics about Jupyter notebooks :nbview:`02_connections.ipynb` 2: Connecting to an HMC :nbview:`03_datamodel.ipynb` 3: Basics about working with HMC resources :nbview:`04_error_handling.ipynb` 4: Error handling ================================== =========================================== Executing code in the tutorials ------------------------------- In order to execute and also modify the code in the tutorials, Jupyter Notebook needs to be installed in a Python environment, preferrably in a `virtual Python environment `_, and you need to have the notebook files locally. There are two options on how to do that: 1. Set up the development environment for zhmcclient (see :ref:`Setting up the development environment`). This will provide you with an installation of Jupyter Notebook and with all the tutorial notebooks in directory ``docs/notebooks``. To start Jupyter Notebook with all tutorial notebooks, issue from the repo work directory: .. code-block:: text $ jupyter notebook --notebook-dir=docs/notebooks If you intend to keep your changes locally, you may want to work on a copy of the ``docs/notebooks`` directory that is outside of the repo work directory. 2. Install Jupyter Notebook and the zhmcclient package into a Python environment (preferrably virtual): .. code-block:: text $ pip install jupyter zhmcclient and download the tutorial notebooks using the download button in the Jupyter Notebook Viewer (following the links in the table above). To start Jupyter Notebook with the downloaded tutorial notebooks, issue: .. code-block:: text $ jupyter notebook --notebook-dir={your-notebook-dir} zhmcclient-0.22.0/docs/_static/0000755000076500000240000000000013414661056016700 5ustar maierastaff00000000000000zhmcclient-0.22.0/docs/_static/classic.css0000644000076500000240000001043112734726233021035 0ustar maierastaff00000000000000/* * default.css_t * ~~~~~~~~~~~~~ * * Sphinx stylesheet -- default theme. * * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: sans-serif; font-size: 100%; background-color: #11303d; color: #000; margin: 0; padding: 0; } div.document { background-color: #1c4e63; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 230px; } div.body { background-color: #ffffff; color: #000000; padding: 0 20px 30px 20px; } div.footer { color: #ffffff; width: 100%; padding: 9px 0 9px 0; text-align: center; font-size: 75%; } div.footer a { color: #ffffff; text-decoration: underline; } div.related { background-color: #133f52; line-height: 30px; color: #ffffff; } div.related a { color: #ffffff; } div.sphinxsidebar { } div.sphinxsidebar h3 { font-family: 'Trebuchet MS', sans-serif; color: #ffffff; font-size: 1.4em; font-weight: normal; margin: 0; padding: 0; } div.sphinxsidebar h3 a { color: #ffffff; } div.sphinxsidebar h4 { font-family: 'Trebuchet MS', sans-serif; color: #ffffff; font-size: 1.3em; font-weight: normal; margin: 5px 0 0 0; padding: 0; } div.sphinxsidebar p { color: #ffffff; } div.sphinxsidebar p.topless { margin: 5px 10px 10px 10px; } div.sphinxsidebar ul { margin: 10px; padding: 0; color: #ffffff; } div.sphinxsidebar a { color: #98dbcc; } div.sphinxsidebar input { border: 1px solid #98dbcc; font-family: sans-serif; font-size: 1em; } /* -- hyperlink styles ------------------------------------------------------ */ a { color: #355f7c; text-decoration: none; } a:visited { color: #355f7c; text-decoration: none; } a:hover { text-decoration: underline; } /* -- body styles ----------------------------------------------------------- */ div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Trebuchet MS', sans-serif; background-color: #f2f2f2; font-weight: normal; color: #20435c; border-bottom: 1px solid #ccc; margin: 20px -20px 10px -20px; padding: 3px 0 3px 10px; } div.body h1 { margin-top: 0; font-size: 200%; } div.body h2 { font-size: 160%; } div.body h3 { font-size: 140%; } div.body h4 { font-size: 120%; } div.body h5 { font-size: 110%; } div.body h6 { font-size: 100%; } a.headerlink { color: #c60f0f; font-size: 0.8em; padding: 0 4px 0 4px; text-decoration: none; } a.headerlink:hover { background-color: #c60f0f; color: white; } div.body p, div.body dd, div.body li, div.body blockquote { text-align: justify; line-height: 130%; /* AM: Added paragraph padding. */ padding: 3px; } div.admonition p.admonition-title + p { display: inline; } div.admonition p { margin-bottom: 5px; } div.admonition pre { margin-bottom: 5px; } div.admonition ul, div.admonition ol { margin-bottom: 5px; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } div.warning { background-color: #ffe4e4; border: 1px solid #f66; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre { padding: 5px; background-color: #eeffcc; color: #333333; line-height: 120%; border: 1px solid #ac9; border-left: none; border-right: none; } code { background-color: #ecf0f3; padding: 0 1px 0 1px; /* AM: Changed font-size from 0.95em. */ font-size: 1.1em; } th { /* AM: This is used for field names such as "Parameters:". Use the inherited background instead of this one. */ /* background-color: #ede; */ } .warning code { background: #efc2c2; } .note code { background: #d6d6d6; } .viewcode-back { font-family: sans-serif; } div.viewcode-block:target { background-color: #f4debf; border-top: 1px solid #ac9; border-bottom: 1px solid #ac9; } div.code-block-caption { color: #efefef; background-color: #1c4e63; } zhmcclient-0.22.0/docs/_static/basic.css0000644000076500000240000002274212734726233020505 0ustar maierastaff00000000000000/* * basic.css * ~~~~~~~~~ * * Sphinx stylesheet -- basic theme. * * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ /* -- main layout ----------------------------------------------------------- */ div.clearer { clear: both; } /* -- relbar ---------------------------------------------------------------- */ div.related { width: 100%; font-size: 90%; } div.related h3 { display: none; } div.related ul { margin: 0; padding: 0 0 0 10px; list-style: none; } div.related li { display: inline; } div.related li.right { float: right; margin-right: 5px; } /* -- sidebar --------------------------------------------------------------- */ div.sphinxsidebarwrapper { padding: 10px 5px 0 10px; } div.sphinxsidebar { float: left; width: 230px; margin-left: -100%; font-size: 90%; } div.sphinxsidebar ul { list-style: none; } div.sphinxsidebar ul ul, div.sphinxsidebar ul.want-points { margin-left: 20px; list-style: square; } div.sphinxsidebar ul ul { margin-top: 0; margin-bottom: 0; } div.sphinxsidebar form { margin-top: 10px; } div.sphinxsidebar input { border: 1px solid #98dbcc; font-family: sans-serif; font-size: 1em; } div.sphinxsidebar #searchbox input[type="text"] { width: 170px; } div.sphinxsidebar #searchbox input[type="submit"] { width: 30px; } img { border: 0; max-width: 100%; } /* -- search page ----------------------------------------------------------- */ ul.search { margin: 10px 0 0 20px; padding: 0; } ul.search li { padding: 5px 0 5px 20px; background-image: url(file.png); background-repeat: no-repeat; background-position: 0 7px; } ul.search li a { font-weight: bold; } ul.search li div.context { color: #888; margin: 2px 0 0 30px; text-align: left; } ul.keywordmatches li.goodmatch a { font-weight: bold; } /* -- index page ------------------------------------------------------------ */ table.contentstable { width: 90%; } table.contentstable p.biglink { line-height: 150%; } a.biglink { font-size: 1.3em; } span.linkdescr { font-style: italic; padding-top: 5px; font-size: 90%; } /* -- general index --------------------------------------------------------- */ table.indextable { width: 100%; } table.indextable td { text-align: left; vertical-align: top; } table.indextable dl, table.indextable dd { margin-top: 0; margin-bottom: 0; } table.indextable tr.pcap { height: 10px; } table.indextable tr.cap { margin-top: 10px; background-color: #f2f2f2; } img.toggler { margin-right: 3px; margin-top: 3px; cursor: pointer; } div.modindex-jumpbox { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; margin: 1em 0 1em 0; padding: 0.4em; } div.genindex-jumpbox { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; margin: 1em 0 1em 0; padding: 0.4em; } /* -- general body styles --------------------------------------------------- */ a.headerlink { visibility: hidden; } h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, dt:hover > a.headerlink, caption:hover > a.headerlink, p.caption:hover > a.headerlink, div.code-block-caption:hover > a.headerlink { visibility: visible; } div.body p.caption { text-align: inherit; } div.body td { text-align: left; } .field-list ul { padding-left: 1em; } .first { margin-top: 0 !important; } p.rubric { margin-top: 30px; font-weight: bold; } img.align-left, .figure.align-left, object.align-left { clear: left; float: left; margin-right: 1em; } img.align-right, .figure.align-right, object.align-right { clear: right; float: right; margin-left: 1em; } img.align-center, .figure.align-center, object.align-center { display: block; margin-left: auto; margin-right: auto; } .align-left { text-align: left; } .align-center { text-align: center; } .align-right { text-align: right; } /* -- sidebars -------------------------------------------------------------- */ div.sidebar { margin: 0 0 0.5em 1em; border: 1px solid #ddb; padding: 7px 7px 0 7px; background-color: #ffe; width: 40%; float: right; } p.sidebar-title { font-weight: bold; } /* -- topics ---------------------------------------------------------------- */ div.topic { border: 1px solid #ccc; padding: 7px 7px 0 7px; margin: 10px 0 10px 0; } p.topic-title { font-size: 1.1em; font-weight: bold; margin-top: 10px; } /* -- admonitions ----------------------------------------------------------- */ div.admonition { margin-top: 10px; margin-bottom: 10px; padding: 7px; } div.admonition dt { font-weight: bold; } div.admonition dl { margin-bottom: 0; } p.admonition-title { margin: 0px 10px 5px 0px; font-weight: bold; } div.body p.centered { text-align: center; margin-top: 25px; } /* -- tables ---------------------------------------------------------------- */ table.docutils { border: 0; border-collapse: collapse; } table caption span.caption-number { font-style: italic; } table caption span.caption-text { } table.docutils td, table.docutils th { padding: 1px 8px 1px 5px; border-top: 0; border-left: 0; border-right: 0; border-bottom: 1px solid #aaa; } table.field-list td, table.field-list th { border: 0 !important; } table.footnote td, table.footnote th { border: 0 !important; } th { text-align: left; padding-right: 5px; } table.citation { border-left: solid 1px gray; margin-left: 1px; } table.citation td { border-bottom: none; } /* -- figures --------------------------------------------------------------- */ div.figure { margin: 0.5em; padding: 0.5em; } div.figure p.caption { padding: 0.3em; } div.figure p.caption span.caption-number { font-style: italic; } div.figure p.caption span.caption-text { } /* -- other body styles ----------------------------------------------------- */ ol.arabic { list-style: decimal; } ol.loweralpha { list-style: lower-alpha; } ol.upperalpha { list-style: upper-alpha; } ol.lowerroman { list-style: lower-roman; } ol.upperroman { list-style: upper-roman; } dl { margin-bottom: 15px; } dd p { margin-top: 0px; } dd ul, dd table { margin-bottom: 10px; } dd { margin-top: 3px; margin-bottom: 10px; margin-left: 30px; } /* AM: Added span:target to highlight targets of normal links. */ dt:target, span:target, .highlighted { background-color: #fbe54e; } dl.glossary dt { /* AM: Changed font-size from 1.1em to 1em, and font-weight from bold to normal. */ font-weight: normal; font-size: 1em; } .field-list ul { margin: 0; padding-left: 1em; } .field-list p { margin: 0; } .optional { font-size: 1.3em; } .sig-paren { font-size: larger; } .versionmodified { font-style: italic; } .system-message { background-color: #fda; padding: 5px; border: 3px solid red; } .footnote:target { background-color: #ffa; } .line-block { display: block; margin-top: 1em; margin-bottom: 1em; } .line-block .line-block { margin-top: 0; margin-bottom: 0; margin-left: 1.5em; } .guilabel, .menuselection { font-family: sans-serif; } .accelerator { text-decoration: underline; } .classifier { font-style: oblique; } abbr, acronym { border-bottom: dotted 1px; cursor: help; } /* -- code displays --------------------------------------------------------- */ pre { overflow: auto; overflow-y: hidden; /* fixes display issues on Chrome browsers */ } td.linenos pre { padding: 5px 0px; border: 0; background-color: transparent; color: #aaa; } table.highlighttable { margin-left: 0.5em; } table.highlighttable td { padding: 0 0.5em 0 0.5em; } div.code-block-caption { padding: 2px 5px; font-size: small; } div.code-block-caption code { background-color: transparent; } div.code-block-caption + div > div.highlight > pre { margin-top: 0; } div.code-block-caption span.caption-number { padding: 0.1em 0.3em; font-style: italic; } div.code-block-caption span.caption-text { } div.literal-block-wrapper { padding: 1em 1em 0; } div.literal-block-wrapper div.highlight { margin: 0; } code.descname { background-color: transparent; font-weight: bold; /* AM: Changed font-size from 1.2em. */ font-size: 1.3em; } code.descclassname { background-color: transparent; } code.xref, a code { background-color: transparent; font-weight: bold; } h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { background-color: transparent; } .viewcode-link { float: right; } .viewcode-back { float: right; font-family: sans-serif; } div.viewcode-block:target { margin: -1px -10px; padding: 0 10px; } /* -- math display ---------------------------------------------------------- */ img.math { vertical-align: middle; } div.body div.math p { text-align: center; } span.eqno { float: right; } /* -- printout stylesheet --------------------------------------------------- */ @media print { div.document, div.documentwrapper, div.bodywrapper { margin: 0 !important; width: 100%; } div.sphinxsidebar, div.related, div.footer, #top-link { display: none; } } zhmcclient-0.22.0/docs/notifications.rst0000644000076500000240000000143013046046052020645 0ustar maierastaff00000000000000.. 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. .. .. _`Notifications`: Notifications ============= .. automodule:: zhmcclient._notification .. autoclass:: zhmcclient.NotificationReceiver :members: :special-members: __str__ zhmcclient-0.22.0/docs/resources.rst0000644000076500000240000003663413364325033020026 0ustar maierastaff00000000000000.. 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. .. .. _`Resources`: Reference: Resources ==================== This section describes the resource model supported by the zhmcclient package. Note that the zhmcclient package supports only a subset of the resources described in the :term:`HMC API` book. We will grow the implemented subset over time, and if you find that a particular resource you need is missing, please open an issue in the `zhmcclient issue tracker`_. .. _zhmcclient issue tracker: https://github.com/zhmcclient/python-zhmcclient/issues See :ref:`Resource model concepts` for a description of the concepts used in representing the resource model. The resource descriptions in this section do not detail the resource properties. The description of the resource properties of a particular HMC resource type can be found in its "Data model" section in the :term:`HMC API` book. Each Python resource class mentions the corresponding HMC resource type. The data types used in these "Data model" sections are represented in Python data types according to the mapping shown in the following table: =========================== ===================== HMC API data type Python data type =========================== ===================== Boolean :class:`py:bool` Byte, Integer, Long, Short :term:`integer` Float :class:`py:float` String, String Enum :term:`unicode string` :term:`timestamp` :term:`integer` Array :class:`py:list` Object :class:`py:dict` =========================== ===================== .. _`CPCs`: CPCs ---- .. automodule:: zhmcclient._cpc .. autoclass:: zhmcclient.CpcManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.CpcManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.CpcManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.Cpc :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.Cpc :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.Cpc :attributes: .. rubric:: Details .. _`Unmanaged CPCs`: Unmanaged CPCs -------------- .. automodule:: zhmcclient._unmanaged_cpc .. autoclass:: zhmcclient.UnmanagedCpcManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.UnmanagedCpcManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.UnmanagedCpcManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.UnmanagedCpc :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.UnmanagedCpc :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.UnmanagedCpc :attributes: .. rubric:: Details .. _`Activation profiles`: Activation profiles ------------------- .. automodule:: zhmcclient._activation_profile .. autoclass:: zhmcclient.ActivationProfileManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.ActivationProfileManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.ActivationProfileManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.ActivationProfile :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.ActivationProfile :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.ActivationProfile :attributes: .. rubric:: Details .. _`LPARs`: LPARs ----- .. automodule:: zhmcclient._lpar .. autoclass:: zhmcclient.LparManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.LparManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.LparManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.Lpar :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.Lpar :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.Lpar :attributes: .. rubric:: Details .. _`Partitions`: Partitions ---------- .. automodule:: zhmcclient._partition .. autoclass:: zhmcclient.PartitionManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.PartitionManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.PartitionManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.Partition :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.Partition :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.Partition :attributes: .. rubric:: Details .. _`Adapters`: Adapters -------- .. automodule:: zhmcclient._adapter .. autoclass:: zhmcclient.AdapterManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.AdapterManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.AdapterManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.Adapter :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.Adapter :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.Adapter :attributes: .. rubric:: Details .. _`Ports`: Ports ----- .. automodule:: zhmcclient._port .. autoclass:: zhmcclient.PortManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.PortManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.PortManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.Port :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.Port :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.Port :attributes: .. rubric:: Details .. _`NICs`: NICs ---- .. automodule:: zhmcclient._nic .. autoclass:: zhmcclient.NicManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.NicManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.NicManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.Nic :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.Nic :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.Nic :attributes: .. rubric:: Details .. _`HBAs`: HBAs ---- .. automodule:: zhmcclient._hba .. autoclass:: zhmcclient.HbaManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.HbaManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.HbaManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.Hba :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.Hba :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.Hba :attributes: .. rubric:: Details .. _`Virtual Functions`: Virtual Functions ----------------- .. automodule:: zhmcclient._virtual_function .. autoclass:: zhmcclient.VirtualFunctionManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.VirtualFunctionManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.VirtualFunctionManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.VirtualFunction :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.VirtualFunction :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.VirtualFunction :attributes: .. rubric:: Details .. _`Virtual Switches`: Virtual Switches ---------------- .. automodule:: zhmcclient._virtual_switch .. autoclass:: zhmcclient.VirtualSwitchManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.VirtualSwitchManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.VirtualSwitchManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.VirtualSwitch :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.VirtualSwitch :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.VirtualSwitch :attributes: .. rubric:: Details .. _`Storage Groups`: Storage Groups ----------------- .. automodule:: zhmcclient._storage_group .. autoclass:: zhmcclient.StorageGroupManager :members: :special-members: __str__ .. autoclass:: zhmcclient.StorageGroup :members: :special-members: __str__ .. _`Storage Volumes`: Storage Volumes ----------------- .. automodule:: zhmcclient._storage_volume .. autoclass:: zhmcclient.StorageVolumeManager :members: :special-members: __str__ .. autoclass:: zhmcclient.StorageVolume :members: :special-members: __str__ .. _`Virtual Storage Resources`: Virtual Storage Resources ------------------------- .. automodule:: zhmcclient._virtual_storage_resource .. autoclass:: zhmcclient.VirtualStorageResourceManager :members: :special-members: __str__ .. autoclass:: zhmcclient.VirtualStorageResource :members: :special-members: __str__ .. _`Console`: Console ------- .. automodule:: zhmcclient._console .. autoclass:: zhmcclient.ConsoleManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.ConsoleManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.ConsoleManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.Console :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.Console :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.Console :attributes: .. rubric:: Details .. _`User`: User ---- .. automodule:: zhmcclient._user .. autoclass:: zhmcclient.UserManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.UserManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.UserManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.User :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.User :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.User :attributes: .. rubric:: Details .. _`User Role`: User Role --------- .. automodule:: zhmcclient._user_role .. autoclass:: zhmcclient.UserRoleManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.UserRoleManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.UserRoleManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.UserRole :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.UserRole :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.UserRole :attributes: .. rubric:: Details .. _`User Pattern`: User Pattern ------------ .. automodule:: zhmcclient._user_pattern .. autoclass:: zhmcclient.UserPatternManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.UserPatternManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.UserPatternManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.UserPattern :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.UserPattern :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.UserPattern :attributes: .. rubric:: Details .. _`Password Rule`: Password Rule ------------- .. automodule:: zhmcclient._password_rule .. autoclass:: zhmcclient.PasswordRuleManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.PasswordRuleManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.PasswordRuleManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.PasswordRule :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.PasswordRule :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.PasswordRule :attributes: .. rubric:: Details .. _`Task`: Task ---- .. automodule:: zhmcclient._task .. autoclass:: zhmcclient.TaskManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.TaskManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.TaskManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.Task :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.Task :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.Task :attributes: .. rubric:: Details .. _`LDAP Server Definition`: LDAP Server Definition ---------------------- .. automodule:: zhmcclient._ldap_server_definition .. autoclass:: zhmcclient.LdapServerDefinitionManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.LdapServerDefinitionManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.LdapServerDefinitionManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.LdapServerDefinition :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.LdapServerDefinition :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.LdapServerDefinition :attributes: .. rubric:: Details zhmcclient-0.22.0/docs/development.rst0000644000076500000240000005161713367665262020352 0ustar maierastaff00000000000000.. 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. .. .. _`Development`: Development =========== This section only needs to be read by developers of the zhmcclient package. People that want to make a fix or develop some extension, and people that want to test the project are also considered developers for the purpose of this section. .. _`Code of Conduct Section`: Code of Conduct --------------- Help us keep zhmcclient open and inclusive. Please read and follow our `Code of Conduct`_. .. _Code of Conduct: https://github.com/zhmcclient/python-zhmcclient/blob/master/CODE_OF_CONDUCT.md .. _`Repository`: Repository ---------- The repository for zhmcclient is on GitHub: https://github.com/zhmcclient/python-zhmcclient .. _`Setting up the development environment`: Setting up the development environment -------------------------------------- The development environment is pretty easy to set up. Besides having a supported operating system with a supported Python version (see :ref:`Supported environments`), it is recommended that you set up a `virtual Python environment`_. .. _virtual Python environment: http://docs.python-guide.org/en/latest/dev/virtualenvs/ Then, with a virtual Python environment active, clone the Git repo of this project and prepare the development environment with ``make develop``: .. code-block:: text $ git clone git@github.com:zhmcclient/python-zhmcclient.git $ cd python-zhmcclient $ make develop This will install all prerequisites the package needs to run, as well as all prerequisites that you need for development. Generally, this project uses Make to do things in the currently active Python environment. The command ``make help`` (or just ``make``) displays a list of valid Make targets and a short description of what each target does. .. _`Building the documentation`: Building the documentation -------------------------- The ReadTheDocs (RTD) site is used to publish the documentation for the zhmcclient package at http://python-zhmcclient.readthedocs.io/ This page automatically gets updated whenever the ``master`` branch of the Git repo for this package changes. In order to build the documentation locally from the Git work directory, issue: .. code-block:: text $ make builddoc The top-level document to open with a web browser will be ``build_doc/html/docs/index.html``. .. _`Testing`: Testing ------- To run unit tests in the currently active Python environment, issue one of these example variants of ``make test``: .. code-block:: text $ make test # Run all unit tests $ TESTCASES=test_resource.py make test # Run only this test source file $ TESTCASES=TestInit make test # Run only this test class $ TESTCASES="TestInit or TestSet" make test # py.test -k expressions are possible To run the unit tests and some more commands that verify the project is in good shape in all supported Python environments, use Tox: .. code-block:: text $ tox # Run all tests on all supported Python versions $ tox -e py27 # Run all tests on Python 2.7 $ tox -e py27 test_resource.py # Run only this test source file on Python 2.7 $ tox -e py27 TestInit # Run only this test class on Python 2.7 $ tox -e py27 TestInit or TestSet # py.test -k expressions are possible The positional arguments of the ``tox`` command are passed to ``py.test`` using its ``-k`` option. Invoke ``py.test --help`` for details on the expression syntax of its ``-k`` option. Running function tests against a real HMC and CPC ------------------------------------------------- The function tests (in ``tests/function/test_*.py``) can be run against a faked HMC/CPC (using the zhmcclient mock support), or against a real HMC/CPC. By default, the function tests are run against the faked HMC/CPC. To run them against a real HMC/CPC, you must: * Specify the name of the target CPC in the ``ZHMC_TEST_CPC`` environment variable. This environment variable is the control point that decides between using a real HMC/CPC and using the faked environment:: export ZHMC_TEST_CPC=S67B * Have an HMC credentials file at location ``examples/hmccreds.yaml`` that specifies the target CPC (among possibly further CPCs) in its ``cpcs`` item:: cpcs: S67B: description: "z13s in DPM mode" contact: "Joe" hmc_host: "10.11.12.13" hmc_userid: myuserid hmc_password: mypassword # ... more CPCs There is an example HMC credentials file in the repo, at ``examples/example_hmccreds.yaml``. For a description of its format, see `Format of the HMC credentials file`_. Enabling logging for function tests ----------------------------------- The function tests always log to stderr. What can be logged are the following two components: * ``api``: Calls to and returns from zhmcclient API functions (at debug level). * ``hmc``: Interactions with the HMC (i.e. HTTP requests and responses, at debug level). By default, the log component and level is set to:: all=warning meaning that all components log at warning level or higher. To set different log levels for the log components, set the ``ZHMC_LOG`` environment variable as follows:: export ZHMC_LOG=COMP=LEVEL[,COMP=LEVEL[,...]] Where: * ``COMP`` is one of: ``all``, ``api``, ``hmc``. * ``LEVEL`` is one of: ``error``, ``warning``, ``info``, ``debug``. For example, to enable logging of the zhmcclient API calls and the interactions with the HMC, use:: export ZHMC_LOG=api=debug,hmc=debug or, shorter:: export ZHMC_LOG=all=debug Format of the HMC credentials file ---------------------------------- The HMC credentials file is used for specifying real HMCs/CPCs to be used by function tests. Its syntax is YAML, and the ``cpcs`` item relevant for function testing has the following structure:: cpcs: "CPC1": description: "z13 test system" contact: "Amy" hmc_host: "10.10.10.11" # required hmc_userid: "myuser1" # required hmc_password: "mypassword1" # required "CPC2": description: "z14 development system" contact: "Bob" hmc_host: "10.10.10.12" hmc_userid: "myuser2" hmc_password: "mypassword2" In the example above, any words in double quotes are data and can change, and any words without double quotes are considered keywords and must be specified as shown. "CPC1" and "CPC2" are CPC names that are used to select an entry in the file. The entry for a CPC contains data about the HMC managing that CPC, with its host, userid and password. If two CPCs are managed by the same HMC, there would be two CPC entries with the same HMC data. .. _`Contributing`: Contributing ------------ Third party contributions to this project are welcome! In order to contribute, create a `Git pull request`_, considering this: .. _Git pull request: https://help.github.com/articles/using-pull-requests/ * Test is required. * Each commit should only contain one "logical" change. * A "logical" change should be put into one commit, and not split over multiple commits. * Large new features should be split into stages. * The commit message should not only summarize what you have done, but explain why the change is useful. * The commit message must follow the format explained below. What comprises a "logical" change is subject to sound judgement. Sometimes, it makes sense to produce a set of commits for a feature (even if not large). For example, a first commit may introduce a (presumably) compatible API change without exploitation of that feature. With only this commit applied, it should be demonstrable that everything is still working as before. The next commit may be the exploitation of the feature in other components. For further discussion of good and bad practices regarding commits, see: * `OpenStack Git Commit Good Practice`_ * `How to Get Your Change Into the Linux Kernel`_ .. _OpenStack Git Commit Good Practice: https://wiki.openstack.org/wiki/GitCommitMessages .. _How to Get Your Change Into the Linux Kernel: https://www.kernel.org/doc/Documentation/process/submitting-patches.rst .. _`Format of commit messages`: Format of commit messages ------------------------- A commit message must start with a short summary line, followed by a blank line. Optionally, the summary line may start with an identifier that helps identifying the type of change or the component that is affected, followed by a colon. It can include a more detailed description after the summary line. This is where you explain why the change was done, and summarize what was done. It must end with the DCO (Developer Certificate of Origin) sign-off line in the format shown in the example below, using your name and a valid email address of yours. The DCO sign-off line certifies that you followed the rules stated in `DCO 1.1`_. In short, you certify that you wrote the patch or otherwise have the right to pass it on as an open-source patch. .. _DCO 1.1: https://raw.githubusercontent.com/zhmcclient/python-zhmcclient/master/DCO1.1.txt We use `GitCop`_ during creation of a pull request to check whether the commit messages in the pull request comply to this format. If the commit messages do not comply, GitCop will add a comment to the pull request with a description of what was wrong. .. _GitCop: http://gitcop.com/ Example commit message: .. code-block:: text cookies: Add support for delivering cookies Cookies are important for many people. This change adds a pluggable API for delivering cookies to the user, and provides a default implementation. Signed-off-by: Random J Developer Use ``git commit --amend`` to edit the commit message, if you need to. Use the ``--signoff`` (``-s``) option of ``git commit`` to append a sign-off line to the commit message with your name and email as known by Git. If you like filling out the commit message in an editor instead of using the ``-m`` option of ``git commit``, you can automate the presence of the sign-off line by using a commit template file: * Create a file outside of the repo (say, ``~/.git-signoff.template``) that contains, for example: .. code-block:: text Signed-off-by: Random J Developer * Configure Git to use that file as a commit template for your repo: .. code-block:: text git config commit.template ~/.git-signoff.template .. _`Releasing a version`: Releasing a version ------------------- This section shows the steps for releasing a version to `PyPI `_. It covers all variants of versions that can be released: * Releasing the master branch as a new major or minor version (M+1.0.0 or M.N+1.0) * Releasing a stable branch as a new update (= fix) version (M.N.U+1) This description assumes that you are authorized to push to the upstream repo at https://github.com/zhmcclient/python-zhmcclient and that the upstream repo has the remote name ``origin`` in your local clone. 1. Switch to your work directory of your local clone of the python-zhmcclient Git repo and perform the following steps in that directory. 2. Set shell variables for the version and branch to be released: * ``MNU`` - Full version number M.N.U this release should have * ``MN`` - Major and minor version numbers M.N of that full version * ``BRANCH`` - Name of the branch to be released When releasing the master branch as a new major or minor version (e.g. ``0.19.0``): .. code-block:: text MNU=0.19.0 MN=0.19 BRANCH=master When releasing a stable branch as a new update (=fix) version (e.g. ``0.18.1``): .. code-block:: text MNU=0.18.1 MN=0.18 BRANCH=stable_$MN 3. Check out the branch to be released, make sure it is up to date with upstream, and create a topic branch for the version to be released: .. code-block:: text git status # Double check the work directory is clean git checkout $BRANCH git pull git checkout -b release_$MNU 4. Edit the change log: .. code-block:: text vi docs/changes.rst and make the following changes in the section of the version to be released: * Finalize the version to the version to be released. * Remove the statement that the version is in development. * Change the release date to today´s date. * Make sure that all changes are described. * Make sure the items shown in the change log are relevant for and understandable by users. * In the "Known issues" list item, remove the link to the issue tracker and add text for any known issues you want users to know about. * Remove all empty list items in that section. 5. Commit your changes and push them upstream: .. code-block:: text git add docs/changes.rst git commit -sm "Release $MNU" git push --set-upstream origin release_$MNU 6. On GitHub, create a Pull Request for branch ``release_$MNU``. This will trigger the CI runs in Travis and Appveyor. Important: When creating Pull Requests, GitHub by default targets the ``master`` branch. If you are releasing a stable branch, you need to change the target branch of the Pull Request to ``stable_M.N``. 7. On GitHub, close milestone ``M.N.U``. 8. On GitHub, once the checks for this Pull Request succeed: * Merge the Pull Request (no review is needed). Because this updates the ``stable_M.N`` branch, it triggers an RTD docs build of its stable version. However, because the git tag for this version is not assigned yet, this RTD build will show an incorrect version (a dev version based on the previous version tag). This will be fixed in a subsequent step. * Delete the branch of the Pull Request (``release_M.N.U``) 9. Checkout the branch you are releasing, update it from upstream, and delete the local topic branch you created: .. code-block:: text git checkout $BRANCH git pull git branch -d release_$MNU 10. Tag the version: Important: This is the basis on which 'pbr' determines the package version. The tag string must be exactly the version string ``M.N.U``. Create a tag for the new version and push the tag addition upstream: .. code-block:: text git status # Double check the branch to be released is checked out git tag $MNU git push --tags The pushing of the tag triggers another RTD docs build of its stable version, this time with the correct version as defined in the tag. If the previous commands fail because this tag already exists for some reason, delete the tag locally and remotely: .. code-block:: text git tag --delete $MNU git push --delete origin $MNU and try again. 11. On GitHub, edit the new tag ``M.N.U``, and create a release description on it. This will cause it to appear in the Release tab. You can see the tags in GitHub via Code -> Releases -> Tags. 12. Do a fresh install of this version in your active Python environment. This ensures that 'pbr' determines the correct version. Otherwise, it may determine some development version. .. code-block:: text # workon zhmc... # make sure your virtual environment is active make clobber install make help # Double check that it shows version ``M.N.U`` 13. Upload the package to PyPI: .. code-block:: text make upload This will show the package version and will ask for confirmation. **Important:** Double check that the correct package version (``M.N.U``, without any development suffix) is shown. **Attention!!** This only works once for each version. You cannot re-release the same version to PyPI, or otherwise update it. Verify that the released version arrived on PyPI: https://pypi.python.org/pypi/zhmcclient/ 14. On RTD, verify that it shows the correct version for its stable version: RTD stable version: https://python-zhmcclient.readthedocs.io/en/stable. If it does not, trigger a build of RTD version "stable" on the RTD project page: RTD build page: https://readthedocs.org/projects/python-zhmcclient/builds/ Once that build is complete, verify again. 15. If you released the master branch, it needs a new fix stream. Create a branch for its fix stream and push it upstream: .. code-block:: text git status # Double check the branch to be released is checked out git checkout -b stable_$MN git push --set-upstream origin stable_$MN Log on to the `RTD project python-zhmcclient `_ and activate the new version (=branch) ``stable_M.N`` as a version to be built. 16. If you released the master branch, a new version should be started as described in :ref:`starting a new version`. This may be a new minor version on the same major version, or a new major version. .. _`Starting a new version`: Starting a new version ---------------------- This section shows the steps for starting development of a new version. These steps may be performed right after the steps for :ref:`releasing a version`, or independently. This section covers all variants of new versions: * A new major or minor version for new development based upon the master branch. * A new update (=fix) version based on a stable branch. This description assumes that you are authorized to push to the upstream repo at https://github.com/zhmcclient/python-zhmcclient and that the upstream repo has the remote name ``origin`` in your local clone. 1. Switch to your work directory of your local clone of the python-zhmcclient Git repo and perform the following steps in that directory. 2. Set shell variables for the version to be started and its base branch: * ``MNU`` - Full version number M.N.U of the new version to be started * ``MN`` - Major and minor version numbers M.N of that full version * ``BRANCH`` - Name of the branch the new version is based upon When starting a (major or minor) version (e.g. ``0.20.0``) based on the master branch: .. code-block:: text MNU=0.20.0 MN=0.20 BRANCH=master When starting an update (=fix) version (e.g. ``0.19.1``) based on a stable branch: .. code-block:: text MNU=0.19.1 MN=0.19 BRANCH=stable_$MN 3. Check out the branch the new version is based on, make sure it is up to date with upstream, and create a topic branch for the new version: .. code-block:: text git status # Double check the work directory is clean git checkout $BRANCH git pull git checkout -b start_$MNU 4. Edit the change log: .. code-block:: text vi docs/changes.rst and insert the following section before the top-most section: .. code-block:: text Version 0.19.0 ^^^^^^^^^^^^^^ Released: not yet **Incompatible changes:** **Deprecations:** **Bug fixes:** **Enhancements:** **Known issues:** * See `list of open issues`_. .. _`list of open issues`: https://github.com/zhmcclient/python-zhmcclient/issues 5. Commit your changes and push them upstream: .. code-block:: text git add docs/changes.rst git commit -sm "Start $MNU" git push --set-upstream origin start_$MNU 6. On GitHub, create a Pull Request for branch ``start_M.N.U``. Important: When creating Pull Requests, GitHub by default targets the ``master`` branch. If you are starting based on a stable branch, you need to change the target branch of the Pull Request to ``stable_M.N``. 7. On GitHub, create a milestone for the new version ``M.N.U``. You can create a milestone in GitHub via Issues -> Milestones -> New Milestone. 8. On GitHub, go through all open issues and pull requests that still have milestones for previous releases set, and either set them to the new milestone, or to have no milestone. 9. On GitHub, once the checks for this Pull Request succeed: * Merge the Pull Request (no review is needed) * Delete the branch of the Pull Request (``start_M.N.U``) 10. Checkout the branch the new version is based on, update it from upstream, and delete the local topic branch you created: .. code-block:: text git checkout $BRANCH git pull git branch -d start_$MNU zhmcclient-0.22.0/docs/appendix.rst0000644000076500000240000003527313364325033017622 0ustar maierastaff00000000000000.. 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. .. .. _`Appendix`: Appendix ======== This section contains information that is referenced from other sections, and that does not really need to be read in sequence. .. _`BaseManager`: .. _`BaseResource`: .. _`Base classes for resources`: Base classes for resources -------------------------- .. automodule:: zhmcclient._manager .. autoclass:: zhmcclient.BaseManager :members: :special-members: __str__ .. automodule:: zhmcclient._resource .. autoclass:: zhmcclient.BaseResource :members: :special-members: __str__ .. _`Glossary`: Glossary -------- This documentation uses a few special terms: .. glossary:: HMC Hardware Management Console; the node the zhmcclient talks to. session-id an opaque string returned by the HMC as the result of a successful logon, for use by subsequent operations instead of credential data. The HMC gives each newly created session-id a lifetime of 10 hours, and expires it after that. fulfillment The act of satisfying requests for creation, modification, or deletion of storage volumes in a storage subsystem (i.e. of the actual storage backing a :term:`storage volume` object). Storage volume objects have a fulfillment state indicating whether the volume is fulfilled, which means that the request for creation or modification has been carried out and the state of the backing volume is now in sync with the storage volume object. :term:`Storage group` objects also have a fulfillment state indicating whether all of its storage volumes are fulfilled. .. _`Special type names`: Special type names ------------------ This documentation uses a few special terms to refer to Python types: .. glossary:: string a :term:`unicode string` or a :term:`byte string` unicode string a Unicode string type (:func:`unicode ` in Python 2, and :class:`py3:str` in Python 3) byte string a byte string type (:class:`py2:str` in Python 2, and :class:`py3:bytes` in Python 3). Unless otherwise indicated, byte strings in this package are always UTF-8 encoded. number one of the number types :class:`py:int`, :class:`py2:long` (Python 2 only), or :class:`py:float`. integer one of the integer types :class:`py:int` or :class:`py2:long` (Python 2 only). timestamp a Timestamp-typed value as used in the HMC API. This is a non-negative :term:`integer` value representing a point in time as milliseconds since the UNIX epoch (1970-01-01 00:00:00 UTC), or the value -1 to indicate special treatment of the value. json object a :class:`py:dict` object that is a Python representation of a valid JSON object. See :ref:`py:py-to-json-table` for details. header dict a :class:`py:dict` object that specifies HTTP header fields, as follows: * `key` (:term:`string`): Name of the header field, in any lexical case. Dictionary key lookup is case sensitive, however. * `value` (:term:`string`): Value of the header field. callable a type for callable objects (e.g. a function, calling a class returns a new instance, instances are callable if they have a :meth:`~py:object.__call__` method). DeprecationWarning a standard Python warning that indicates the use of deprecated functionality. See section :ref:`Deprecations` for details. HMC API version an HMC API version, as a tuple of (api_major_version, api_minor_version), where: * `api_major_version` (:term:`integer`): The numeric major version of the HMC API. * `api_minor_version` (:term:`integer`): The numeric minor version of the HMC API. .. _`Resource model`: Resource model -------------- This section lists the resources that are available at the :term:`HMC API`. The term *resource* in this documentation is used to denote a managed object in the system. The result of retrieving a resource through the HMC API is termed a *resource representation*. Python classes for resources are termed to *represent* a resource. For resources within a :term:`CPC`, this section covers CPCs in DPM mode and classic mode, but omits any resources that are available only in ensemble mode. See section :ref:`CPCs` for a definition of the CPC modes. Some of the items in this section are qualified as *short terms*. They are not separate types of resources, but specific usages of resources. For example, "storage adapter" is a short term for the resource "adapter" when used for attaching storage. Resources scoped to the HMC ^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. glossary:: Console The HMC itself. Group TBD - Not yet supported. Hardware Message TBD - Not yet supported. Also scoped to CPCs in any mode. Job The execution of an asynchronous HMC operation. LDAP Server Definition The information in an HMC about an LDAP server that may be used for HMC user authorization purposes. Metrics Context A user-created definition of metrics that can be retrieved. Password Rule A rule which HMC users need to follow when creating a HMC logon password. Session A session between a client of the HMC API and the HMC. Task An action that an HMC user with appropriate authority can perform. User An HMC user. User Pattern A pattern for HMC user IDs that are not defined on the HMC as users but can be verified by an LDAP server for user authentication. User Role An authority role which can be assigned to one or more HMC users. Resources scoped to CPCs in any mode ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. glossary:: Capacity Record TBD - Not yet supported. CPC Central Processor Complex, a physical IBM Z or LinuxONE computer. For details, see section :ref:`CPCs`. Resources scoped to CPCs in DPM mode ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. glossary:: Accelerator Adapter Short term for an :term:`Adapter` providing accelerator functions (e.g. the z Systems Enterprise Data Compression (zEDC) adapter for data compression). Adapter A physical adapter card (e.g. OSA-Express adapter, Crypto adapter) or a logical adapter (e.g. HiperSockets switch). For details, see section :ref:`Adapters`. Adapter Port Synonym for :term:`Port`. Capacity Group TBD - Not yet supported. Crypto Adapter Short term for an :term:`Adapter` providing cryptographic functions. FCP Adapter Short term for a :term:`Storage Adapter` supporting FCP (Fibre Channel Protocol). FCP Port Short term for a :term:`Port` of an :term:`FCP Adapter`. HBA A logical entity that provides a :term:`Partition` with access to external storage area networks (SANs) through an :term:`FCP Adapter`. For details, see section :ref:`HBAs`. HBA resource objects only exist when the "dpm-storage-management" feature is not enabled. See section :ref:`Storage Groups` for details. Network Adapter Short term for an :term:`Adapter` for attaching networks (e.g. OSA-Express adapter). Network Port Short term for a :term:`Port` of a :term:`Network Adapter`. NIC Network Interface Card, a logical entity that provides a :term:`Partition` with access to external communication networks through a :term:`Network Adapter`. For details, see section :ref:`NICs`. Partition A subset of the hardware resources of a :term:`CPC` in DPM mode, virtualized as a separate computer. For details, see section :ref:`Partitions`. Port The physical connector port (jack) of an :term:`Adapter`. For details, see section :ref:`Ports`. Storage Adapter Short term for an :term:`Adapter` for attaching storage. Storage Group A grouping entity for a set of FCP or ECKD (=FICON) :term:`storage volumes `. A storage group can be attached to a :term:`partition` which will cause its storage volumes to be attached to the partition. Storage Group objects exist only when the "dpm-storage-management" feature is enabled on the CPC. For details, see section :ref:`Storage Groups`. Storage Port Short term for a :term:`Port` of a :term:`Storage Adapter`. Storage Volume An FCP or ECKD (=FICON) storage volume defined in context of a :term:`storage group`. The life cycle of a storage volume includes being defined but not :term:`fulfilled `, being fulfilled but not attached, and finally being attached to a :term:`partition`. Storage Volume objects exist only when the "dpm-storage-management" feature is enabled on the CPC. For details, see section :ref:`Storage Groups`. vHBA Synonym for :term:`HBA`. In this resource model, HBAs are always virtualized because they belong to a :term:`Partition`. Therefore, the terms vHBA and HBA can be used interchangeably. Virtual Function A logical entity that provides a :term:`Partition` with access to an :term:`Accelerator Adapter`. For details, see section :ref:`Virtual functions`. Virtual Storage Resource A representation of a storage-related z/Architecture device in a :term:`partition`. For FCP type storage volumes, a Virtual Storage Resource object represents an :term:`HBA` through which the attached storage volume is accessed. For FICON (ECKD) type storage volumes, a Virtual Storage Resource object represents the attached storage volume itself. Virtual Storage Resource objects exist only when the "dpm-storage-management" feature is enabled on the CPC. For details, see section :ref:`Storage Groups`. Virtual Switch A virtualized networking switch connecting :term:`NICs ` with a :term:`Network Port`. For details, see section :ref:`Virtual switches`. vNIC Synonym for :term:`NIC`. In this resource model, NICs are always virtualized because they belong to a :term:`Partition`. Therefore, the terms vNIC and NIC can be used interchangeably. Resources scoped to CPCs in classic (and ensemble) mode ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. glossary:: Activation Profile A general term for specific types of activation profiles: * :term:`Reset Activation Profile` * :term:`Image Activation Profile` * :term:`Load Activation Profile` Group Profile TBD Image Activation Profile A specific :term:`Activation Profile` that defines characteristics of an :term:`LPAR`. Load Activation Profile A specific :term:`Activation Profile` that defines an operating system image that can be loaded (booted) into an :term:`LPAR`. Logical Partition LPAR A subset of the hardware resources of a :term:`CPC` in classic mode (or ensemble mode), virtualized as a separate computer. For details, see section :ref:`LPARs`. Reset Activation Profile A specific :term:`Activation Profile` that defines characteristics of a :term:`CPC`. .. _`Bibliography`: Bibliography ------------ .. glossary:: X.509 `ITU-T X.509, Information technology - Open Systems Interconnection - The Directory: Public-key and attribute certificate frameworks `_ RFC2616 `IETF RFC2616, Hypertext Transfer Protocol - HTTP/1.1, June 1999 `_ RFC2617 `IETF RFC2617, HTTP Authentication: Basic and Digest Access Authentication, June 1999 `_ RFC3986 `IETF RFC3986, Uniform Resource Identifier (URI): Generic Syntax, January 2005 `_ RFC6874 `IETF RFC6874, Representing IPv6 Zone Identifiers in Address Literals and Uniform Resource Identifiers, February 2013 `_ HMC API The Web Services API of the z Systems Hardware Management Console, described in the following books: HMC API 2.11.1 `IBM SC27-2616, z Systems Hardware Management Console Web Services API (Version 2.11.1) `_ HMC API 2.12.0 `IBM SC27-2617, z Systems Hardware Management Console Web Services API (Version 2.12.0) `_ HMC API 2.12.1 `IBM SC27-2626, z Systems Hardware Management Console Web Services API (Version 2.12.1) `_ HMC API 2.13.0 `IBM SC27-2627, z Systems Hardware Management Console Web Services API (Version 2.13.0) `_ HMC API 2.13.1 `IBM SC27-2634, z Systems Hardware Management Console Web Services API (Version 2.13.1) `_ HMC API 2.14.0 `IBM SC27-2636, z Systems Hardware Management Console Web Services API (Version 2.14.0) `_ HMC Operations Guide The operations guide of the z Systems Hardware Management Console, described in the following books: HMC Operations Guide 2.11.1 `IBM SC28-6905, System z Hardware Management Console Operations Guide (Version 2.11.1) `_ HMC Operations Guide 2.13.1 `IBM z Systems Hardware Management Console Operations Guide (Version 2.13.1) `_ KVM for IBM z Systems V1.1.2 System Administration `IBM SC27-8237, KVM for IBM z Systems V1.1.2 System Administration `_ .. _`Related projects`: Related projects ---------------- .. glossary:: zhmccli project `zhmccli project at GitHub `_ zhmccli package `zhmccli package on Pypi `_ .. include:: changes.rst zhmcclient-0.22.0/docs/changes.rst0000644000076500000240000012246213414660641017422 0ustar maierastaff00000000000000.. Copyright 2016-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. .. .. _`Change log`: Change log ---------- Version 0.22.0 ^^^^^^^^^^^^^^ Released: 2019-01-07 **Enhancements:** * Added a mitigation for a firmware defect that causes filtering of adapters by adapter-id to return an empty result when the specified adapter-id contains hex digits ('a' to 'f'). See issue #549. Version 0.21.0 ^^^^^^^^^^^^^^ Released: 2018-10-31 **Bug fixes:** * Update Requests package to 2.20.0 to fix following vulnerability of the National Vulnerability Database: https://nvd.nist.gov/vuln/detail/CVE-2018-18074 Version 0.20.0 ^^^^^^^^^^^^^^ Released: 2018-10-24 **Bug fixes:** * Docs: Added missing support statements for the LinuxOne Emperor II machine generations to the documentation (The corresponding z14 was already listed). **Enhancements:** * Docs: Streamlined, improved and fixed the description how to release a version and how to start a new version, in the development section of the documentation. * Added support for Python 3.7. This required increasing the minimum versions of several Python packages in order to pick up their Python 3.7 support: - `pyzmq` from 16.0.2 to 16.0.4 (While 16.0.4 works for this, only 17.0.0 declares Python 3.6(!) support on Pypi, and Python 3.7 support is not officially declared on Pypi yet for this package). - `PyYAML` from 3.12 to 3.13 (see PyYAML issue https://github.com/yaml/pyyaml/issues/126). * Docs: Added support statements for the z14-ZR1 and LinuxONE Rockhopper II machine generations to the documentation. * Added support for the z14-ZR1 and LinuxONE Rockhopper II machine generations to the `Cpc.maximum_active_partitions()` method. * Provided direct access to the (one) `Console` object, from the `ConsoleManager` and `CpcManager` objects, via a new `console` property. This is for convenience and avoids having to code `find()` or `list()` calls. The returned `Console` object is cached in the manager object. Also, added a `console` property to the `FakedConsoleManager` class in the mock support, for the same purpose. * Added a property `client` to class `CpcManager` for navigating from a `Cpc` object back to the `Client` object which is the top of the resource tree. * Added support for the new concept of firmware features to Cpcs and Partitions, by adding methods `feature_enabled()` and `feature_info()` to classes `Cpc` and `Partition` for inspection of firmware features. The firmware feature concept was introduced starting with the z14-ZR1 and LinuxONE Rockhopper II machine generation. The DPM storage management feature is the first of these new firmware features. * Added support for the DPM storage management feature that is available starting with the z14-ZR1 and LinuxONE Rockhopper II machine generation. This includes new resources like Storage Groups, Storage Volumes, and Virtual Storage Resources. It also includes new methods for managing storage group attachment to Partitions. The new items in the documentation are: - In 5.1. CPCs: `list_associated_storage_groups()`, `validate_lun_path()`. - In 5.5. Partitions: `attach_storage_group()`, `detach_storage_group()`, `list_attached_storage_groups()`. - 5.12. Storage Groups - 5.13. Storage Volumes - 5.14. Virtual Storage Resources - In 5.15 Console: `storage_groups` * Added support for changing the type of storage adapters between FICON and FCP, via a new method `Adapter.change_adapter_type()`. This capability was introduced with the z14-ZR1 and LinuxONE Rockhopper II machine generation. Version 0.19.11 ^^^^^^^^^^^^^^^ Released: 2018-05-14 Note: The version number of this release jumped from 0.19.0 right to 0.19.11, for tooling reasons. **Enhancements:** * Docs: Improved the description of installation without Internet access, and considerations on system Python vs. virtual Python environments. * Lowered the minimum version requirements for installing the zhmcclient package, for the packages: requests, pbr, decorator. Added support for tolerating decorator v3.4 in the zhmcclient _logging module. * Adjusted development environment to changes in Appveyor CI environment. Version 0.19.0 ^^^^^^^^^^^^^^ Released: 2018-03-15 **Incompatible changes:** * The ``Lpar.deactivate()`` method is now non-forceful by default, but can be made to behave like previously by specifying the new ``force`` parameter. In force mode, the deactivation operation is permitted when the LPAR status is "operating". **Bug fixes:** * Fixed a flawed setup of setuptools in Python 2.7 on the Travis CI, where the metadata directory of setuptools existed twice, by adding a script `remove_duplicate_setuptools.py` that removes the moot copy of the metadata directory (issue #434). * Fixed a bug where multiple Session objects shared the same set of HTTP header fields, causing confusion in the logon status. **Enhancements:** * Migrated all remaining test cases from unittest to pytest, and started improving the testcases using pytest specific features such as parametrization. * Added support for a ``force`` parameter in the ``Lpar.activate()``, ``Lpar.deactivate()``, and ``Lpar.load()`` methods. It controls whether the operation is permitted when the LPAR status is "operating". Note that this changes ``Lpar.deactivate()`` to be non-forceful by default (force=True was hard coded before this change). * Added support for an ``activation_profile_name`` option in the ``Lpar.activate()`` method, that allows specifying the activation profile to be used. The default is as before: The profile that is specified in the ``next-activation-profile`` property of the ``Lpar`` object. * Made the ``load_address`` parameter of ``Lpar.load()`` optional in order to support z14. Up to z13, the HMC now returns an error if no load address is specified. Adjusted the zhmcclient mock support accordingly. * Added LPAR status checks in the zhmcclient mock support, so that activate, deactivate and load returns the same errors as the real system when the initial LPAR status is not permitted, or when the activation profile name does not match the LPAR name, or when no load address is specified. * Improved the testcases for the Lpar and LparManager classes. * Added the ability to mock the resulting status of the faked Lpars in the zhmcclient mock support, for the Activate, Deactivate, and Load operations. Added a new chapter "URI handlers" in section "Mock support" of the documentation, to describe this new ability. * Added support for CPC energy management operations: - ``Cpc.set_power_save()`` (HMC: "Set CPC Power Save") - ``Cpc.set_power_capping()`` (HMC: "Set CPC Power Capping") - ``Cpc.get_energy_management_properties()`` (HMC: "Get CPC Energy Management Data") * The zhmcclient package no longer adds a NullHandler to the Python root logger (but still to the zhmcclient.api/.hmc loggers). * Added a function test concept that tests against a real HMC. Version 0.18.0 ^^^^^^^^^^^^^^ Released: 2017-10-19 **Incompatible changes:** * Removed the zhmc CLI support from this project, moving it into a new GitHub project ``zhmcclient/zhmccli``. This removes the following prerequisite Python packages for the zhmcclient package: - click - click-repl - click-spinner - progressbar2 - tabulate - prompt_toolkit (from click-repl) - python-utils (from progressbar2) - wcwidth (from prompt-toolkit -> click-repl) **Bug fixes:** * Fixed a flawed setup of setuptools in Python 2.7 on the Travis CI, where the metadata directory of setuptools existed twice, by adding a script `remove_duplicate_setuptools.py` that removes the moot copy of the metadata directory (issue #434). Version 0.17.0 ^^^^^^^^^^^^^^ Released: 2017-09-20 **Incompatible changes:** * The zhmcclient mock support for Partitions no longer allows to stop a partition when it is in status 'degraded' or 'reservation-error'. That is consistent with the real HMC as described in the HMC API book. * In the `HTTPError` exception class, `args[0]` was set to the `body` argument, i.e. to the entore response body. Because by convention, `args[0]` should be a human readable message, this has been changed to now set `args[0]` to the 'message' field in the response body, or to `None` if not present. **Bug fixes:** * Fixed the bug that aborting a confirmation question in the CLI (e.g. for "zhmc partition delete") caused an AttributeError to be raised. It now prints "Aborted!" and in interactive mode, terminates only the current command. (issue #418). * Fixed an AttributeError when calling 'zhmc vfunction update'. Access to a partition from nic and vfunction is done via the respective manager (issue #416). * In the zhmc CLI, fixed that creating a new session reused an existing session. This prevented switching between userids on the same HMC (issue #422). * Docs: In the "Introduction" chapter of the documentation, fixed the HMC API version shown for z14. * Docs: In the Appendix of the documentation, added IBM book number and link for the HMC API book of z14. **Enhancements:** * Avoided `DeprecationWarning` on Python 3 for invalid escape sequences in some places. * The zhmcclient mock support for various resource classes did not always check for invalid CPC status and for invalid Partition status as described in the HMC API book. It now does. * In the mock support, invalid input to faked resource classes (mainly when adding faked resources) is now handled by raising a new exception ``zhmcclient_mock.InputError`` (instead of ``ValueError``). The URI handler of the mock support now converts that into an HTTP error 400 (Bad Request), consistent with the HMC API book. * Added ``datetime_from_timestamp()`` and ``datetime_from_timestamp()`` functions that convert between Python ``datetime`` objects and HMC timestamp numbers. * Added mock support for Metrics resources. * Added a ``verify`` argument to ``Session.logoff()``, consistent with ``Session.logon()``. This was needed as part of fixing issue #422. * Added a `__repr__()` function to the `Session` class, for debug purposes. * In the `ParseError` exception class, a message of `None` is now tolerated, for consistency with the other zhmcclient exception classes. * In the `NotFound` exception class, a `filter_args` parameter of `None` is now tolerated, for consistency with the `NoUniqueMatch` exception class. * Documented for the zhmcclient exception classes how `args[0]` is set. * Clarified in the documentation that the `manager` and `resources` parameters of the `NoUniqueMatch` and `NotFound` exception classes must not be `None`. * Improved the unit test cases for the `Client` class and for the zhmcclient exception classes, and migrated them to py.test. * Migrated the unit tests for HBAs from unittest to py.test, and improved the test cases. * In the `Hba.reassign_port()` method, updated the `Hba` object with the changed port, consistent with other update situations. * Clarified in the description of `HbaManager.list()` that only the 'element-uri' property is returned and can be used for filtering. * The mock support for the "Create NIC" operation now performs more checking on the URIs specified in the 'network-adapter-port' or 'virtual-switch-uri' input properties, raising HTTP status 404 (Not Found) as specified in the HMC API book. * In the ``FakedNic.add()`` method of the mock support, the checking for the URIs specified in the 'network-adapter-port' or 'virtual-switch-uri' input properties was relaxed to only the minimum, in order to make the setting up of faked resources easier. * Migrated the unit tests for ``Nic`` and ``NicManager`` from unittest to py.test, and improved them. * Improved the way the named tuples ``MetricGroupDefinition`` and ``MetricDefinition`` are documented. * Added support for ``Console`` resource and its child resources ``User``, ``User Role``, ``User Pattern``, ``Password Rule``, ``Task``, and ``LDAP Server Definition``, both for the zhmcclient API and for the zhmcclient mock support. * As part of support for the ``Console`` resource, added a new resource class ``UnmanagedCpc`` which representd unmanaged CPCs that have been discovered by the HMC. The existing ``Cpc`` class continues to represent only managed CPCs; this has been clarified in the documentation. * As part of support for the ``Console`` resource, added a method ``wait_for_available()`` to the ``Client`` class, which waits until the HMC is available again after a restart. This method is used by ``Console.restart()``, but it can also be used by zhmcclient users. * As part of support for the ``Console`` resource, improved ``Session.post()`` to allow for an empty response body when the operation returns with HTTP status 202 (Accepted). This status code so far was always assumed to indicate that an asynchronous job had been started, but it can happen in some ``Console`` operations as well. * Improved the error information in the ``ParseError`` exception, by adding the "Content-Type" header in cases where that is interesting. * Add CLI commmands to mount and unmount an ISO to a Partition. Version 0.16.0 ^^^^^^^^^^^^^^ Released: 2017-08-29 **Bug fixes:** * Fixed CLI: Remove defaults for options for 'partition update' (issue #405). **Enhancements:** * Added Code Climate support. Version 0.15.0 ^^^^^^^^^^^^^^ Released: 2017-08-15 **Incompatible changes:** * In case the user code was specifically processing the reason code 900 used for HTML-formatted error responses with HTTP status 500: This reason code has been split up into multiple reason codes. See the corresponding item in section "Enhancements". **Bug fixes:** * Fixed a TypeError: "'odict_values' object does not support indexing" on Python 3.x (issue #372). * Minor fixes in the documentation (e.g. fixed name of ``MetricGroupValues`` class). * Fixed the zhmc CLI for Python 3 where multiple commands raised AttributeError: "'dict' object has no attribute 'iteritems' in ``zhmccli/_helper.py``. (issue #396). **Enhancements:** * Added support for the HMC Metric Service. For details, see section 'Metrics' in the zhmcclient documentation. There is an example script ``metrics.py`` demonstrating the use of the metrics support. The metrics support caused an additional package requirement for the ``pytz`` package. * Added support for a "metrics" command to the zhmc CLI. * Added support for the IBM z14 system (in internal machine type tables and in the documentation). * zhmccli: Support for 'authorization controls' of a Partition (issue #380) * Added CLI support for processing weights (issue #383) * The `HTTPError` raised at the API for HMC Web Services not enabled now has a simple error message and uses a specific reason code of 900. Previously, the returned HTML-formatted response body was used for the message and a generic reason code of 999. All other HTML-formatted error responses still use the generic reason code 999. That reason code 999 is now documented to be changed to more specific reason codes, over time. (issue #296). * Reduced the package requirements to only the direct dependencies of this package. * Changed the experimental ``Cpc.get_free_crypto_domains()`` method to test only control-usage access to the specified adapters. Improved that method by supporting `None` for the list of adapters which means to inspect all crypto adapters of the CPC. Version 0.14.0 ^^^^^^^^^^^^^^ Released: 2017-07-07 **Incompatible changes:** * Changed the return value of ``TimeStatsKeeper.snapshot()`` from a list of key/value tuples to a dictionary. This is more flexible and reduces the number of data structure conversions in different scenarios. See issue #269. * Changed the arguments of ``Partition.mount_iso_image()`` incompatibly, in order to fix issue #57. **Bug fixes:** * Fixed the documentation of several asynchronous ``Partition`` methods that incorrectly documented returning ``None`` in case of synchronous invocation, to now document returning an empty dictionary: - ``Partition.start()`` - ``Partition.stop()`` - ``Partition.dump_partition()`` - ``Partition.psw_restart()`` All other asynchronous methods did not have this issue. See issue #248. * Clarified in the documentation of all exceptions that have a ``details`` instance variable, that it is never ``None``. * Fixed using '--ssc-dns-servers' option for the CLI commands 'zhmc partition create/update'. See issue #310. * Fixed the incorrect parameters of ``Partition.mount_iso_image()``. See issue #57. * Reads the vlan-id as a integer instead as a string for the 'zhmc nic create/update' cli command. See issue #337. * Fixed the AttributeError that occurred when using zhmcclient in Jupyter notebooks, or in the python interactive mode. See issue #341. **Enhancements:** * Improved content of ``zhmcclient.ParseError`` message for better problem analysis. * Increased the default status timeout from 60 sec to 15 min, in order to accomodate for some large environments. The status timeout applies to waiting for reaching the desired LPAR status after the HMC operation 'Activate LPAR' or 'Deactivate LPAR' has completed. * Allow ``None`` as a value for the ``load_parameter`` argument of ``Lpar.load()``, and changed the default to be ``None`` (the latter change does not change the behavior). * Added actual status, desired statuses and status timeout as attributes to the ``StatusTimeout`` exception, for programmatic processing by callers. * In the zhmc CLI, added a ``--allow-status-exceptions`` option for the ``lpar activate/deactivate/load`` commands. Setting this option causes the LPAR status "exceptions" to be considered an additional valid end status when waiting for completion of the operation. * Improved documentation of CLI output formats. * Simplified the message of the ``OperationTimeout`` exception. * Split the ``AuthError`` exception into ``ClientAuthError`` and ``ServerAuthError`` that are used depending on where the authentication issue is detected. Reason for the split was that the two subclasses have different instance variables. The ``AuthError`` exception class is now an abstract base class that is never raised but can be used to catch exceptions. * Made error data available as instance variables of the following exceptions: ``ConnectTimeout``, ``ReadTimeout``, ``RetriesExceeded``, ``ClientAuthError``, ``ServerAuthError``, ``OperationTimeout``, and ``StatusTimeout``, ``NotFound``, ``NoUniqueMatch``. * Improved unit test cases for ``zhmcclient._exceptions`` module. * Added support to the zhmc CLI for an interactive session to the console of the operating system running in a partition (``zhmc partition console``) or LPAR (``zhmc lpar console``). * Added ``str_def()`` method to all exception classes, which returns a definition-style string for parsing by scripts. * In the zhmc CLI, added options ``-e``, ``--error-format`` for controlling the format of error messages. The ``-e def`` option selects the format returned by the new ``str_def()`` methods. This format provides for easier parsing of details of error messages by invoking scripts. * Added ``wait_for_status()`` methods to the ``Lpar`` and ``Partition`` classes, in order to ease the work for users that need to ensure that a particular LPAR or partition status is reached. * Added support for crypto-related methods on the ``Partition`` and ``Adapter`` resource classes. Added zhmcclient mock support for the faked partition (not yet for the faked adapter). * Added that ``Partition.start()`` waits for reaching the desired status 'active' or 'degraded', because it transitions through status 'paused' when starting a partition. * Improved the ``NoUniqueMatch`` exception so that the list of resources that did match the filter, are shown with their URIs in the error message, and are available as new ``resources`` and ``resource_uris`` attributes. This change adds a required argument ``resources`` to the constructor of ``NoUniqueMatch``. However, since this exception is only supposed to be raised by the zhmcclient implementation, this change is compatible to zhmcclient users. * Moved the invocation of PyLint from the "make check" target into its own "make pylint" target, inorder to speed up the CI testing. * Added the ability for ``Session.post()`` to support binary data as the payload. The ``body`` argument may now be a dictionary which is represented as a JSON string, a binary string which is used directly, or a unicode string which is encoded using UTF-8. This was necessary to fix issue #57. * In the zhmcclient mock support, added a Python property ``name`` to all faked resources, which returns the value of the 'name' resource property. * Added a Python property ``maximum_crypto_domains`` to the ``Adapter`` class, which returns the maximum number of crypto domains of a crypto adapter. * Added a Python property ``maximum_active_partitions`` to the ``Cpc`` class, which returns the maximum number of active LPARs or partitions of a CPC. * Added ``get_free_crypto_domains()`` method to the ``Cpc`` class, in order to find out free domain index numbers for a given set of crypto adapters. Note: This method is considered experimental in this version. * Added an ``update_properties()`` method to the ``Lpar`` and ``Cpc`` resource classes. * Improved the description of the ``Hba.create()`` and ``Nic.create()`` methods to describe how the backing adapter port is specified. * Extended the zhmcclient mock support by adding support for all operations thet are supported at the zhmcclient API but were not yet supported for mocking, so far. Version 0.13.0 ^^^^^^^^^^^^^^ Released: 2017-05-18 **Incompatible changes:** * In the CLI, changed the default for number of processors for the ``zhmc partition create`` command to create 1 IFL by default, if neither IFLs nor CPs had been specified. Also, a specified number of 0 processors is now passed on to the HMC (and rejected there) instead of being removed by the CLI. This keeps the logic simpler and more understandable. See also issue #258. **Deprecations:** * Deprecated the ``BaseManager.flush()`` method in favor of the new ``BaseManager.invalidate_cache()`` method. **Bug fixes:** * Fixed that the defaults for memory for the ``zhmc partition create`` command were ignored (issue #246). * The default values for the retry / timeout configuration for a session has been changed to disable read retries and to set the read timeout to 1 hour. In addition, read retries are now restricted to HTTP GET methods, in case the user enabled read retries. See issue #249. * Fixed that resource creation, deletion, and resource property updating now properly updates the resource name-to-URI cache in the zhmcclient that is maintained in the `*Manager` objects. As part of that, the `BaseManager` init function got an additional required argument `session`, but because creation of manager objects is not part of the external API, this should not affect users. See issue #253. * In the unit testcases for the `update_properties()` and `delete()` methods of resource classes, fixed incorrect assumptions about their method return values. See issue #256. * In the unit testcases for the `update_properties()` and `delete()` methods of resource classes, fixed incorrectly returned response bodies for mocked DELETE and POST (for update), and replaced that with status 204 (no content). This came up as part of fixing issue #256. * Fixed that ``find(name)`` raised ``NotFound`` for existing resources, for resource types that are elements (i.e. NICs, HBAs, VFs, Ports) (issue #264). * Fixed that the filter arguments for ``find()``, ``findall()``, and ``list()`` for string properties when matched on the client side are matched using regular expressions instead of exact matching, consistent with the zhmcclient documentation, and with server-side matching on the HMC. See issue #263. * Fixed that the filter arguments for ``find()``, ``findall()``, and ``list()`` when used with lists of match values incorrectly applied ANDing between the list items. They now apply ORing, consistent with the zhmcclient documentation, and with server-side matching on the HMC. See issue #267. * Fixed that the ``Cpc.dpm_enabled`` property incorrectly returned ``True`` on a z13 in classic mode. See issue #277. * Fixed errors in zhmcclient mock support related to DPM mode checking. * Fixed that filter arguments specifying properties that are not on each resource, resulted in raising KeyError. An example was when the "card-location" property was specified when finding adapters; that property does not exist for Hipersocket adapters, but for all other types. This situation is now handled by treating such resources as non-matching. See issue #271. * Fix when providing 'load-parameter' option. See issue #273 **Enhancements:** * Added content to the "Concepts" chapter in the documentation. * The `update_properties()` method of all Python resource objects now also updates the properties of that Python resource object with the properties provided by the user (in addition to issuing the corresponding Update Properties HMC operation. This was done because that is likely the expectation of users, and we already store user-provided properties in Python resource objects when creating resources so it is now consistent with that. This came up as part of issue #253. * As part of fixing the name-to-URI cache, a new attribute `name_uri_cache_timetolive` was added to class `RetryTimeoutConfig`, which allows controlling after what time the name-to-URI cache is automatically invalidated. The default for that is set in a new `DEFAULT_NAME_URI_CACHE_TIMETOLIVE` constant. Also, the `*Manager` classes now have a new method `invalidate_cache()` which can be used to manually invalidate the name-to-URI cache, for cases where multiple parties (besides the current zhmcclient instance) change resources on the HMC. This came up as part of issue #253. * Improved the documentation of the lookup methods (list(), find(), findall()) and of the resource filtering concept in section 'Filtering'. Related to issue #261. * Added zhmcclient mock support for the Create Hipersocket and Delete Hipersocket operations. * Added support for filtering in the zhmcclient mock support. * In order to improve the ability to debug the resource and manager objects at the API and the faked resource and manager objects of the mock support, the ``__repr()__`` methods ahave been improved. Because these functions now display a lot of data, and because testing their string layout is not very interesting, all unit test cases that tested the result of ``__repr()__`` methods have been removed. * Add basic Secure Service Container support to the CLI. Version 0.12.0 ^^^^^^^^^^^^^^ Released: 2017-04-13 **Incompatible changes:** * The password retrieval function that can optionally be passed to ``Session()`` has changed its interface; it is now being called with host and userid. Related to issue #225. **Bug fixes:** * Added WWPN support in mocking framework (issue #212). * Fixed error in mock support where the `operation_timeout` argument to `FakedSession.post()` was missing. * Fixed a bug in the unit test for the mock support, that caused incomplete expected results not to be surfaced, and fixed the incomplete testcases. * Fixed in the CLI that the spinner character was part of the output. * Improved robustness of timestats tests by measuring the actual sleep time instead of going by the requested sleep time. * Added support for 'error' field in 'job-results' (fixes issue #228). * Fixed version mismatches in CI test environment when testing with the minimum package level by consistently using the latest released packages as of zhmcclient v0.9.0 (2016-12-27). This caused an increase in versions of packages needed for the runtime. **Enhancements:** * Improved the mock support by adding the typical attributes of its superclass `FakedBaseResource` to the `FakedHmc` class. * Improved the mock support by adding `__repr__()` methods to all `Faked*` classes that return an object representation suitable for debugging. * In the mock support, the following resource properties are now auto-set if not specified in the input properties: - Cpc: - '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'. - Partition: 'status' is auto-set to 'stopped', if not specified. - Lpar: 'status' is auto-set to 'not-activated', if not specified. - Adapter: 'status' is auto-set to 'active', if not specified. * In the CLI, added ``-y`` as a shorter alternative to the existing ``--yes`` options, that allow skipping confirmation prompts. * Added OS-X as a test environment to the Travis CI setup. * In the CLI, added a ``-p`` / ``--password`` option for specifying the HMC password (issue #225). * Added logging support to the zhmc CLI (issue #113). * Added 'load-parameter' option to 'zhmc lpar load' (issue #226). Version 0.11.0 ^^^^^^^^^^^^^^ Released: 2017-03-16 **Incompatible changes:** * Changed the return value of all methods on resource classes that invoke asynchronous operations (i.e. all methods that have a `wait_for_completion` parameter), as follows: - For `wait_for_completion=True`, the JSON object in the 'job-results' field is now returned, or `None` if not present (i.e. no result data). Previously, the complete response was returned as a JSON object. - For `wait_for_completion=False`, a new `Job` object is now returned that allows checking and waiting for completion directly on the `Job` object. Previously, the whole response of the 'Query Job Status' operation was returned as a JSON object, and the job completion was checked on the `Session` object, and one could not wait for completion. * Changed the default value of the `wait_for_completion` parameter of the `Session.post()` method from `True` to `False`, in order to avoid superfluos timestats entries. This method is not normally used by users of the zhmcclient package. * Removed the version strings from the ``args[]`` property of the ``zhmcclient.VersionError`` exception class. They had been available as ``args[1]`` and ``args[2]``. ``args[0]`` continues to be the error message, and the ``min_api_version`` and ``api_version`` properties continue to provide the version strings. * Changed the names of the Python loggers as follows: 1. Logger 'zhmcclient.api' logs API calls made by the user of the package, at log level DEBUG. Internal calls to API functions are no longer logged. 2. Logger 'zhmcclient.hmc' logs HMC operations. Their log level has been changed from INFO to DEBUG. * Removed the log calls for the HMC request ID. **Bug fixes:** * Added a minimum version requirement `>=4.0.0` for the dependency on the "decorate" Python package (issue #199). * Increased minimum version of "click-spinner" package to 0.1.7, in order to pick up the fix for zhmcclient issue #116. * Fixed CLI help text for multiple commands, where the text was incorrectly flowed into a paragraph. **Enhancements:** * Added support for retry/timeout configuration of HTTP sessions, via a new ``RetryTimeoutConfig`` class that can be specified for the ``Session`` object. The retry/timeout configuration can specify: - HTTP connect timeout and number of retries. - HTTP read timeout (of HTTP responses), and number of retries. - Maximum number of HTTP redirects. * Added new exceptions ``zhmcclient.ConnectTimeout`` (for HTTP connect timeout), ``zhmcclient.ResponseReadTimeout`` (for HTTP response read timeout), and ``zhmcclient.RequestRetriesExceeded`` (for HTTP request retry exceeded). They are all derived from ``zhmcclient.ConnectionError``. * Fixed a discrepancy between documentation and actual behavior of the return value of all methods on resource classes that invoke asynchronous operations (i.e. all methods that have a `wait_for_completion` parameter). See also the corresponding incompatible change (issue #178). * In the CLI, added a 'help' command that displays help for interactive mode, and a one-line hint that explains how to get help and how to exit interactive mode (issue #197). * In the CLI, added support for command history. The history is stored in the file `~/.zhmc_history`. * In the CLI, changed the prompt of the interactive mode to ``zhmc>``. * Added support for tolerating HTML content in the response, instead of JSON. An HTML formatted error message may be in the response for some 4xx and 5xx HTTP status codes (e.g. when the WS API is disabled). Such responses are raised as ``HTTPError`` exceptions with an artificial reason code of 999. * Fixed an incorrect use of the ``zhmcclient.AuthError`` exception and unnecessary checking of HMC behavior, i.e. when the HMC fails with "API session token expired" for an operation that does not require logon. This error should never be returned for operations that do not require logon. If it would be returned, it is now handled in the same way as when the operation does require logon, i.e. by a re-logon. * Added support for deferred status polling to the `Lpar.activate/deactivate/load()` methods. The HMC operations issued by these methods exhibit "deferred status" behavior, which means that it takes a few seconds after successful completion of the asynchronous job that executes the operation, until the new status can be observed in the 'status' property of the LPAR resource. These methods will poll the LPAR status until the desired status value is reached. A status timeout can be specified via a new `status_timeout` parameter to these methods, which defaults to 60 seconds. If the timeout expires, a new `StatusTimeout` exception is raised (issue #191). * Added operation timeout support to `Session.post()` and to all resource methods with a `wait_for_completion` parameter (i.e. the asynchronous methods). The operation timeout on the asynchronous methods can be specified via a new `operation_timeout` parameter, which defaults to 3600 seconds. If the timeout expires, a new `OperationTimeout` exception is raised (issue #6). * Added a new module that defines public constants, and that defines default timeout and retry values. * Experimental: In the CLI, added more supported table formats (plain, simple, psql, rst, mediawiki, html, LaTeX). * Improved the content of the log messages for logged API calls and HMC operations to now contain the function call arguments and return values (for API calls) and the HTTP request and response details (for HMC operations). For HMC operations and API calls that contain the HMC password, the password is hidden in the log message by replacing it with a few '*' characters. Version 0.10.0 ^^^^^^^^^^^^^^ Released: 2017-02-02 **Incompatible changes:** * The support for server-side filtering caused an incompatibility for the `find()` and `findall()` methods: For String typed resource properties, the provided filter string is now interpreted as a regular expression that is matched against the actual property value, whereby previously it was matched by exact string comparison. * The parameter signatures of the `__init__()` methods of `BaseResource` and `BaseManager` have changed incompatibly. These methods have always been considered internal to the package. They are now explicitly stated to be internal and their parameters are no longer documented. If users have made themselves dependent on these parameters (e.g. by writing a mock layer), they will need to adjust to the new parameter signature. See the code for details. **Bug fixes:** * Fixed a bug where the CLI code tries to access 'cpc' from the 'partition' directly without going via the manager property. This caused an AttributeError (issue #161). * Fixed unrecognized field ('adapter-port') during 'HBA create' (issue #163). **Enhancements:** * Added filter arguments to the `list()` method, and added support for processing as many filter arguments as supported on the server side via filter query parameters in the URI of the HMC List operation. The remaining filter arguments are processed on the client side in the `list()` method. * Changed the keyword arguments of the `find()` and `findall()` methods to be interpreted as filter arguments that are passed to the `list()` method. * Documented the authorization requirements for each method, and in total in a new section "Setting up the HMC". * Added a method `open_os_message_channel()` on Partition and Lpar objects, that returns a notification token for receiving operating system messages as HMC notifications. * Experimental: Added a class `NotificationReceiver` that supports receiving and iterating through HMC notificationsi for a notification token, e.g. those produced by `open_os_message_channel()`. Version 0.9.0 ^^^^^^^^^^^^^ Released: 2017-01-11 **Bug fixes:** * Fixed a bug where accessing the 'name' property via the `properties` attribute caused `KeyError` to be raised (issue #137). Note that there is now a recommendation to use `get_property()` or the `name` or `uri` attributes for accessing specific properties. The `properties` attribute should only be used for iterating over the currently present resource properties, but not for expecting particular properties. * Fixing regression in findall(name=..) (issue #141). **Enhancements:** * Changed links to HMC API books in Bibliography to no longer require IBM ID (issue #131). * Added example shell script showing how to use the command line interface. * Improved the examples with better print messages, exception handling, access of resource properties, and refreshing of resources. * Added support for load-parameter field in lpar.load(). Version 0.8.0 ^^^^^^^^^^^^^ Released: 2016-12-27 **Enhancements:** * Added support in CLI for remaining cmds; client improvements. * Added a tool 'tools/cpcdata' for gathering information about all CPCs managed by a set of HMCs. The data can optionally be appended to a CSV spreadsheet, for regular monitoring. Version 0.7.0 ^^^^^^^^^^^^^ Released: 2016-12-08 **Bug fixes:** * IOError during click-spinner 0.1.4 install (issue #120) **Enhancements:** * Documentation for zhmc CLI Version 0.6.0 ^^^^^^^^^^^^^ Released: 2016-12-07 **Bug fixes:** * Fixed typo in help message of cpcinfo. * Fixed KeyError: 'status' when running example5.py (issue #99). * Fixed documentation of field Partition.hbas (issue #101). * Fixed new Flake8 issue E305. **Enhancements:** * Started raising a `ParseError` exception when the JSON payload in a HTTP response cannot be parsed, and improved the definition of the ParseError exception by adding line and column information. * Improved the `AuthError` and `ConnectionError` exceptions by adding a `details` property that provides access to the underlying exception describing details. * For asynchronous operations that are invoked with `wait_for_completion`, added an entry in the time statistics for the overall operation from the start to completion of the asynchronous operation. That entry is for a URI that is the target URI, appended with "+completion". * Added time statistics entry for overall asynchronous operations. * Improved VersionError exception class and removed number-of-args tests. * Added the option to create a session object with a given session id. * Added base implementation of a command line interface (zhmc) for the zhmcclient. Version 0.5.0 ^^^^^^^^^^^^^ Released: 2016-10-04 **Incompatible changes:** * In ``VirtualSwitch.get_connected_vnics()``, renamed the method to :meth:`~zhmcclient.VirtualSwitch.get_connected_nics` and changed its return value to return :class:`~zhmcclient.Nic` objects instead of their URIs. **Bug fixes:** * Fixed that in `Partition.dump_partition()`, `wait_for_completion` was always passed on as `True`, ignoring the corresponding input argument. **Enhancements:** * Added a script named ``tools/cpcinfo`` that displays information about CPCs. Invoke with ``-h`` for help. * Added a :meth:`~zhmcclient.BaseResource.prop` method for resources that allows specifying a default value in case the property does not exist. * Added :meth:`~zhmcclient.Cpc.get_wwpns` which performs HMC operation 'Export WWPN List'. * Added :meth:`~zhmcclient.Hba.reassign_port` which performs HMC operation 'Reassign Storage Adapter Port'. * Clarifications in the :ref:`Resource model` section. * Optimized :attr:`~zhmcclient.Cpc.dpm_enabled` property to use 'List Partitions' and 'List Logical Partitions' operations, in order to avoid the 'List CPC Properties' operation. * Improved tutorials. Version 0.4.0 ^^^^^^^^^^^^^ Released: 2016-09-13 This is the base version for this change log. zhmcclient-0.22.0/docs/notebooks/0000755000076500000240000000000013414661056017255 5ustar maierastaff00000000000000zhmcclient-0.22.0/docs/notebooks/tututils.py0000644000076500000240000000335113364325033021522 0ustar maierastaff00000000000000# 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 module for the Jupyter notebooks used as tutorials. """ import getpass import requests import six import zhmcclient requests.packages.urllib3.disable_warnings() USERID = None PASSWORD = None def make_client(zhmc, userid=None, password=None): """ Create a `Session` object for the specified HMC and log that on. Create a `Client` object using that `Session` object, and return it. If no userid and password are specified, and if no previous call to this method was made, userid and password are interactively inquired. Userid and password are saved in module-global variables for future calls to this method. """ global USERID, PASSWORD # pylint: disable=global-statement USERID = userid or USERID or \ six.input('Enter userid for HMC {}: '.format(zhmc)) PASSWORD = password or PASSWORD or \ getpass.getpass('Enter password for {}: '.format(USERID)) session = zhmcclient.Session(zhmc, USERID, PASSWORD) session.logon() client = zhmcclient.Client(session) print('Established logged-on session with HMC {} using userid {}'. format(zhmc, USERID)) return client zhmcclient-0.22.0/docs/notebooks/03_datamodel.ipynb0000644000076500000240000002705713127454403022564 0ustar maierastaff00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Tutorial 3: Python objects representing HMC resources" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This tutorial explains how the zhmcclient package maps the HMC operations and the resources exposed by the HMC to Python objects, and how to navigate between these objects. This tutorial mostly uses the CPC and its partitions as example resources, but the same principles apply to nearly all types of resources exposed by the HMC." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In order to keep the code in these tutorials simple, the creation of the `Session` and `Client` objects has been moved into a function `make_client()` in a utility module `tututils`.\n", "\n", "The following code section creates a logged-on client for the specified HMC. When invoked for the first time in a notebook, `make_client()` asks for userid and password and saves that in the module. On subsequent invocations (within the same notebook), it uses the saved userid and password." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import tututils\n", "zhmc = '9.152.150.65' # edit this to your HMC's IP address or host name\n", "user = 'ensadmin' # edit this to the userid on that HMC\n", "client = tututils.make_client(zhmc, user)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " In the zhmcclient package, all resources exposed by the HMC are encapsulated as Python objects. The following code section lists the CPCs managed by the HMC and examines the first [`Cpc`](https://python-zhmcclient.readthedocs.io/en/latest/resources.html#zhmcclient.Cpc) object in the list:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from pprint import pprint\n", "cpcs = client.cpcs.list()\n", "cpc = cpcs[0]\n", "print(type(cpc))\n", "print(\"Public symbols:\")\n", "pprint([s for s in sorted(dir(cpc)) if not s.startswith('_')])\n", "print(\"Resource properties:\")\n", "pprint(cpc.properties)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The public symbols are (Python) properties or methods and are described in the zhmcclient documentation (see [`Cpc`](https://python-zhmcclient.readthedocs.io/en/latest/resources.html#zhmcclient.Cpc)).\n", "\n", "This `Cpc` object has only three resource properties: `name`, `object-uri`, and `status`. The zhmcclient package provides these resource properties as a dictionary in the [`properties`](https://python-zhmcclient.readthedocs.io/en/latest/appendix.html#zhmcclient.BaseResource.properties) instance variable of the `Cpc` object. The names of these resource properties are unchanged from what the [HMC API](https://python-zhmcclient.readthedocs.io/en/latest/appendix.html#term-hmc-api) book defines. The zhmcclient documentation refers to the HMC API book for a list and description of the resource properties.\n", "\n", "The [`list()`](https://python-zhmcclient.readthedocs.io/en/latest/resources.html#zhmcclient.CpcManager.list) method only returned these three resource properties, but a CPC resource has many more properties. In the HMC API, list operations generally return only a small set of the most important properties, mostly for identification and status of the resource.\n", "\n", "The following code retrieves the full set of resource properties for that CPC and prints them:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "cpc.pull_full_properties()\n", "print(\"Properties:\")\n", "pprint(cpc.properties)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because of this behavior, a Python object representing a resource may not always have all properties of the resource present. The [`get_property()`](https://python-zhmcclient.readthedocs.io/en/latest/appendix.html#zhmcclient.BaseResource.get_property) method allows accessing a specific named property, and retrieves it from the HMC if not currently present in the Python object.\n", "\n", "The following code section again lists the CPCs, creating a `Cpc` object with only three resource properties. The `get_property()` method is then used to access a property that is not among the initial three properties. This causes all resource properties to be retrieved from the HMC and stored in the `Cpc` object. The requested one is returned from the method:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "cpcs = client.cpcs.list()\n", "cpc = cpcs[0]\n", "print(\"Cpc object returned by list() has {} properties\".format(len(cpc.properties)))\n", "\n", "print(\"Accessing a property that is not yet present ...\")\n", "machine_type = cpc.get_property('machine-type')\n", "print(\"CPC machine type: {}\".format(machine_type))\n", "print(\"After retrieving machine-type, the Cpc object has {} properties\".format(len(cpc.properties)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `Cpc` object knows that it now has the full set of resource properties, so it uses them without further communication with the HMC:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(\"CPC machine model: {}\".format(cpc.get_property('machine-model')))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Accessing invalid resource properties (i.e. properties not described in the HMC API book for the resource) causes a `KeyError` exception to be raised:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "try:\n", " print(\"CPC foo: {}\".format(cpc.get_property('foo')))\n", "except Exception as exc:\n", " print(\"{}: {}\".format(exc.__class__.__name__, exc))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The [`prop()`](https://python-zhmcclient.readthedocs.io/en/latest/appendix.html#zhmcclient.BaseResource.prop) method returns a resource property value and allows specifying a default value in case the property is invalid:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(\"CPC foo: {}\".format(cpc.prop('foo', 'invalid')))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The resources in the HMC are organized as a tree. The zhmcclient package reflects that in the organization of the Python objects representing these resources. The top-level object is [`Client`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.Client) which represents the HMC. It allows navigating to the CPCs managed by the HMC via its [`cpcs`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.Client.cpcs) property.\n", "\n", "Each Python object representing a resource allows navigating down to its child resources, and each child resource allows navigating up to its parent resource. For example, a [`Cpc`](https://python-zhmcclient.readthedocs.io/en/latest/resources.html#zhmcclient.Cpc) object represents a CPC, and its [`lpars`](https://python-zhmcclient.readthedocs.io/en/latest/resources.html#zhmcclient.Cpc.lpars) instance variable allows navigating to the LPARs of the CPC, represented by [`Lpar`](https://python-zhmcclient.readthedocs.io/en/latest/resources.html#zhmcclient.Lpar) objects. An `Lpar` object allows navigating up to its parent `Cpc` object via the generic [`manager.parent`](https://python-zhmcclient.readthedocs.io/en/latest/appendix.html#zhmcclient.BaseManager.parent) instance variable, and also via the specific [`manager.cpc`](https://python-zhmcclient.readthedocs.io/en/latest/resources.html#zhmcclient.LparManager.cpc) instance variable that is named according to the type of parent.\n", "\n", "The following code navigates from a `Cpc` object to its partitions (`Lpar` or `Partition` dependent on the CPC mode) and navigates back up from the first partition to its parent resource, which is the same `Cpc` Python object we started from:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# We use the cpc object from above\n", "print(\"CPC: name={}, Python object id={}\".format(cpc.prop('name'), id(cpc)))\n", "\n", "if cpc.dpm_enabled:\n", " parts = cpc.partitions.list()\n", "else:\n", " parts = cpc.lpars.list()\n", "part = parts[0]\n", "kind = part.__class__.__name__\n", "\n", "print(\"Found {} partitions ({} child resources)\".format(len(parts), kind))\n", "print(\"First {}: name={}, Python object id={}\".format(kind, part.prop('name'), id(part)))\n", "\n", "p_cpc = part.manager.cpc\n", "print(\"Parent CPC: name={}, Python object id={}\".format(p_cpc.prop('name'), id(p_cpc)))\n", "print(\"Same Cpc Python objects: {}\".format(cpc is p_cpc))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The [`find()`](https://python-zhmcclient.readthedocs.io/en/latest/appendix.html#zhmcclient.BaseManager.find) method retrieves a resource by specifying the value of one (or more) of its properties.\n", "\n", "The following code finds the CPC we already know, based upon its name:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "cpc_name = cpc.prop('name') # could also have been specified as input\n", "\n", "print(\"Finding CPC by name={} ...\".format(cpc_name))\n", "cpc2 = client.cpcs.find(name=cpc_name)\n", "\n", "print(\"Found CPC: name={}, Python object id={}\".format(cpc2.prop('name'), id(cpc2)))\n", "print(\"Same Cpc Python objects: {}\".format(cpc is cpc2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the found `Cpc` Python object is not the same as the original `Cpc` Python object. These two Python objects represent the same CPC resource, but their state may be different (e.g. different resource properties present, properties obtained at different points in time, etc.).\n", "\n", "You generally cannot rely that the zhmcclient API always returns the same Python object for a specific HMC resource. The zhmcclient package tries to minimize the use of different objects (as we saw in the case of navigating back to the parent resource), but sometimes it cannot be avoided to return multiple Python objects for the same resource. The zhmcclient is a very thin client that abstracts the HMC API into a Python API without adding things like a shared resource cache." ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 0 } zhmcclient-0.22.0/docs/notebooks/01_notebook_basics.ipynb0000644000076500000240000001165713033142515023765 0ustar maierastaff00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Tutorial 1: Basics about Jupyter Notebooks" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You only need to look at this tutorial if you are new to Jupyter Notebooks.\n", "\n", "This page (and its file, `01_notebook_basics.ipynb`) is termed a *notebook*. Each notebook when opened in Jupyter, is a Python main module that can do anything a normal Python module can do. Specifically, different code sections can be executed one by one and execute all in the same Python main module.\n", "\n", "If you view this notebook using the Online Notebook Viewer (e.g. because you clicked on the link in section [Tutorials](https://python-zhmcclient.readthedocs.io/en/latest/tutorial.html#tutorials)), you cannot execute the code sections in the notebook or modify the notebook. Section [Executing code in the tutorials](https://python-zhmcclient.readthedocs.io/en/latest/tutorial.html#executing-code-in-the-tutorials) describes how to do that.\n", "\n", "A code section can be executed by selecting it and pressing Ctrl-Enter (That is for Linux; the key combination depends on the operating system you are using).\n", "\n", "Please execute this code section:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "a = 42" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And this code section:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The second code section prints the value only if you have executed the first code section before. You can see the order of code section executions in the number that is shown in brackets to the left of the code section.\n", "\n", "You can repeat the execution of a code section that was executed earlier and you can edit code sections (by double clicking on it).\n", "\n", "For example, you can edit the first code section above to use a different value, and execute both sections again." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Python environment used by Jupyter for a notebook is exactly the Python environment in which the `jupyter notebook` command was issued. It has the same Python version and the same Python packages installed:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import sys\n", "print(sys.version)\n", "print(sys.modules['six'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Jupyter dynamically creates a Python module for each notebook that is open. This notebook here for example runs in the following Python module:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "this_module = sys.modules[__name__]\n", "print(this_module)\n", "print('__name__ = %r' % this_module.__name__)\n", "print('__doc__ = %r' % this_module.__doc__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Different notebooks run in different Python processes and thus have no connection between their modules." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The current working directory in a notebook is the notebooks directory (that was specified when invoking `jupyter notebook`):" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import os\n", "print(os.getcwd())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Python module search path within a notebook adds some Jupyter directories at the end but otherwise is the normal search path that would be available when invoking `python`, i.e. it reflects the currently active Python environment. Specifically, it includes the current directory (the empty string), i.e. the notebooks directory." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import sys\n", "from pprint import pprint\n", "pprint(sys.path)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 0 } zhmcclient-0.22.0/docs/notebooks/04_error_handling.ipynb0000644000076500000240000002055613364325033023624 0ustar maierastaff00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Tutorial 4: Error handling" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This tutorial explains how errors are reported by the zhmcclient package to its users, and how they can be handled." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Error handling in the zhmcclient package is based on Python exceptions.\n", "\n", "If you have not dealt with Python exceptions yet, here are a few good articles about them:\n", "\n", "* [Jeff Knupp: Write Cleaner Python: Use Exceptions](https://jeffknupp.com/blog/2013/02/06/write-cleaner-python-use-exceptions/)\n", "* [Eli Bendersky: Robust exception handling](http://eli.thegreenplace.net/2008/08/21/robust-exception-handling/)\n", "* [Ian Bicking: Re-raising Exceptions](http://www.ianbicking.org/blog/2007/09/re-raising-exceptions.html)\n", "* [Sheena: Writing and Using Custom Exceptions in Python](https://www.codementor.io/python/tutorial/how-to-write-python-custom-exceptions)\n", "* [Joel Spolsky: Exceptions](http://www.joelonsoftware.com/items/2003/10/13.html) (a critical view on exceptions)\n", "\n", "The zhmcclient package raises two kinds of exceptions:\n", "\n", "* Exceptions derived from [`zhmcclient.Error`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.Error). Such exceptions indicate for example errors in the communication with the HMC, or authentication errors, or the HMC was unable to find a particular resource by name.\n", "\n", "* Other exceptions. Other exceptions should not normally be raised, and most of the time indicate a programming error, either on behalf of the zhmcclient package, or on behalf of the user using it.\n", "\n", "The documentation of the zhmcclient API lists for the most part only the exceptions derived from `zhmcclient.Error`. Other exceptions may in addition be raised and should be considered programming errors." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following list shows the class hierarchy of `zhmcclient.Error` exceptions and its derived exceptions:\n", "\n", "* [`zhmcclient.Error`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.Error) - exception base class. It is an abstract base class, meaning that this exception is not raised; but it is the base class for all zhmcclient-defined exception classes and can therefore be used to catch all zhmcclient-specific exceptions:\n", " * [`zhmcclient.ConnectionError`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.ConnectionError) - indicates a problem with the connection to the HMC, at the transport level or below. Is used both for being raised, and as a base class for more specific exceptions:\n", " * [`zhmcclient.ConnectTimeout`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.ConnectTimeout) - indicates that a connection to the HMC timed out after exhausting the connect retries.\n", " * [`zhmcclient.RetriesExceeded`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.RetriesExceeded) - 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.\n", " * [`zhmcclient.ReadTimeout`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.ConnectTimeout) - indicates that reading an HTTP response from the HMC timed out after exhausting the read retries.\n", " * [`zhmcclient.AuthError`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.AuthError) - indicates an authentication error with the HMC, either at the TLS/SSL handshake level (e.g. with CA certificates), or at the HTTP level. Is used both for being raised, and as a base class for more specific exceptions:\n", " * [`zhmcclient.ClientAuthError`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.ClientAuthError) - indicates an authentication related problem detected on the client side.\n", " * [`zhmcclient.ServerAuthError`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.ServerAuthError) - indicates an authentication error with the HMC.\n", " * [`zhmcclient.HTTPError`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.HTTPError) - indicates that the HMC returned an HTTP response with a bad HTTP status code.\n", " * [`zhmcclient.OperationTimeout`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.OperationTimeout) - indicates that the waiting for completion of an asynchronous HMC operation has timed out.\n", " * [`zhmcclient.StatusTimeout`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.StatusTimeout) - indicates that the waiting for reaching a desired LPAR or Partition status has timed out.\n", " * [`zhmcclient.ParseError`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.ParseError) - indicates a parsing error while processing a response from the HMC.\n", " * [`zhmcclient.VersionError`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.VersionError) - indicates that there is a version mismatch between the HMC API versions supported by the client and by the HMC.\n", " * [`zhmcclient.NoUniqueMatch`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.NoUniqueMatch) - indicates that more than one resource matched the filter arguments.\n", " * [`zhmcclient.NotFound`](https://python-zhmcclient.readthedocs.io/en/latest/general.html#zhmcclient.NotFound) - indicates that a resource was not found based on the filter arguments. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following code can be used to play around and see how some exceptions are triggered:\n", "\n", "* `zhmcclient.ConnectionError`, if you specify an IP address in the `zhmc` variable that cannot be reached from where you run this Jupyter notebook.\n", "* `zhmcclient.AuthError`, if you specify an HMC userid that does not exist or if the password is invalid (assuming the IP address can be reached).\n", "* `zhmcclient.HTTPError`, if the password is the empty string.\n", "* Python `RuntimeError`, if you run this on a Python version that is not supported for the zhmcclient package (this may be a stupid example, but it is an example of a non-zhmcclient exception and indicates a user error)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import tututils\n", "import zhmcclient\n", "zhmc = '9.152.150.65' # edit this to your HMC's IP address or host name\n", "user = 'ensadmin' # edit this to the userid on that HMC\n", "client = tututils.make_client(zhmc, user)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The basic approach for handling exceptions with the zhmcclient package is the following:\n", "* Handle all exceptions derived from `zhmcclient.Error`.\n", "* Ignore all other exceptions (or let them be handled like you would handle programming errors)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are also situations where you want to handle specific zhmcclient exceptions in order to react to the situation. The following example code uses a backup CPC is the primary CPC cannot be found:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "primary_cpc_name = 'CPC1'\n", "backup_cpc_name = 'CPC1B'\n", "try:\n", " cpc = client.cpcs.find(name=primary_cpc_name)\n", "except zhmcclient.NotFound:\n", " cpc = client.cpcs.find(name=backup_cpc_name)\n", "print(\"Using CPC %s\" % cpc.name)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.5" } }, "nbformat": 4, "nbformat_minor": 1 } zhmcclient-0.22.0/docs/notebooks/02_connections.ipynb0000644000076500000240000001144313033142515023135 0ustar maierastaff00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Tutorial 2: Connecting to an HMC" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In order to use the zhmcclient package in a Jupyter notebook, it must be installed in the Python environment that was used to start Jupyter. Trying to import it shows whether it is installed:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import zhmcclient" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If it was not installed, close Jupyter, [install zhmcclient](https://python-zhmcclient.readthedocs.io/en/latest/intro.html#installation), and start Jupyter again." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When connecting to an HMC, the user needs to create two objects:\n", "\n", "* A `Session` object that represents a session with the HMC. A `Session` object can be created with or without credentials. It automatically logs on using the provided credentials if a particular HMC operation requires to be logged on. There are a few HMC operations that work without being logged on (e.g. retrieving the API version).\n", "\n", "* A `Client` object that is created on top of a `Session` object, and that provides the main entry point for the resources managed by the HMC. For example, it can list the CPCs managed by the HMC.\n", "\n", "The following code creates these two objects for a particular HMC without providing credentials:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "zhmc = '9.152.150.65'\n", "\n", "session = zhmcclient.Session(zhmc)\n", "client = zhmcclient.Client(session)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following code prints the API version supported by that HMC. If you have no connection to the HMC, a `ConnectionError` will be raised after a while." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "vi = client.version_info()\n", "print(\"HMC API version: {}.{}\".format(vi[0], vi[1]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The previous code section most likely will also show a `InsecureRequestWarning` because certificates are not used in the communication with the HMC.\n", "\n", "The following code section turns off that warning and repeats the version gathering:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import requests\n", "requests.packages.urllib3.disable_warnings()\n", "\n", "vi = client.version_info()\n", "print(\"HMC API version: {}.{}\".format(vi[0], vi[1]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This code section attempts to list the CPCs managed by that HMC:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(\"Listing CPCs managed by HMC %s ...\" % zhmc)\n", "cpcs = client.cpcs.list()\n", "for cpc in cpcs:\n", " print(cpc)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Executing the previous code section reveals that listing the CPCs requires to be logged on, but we did not specify credentials. As a result, an `AuthError` was raised.\n", "\n", "The following code section specifies credentials and performs the list opereration again:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [], "source": [ "import getpass\n", "\n", "userid = raw_input('Enter userid for HMC %s: ' % zhmc)\n", "password = getpass.getpass('Enter password for %s: ' % userid)\n", "\n", "session = zhmcclient.Session(zhmc, userid, password)\n", "client = zhmcclient.Client(session)\n", "\n", "print(\"Listing CPCs managed by HMC %s ...\" % zhmc)\n", "cpcs = client.cpcs.list()\n", "for cpc in cpcs:\n", " print(cpc)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 0 } zhmcclient-0.22.0/docs/general.rst0000644000076500000240000002322613364325033017422 0ustar maierastaff00000000000000.. 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. .. .. _`General features`: Reference: General features =========================== .. _`Session`: Session ------- .. automodule:: zhmcclient._session .. autoclass:: zhmcclient.Session :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.Session :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.Session :attributes: .. rubric:: Details .. autoclass:: zhmcclient.Job :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.Job :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.Job :attributes: .. rubric:: Details .. autofunction:: zhmcclient.get_password_interface .. _`Retry-timeout configuration`: Retry / timeout configuration ----------------------------- .. autoclass:: zhmcclient.RetryTimeoutConfig :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.RetryTimeoutConfig :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.RetryTimeoutConfig :attributes: .. rubric:: Details .. _`Client`: Client ------ .. automodule:: zhmcclient._client .. autoclass:: zhmcclient.Client :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.Client :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.Client :attributes: .. rubric:: Details .. _`Time Statistics`: Time Statistics --------------- .. automodule:: zhmcclient._timestats .. autoclass:: zhmcclient.TimeStatsKeeper :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.TimeStatsKeeper :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.TimeStatsKeeper :attributes: .. rubric:: Details .. autoclass:: zhmcclient.TimeStats :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.TimeStats :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.TimeStats :attributes: .. rubric:: Details .. _`Metrics`: Metrics ------- .. automodule:: zhmcclient._metrics .. autoclass:: zhmcclient.MetricsContextManager :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.MetricsContextManager :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.MetricsContextManager :attributes: .. rubric:: Details .. autoclass:: zhmcclient.MetricsContext :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.MetricsContext :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.MetricsContext :attributes: .. rubric:: Details .. autoclass:: zhmcclient.MetricGroupDefinition :members: :special-members: __str__ .. autoclass:: zhmcclient.MetricDefinition :members: :special-members: __str__ .. autoclass:: zhmcclient.MetricsResponse :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.MetricsResponse :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.MetricsResponse :attributes: .. rubric:: Details .. autoclass:: zhmcclient.MetricGroupValues :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.MetricGroupValues :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.MetricGroupValues :attributes: .. rubric:: Details .. autoclass:: zhmcclient.MetricObjectValues :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.MetricObjectValues :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.MetricObjectValues :attributes: .. rubric:: Details .. _`Logging`: Logging ------- .. automodule:: zhmcclient._logging .. _`Exceptions`: Exceptions ---------- .. automodule:: zhmcclient._exceptions .. autoclass:: zhmcclient.Error :members: :special-members: __str__ .. rubric:: Details .. autoclass:: zhmcclient.ConnectionError :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.ConnectionError :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.ConnectionError :attributes: .. rubric:: Details .. autoclass:: zhmcclient.ConnectTimeout :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.ConnectTimeout :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.ConnectTimeout :attributes: .. rubric:: Details .. autoclass:: zhmcclient.ReadTimeout :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.ReadTimeout :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.ReadTimeout :attributes: .. rubric:: Details .. autoclass:: zhmcclient.RetriesExceeded :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.RetriesExceeded :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.RetriesExceeded :attributes: .. rubric:: Details .. autoclass:: zhmcclient.AuthError :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.AuthError :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.AuthError :attributes: .. rubric:: Details .. autoclass:: zhmcclient.ClientAuthError :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.ClientAuthError :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.ClientAuthError :attributes: .. rubric:: Details .. autoclass:: zhmcclient.ServerAuthError :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.ServerAuthError :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.ServerAuthError :attributes: .. rubric:: Details .. autoclass:: zhmcclient.ParseError :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.ParseError :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.ParseError :attributes: .. rubric:: Details .. autoclass:: zhmcclient.VersionError :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.VersionError :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.VersionError :attributes: .. rubric:: Details .. autoclass:: zhmcclient.HTTPError :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.HTTPError :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.HTTPError :attributes: .. rubric:: Details .. autoclass:: zhmcclient.OperationTimeout :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.OperationTimeout :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.OperationTimeout :attributes: .. rubric:: Details .. autoclass:: zhmcclient.StatusTimeout :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.StatusTimeout :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.StatusTimeout :attributes: .. rubric:: Details .. autoclass:: zhmcclient.NotFound :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.NotFound :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.NotFound :attributes: .. rubric:: Details .. autoclass:: zhmcclient.NoUniqueMatch :members: :special-members: __str__ .. rubric:: Methods .. autoautosummary:: zhmcclient.NoUniqueMatch :methods: :nosignatures: .. rubric:: Attributes .. autoautosummary:: zhmcclient.NoUniqueMatch :attributes: .. rubric:: Details .. _`Constants`: Constants --------- .. automodule:: zhmcclient._constants :members: .. _`Utilities`: Utilities --------- .. # Note: In order to avoid the issue that automodule members are shown .. # in their module namespace (e.g. zhmcclient._utils), we maintain the .. # members of the _utils module manually. .. automodule:: zhmcclient._utils .. autofunction:: zhmcclient.datetime_from_timestamp .. autofunction:: zhmcclient.timestamp_from_datetime zhmcclient-0.22.0/docs/_extra/0000755000076500000240000000000013414661056016534 5ustar maierastaff00000000000000zhmcclient-0.22.0/docs/_extra/.dummy0000644000076500000240000000000013364325033017652 0ustar maierastaff00000000000000zhmcclient-0.22.0/minimum-constraints.txt0000644000076500000240000001021013367665262021107 0ustar maierastaff00000000000000# Pip constraints file for runtime and development. # # This constraints file specifies constraints that match the minimum versions # specified in the requirements files for runtime and development. The reason # for this approach is that in the CI systems, we want to be able to test with # the minimum package versions in order to catch any incorrect minimum versions # (see zhmcclient issue #199 as one example where a minimum version was # missing). # The versions specified in this file were the latest versions released on Pypi # as of zhmcclient v0.9.0 (2016-12-27, see Travis CI run #576 # https://travis-ci.org/zhmcclient/python-zhmcclient/builds/186986898). # Make sure that the package versions in minimum-constraints.txt are also # the minimum versions required in requirements.txt and dev-requirements.txt. # Dependencies for installation with Pip (must be installed in a separate pip call) # # Info: OS-installed package versions for some Linux distros: # * RHEL/CentOS 7.4.1708: # Python 2.7.5 2013-05-15 # pip 8.1.2 2016-05-11 (epel) # setuptools 0.9.8 2013-07-25 # wheel 0.24.0 2014-07-06 (epel) # pbr 1.8.1 2015-10-07 (epel) # * Ubuntu 16.04.03: # Python 2.7.12 2016-11-19 # pip 8.1.1 2016-03-17 # setuptools 20.7.0 2016-04-10 # wheel 0.29.0 2016-02-06 # pbr 1.8.0 2015-09-14 # * Ubuntu 17.04: # Python 2.7.12 2016-11-19 # pip 9.0.1 2016-11-06 # setuptools 33.1.1 2017-01-16 # wheel 0.29.0 2016-02-06 # pbr 1.10.0 2016-05-23 pip==9.0.1 setuptools==33.1.1 wheel==0.29.0 # Direct dependencies for runtime (must be consistent with requirements.txt) decorator==4.0.10 pbr==1.10.0 pytz==2016.10 requests==2.20.0 six==1.10.0 stomp.py==4.1.15 # Indirect dependencies for runtime (must be consistent with requirements.txt) certifi==2016.9.26 chardet==3.0.3 idna==2.5 urllib3==1.21.1 # Direct dependencies for development (must be consistent with dev-requirements.txt) # zhmcclient examples (imports into the example scripts): PyYAML==3.13 # Unit test (imports into testcases): pytest==3.0.5 mock==2.0.0 requests-mock==1.2.0 testfixtures==4.13.3 # Unit test (no imports, invoked via py.test script): pytest-cov==2.4.0 # Coverage reporting (no imports, invoked via coveralls script): python-coveralls==2.9.0 # Sphinx (no imports, invoked via sphinx-build script): Sphinx==1.7.6 sphinx-git==10.1.1 GitPython==2.1.1 # PyLint (no imports, invoked via pylint script): pylint==1.6.4 #; python_version == '2.7' # Flake8 (no imports, invoked via flake8 script): flake8==3.2.1 # Twine (no imports, invoked via twine script): twine==1.8.1 # Jupyter Notebook (no imports, invoked via jupyter script): jupyter==1.0.0 # Indirect dependencies for development (must be consistent with dev-requirements.txt) alabaster==0.7.9 appnope==0.1.0 #; sys_platform == "darwin" args==0.1.0 astroid==1.4.9 #; python_version == '2.7' Babel==2.3.4 backports-abc==0.5 backports.functools-lru-cache==1.3 backports.shutil-get-terminal-size==1.0.0 backports.ssl-match-hostname==3.5.0.1 bleach==1.5.0 clint==0.5.1 configparser==3.5.0 coverage==4.0.3 docutils==0.13.1 entrypoints==0.2.2 enum34==1.1.6 funcsigs==1.0.2 #; python_version < '3.3' functools32==3.2.3.post2 #; python_version == '2.7' gitdb2==2.0.0 html5lib==0.9999999 imagesize==0.7.1 ipykernel==4.5.2 ipython==5.1.0 ipython_genutils==0.1.0 ipywidgets==5.2.2 isort==4.2.5 Jinja2==2.8 jsonschema==2.5.1 jupyter_client==4.4.0 jupyter_console==5.0.0 jupyter_core==4.2.1 lazy-object-proxy==1.2.2 MarkupSafe==0.23 mccabe==0.5.3 mistune==0.7.3 nbconvert==5.0.0 nbformat==4.2.0 notebook==4.3.1 pandocfilters==1.4.1 pathlib2==2.1.0 pexpect==4.2.1 pickleshare==0.7.4 pkginfo==1.4.1 ptyprocess==0.5.1 py==1.4.32 pycodestyle==2.2.0 pyflakes==1.3.0 Pygments==2.1.3 python-dateutil==2.6.0 pyzmq==16.0.4 qtconsole==4.2.1 requests-toolbelt==0.7.0 scandir==1.5 simplegeneric==0.8.1 singledispatch==3.4.0.3 smmap2==2.0.1 snowballstemmer==1.2.1 sphinxcontrib-websupport==1.0.1 terminado==0.6 testpath==0.3 tornado==4.4.2 tqdm==4.11.2 traitlets==4.3.1 typing==3.6.1 webencodings==0.5.1 widgetsnbextension==1.2.6 wrapt==1.10.8 zhmcclient-0.22.0/appveyor.yml0000644000076500000240000001005713406001351016700 0ustar maierastaff00000000000000# Control file for Appveyor CI (https://www.appveyor.com/) # Must be located in the root directory of the Git repository. environment: matrix: - PYTHON_VERSION: 2.7 PYTHON_ARCH: 32 PYTHON_HOME: C:\Python27 # - PYTHON_VERSION: 2.7 # PYTHON_ARCH: 64 # PYTHON_HOME: C:\Python27-x64 # - PYTHON_VERSION: 3.4 # PYTHON_ARCH: 32 # PYTHON_HOME: C:\Python34 # - PYTHON_VERSION: 3.4 # PYTHON_ARCH: 64 # PYTHON_HOME: C:\Python34-x64 # - PYTHON_VERSION: 3.5 # PYTHON_ARCH: 32 # PYTHON_HOME: C:\Python35 # - PYTHON_VERSION: 3.5 # PYTHON_ARCH: 64 # PYTHON_HOME: C:\Python35-x64 # - PYTHON_VERSION: 3.6 # PYTHON_ARCH: 32 # PYTHON_HOME: C:\Python36 # - PYTHON_VERSION: 3.6 # PYTHON_ARCH: 64 # PYTHON_HOME: C:\Python36-x64 # - PYTHON_VERSION: 3.7 # PYTHON_ARCH: 32 # PYTHON_HOME: C:\Python37 - PYTHON_VERSION: 3.7 PYTHON_ARCH: 64 PYTHON_HOME: C:\Python37-x64 configuration: # These values will become the values of the PACKAGE_LEVEL env.var. # - minimum - latest install: - git --version - if %APPVEYOR_REPO_BRANCH%.==manual-ci-run. set _NEED_REBASE=true # This Git version requires user configuration in rebase step - if %_NEED_REBASE%.==true. git config user.name "dummy" - if %_NEED_REBASE%.==true. git config user.email "dummy@dummy" - if %_NEED_REBASE%.==true. git fetch origin master - if %_NEED_REBASE%.==true. git branch master FETCH_HEAD - if %_NEED_REBASE%.==true. git rebase master - git branch -av # TODO: Use the _MANUAL_CI_RUN variable in tox.ini to run certain parts only when set - if %APPVEYOR_REPO_BRANCH%.==manual-ci-run. set _MANUAL_CI_RUN=true - if %APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH%.==manual-ci-run. set _MANUAL_CI_RUN=true # Set PACKAGE_LEVEL for make - set PACKAGE_LEVEL=%configuration% # Examine the environment - echo %PATH% - echo %INCLUDE% - echo %LIB% - dir C:\ - dir # Show OS-level packages available for installation via "choco" # Note: "choco" is a Windows installer, similar to "yum" on RHEL. - choco source list # Add Python #- reg ADD HKCU\Software\Python\PythonCore\%PYTHON_VERSION%\InstallPath /ve /d "%PYTHON_HOME%" /t REG_SZ /f #- reg ADD HKLM\Software\Python\PythonCore\%PYTHON_VERSION%\InstallPath /ve /d "%PYTHON_HOME%" /t REG_SZ /f - set PATH=%PYTHON_HOME%;%PYTHON_HOME%\Scripts;%PATH% ## Install pip via get-pip.py - disabled because pip is already installed #- ps: (new-object System.Net.WebClient).Downloadfile('https://bootstrap.pypa.io/get-pip.py', 'C:\Users\appveyor\get-pip.py') #- ps: Start-Process -FilePath "C:\Python27\python.exe" -ArgumentList "C:\Users\appveyor\get-pip.py" -Wait -Passthru # Add CygWin - set PATH=C:\cygwin\bin;%PATH% ## Example for installing an OS-level package via "choco": # #- choco install -y #- set PATH=;%PATH% ## Example for manually installing OS-level prereqs: # ## Preparation #- echo set _PWD=%%%%~dp0>tmp_prereq_dir.bat #- call tmp_prereq_dir.bat #- rm tmp_prereq_dir.bat #- set _PREREQ_DIR=prereqs #- set _PREREQ_ABSDIR=%_PWD%%_PREREQ_DIR% #- echo Installing OS-level prereqs into: %_PREREQ_ABSDIR% #- mkdir %_PREREQ_DIR% # ## Install libxml2 #- set _PKGFILE=libxml2-2.7.8.win32.zip #- set _PKGDIR=libxml2-2.7.8.win32 #- wget -q -P %_PREREQ_DIR% ftp://ftp.zlatkovic.com/libxml/%_PKGFILE% #- unzip -q -d %_PREREQ_DIR% %_PREREQ_DIR%/%_PKGFILE% #- set INCLUDE=%_PREREQ_ABSDIR%\%_PKGDIR%\include;%INCLUDE% #- set LIB=%_PREREQ_ABSDIR%\%_PKGDIR%\lib;%LIB% #- set PATH=%_PREREQ_ABSDIR%\%_PKGDIR%\bin;%PATH% # Install tox - pip install tox==2.0.0 # Verify that the commands used in tox.ini are available - tox --version - make --version - sh --version # Verify that the commands used in Makefile are available - bash --version - rm --version - mv --version - find --version - tee --version - pip --version - python --version # This is not a C# project, build stuff at the test step instead: build: false before_test: test_script: - tox -e pywin zhmcclient-0.22.0/setup.py0000644000076500000240000000133013364325033016025 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Setup script. """ import setuptools setuptools.setup( setup_requires=['pbr>=1.8'], pbr=True) zhmcclient-0.22.0/examples/0000755000076500000240000000000013414661056016140 5ustar maierastaff00000000000000zhmcclient-0.22.0/examples/metrics.py0000755000076500000240000000752513367665262020206 0ustar maierastaff00000000000000#!/usr/bin/env python # Copyright 2016 IBM Corp. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Example: Create a metrics context, retrieve metrics and delete metrics context. """ import sys import yaml import time import requests.packages.urllib3 import zhmcclient requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % hmccreds_file) sys.exit(1) example = examples.get("metrics", None) if example is None: print("metrics section not found in credentials file %s" % hmccreds_file) sys.exit(1) hmc = example["hmc"] cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % (hmc, hmccreds_file)) sys.exit(1) userid = cred['userid'] password = cred['password'] try: print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) timestats = example.get("timestats", None) if timestats: session.time_stats_keeper.enable() metric_groups = [ 'cpc-usage-overview', # Only in classic mode 'logical-partition-usage', # Only in classic mode 'channel-usage', # Only in classic mode 'dpm-system-usage-overview', # Only in DPM mode 'partition-usage', # Only in DPM mode 'adapter-usage', # Only in DPM mode 'crypto-usage', # Only in DPM mode 'flash-memory-usage', # Only in DPM mode 'roce-usage', # Only in DPM mode 'virtualization-host-cpu-memory-usage', # Only in ensemble mode ] print("Creating Metrics Context ...") mc = cl.metrics_contexts.create( {'anticipated-frequency-seconds': 15, 'metric-groups': metric_groups}) sleep_time = 15 # seconds print("Sleeping for %s seconds ..." % sleep_time) time.sleep(sleep_time) print("Retrieving the current metric values ...") mr_str = mc.get_metrics() print("Current metric values:") mr = zhmcclient.MetricsResponse(mc, mr_str) for mg in mr.metric_group_values: 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'))) if not mg.object_values: print(" No resources") print("Deleting Metrics Context ...") mc.delete() print("Logging off ...") session.logoff() if timestats: print(session.time_stats_keeper) print("Done.") except zhmcclient.Error as exc: print("%s: %s" % (exc.__class__.__name__, exc)) sys.exit(1) zhmcclient-0.22.0/examples/get_inventory.py0000755000076500000240000000661413364325033021414 0ustar maierastaff00000000000000#!/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. """ Example showing the Get Inventory operation. """ import sys import logging import yaml import json import requests.packages.urllib3 from pprint import pprint import contextlib from collections import OrderedDict import zhmcclient @contextlib.contextmanager def pprint_for_ordereddict(): """ Context manager that causes pprint() to print OrderedDict objects as nicely as standard Python dictionary objects. """ od_saved = OrderedDict.__repr__ try: OrderedDict.__repr__ = dict.__repr__ yield finally: OrderedDict.__repr__ = od_saved requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) get_inventory = examples.get("get_inventory", None) if get_inventory is None: print("get_inventory not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) loglevel = get_inventory.get("loglevel", None) if loglevel is not None: level = getattr(logging, loglevel.upper(), None) if level is None: print("Invalid value for loglevel in credentials file %s: %s" % \ (hmccreds_file, loglevel)) sys.exit(1) logging.basicConfig(level=level) hmc = get_inventory["hmc"] resources = get_inventory["resources"] cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % \ (hmc, hmccreds_file)) sys.exit(1) userid = cred["userid"] password = cred["password"] print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) timestats = get_inventory.get("timestats", False) if timestats: session.time_stats_keeper.enable() print("Invoking get_inventory() for resources: %r" % resources) result = cl.get_inventory(resources) if True: print("List of returned resources, with subset of properties:") format_str = "%-26.26s %-32.32s %s" print(format_str % ("Class", "Name", "URI")) for res in result: uri = res.get('object-uri') or \ res.get('element-uri') or \ '???' line = format_str % (res['class'], res.get('name', '???'), uri) if 'adapter-family' in res: line += " adapter-family=%s" % res['adapter-family'] print(line) if False: print("Full dump of returned result:") with pprint_for_ordereddict(): pprint(result) print("Logging off ...") session.logoff() if timestats: print(session.time_stats_keeper) print("Done.") zhmcclient-0.22.0/examples/example_hmccreds.yaml0000644000076500000240000000503713364325033022330 0ustar maierastaff00000000000000examples: api_version: hmc: "192.168.111.5" #loglevel: debug list_cpc_and_lpar: hmc: "192.168.111.5" cpcname: P0000P30 #loglevel: debug #logmodule: zhmcclient._session #timestats: yes lpar_operations: hmc: "192.168.111.5" cpcname: P0000P30 lparname: PART8 loaddev: "5172" deactivate: yes #loglevel: debug #timestats: yes get_partial_and_full_properties: hmc: "192.168.111.5" cpcname: P0000P30 #loglevel: debug #timestats: yes async_operation_polling: hmc: "192.168.111.5" cpcname: P0000P30 cpcstatus: service-required lparname: PART8 #loglevel: debug #timestats: yes partition_lifecycle: hmc: "192.168.133.140" cpcname: JLSE1 partname: TestPart1 # loglevel: debug # timestats: yes mount_iso: hmc: "192.168.133.140" cpcname: JLSE1 partname: TestPart1 imagename: sles-12.iso imageinsfile: /suse.ins imagefile: images/sles-12.iso # loglevel: debug # timestats: yes activation_profiles: hmc: "192.168.111.5" cpcname: P0000P30 cpcstatus: service-required lparname: PART8 #loglevel: debug #timestats: yes jms_notifications: hmc: "192.168.111.209" cpcname: DPMSE2 cpcstatus: Active partname: sree_test amqport: 61612 adapter_port_vswitch: hmc: "192.168.111.5" #loglevel: debug #timestats: yes show_os_messages: hmc: "192.168.111.5" cpcname: P0000P30 partname: PART8 #loglevel: debug #logmodule: zhmcclient._session metrics: hmc: "9.152.150.65" #loglevel: debug #timestats: yes get_inventory: hmc: "192.168.111.5" resources: - cpc - partition - adapter #loglevel: info #timestats: yes list_free_crypto_domains: hmc: "192.168.111.5" cpcname: DPMSE2 crypto_adapter_names: - "Crypto 0124 A13B-12" #loglevel: info #timestats: yes list_storage_groups: hmc: "192.168.111.5" cpcname: DPMSE2 #sgname: BigOne #loglevel: info #logmodule: zhmcclient._session #timestats: yes "192.168.111.5": userid: ensadmin password: 1234 "192.168.133.140": userid: ensadmin password: 1234 "192.168.111.209": userid: ensadmin password: 1234 cpcs: P0000P05: description: "EC12" contact: "Joe" hmc_host: "192.168.111.5" hmc_userid: ensadmin hmc_password: 1234 zhmcclient-0.22.0/examples/api_version.py0000755000076500000240000000355713364325033021041 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Example shows the API version of an HMC. """ import sys import logging import yaml import requests.packages.urllib3 import zhmcclient requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) api_version = examples.get("api_version", None) if api_version is None: print("api_version not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) loglevel = api_version.get("loglevel", None) if loglevel is not None: level = getattr(logging, loglevel.upper(), None) if level is None: print("Invalid value for loglevel in credentials file %s: %s" % \ (hmccreds_file, loglevel)) sys.exit(1) logging.basicConfig(level=level) hmc = api_version["hmc"] print(__doc__) print("Using HMC %s with an unauthenticated session ..." % hmc) session = zhmcclient.Session(hmc) cl = zhmcclient.Client(session) vi = cl.version_info() print("HMC API version: {}.{}".format(vi[0], vi[1])) zhmcclient-0.22.0/examples/list_cpc_and_lpar.py0000755000076500000240000000700513364325033022153 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Example shows list CPCs and LPARs/partitions on a CPC; demonstrate logging. """ import sys import logging import yaml import requests.packages.urllib3 import zhmcclient requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) list_cpc_and_lpar = examples.get("list_cpc_and_lpar", None) if list_cpc_and_lpar is None: print("list_cpc_and_lpar not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) loglevel = list_cpc_and_lpar.get("loglevel", None) if loglevel is not None: level = getattr(logging, loglevel.upper(), None) if level is None: print("Invalid value for loglevel in credentials file %s: %s" % \ (hmccreds_file, loglevel)) sys.exit(1) logmodule = list_cpc_and_lpar.get("logmodule", None) if logmodule is None: logmodule = '' # root logger print("Logging for module %s with level %s" % (logmodule, loglevel)) handler = logging.StreamHandler() format_string = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' handler.setFormatter(logging.Formatter(format_string)) logger = logging.getLogger(logmodule) logger.addHandler(handler) logger.setLevel(level) hmc = list_cpc_and_lpar["hmc"] cpcname = list_cpc_and_lpar["cpcname"] cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % \ (hmc, hmccreds_file)) sys.exit(1) userid = cred['userid'] password = cred['password'] print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) timestats = list_cpc_and_lpar.get("timestats", False) if timestats: session.time_stats_keeper.enable() print("Listing CPCs ...") cpcs = cl.cpcs.list() for cpc in cpcs: print(cpc.name, cpc.get_property('status'), cpc.uri) print("Finding CPC by name=%s ..." % cpcname) try: cpc = cl.cpcs.find(name=cpcname) except zhmcclient.NotFound: print("Could not find CPC %s on HMC %s" % (cpcname, hmc)) sys.exit(1) print("Found CPC %s at: %s" % (cpc.name, cpc.uri)) print("Checking if DPM is enabled on CPC %s..." % cpc.name) if cpc.dpm_enabled: print("CPC %s is in DPM mode: Listing Partitions ..." % cpc.name) partitions = cpc.partitions.list() else: print("CPC %s is in classic mode: Listing LPARs ..." % cpc.name) partitions = cpc.lpars.list() for partition in partitions: print(partition.name, partition.get_property('status'), partition.uri) print("Logging off ...") session.logoff() if timestats: print(session.time_stats_keeper) print("Done.") zhmcclient-0.22.0/examples/mount_iso.py0000755000076500000240000001040613364325033020526 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Example mounting an ISO image to an existing partition and starting the partition. """ import sys import io import logging import yaml import json import requests.packages.urllib3 from pprint import pprint import zhmcclient requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) mount_iso = examples.get("mount_iso", None) if mount_iso is None: print("mount_iso not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) loglevel = mount_iso.get("loglevel", None) if loglevel is not None: level = getattr(logging, loglevel.upper(), None) if level is None: print("Invalid value for loglevel in credentials file %s: %s" % \ (hmccreds_file, loglevel)) sys.exit(1) logging.basicConfig(level=level) hmc = mount_iso["hmc"] cpcname = mount_iso["cpcname"] partname = mount_iso["partname"] imagename = mount_iso["imagename"] imagefile = mount_iso["imagefile"] imageinsfile = mount_iso["imageinsfile"] cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % \ (hmc, hmccreds_file)) sys.exit(1) userid = cred["userid"] password = cred["password"] print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) timestats = mount_iso.get("timestats", False) if timestats: session.time_stats_keeper.enable() try: print("Finding CPC by name=%s ..." % cpcname) try: cpc = cl.cpcs.find(name=cpcname) except zhmcclient.NotFound: print("Error: Could not find CPC %s on HMC %s" % (cpcname, hmc)) sys.exit(1) #print("Checking if DPM is enabled on CPC %s..." % cpc.name) #if not cpc.dpm_enabled: # print("Error: CPC %s is not in DPM mode." % cpc.name) # sys.exit(1) print("Finding Partition by name=%s on CPC %s ..." % (partname, cpc.name)) try: partition = cpc.partitions.find(name=partname) except zhmcclient.NotFound: print("Error: Partition %s does not exist" % partname) sys.exit(1) status = partition.get_property('status') print("Partition %s status: %s" % (partition.name, status)) if status == 'active': print("Stopping Partition %s ..." % partition.name) partition.stop() print("Opening image file %s ..." % imagefile) image_fp = open(imagefile, 'rb') print("Mounting image file as ISO image named %r with INS file %r in Partition %s ..." % (imagename, imageinsfile, partition.name)) partition.mount_iso_image(image_fp, imagename, imageinsfile) partition.pull_full_properties() print("Partition property 'boot-iso-image-name' has been set to image name: %r" % (partition.get_property('boot-iso-image-name'))) print("Setting 'iso-image' as a boot device ...") partition.update_properties({'boot-device': 'iso-image'}) print("Starting Partition %s ..." % partition.name) try: partition.start() except zhmcclient.HTTPError as exc: print("Error: %s" % exc) sys.exit(1) partition.pull_full_properties() status = partition.get_property('status') print("Partition %s status: %s" % (partition.name, status)) finally: print("Logging off ...") session.logoff() if timestats: print(session.time_stats_keeper) zhmcclient-0.22.0/examples/list_free_crypto_domains.py0000755000076500000240000000740713364325033023607 0ustar maierastaff00000000000000#!/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. """ Example showing the free crypto domains of a set of crypto adapters in a CPC. """ import sys import logging import yaml import json import requests.packages.urllib3 import operator import itertools import zhmcclient requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) list_free_crypto_domains = examples.get("list_free_crypto_domains", None) if list_free_crypto_domains is None: print("list_free_crypto_domains not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) loglevel = list_free_crypto_domains.get("loglevel", None) if loglevel is not None: level = getattr(logging, loglevel.upper(), None) if level is None: print("Invalid value for loglevel in credentials file %s: %s" % \ (hmccreds_file, loglevel)) sys.exit(1) logging.basicConfig(level=level) hmc = list_free_crypto_domains["hmc"] cpcname = list_free_crypto_domains["cpcname"] crypto_adapter_names = list_free_crypto_domains["crypto_adapter_names"] cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % \ (hmc, hmccreds_file)) sys.exit(1) userid = cred["userid"] password = cred["password"] print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) timestats = list_free_crypto_domains.get("timestats", False) if timestats: session.time_stats_keeper.enable() cpc = cl.cpcs.find(name=cpcname) if crypto_adapter_names: crypto_adapters = [cpc.adapters.find(name=ca_name) for ca_name in crypto_adapter_names] else: crypto_adapters = cpc.adapters.findall(type='crypto') crypto_adapter_names = [ca.name for ca in crypto_adapters] print("Determining crypto configurations of all partitions on CPC %r ..." % cpc.name) for partition in cpc.partitions.list(full_properties=True): crypto_config = partition.get_property('crypto-configuration') if crypto_config: print("Partition %r has crypto configuration:" % partition.name) print(json.dumps(crypto_config, indent=4)) print("Determining free crypto domains on all of the crypto adapters %r on " "CPC %r ..." % (crypto_adapter_names, cpc.name)) free_domains = cpc.get_free_crypto_domains(crypto_adapters) # Convert this list of numbers into better readable number ranges: ranges = [] for k, g in itertools.groupby(enumerate(free_domains), lambda (i, x): i - x): group = map(operator.itemgetter(1), g) if group[0] == group[-1]: ranges.append("{}".format(group[0])) else: ranges.append("{}-{}".format(group[0], group[-1])) free_domains_str = ', '.join(ranges) print("Free domains: %s" % free_domains_str) print("Logging off ...") session.logoff() if timestats: print(session.time_stats_keeper) print("Done.") zhmcclient-0.22.0/examples/jms_notifications.py0000755000076500000240000001332313364325033022235 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Example demonstrates JMS notifications for completion of async operation """ import sys from time import sleep import threading from pprint import pprint import yaml import requests.packages.urllib3 import stomp import zhmcclient __callback = None requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % (hmccreds_file)) sys.exit(1) jms_notifications = examples.get("jms_notifications", None) if jms_notifications is None: print("jms_notifications not found in credentials file %s" % (hmccreds_file)) sys.exit(1) hmc = jms_notifications["hmc"] cpcname = jms_notifications["cpcname"] partname = jms_notifications["partname"] amqport = jms_notifications['amqport'] callback = None topic = None cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % (hmc, hmccreds_file)) sys.exit(1) userid = cred['userid'] password = cred['password'] # Thread-safe handover of notifications between listener and main threads NOTI_DATA = None NOTI_LOCK = threading.Condition() class MyListener(object): def on_connecting(self, host_and_port): print("Listener: Attempting to connect to message broker") sys.stdout.flush() def on_connected(self, headers, message): print("Listener: Connected to broker") sys.stdout.flush() def on_disconnected(self): print("Listener: Disconnected from broker") sys.stdout.flush() def on_error(self, headers, message): print('Listener: Received an error: %s' % message) sys.stdout.flush() def on_message(self, headers, message): global NOTI_DATA, NOTI_LOCK print('Listener: Received a notification') sys.stdout.flush() with NOTI_LOCK: # Wait until main program has processed the previous notification while NOTI_DATA: NOTI_LOCK.wait() # Indicate to main program that there is a new notification NOTI_DATA = headers NOTI_LOCK.notifyAll() print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) print("Retrieving notification topics ...") topics = session.get_notification_topics() for topic in topics: if topic['topic-type'] == 'job-notification': job_topic_name = topic['topic-name'] break conn = stomp.Connection([(session.host, amqport)], use_ssl="SSL") conn.set_listener('', MyListener()) conn.start() conn.connect(userid, password, wait=True) sub_id = 42 # subscription ID print("Subscribing for job notifications using topic: %s" % job_topic_name) conn.subscribe(destination="/topic/"+job_topic_name, id=sub_id, ack='auto') print("Finding CPC by name=%s ..." % cpcname) try: cpc = cl.cpcs.find(name=cpcname) except zhmcclient.NotFound: print("Could not find CPC %s on HMC %s" % (cpcname, hmc)) sys.exit(1) print("Finding partition by name=%s ..." % partname) try: partition = cpc.partitions.find(name=partname) except zhmcclient.NotFound: print("Could not find partition %s in CPC %s" % (partname, cpc.name)) sys.exit(1) print("Accessing status of partition %s ..." % partition.name) partition_status = partition.get_property('status') print("Status of partition %s: %s" % (partition.name, partition_status)) if partition_status == 'active': print("Stopping partition %s asynchronously ..." % partition.name) job = partition.stop(wait_for_completion=False) elif partition_status in ('inactive', 'stopped'): print("Starting partition %s asynchronously ..." % partition.name) job = partition.start(wait_for_completion=False) else: raise zhmcclient.Error("Cannot deal with partition status: %s" % \ partition_status) print("Waiting for completion of job %s ..." % job.uri) sys.stdout.flush() # Just for demo purposes, we show how a loop for processing multiple # notifications would look like. while True: with NOTI_LOCK: # Wait until listener has a new notification while not NOTI_DATA: NOTI_LOCK.wait() # Process the notification print("Received notification:") pprint(NOTI_DATA) sys.stdout.flush() # This test is just for demo purposes, it should always be our job # given what we subscribed for. if NOTI_DATA['job-uri'] == job.uri: break else: print("Unexpected completion received for job %s" % \ NOTI_DATA['job-uri']) sys.stdout.flush() # Indicate to listener that we are ready for next notification NOTI_DATA = None NOTI_LOCK.notifyAll() print("Job has completed: %s" % job.uri) sys.stdout.flush() conn.disconnect() sleep(1) # Allow listener to print disconnect message (just for demo) print("Done.") zhmcclient-0.22.0/examples/list_storage_groups.py0000755000076500000240000001151313364325033022610 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Example that lists storage groups (DPM mode, z14). """ import sys import logging import yaml import requests.packages.urllib3 import zhmcclient requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) list_storage_groups = examples.get("list_storage_groups", None) if list_storage_groups is None: print("list_storage_groups not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) loglevel = list_storage_groups.get("loglevel", None) if loglevel is not None: level = getattr(logging, loglevel.upper(), None) if level is None: print("Invalid value for loglevel in credentials file %s: %s" % \ (hmccreds_file, loglevel)) sys.exit(1) logmodule = list_storage_groups.get("logmodule", None) if logmodule is None: logmodule = '' # root logger print("Logging for module %s with level %s" % (logmodule, loglevel)) handler = logging.StreamHandler() format_string = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' handler.setFormatter(logging.Formatter(format_string)) logger = logging.getLogger(logmodule) logger.addHandler(handler) logger.setLevel(level) hmc = list_storage_groups["hmc"] cpcname = list_storage_groups["cpcname"] cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % \ (hmc, hmccreds_file)) sys.exit(1) userid = cred['userid'] password = cred['password'] print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) timestats = list_storage_groups.get("timestats", False) if timestats: session.time_stats_keeper.enable() print("Finding CPC %s ..." % cpcname) try: cpc = cl.cpcs.find(name=cpcname) except zhmcclient.NotFound: print("Could not find CPC %s on HMC %s" % (cpcname, hmc)) sys.exit(1) if False: print("Checking CPC %s to be in DPM mode ..." % cpcname) if not cpc.dpm_enabled: print("Storage groups require DPM mode, but CPC %s is not in DPM mode" % cpcname) sys.exit(1) print("Storage Groups of CPC %s ..." % cpcname) storage_groups = cpc.storage_groups.list() sgname = list_storage_groups.get("sgname", None) for sg in storage_groups: if sgname and sg.name != sgname: print(" Skipping storage group: %s" % sg.name) continue part_names = [p.name for p in sg.list_attached_partitions()] part_names_str = ', '.join(part_names) if part_names else "" print(" Storage Group: %s (type: %s, shared: %s, fulfillment: %s, " "attached to partitions: %s)" % (sg.name, sg.get_property('type'), sg.get_property('shared'), sg.get_property('fulfillment-state'), part_names_str)) try: volumes = sg.storage_volumes.list() except zhmcclient.HTTPError as exc: print("Error listing storage volumes of storage group %s:\n" "HTTPError: %s" % (sg.name, exc)) volumes = [] for sv in volumes: print(" Storage Volume: %s (oid: %s, uuid: %s, size: %s GiB, " "fulfillment: %s)" % (sv.name, sv.oid, sv.prop('uuid', 'N/A'), sv.get_property('size'), sv.get_property('fulfillment-state'))) try: vsrs = sg.virtual_storage_resources.list() except zhmcclient.HTTPError as exc: print("Error listing virtual storage resources of storage group %s:\n" "HTTPError: %s" % (sg.name, exc)) vsrs = [] for vsr in vsrs: port = vsr.adapter_port adapter = port.manager.parent print(" Virtual Storage Resource: %s (devno: %s, " "adapter.port: %s.%s, attached to partition: %s)" % (vsr.name, vsr.get_property('device-number'), adapter.name, port.name, vsr.attached_partition.name)) session.logoff() if timestats: print(session.time_stats_keeper) zhmcclient-0.22.0/examples/lpar_operations.py0000755000076500000240000001270413364325033021716 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Example shows how to find an LPAR in a CPC and activate/deactivate/load of an LPAR. """ import sys import logging import yaml import time import requests.packages.urllib3 import zhmcclient requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) lpar_operations = examples.get("lpar_operations", None) if lpar_operations is None: print("lpar_operations not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) loglevel = lpar_operations.get("loglevel", None) if loglevel is not None: level = getattr(logging, loglevel.upper(), None) if level is None: print("Invalid value for loglevel in credentials file %s: %s" % \ (hmccreds_file, loglevel)) sys.exit(1) logging.basicConfig(level=level) hmc = lpar_operations["hmc"] cpcname = lpar_operations["cpcname"] lparname = lpar_operations["lparname"] loaddev = lpar_operations["loaddev"] deactivate = lpar_operations["deactivate"] cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % \ (hmc, hmccreds_file)) sys.exit(1) userid = cred['userid'] password = cred['password'] print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) timestats = lpar_operations.get("timestats", None) if timestats: session.time_stats_keeper.enable() retries = 10 print("Finding CPC by name=%s ..." % cpcname) try: cpc = cl.cpcs.find(name=cpcname) except zhmcclient.NotFound: print("Could not find CPC %s on HMC %s" % (cpcname, hmc)) sys.exit(1) print("Found CPC %s at: %s" % (cpc.name, cpc.uri)) print("Finding LPAR by name=%s ..." % lparname) # We use list() instead of find() because find(name=..) is optimized by using # the name-to-uri cache and therefore returns an Lpar object with only a # minimal set of properties, and particularly no 'status' property. # That would drive an extra "Get Logical Partition Properties" operation when # the status property is accessed. lpars = cpc.lpars.list(filter_args={'name': lparname}) if len(lpars) != 1: print("Could not find LPAR %s in CPC %s" % (lparname, cpc.name)) sys.exit(1) lpar = lpars[0] print("Found LPAR %s at: %s" % (lpar.name, lpar.uri)) status = lpar.get_property('status') print("Status of LPAR %s: %s" % (lpar.name, status)) if status != "not-activated": print("Deactivating LPAR %s ..." % lpar.name) lpar.deactivate() for i in range(0, retries): print("Refreshing ...") lpar = cpc.lpars.list(filter_args={'name': lparname})[0] status = lpar.get_property('status') print("Status of LPAR %s: %s" % (lpar.name, status)) if status == 'not-activated': break time.sleep(1) else: print("Warning: After %d retries, status of LPAR %s after Deactivate " "is still: %s" % (retries, lpar.name, status)) print("Activating LPAR %s ..." % lpar.name) lpar.activate() for i in range(0, retries): print("Refreshing ...") lpar = cpc.lpars.list(filter_args={'name': lparname})[0] status = lpar.get_property('status') print("Status of LPAR %s: %s" % (lpar.name, status)) if status == 'not-operating': break time.sleep(1) else: print("Warning: After %d retries, status of LPAR %s after Activate " "is still: %s" % (retries, lpar.name, status)) print("Loading LPAR %s from device %s ..." % (lpar.name, loaddev)) lpar.load(loaddev) for i in range(0, retries): print("Refreshing ...") lpar = cpc.lpars.list(filter_args={'name': lparname})[0] status = lpar.get_property('status') print("Status of LPAR %s: %s" % (lpar.name, status)) if status == 'operating': break time.sleep(1) else: print("Warning: After %d retries, status of LPAR %s after Load " "is still: %s" % (retries, lpar.name, status)) if deactivate == "yes": print("Deactivating LPAR %s ..." % lpar.name) lpar.deactivate() for i in range(0, retries): print("Refreshing ...") lpar = cpc.lpars.list(filter_args={'name': lparname})[0] status = lpar.get_property('status') print("Status of LPAR %s: %s" % (lpar.name, status)) if status == 'not-activated': break time.sleep(1) else: print("Warning: After %d retries, status of LPAR %s after Deactivate " "is still: %s" % (retries, lpar.name, status)) print("Logging off ...") session.logoff() if timestats: print(session.time_stats_keeper) print("Done.") zhmcclient-0.22.0/examples/show_os_messages.py0000755000076500000240000001042513364325033022063 0ustar maierastaff00000000000000#!/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. """ Example that shows the OS messages of the OS in a Partition or LPAR. """ import sys import logging import yaml import requests.packages.urllib3 import zhmcclient requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) show_os_messages = examples.get("show_os_messages", None) if show_os_messages is None: print("show_os_messages not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) loglevel = show_os_messages.get("loglevel", None) if loglevel is not None: level = getattr(logging, loglevel.upper(), None) if level is None: print("Invalid value for loglevel in credentials file %s: %s" % \ (hmccreds_file, loglevel)) sys.exit(1) logmodule = show_os_messages.get("logmodule", None) if logmodule is None: logmodule = '' # root logger print("Logging for module %s with level %s" % (logmodule, loglevel)) handler = logging.StreamHandler() format_string = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' handler.setFormatter(logging.Formatter(format_string)) logger = logging.getLogger(logmodule) logger.addHandler(handler) logger.setLevel(level) hmc = show_os_messages["hmc"] cpcname = show_os_messages["cpcname"] partname = show_os_messages["partname"] cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % \ (hmc, hmccreds_file)) sys.exit(1) userid = cred['userid'] password = cred['password'] print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) timestats = show_os_messages.get("timestats", False) if timestats: session.time_stats_keeper.enable() try: cpc = cl.cpcs.find(name=cpcname) except zhmcclient.NotFound: print("Could not find CPC %s on HMC %s" % (cpcname, hmc)) sys.exit(1) try: if cpc.dpm_enabled: partkind = "partition" partition = cpc.partitions.find(name=partname) else: partkind = "LPAR" partition = cpc.lpars.find(name=partname) except zhmcclient.NotFound: print("Could not find %s %s on CPC %s" % (partkind, partname, cpcname)) sys.exit(1) print("Opening OS message channel for %s %s on CPC %s ..." % (partkind, partname, cpcname)) topic = partition.open_os_message_channel(include_refresh_messages=True) print("OS message channel topic: %s" % topic) receiver = zhmcclient.NotificationReceiver(topic, hmc, userid, password) print("Showing OS messages (including refresh messages) ...") 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_id = os_msg['message-id'] held = os_msg['is-held'] priority = os_msg['is-priority'] prompt = os_msg.get('prompt-text', None) print("# OS message %s (held: %s, priority: %s, prompt: %r):" % (msg_id, held, priority, prompt)) msg_txt = os_msg['message-text'].strip('\n') print(msg_txt) except KeyboardInterrupt: print("Keyboard interrupt: Closing OS message channel...") receiver.close() print("Logging off...") session.logoff() if timestats: print(session.time_stats_keeper) print("Done.") zhmcclient-0.22.0/examples/adapter_port_vswitch.py0000755000076500000240000001010613364325033022742 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Example shows how to use the Adapter, Port, Virtual Switch, NIC, HBA and Virtual Function interface. """ import sys import yaml import requests.packages.urllib3 import time import zhmcclient requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) adapter_port_vswitch = examples.get("adapter_port_vswitch", None) if adapter_port_vswitch is None: print("adapter_port_vswitch not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) loglevel = adapter_port_vswitch.get("loglevel", None) if loglevel is not None: level = getattr(logging, loglevel.upper(), None) if level is None: print("Invalid value for loglevel in credentials file %s: %s" % \ (hmccreds_file, loglevel)) sys.exit(1) logging.basicConfig(level=level) hmc = adapter_port_vswitch["hmc"] cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % \ (hmc, hmccreds_file)) sys.exit(1) userid = cred['userid'] password = cred['password'] print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) timestats = adapter_port_vswitch.get("timestats", None) if timestats: session.time_stats_keeper.enable() print("Listing CPCs ...") cpcs = cl.cpcs.list() for cpc in cpcs: print(cpc) print("\tListing Adapters for %s ..." % cpc.name) adapters = cpc.adapters.list() for i, adapter in enumerate(adapters): print('\t' + str(adapter)) ports = adapter.ports.list(full_properties=False) for p, port in enumerate(ports): if p == 0: print("\t\tListing Ports for %s ..." % adapter.name) # port.pull_full_properties() print('\t\t' + str(port)) print("\tListing Virtual Switches for %s ..." % cpc.name) vswitches = cpc.vswitches.list() for i, vswitch in enumerate(vswitches): print('\t' + str(vswitch)) print("\tListing Partitions for %s ..." % cpc.name) partitions = cpc.partitions.list() for i, partition in enumerate(partitions): print('\t' + str(partition)) nics = partition.nics.list(full_properties=False) for j, nic in enumerate(nics): if j == 0: print("\t\tListing NICs for %s ..." % partition.name) print('\t\t' + str(nic)) hbas = partition.hbas.list(full_properties=False) for j, hba in enumerate(hbas): if j == 0: print("\t\tListing HBAs for %s ..." % partition.name) print('\t\t' + str(hba)) # hba.pull_full_properties() # print('\t\t' + str(hba.properties)) vfs = partition.virtual_functions.list(full_properties=False) for k, vf in enumerate(vfs): if k == 0: print("\t\tListing Virtual Functions for %s ..." % partition.name) print('\t\t' + str(vf)) # vf.pull_full_properties() # print('\t\t' + str(vf.properties)) print("Logging off ...") session.logoff() if timestats: print(session.time_stats_keeper) print("Done.") zhmcclient-0.22.0/examples/activation_profiles.py0000755000076500000240000001113713364325033022560 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Example shows Activation Profiles handling. """ import sys import logging import yaml import requests.packages.urllib3 import zhmcclient requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) activation_profiles = examples.get("activation_profiles", None) if activation_profiles is None: print("activation_profiles not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) loglevel = activation_profiles.get("loglevel", None) if loglevel is not None: level = getattr(logging, loglevel.upper(), None) if level is None: print("Invalid value for loglevel in credentials file %s: %s" % \ (hmccreds_file, loglevel)) sys.exit(1) logging.basicConfig(level=level) hmc = activation_profiles["hmc"] cpcname = activation_profiles["cpcname"] lparname = activation_profiles["lparname"] cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % \ (hmc, hmccreds_file)) sys.exit(1) userid = cred["userid"] password = cred["password"] print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) timestats = activation_profiles.get("timestats", False) if timestats: session.time_stats_keeper.enable() print("Listing CPCs ...") cpcs = cl.cpcs.list() for cpc in cpcs: print(cpc.name, cpc.get_property('status'), cpc.uri) print("Finding CPC by name=%s ..." % cpcname) try: cpc = cl.cpcs.find(name=cpcname) except zhmcclient.NotFound: print("Could not find CPC %s on HMC %s" % (cpcname, hmc)) sys.exit(1) print("Checking if DPM is enabled on CPC %s..." % cpcname) if cpc.dpm_enabled: print("CPC %s is in DPM mode." % cpcname) sys.exit(1) managers = {'reset': 'reset_activation_profiles', 'image' : 'image_activation_profiles', 'load' : 'load_activation_profiles'} for profile_type, manager in managers.items(): profiles = getattr(cpc, manager).list() print("Listing %d %s Activation Profiles ..." % (len(profiles), profile_type.capitalize())) for profile in profiles: print(profile.name, profile.get_property('element-uri')) if profile_type == 'image': print("Finding %s Activation Profile by name=%s ..." % (profile_type.capitalize(), lparname)) profile = getattr(cpc, manager).find(name=lparname) print("Printing info properties:") print(profile.properties) # print("Printing full properties:") # profile.pull_full_properties() # print(profile.properties) original_description = profile.get_property('description') print("description: %s" % original_description) updated_properties = dict() updated_properties["description"] = "Test Test Test" profile.update_properties(updated_properties) print("Pull full properties of Image Activation Profile %s ..." % lparname) profile.pull_full_properties() print("Updated description of Image Activation Profile %s: %s" % (lparname, profile.get_property('description'))) print("Re-setting description ...") original_properties = dict() original_properties["description"] = original_description # original_properties["description"] = "OpenStack zKVM" profile.update_properties(original_properties) profile.pull_full_properties() print("Updated description of Image Activation Profile %s: %s" % (lparname, profile.get_property('description'))) print("Logging off ...") session.logoff() if timestats: print(session.time_stats_keeper) print("Done.") zhmcclient-0.22.0/examples/async_operation_polling.py0000755000076500000240000000666313364325033023445 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Example shows how to use the asynchronous interface in polling mode. """ import sys import logging import yaml import requests.packages.urllib3 import time import zhmcclient requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) async_operation_polling = examples.get("async_operation_polling", None) if async_operation_polling is None: print("async_operation_polling not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) loglevel = async_operation_polling.get("loglevel", None) if loglevel is not None: level = getattr(logging, loglevel.upper(), None) if level is None: print("Invalid value for loglevel in credentials file %s: %s" % \ (hmccreds_file, loglevel)) sys.exit(1) logging.basicConfig(level=level) hmc = async_operation_polling["hmc"] cpcname = async_operation_polling["cpcname"] cpcstatus = async_operation_polling["cpcstatus"] lparname = async_operation_polling["lparname"] cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % \ (hmc, hmccreds_file)) sys.exit(1) userid = cred['userid'] password = cred['password'] print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) timestats = async_operation_polling.get("timestats", None) if timestats: session.time_stats_keeper.enable() print("Finding CPC by name=%s and status=%s ..." % (cpcname, cpcstatus)) try: cpc = cl.cpcs.find(name=cpcname, status=cpcstatus) except zhmcclient.NotFound: print("Could not find CPC %s with status %s on HMC %s" % (cpcname, cpcstatus, hmc)) sys.exit(1) print("Finding LPAR by name=%s ..." % lparname) try: lpar = cpc.lpars.find(name=lparname) except zhmcclient.NotFound: print("Could not find LPAR %s in CPC %s" % (lparname, cpc.name)) sys.exit(1) print("Accessing status of LPAR %s ..." % lpar.name) status = lpar.get_property('status') print("Status of LPAR %s: %s" % (lpar.name, status)) print("De-Activating LPAR %s (async.) ..." % lpar.name) job = lpar.deactivate(wait_for_completion=False) while True: print("Retrieving job status ...") job_status, _ = job.check_for_completion() print("Job status: %s" % job_status) if job_status == 'complete': break time.sleep(1) print('De-Activation complete!') print("Logging off ...") session.logoff() if timestats: print(session.time_stats_keeper) print("Done.") zhmcclient-0.22.0/examples/partition_lifecycle.py0000755000076500000240000001116613364325033022546 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Example shows lifecycle (Create-Read-Update-Delete) of a Partition. """ import sys import logging import yaml import json import requests.packages.urllib3 from pprint import pprint import zhmcclient requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) partition_lifecycle = examples.get("partition_lifecycle", None) if partition_lifecycle is None: print("partition_lifecycle not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) loglevel = partition_lifecycle.get("loglevel", None) if loglevel is not None: level = getattr(logging, loglevel.upper(), None) if level is None: print("Invalid value for loglevel in credentials file %s: %s" % \ (hmccreds_file, loglevel)) sys.exit(1) logging.basicConfig(level=level) hmc = partition_lifecycle["hmc"] cpcname = partition_lifecycle["cpcname"] partname = partition_lifecycle["partname"] cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % \ (hmc, hmccreds_file)) sys.exit(1) userid = cred["userid"] password = cred["password"] print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) timestats = partition_lifecycle.get("timestats", False) if timestats: session.time_stats_keeper.enable() print("Listing CPCs ...") cpcs = cl.cpcs.list() for cpc in cpcs: print(cpc) print("Finding CPC by name=%s ..." % cpcname) try: cpc = cl.cpcs.find(name=cpcname) except zhmcclient.NotFound: print("Could not find CPC %s on HMC %s" % (cpcname, hmc)) sys.exit(1) print("Checking if DPM is enabled on CPC %s..." % cpc.name) if not cpc.dpm_enabled: print("CPC %s is not in DPM mode." % cpc.name) sys.exit(1) print("CPC %s is in DPM mode." % cpc.name) print("Finding Partition by name=%s on CPC %s ..." % (partname, cpc.name)) try: partition = cpc.partitions.find(name=partname) except zhmcclient.NotFound: print("Partition %s does not exist yet" % partname) else: print("Partition %s already exists - cleaning it up" % partition.name) status = partition.get_property('status') print("Partition %s status: %s" % (partition.name, status)) if status == 'active': print("Stopping Partition %s ..." % partition.name) partition.stop() print("Deleting Partition %s ..." % partition.name) partition.delete() properties = { 'name': partname, 'description': 'Original partition description.', 'cp-processors': 2, 'initial-memory': 1024, 'maximum-memory': 2048, 'processor-mode': 'shared', 'boot-device': 'test-operating-system' } print("Creating a new Partition %s on CPC %s with following properties ..." % (partname, cpcname)) pprint(properties) new_partition = cpc.partitions.create(properties) print("New Partition %s created at: %s" % (new_partition.name, new_partition.uri)) print("Starting Partition %s ..." % new_partition.name) new_partition.start() print("Description of Partition %s: %s" % (new_partition.name, new_partition.get_property('description'))) new_description = "Updated partition description." print("Updating partition description to: %s" % new_description) updated_properties = dict() updated_properties["description"] = new_description new_partition.update_properties(updated_properties) print("Refreshing properties of Partition %s ..." % new_partition.name) new_partition.pull_full_properties() print("Description of Partition %s: %s" % (new_partition.name, new_partition.get_property('description'))) print("Logging off ...") session.logoff() if timestats: print(session.time_stats_keeper) print("Done.") zhmcclient-0.22.0/examples/get_partial_and_full_properties.py0000755000076500000240000000705313364325033025131 0ustar maierastaff00000000000000#!/usr/bin/env python # 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. """ Example shows how to get partial and full properties for CPCs and for LPARs of a CPC. """ import sys import logging import yaml import requests.packages.urllib3 from datetime import datetime import zhmcclient requests.packages.urllib3.disable_warnings() if len(sys.argv) != 2: print("Usage: %s hmccreds.yaml" % sys.argv[0]) sys.exit(2) hmccreds_file = sys.argv[1] with open(hmccreds_file, 'r') as fp: hmccreds = yaml.load(fp) examples = hmccreds.get("examples", None) if examples is None: print("examples not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) get_partial_and_full_properties = examples.get("get_partial_and_full_properties", None) if get_partial_and_full_properties is None: print("get_partial_and_full_properties not found in credentials file %s" % \ (hmccreds_file)) sys.exit(1) loglevel = get_partial_and_full_properties.get("loglevel", None) if loglevel is not None: level = getattr(logging, loglevel.upper(), None) if level is None: print("Invalid value for loglevel in credentials file %s: %s" % \ (hmccreds_file, loglevel)) sys.exit(1) logging.basicConfig(level=level) hmc = get_partial_and_full_properties["hmc"] cpcname = get_partial_and_full_properties["cpcname"] cred = hmccreds.get(hmc, None) if cred is None: print("Credentials for HMC %s not found in credentials file %s" % \ (hmc, hmccreds_file)) sys.exit(1) userid = cred['userid'] password = cred['password'] print(__doc__) print("Using HMC %s with userid %s ..." % (hmc, userid)) session = zhmcclient.Session(hmc, userid, password) cl = zhmcclient.Client(session) timestats = get_partial_and_full_properties.get("timestats", None) if timestats: session.time_stats_keeper.enable() for full_properties in (False, True): print("Listing CPCs with full_properties=%s ..." % full_properties) start_dt = datetime.now() cpcs = cl.cpcs.list(full_properties) end_dt = datetime.now() duration = end_dt - start_dt print("Duration: %s" % duration) for cpc in cpcs: print("Number of properties of CPC %s: %s" % (cpc.name, len(cpc.properties))) print("Finding CPC by name=%s ..." % cpcname) try: cpc = cl.cpcs.find(name=cpcname) except zhmcclient.NotFound: print("Could not find CPC %s on HMC %s" % (cpcname, hmc)) sys.exit(1) print("Found CPC %s at: %s" % (cpc.name, cpc.uri)) for full_properties in (False, True): print("Listing LPARs on CPC %s with full_properties=%s ..." % (cpc.name, full_properties)) start_dt = datetime.now() lpars = cpc.lpars.list(full_properties) end_dt = datetime.now() duration = end_dt - start_dt print("Duration: %s" % duration) for lpar in lpars: print("Number of properties of LPAR %s: %s" % (lpar.name, len(lpar.properties))) print("Logging off ...") session.logoff() if timestats: print(session.time_stats_keeper) print("Done.") zhmcclient-0.22.0/.github/0000755000076500000240000000000013414661056015662 5ustar maierastaff00000000000000zhmcclient-0.22.0/.github/ISSUE_TEMPLATE.md0000644000076500000240000000024013364325033020357 0ustar maierastaff00000000000000### Actual behavior ### Expected behavior ### Execution environment * zhmcclient version: * Operating system (type+version): * HMC version: * CPC version: zhmcclient-0.22.0/tox.ini0000644000076500000240000000205313364325033015631 0ustar maierastaff00000000000000# ----------------------------------------------------------------------------- # Tox config file for the zhmcclient project # # Supported OS platforms: # Linux # Windows # OS-X (not tested) [tox] minversion = 1.9 envlist = py27,py34,py35,py36,check skip_missing_interpreters = true skipsdist = true [testenv] skip_install = true whitelist_externals = sh commands = sh -c "echo Installing Python packages with PACKAGE_LEVEL=$PACKAGE_LEVEL" sh -c "export TESTCASES={posargs}; make -B install develop test pyshow" [testenv:check] basepython = python2.7 whitelist_externals = make commands = make -B check build builddoc [testenv:py27] basepython = python2.7 passenv = PACKAGE_LEVEL [testenv:py34] basepython = python3.4 passenv = PACKAGE_LEVEL [testenv:py35] basepython = python3.5 passenv = PACKAGE_LEVEL [testenv:py36] basepython = python3.6 passenv = PACKAGE_LEVEL [testenv:pywin] basepython = {env:PYTHON_HOME:}\python.exe passenv = ProgramFiles APPVEYOR LOGNAME USER LNAME USERNAME HOME USERPROFILE PATH INCLUDE LIB PACKAGE_LEVEL zhmcclient-0.22.0/Vagrantfile0000644000076500000240000000045612732507354016516 0ustar maierastaff00000000000000# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "ubuntu/trusty64" config.vm.provision "shell", path: "provision.sh" end zhmcclient-0.22.0/setup.cfg0000644000076500000240000000244513414661056016150 0ustar maierastaff00000000000000[metadata] name = zhmcclient summary = A pure Python client library for the IBM Z HMC Web Services API. description-file = README.rst license = Apache License, Version 2.0 author = Juergen Leopold, Andreas Maier author-email = leopoldj@de.ibm.com, maiera@de.ibm.com maintainer = Juergen Leopold, Andreas Maier maintainer-email = leopoldj@de.ibm.com, maiera@de.ibm.com home-page = https://github.com/zhmcclient/python-zhmcclient classifier = Development Status :: 3 - Alpha Environment :: Console Intended Audience :: Developers Intended Audience :: Information Technology License :: OSI Approved :: Apache Software License Operating System :: OS Independent Programming Language :: Python 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 [files] packages = zhmcclient zhmcclient_mock scripts = tools/cpcinfo tools/cpcdata [wheel] universal = 1 [pbr] warnerrors = true [flake8] ignore = # unable to detect undefined names (when using wildcard import) F403 exclude = .git, .tox, __pycache__, *.pyc, docs/conf.py, build_doc, dist [egg_info] tag_build = tag_date = 0 zhmcclient-0.22.0/README.rst0000644000076500000240000001451413364325033016012 0ustar maierastaff00000000000000.. 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://travis-ci.org/zhmcclient/python-zhmcclient.svg?branch=master :target: https://travis-ci.org/zhmcclient/python-zhmcclient :alt: Travis test status (master) .. image:: https://ci.appveyor.com/api/projects/status/i022iaeu3dao8j5x/branch/master?svg=true :target: https://ci.appveyor.com/project/leopoldjuergen/python-zhmcclient :alt: Appveyor test status (master) .. image:: https://readthedocs.org/projects/python-zhmcclient/badge/?version=latest :target: http://python-zhmcclient.readthedocs.io/en/latest/ :alt: Docs build status (latest) .. image:: https://img.shields.io/coveralls/zhmcclient/python-zhmcclient.svg :target: https://coveralls.io/r/zhmcclient/python-zhmcclient :alt: Test coverage (master) .. image:: https://codeclimate.com/github/zhmcclient/python-zhmcclient/badges/gpa.svg :target: https://codeclimate.com/github/zhmcclient/python-zhmcclient :alt: Code Climate .. 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/stable/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 ============= The zhmcclient documentation is on RTD: * `Documentation for latest version on Pypi`_ * `Documentation for master branch in Git repo`_ .. _Documentation for latest version on Pypi: http://python-zhmcclient.readthedocs.io/en/stable/ .. _Documentation for master branch in Git repo: http://python-zhmcclient.readthedocs.io/en/latest/ 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/stable/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 zhmcclient-0.22.0/.mailmap0000644000076500000240000000055513021231665015741 0ustar maierastaff00000000000000# git mailmap file for consolidating git commit names and email addresses # For details, see: https://git-scm.com/docs/git-check-mailmap # # Format for replacing name and email based upon commit email: # Proper Name # Andreas Maier Andreas Maier zhmcclient-0.22.0/.travis.yml0000644000076500000240000001452413406001351016424 0ustar maierastaff00000000000000# Control file for Travis CI (http://travis-ci.org) # Must be located in the root directory of the Git repository. # By default, notification emails are sent to the PR creator and commiter. notifications: email: false # We define the job matrix explicitly, in order to minimize the # combinations. # For OS-X, using an explicit matrix is required, because Travis at # this point only has half-baked support for Python on OS-X that does # not work. Also, on OS-X, it needs to be invoked with language=generic # and an empty 'python' variable in order to prevent that Travis attempts # to install Python. # TODO: Figure out how specific versions of Python 3.x can be used with OS-X # When defining the job matrix explicitly, there are Travis environments # that produce an additional default job. See these Travis issues: # https://github.com/travis-ci/travis-ci/issues/1228 # https://github.com/travis-ci/travis-ci/issues/4681 # https://github.com/travis-ci/travis-ci/issues/9843 # The public Travis does not seem to have this issue anymore, # but Travis@IBM does have this issue (as of 9/2018). The workaround for # this issue is to define variables globally and to exclude this same # variable value in the matrix definition. Experiments have shown that # not all variable combinations work. Using a combination of 'language' # and 'os' set to the default values works. # See note about explicit job matrix, above. language: ruby os: linux matrix: # See note about explicit job matrix, above. exclude: - language: ruby - os: linux include: # - os: linux # language: python # python: "2.7" # env: # - PACKAGE_LEVEL=minimum # cache: pip - os: linux language: python python: "2.7" env: - PACKAGE_LEVEL=latest cache: pip - os: linux language: python python: "3.4" env: - PACKAGE_LEVEL=minimum cache: pip # - os: linux # language: python # python: "3.4" # env: # - PACKAGE_LEVEL=latest # cache: pip # - os: linux # language: python # python: "3.5" # env: # - PACKAGE_LEVEL=minimum # cache: pip # - os: linux # language: python # python: "3.5" # env: # - PACKAGE_LEVEL=latest # cache: pip # - os: linux # language: python # python: "3.6" # env: # - PACKAGE_LEVEL=minimum # cache: pip # - os: linux # language: python # python: "3.6" # env: # - PACKAGE_LEVEL=latest # cache: pip # - os: linux # dist: xenial # sudo: required # language: python # python: "3.7" # env: # - PACKAGE_LEVEL=minimum # cache: pip - os: linux dist: xenial sudo: required language: python python: "3.7" env: - PACKAGE_LEVEL=latest cache: pip # - os: linux # language: python # python: "pypy-5.3.1" # Python 2.7.10 # env: # - PACKAGE_LEVEL=minimum # cache: pip # - os: linux # language: python # python: "pypy-5.3.1" # Python 2.7.10 # env: # - PACKAGE_LEVEL=latest # cache: pip - os: osx language: generic python: env: - PACKAGE_LEVEL=minimum - PYTHON=2 cache: pip # - os: osx # language: generic # python: # env: # - PACKAGE_LEVEL=latest # - PYTHON=2 # cache: pip # - os: osx # language: generic # python: # env: # - PACKAGE_LEVEL=minimum # - PYTHON=3 # cache: pip - os: osx language: generic python: env: - PACKAGE_LEVEL=latest - PYTHON=3 cache: pip before_install: - env | sort # The following statement is a workaround to leave an OS-X job # when running on Linux. That happens on Travis@IBM which does not # have OS-X support but still runs os=osx on Linux. - if [[ "$TRAVIS_OS_NAME" == "osx" && "$_system_type" == "Linux" ]]; then echo "Exiting from OS-X job running on Linux"; exit; fi # The following statement is a safety net in case the matrix exclusion # does not work for some reason. - if [[ "$TRAVIS_LANGUAGE" == "ruby" ]]; then echo "Exiting from unwanted default Ruby job"; exit; fi - if [[ "$TRAVIS_BRANCH" == "manual-ci-run" ]]; then export _NEED_REBASE=true; fi - if [[ -n $_NEED_REBASE ]]; then git fetch origin master; fi - if [[ -n $_NEED_REBASE ]]; then git branch master FETCH_HEAD; fi - if [[ -n $_NEED_REBASE ]]; then git rebase master; fi - git branch -av # commands to install dependencies install: - which python - python --version - if [[ "$TRAVIS_BRANCH" == "manual-ci-run" || "$TRAVIS_PULL_REQUEST_BRANCH" == "manual-ci-run" ]]; then export _MANUAL_CI_RUN=true; fi - if [[ $TRAVIS_OS_NAME == 'osx' ]]; then if [[ ${PYTHON:0:1} == '2' ]]; then OSX_PYTHON_PKG=python; OSX_PYTHON_CMD=python; else OSX_PYTHON_PKG=python3; OSX_PYTHON_CMD=python3; fi; which $OSX_PYTHON_CMD || brew install $OSX_PYTHON_PKG; fi # Reason for setting up a Python virtualenv on OS-X: # Some Ansible scripts are invoked directly from the cloned Ansible repo # directory, so their hashbang is '/usr/bin/env python'. This requires # that the 'python' command and environment is the desired one. On OS-X, # when installing Python 3, only the 'python3' command invokes Python 3, # but the 'python' command invokes Python 2. Therefore, a virtualenv is # needed in which the desired Python version is available as the # 'python'. command. Travis does not set up a virtualenv on its OS-X # machines, so we need to do that here. - if [[ $TRAVIS_OS_NAME == 'osx' ]]; then pip install virtualenv; VENV=$HOME/virtualenv/$OSX_PYTHON_CMD; virtualenv -p $OSX_PYTHON_CMD $VENV; source $VENV/bin/activate; fi - which python - python --version - which pip - pip --version - pip list - make install - pip list - make develop - pip list - pip install python-coveralls - pip list # commands to run builds & tests script: - make check - if [[ -n $_MANUAL_CI_RUN ]]; then make pylint; fi - make test - make build - make builddoc after_success: - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PYTHON_VERSION" == "2.7" && "$PACKAGE_LEVEL" == "latest" && -z $_MANUAL_CI_RUN ]]; then coveralls; fi zhmcclient-0.22.0/.codeclimate.yml0000644000076500000240000000100313364325033017362 0ustar maierastaff00000000000000--- engines: duplication: enabled: true config: languages: python: mass_threshold: 40 checks: Similar code: enabled: false radon: enabled: true config: threshold: "D" fixme: enabled: true config: strings: - FIXME ratings: paths: - "**.py" # Including paths "**.py" also includes the ".pylintrc" file, so # we are excluding it again via ".*". exclude_paths: - ".*" - "design/" - "dist/" - "docs/" - "examples/" - "tests/"