diff --git a/docs/changes.rst b/docs/changes.rst index caee6039..2a935296 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -47,6 +47,20 @@ Released: not yet **Enhancements:** +* In Console.list_permitted_lpars/partitions(), added CPC-related properties + to the returned resource objects, that are returned by the HMC: 'cpc-name', + 'cpc-object-uri', 'se-version'. (issue #1421) + +* In Console.list_permitted_lpars(), the additional_properties parameter + is now supported also for HMC versions older than 2.16 GA 1.5. In that + case, the zhmcclient handles adding the properties. (related to issue #1421) + +* The pull_full_properties() and pull_properties() methods of zhmcclient + resource objects no longer replace existing properties but now update them, + so that additionally present properties (e.g. the CPC-related properties + returned from Console.list_permitted_lpars/partitions()) are preserved. + (related to issue #1421) + **Cleanup:** **Known issues:** diff --git a/tests/end2end/test_lpar.py b/tests/end2end/test_lpar.py index f4a035b2..61e95696 100644 --- a/tests/end2end/test_lpar.py +++ b/tests/end2end/test_lpar.py @@ -51,6 +51,14 @@ # Properties in Lpar objects returned by list() without full props LPAR_LIST_PROPS = ['object-uri', 'name', 'status'] +# Properties returned by default from list_permitted_lpars() +LPAR_LIST_PERMITTED_PROPS = [ + 'name', 'object-uri', 'activation-mode', 'status', + 'has-unacceptable-status', 'cpc-name', 'cpc-object-uri', + # HMCs return 'se-version' on HMC API version 4.10 or higher +] + + # Properties whose values can change between retrievals of Lpar objects LPAR_VOLATILE_PROPS = [] @@ -116,6 +124,109 @@ def test_lpar_property(classic_mode_cpcs): # noqa: F811 runtest_get_properties(client, lpar.manager, non_list_prop, (2, 14)) +TESTCASES_CONSOLE_LIST_PERMITTED_LPARS = [ + # Testcases for test_console_list_permitted_lpars() + + # Each testcase is a tuple of: + # * desc: description + # * input_kwargs: Keyword arguments to the test function + # * exp_prop_names: List of expected property names + ( + "Default arguments", + {}, + LPAR_LIST_PERMITTED_PROPS, + ), + ( + "Explicit defaults for arguments", + { + 'full_properties': False, + 'filter_args': None, + 'additional_properties': None + }, + LPAR_LIST_PERMITTED_PROPS, + ), + ( + "One server-side filter", + { + 'filter_args': {'status': 'not-operating'}, + }, + LPAR_LIST_PERMITTED_PROPS, + ), + ( + "One client-side filter that is not in result", + { + 'filter_args': {'has-operating-system-messages': False}, + }, + LPAR_LIST_PERMITTED_PROPS, + ), + ( + "Empty additional properties", + { + 'additional_properties': [], + }, + LPAR_LIST_PERMITTED_PROPS, + ), + ( + "One additional property", + { + 'additional_properties': ['description'], + }, + LPAR_LIST_PERMITTED_PROPS + ['description'], + ), + ( + "Full properties", + { + 'full_properties': True, + }, + LPAR_LIST_PERMITTED_PROPS + ['has-operating-system-messages'], + ), +] + + +@pytest.mark.parametrize( + "desc, input_kwargs, exp_prop_names", + TESTCASES_CONSOLE_LIST_PERMITTED_LPARS) +def test_console_list_permitted_lpars( + classic_mode_cpcs, desc, input_kwargs, exp_prop_names): # noqa: F811 + # pylint: disable=redefined-outer-name + """ + Test Console.list_permitted_lpars() method + """ + if not classic_mode_cpcs: + pytest.skip("HMC definition does not include any CPCs in classic mode") + + logger = setup_logging(LOGGING, 'test_console_list_permitted_lpars', + LOG_FILE) + logger.debug("Entered test function with: " + "desc=%r, input_kwargs=%r, exp_prop_names=%r", + desc, input_kwargs, exp_prop_names) + + for cpc in classic_mode_cpcs: + assert not cpc.dpm_enabled + + logger.debug("Testing with CPC %s", cpc.name) + + client = cpc.manager.client + console = client.consoles.console + + logger.debug("Calling list_permitted_lpars() with kwargs: %r", + input_kwargs) + + # Execute the code to be tested + lpars = console.list_permitted_lpars(**input_kwargs) + + logger.debug("list_permitted_lpars() returned %d LPARs", len(lpars)) + + for lpar in lpars: + lpar_props = dict(lpar.properties) + for pname in exp_prop_names: + assert pname in lpar_props, ( + "Property {!r} missing from returned LPAR properties, " + "got: {!r}".format(pname, lpar_props)) + + logger.debug("Leaving test function") + + def test_lpar_list_os_messages(classic_mode_cpcs): # noqa: F811 # pylint: disable=redefined-outer-name """ diff --git a/tests/unit/zhmcclient/test_lpar.py b/tests/unit/zhmcclient/test_lpar.py index 43f94a56..878ae20f 100644 --- a/tests/unit/zhmcclient/test_lpar.py +++ b/tests/unit/zhmcclient/test_lpar.py @@ -50,6 +50,14 @@ CPC_NAME = 'fake-cpc1-name' +# Properties returned by default from list_permitted_lpars() +LIST_PERMITTED_LPARS_PROPS = [ + 'name', 'object-uri', 'activation-mode', 'status', + 'has-unacceptable-status', 'cpc-name', 'cpc-object-uri', + # The zhmcclient_mock support always returns 'se-version' + 'se-version' +] + class TestLpar(object): """All tests for Lpar and LparManager classes.""" @@ -1465,21 +1473,25 @@ def test_console_list_permitted_lpars(self, filter_args, exp_names): names = [p.properties['name'] for p in lpars] assert set(names) == set(exp_names) + for lpar in lpars: + lpar_props = dict(lpar.properties) + for pname in LIST_PERMITTED_LPARS_PROPS: + assert pname in lpar_props, ( + "Property {!r} missing from returned LPAR properties, " + "got: {!r}".format(pname, lpar_props)) + @pytest.mark.parametrize( "list_kwargs, prop_names", [ ({}, - ['object-uri', 'name', 'status']), + LIST_PERMITTED_LPARS_PROPS), (dict(additional_properties=[]), - ['object-uri', 'name', 'status']), + LIST_PERMITTED_LPARS_PROPS), (dict(additional_properties=['description']), - ['object-uri', 'name', 'status', 'description']), + LIST_PERMITTED_LPARS_PROPS + ['description']), (dict(additional_properties=['description', 'activation-mode']), - ['object-uri', 'name', 'status', 'description', - 'activation-mode']), + LIST_PERMITTED_LPARS_PROPS + ['description', 'activation-mode']), (dict(additional_properties=['ssc-host-name']), - ['object-uri', 'name', 'status', 'ssc-host-name'] - # ssc-host-name is not on every lpar - ), + LIST_PERMITTED_LPARS_PROPS + ['ssc-host-name']), ] ) def test_console_list_permlpars_add_props( diff --git a/tests/unit/zhmcclient/test_partition.py b/tests/unit/zhmcclient/test_partition.py index bf1c7174..f02905c5 100644 --- a/tests/unit/zhmcclient/test_partition.py +++ b/tests/unit/zhmcclient/test_partition.py @@ -36,6 +36,14 @@ CPC_NAME = 'fake-cpc1-name' +# Properties returned by default from list_permitted_partitions() +LIST_PERMITTED_PARTITIONS_PROPS = [ + 'name', 'object-uri', 'type', 'status', 'has-unacceptable-status', + 'cpc-name', 'cpc-object-uri', + # The zhmcclient_mock support always returns 'se-version' + 'se-version' +] + class TestPartition(object): """All tests for the Partition and PartitionManager classes.""" @@ -985,6 +993,13 @@ def test_console_list_permitted_partitions(self, filter_args, exp_names): names = [p.properties['name'] for p in partitions] assert set(names) == set(exp_names) + for partition in partitions: + partition_props = dict(partition.properties) + for pname in LIST_PERMITTED_PARTITIONS_PROPS: + assert pname in partition_props, ( + "Property {!r} missing from returned partition properties, " + "got: {!r}".format(pname, partition_props)) + # TODO: Test for Partition.send_os_command() # TODO: Test for Partition.wait_for_status() diff --git a/zhmcclient/_console.py b/zhmcclient/_console.py index 1025e360..6f2f20d4 100644 --- a/zhmcclient/_console.py +++ b/zhmcclient/_console.py @@ -609,6 +609,32 @@ def list_permitted_partitions( The partitions in the result can be additionally limited by specifying filter arguments. + The partitions in the result will have the following partition + properties: + + * name (string): Name of the partition. + + * object-uri (string): Object URI of the partition. + + * type (string): Type of the partition (i.e. "linux", "ssc", "zvm"). + + * status (string): Status of the partition. See the data model of + the Partition object in the :term:`HMC API` book for values. + + * has-unacceptable-status (bool): Whether the status is unacceptable, + according to the values in the 'acceptable-status' property. + + and the following properties from their parent CPC: + + * cpc-name (string): Name of the parent CPC of the partition. + + * cpc-object-uri (string): Object URI of the parent CPC of the + partition. + + * se-version (string): SE version of the parent CPC of the partition, + as M.N.U string. Note that this property is returned only on newer + HMC 2.16 versions (HMC API version 4.10 or higher). + Authorization requirements: * Object permission to the partition objects included in the result. @@ -677,12 +703,10 @@ def list_permitted_partitions( partition_items = result['partitions'] for partition_item in partition_items: - # The partition items have the following partition properties: - # * name, object-uri, type, status, has-unacceptable-status - # And the following properties for their parent CPC: - # * cpc-name (CPC property 'name') - # * cpc-object-uri (CPC property 'object-uri') - # * se-version (CPC property 'se-version') (if >=2.14.1) + # The partition_item dicts have the following properties: + # * name, object-uri, type, status, has-unacceptable-status, + # cpc-name, cpc-object-uri + # * se-version (if HMC>=2.14.1) cpc_uri = partition_item['cpc-object-uri'] try: @@ -700,16 +724,9 @@ def list_permitted_partitions( cpcs_by_uri[cpc_uri] = cpc + partition_props = dict(partition_item) partition_obj = cpc.partitions.resource_object( - partition_item['object-uri'], - { - 'name': partition_item['name'], - 'type': partition_item['type'], - 'status': partition_item['status'], - 'has-unacceptable-status': - partition_item['has-unacceptable-status'], - }, - ) + partition_item['object-uri'], partition_props) # Apply client-side filtering if matches_filters(partition_obj, client_filters): @@ -734,6 +751,32 @@ def list_permitted_lpars( The LPARs in the result can be additionally limited by specifying filter arguments. + The LPARs in the result will have the following LPAR properties: + + * name (string): Name of the LPAR. + + * object-uri (string): Object URI of the LPAR. + + * activation-mode (string): Activation mode of the LPAR. See the data + model of the Logical Partition object in the :term:`HMC API` book for + values. + + * status (string): Status of the LPAR. See the data model of + the Logical Partition object in the :term:`HMC API` book for values. + + * has-unacceptable-status (bool): Whether the status is unacceptable, + according to the values in the 'acceptable-status' property. + + and the following properties from their parent CPC: + + * cpc-name (string): Name of the parent CPC of the LPAR. + + * cpc-object-uri (string): Object URI of the parent CPC of the LPAR. + + * se-version (string): SE version of the parent CPC of the LPAR, as + M.N.U string. Note that this property is returned only on newer HMC + 2.16 versions (HMC API version 4.10 or higher). + Authorization requirements: * Object permission to the LPAR objects included in the result. @@ -775,7 +818,9 @@ def list_permitted_lpars( List of property names that are to be returned in addition to the default properties. - This parameter requires HMC 2.16.0 or higher. + Note: This parameter is handled by the HMC starting with HMC API + version 4.10 (HMC 2.16 GA 1.5); with older HMC API versions it is + handled by zhmcclient. Returns: @@ -791,7 +836,11 @@ def list_permitted_lpars( query_parms, client_filters = divide_filter_args( ['name', 'type', 'status', 'has-unacceptable-status', 'cpc-name'], filter_args) - if additional_properties: + + api_version_info = self.manager.client.version_info() + hmc_supports_additional_properties = api_version_info >= (4, 10) + + if additional_properties and hmc_supports_additional_properties: ap_parm = 'additional-properties={}'.format( ','.join(additional_properties)) query_parms.append(ap_parm) @@ -834,23 +883,20 @@ def list_permitted_lpars( cpcs_by_uri[cpc_uri] = cpc - lpar_props = { - 'name': lpar_item['name'], - 'activation-mode': lpar_item['activation-mode'], - 'status': lpar_item['status'], - 'has-unacceptable-status': - lpar_item['has-unacceptable-status'], - } + lpar_props = dict(lpar_item) + pull_props = [] if additional_properties: for prop in additional_properties: try: lpar_props[prop] = lpar_item[prop] except KeyError: - pass + pull_props.append(prop) lpar_obj = cpc.lpars.resource_object( lpar_item['object-uri'], lpar_props, ) + if pull_props: + lpar_obj.pull_properties(pull_props) # Apply client-side filtering if matches_filters(lpar_obj, client_filters): diff --git a/zhmcclient/_resource.py b/zhmcclient/_resource.py index 551dfc11..af753a8c 100644 --- a/zhmcclient/_resource.py +++ b/zhmcclient/_resource.py @@ -271,7 +271,7 @@ def pull_full_properties(self): raise with self._property_lock: - self._properties = dict(full_properties) + self._properties.update(full_properties) self._properties_timestamp = int(time.time()) self._full_properties = True @@ -374,7 +374,7 @@ def pull_properties(self, properties): with self._property_lock: if is_full: - self._properties = dict(subset_properties) + self._properties.update(subset_properties) self._properties_timestamp = int(time.time()) self._full_properties = True else: