From 9268bdca83b99840e8ce64b178e0848b23855cd2 Mon Sep 17 00:00:00 2001 From: Andreas Maier Date: Sun, 5 Nov 2023 09:23:01 +0100 Subject: [PATCH] Added support for creation and deletion of activation profiles on z16 Details: * Added a create() method to zhmcclient.ActivationProfileManager. At this point, the method deals with the fact that the implementation does not return any data, and builds the resource URI from the provided name (this is documented differently in the API book). The method also deals with the fact that all three create operations take 'profile-name' as input for the profile name (this is documented as 'name' for image and load profiles). * Added a delete() method to zhmcclient.ActivationProfile. * Added end2end testcases for create, delete and update. Signed-off-by: Andreas Maier --- docs/changes.rst | 5 + tests/end2end/test_activation_profile.py | 128 ++++++++++++++++++++++- zhmcclient/_activation_profile.py | 85 +++++++++++++++ 3 files changed, 213 insertions(+), 5 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 847c96ef..e8a2dda3 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -154,6 +154,11 @@ Released: not yet Job.check_for_completion() and Job.wait_for_completion() already existed. (issue #1299) +* Added support for creation and deletion of activation profiles on z16. + This requires the SE to have a code level that has the + 'create-delete-activation-profiles' API feature enabled. + (issue #1329) + **Cleanup:** **Known issues:** diff --git a/tests/end2end/test_activation_profile.py b/tests/end2end/test_activation_profile.py index cfb6eb84..f031455f 100644 --- a/tests/end2end/test_activation_profile.py +++ b/tests/end2end/test_activation_profile.py @@ -20,19 +20,26 @@ from __future__ import absolute_import, print_function +import warnings + import pytest from requests.packages import urllib3 +import zhmcclient # pylint: disable=line-too-long,unused-import from zhmcclient.testutils import hmc_definition, hmc_session # noqa: F401, E501 from zhmcclient.testutils import classic_mode_cpcs # noqa: F401, E501 # pylint: enable=line-too-long,unused-import from .utils import skip_warn, pick_test_resources, runtest_find_list, \ - runtest_get_properties + runtest_get_properties, setup_logging, End2endTestWarning urllib3.disable_warnings() +# Logging for zhmcclient HMC interactions and test functions +LOGGING = False +LOG_FILE = 'test_profile.log' + # Properties in minimalistic ActivationProfile objects (e.g. find_by_name()) ACTPROF_MINIMAL_PROPS = ['element-uri', 'name'] @@ -46,6 +53,117 @@ ACTPROF_VOLATILE_PROPS = [] +def standard_activation_profile_props(cpc, profile_name, profile_type): + """ + Return the input properties for creating standard activation profile in the + specified CPC. + """ + actprof_input_props = { + 'profile-name': profile_name, + 'description': ( + '{} profile for zhmcclient end2end tests'.format(profile_type)), + } + if profile_type == 'image': + # We provide the minimum set of properties needed to create a profile. + if cpc.prop('processor-count-ifl'): + actprof_input_props['number-shared-ifl-processors'] = 1 + actprof_input_props['operating-mode'] = 'linux-only' + elif cpc.prop('processor-count-general-purpose'): + actprof_input_props['number-shared-general-purpose-processors'] = 1 + actprof_input_props['operating-mode'] = 'esa390' + else: + actprof_input_props['number-shared-general-purpose-processors'] = 1 + actprof_input_props['operating-mode'] = 'esa390' + warnings.warn( + "CPC {c} shows neither IFL nor CP processors, specifying 1 CP " + "for image activation profile creation.". + format(c=cpc.name), End2endTestWarning) + + return actprof_input_props + + +@pytest.mark.parametrize( + "profile_type", ['reset', 'image', 'load'] +) +def test_actprof_crud(classic_mode_cpcs, profile_type): # noqa: F811 + # pylint: disable=redefined-outer-name + """ + Test create, read, update and delete an activation profile. + """ + if not classic_mode_cpcs: + pytest.skip("HMC definition does not include any CPCs in classic mode") + + logger = setup_logging(LOGGING, 'test_actprof_crud', LOG_FILE) + + for cpc in classic_mode_cpcs: + assert not cpc.dpm_enabled + + actprof_mgr = getattr(cpc, profile_type + '_activation_profiles') + + msg = "Testing on CPC {c}".format(c=cpc.name) + print(msg) + logger.info(msg) + + actprof_name = 'ZHMC{}1'.format(profile_type[0].upper()) + + # Preparation: Ensure clean starting point for this test + try: + _actprof = actprof_mgr.find(name=actprof_name) + except zhmcclient.NotFound: + pass + else: + msg = ("Preparation: Delete {pt} activation profile {ap!r} on CPC " + "{c} from previous run". + format(pt=profile_type, ap=actprof_name, c=cpc.name)) + warnings.warn(msg, UserWarning) + logger.info(msg) + _actprof.delete() + + # Test creating the activation profile + actprof_input_props = standard_activation_profile_props( + cpc, actprof_name, profile_type) + + logger.info("Test: Create %s activation profile %r on CPC %s", + profile_type, actprof_name, cpc.name) + + # The code to be tested + actprof = actprof_mgr.create(actprof_input_props) + + try: + for pn, exp_value in actprof_input_props.items(): + assert actprof.properties[pn] == exp_value, \ + "Unexpected value for property {!r}".format(pn) + actprof.pull_full_properties() + for pn, exp_value in actprof_input_props.items(): + if pn == 'profile-name': + pn = 'name' + assert actprof.properties[pn] == exp_value, \ + "Unexpected value for property {!r}".format(pn) + + # Test updating a property of the activation profile + + new_desc = "Updated activation profile description." + + logger.info("Test: Update a property of %s activation profile " + "%r on CPC %s", profile_type, actprof_name, cpc.name) + + # The code to be tested + actprof.update_properties(dict(description=new_desc)) + + assert actprof.properties['description'] == new_desc + actprof.pull_full_properties() + assert actprof.properties['description'] == new_desc + + finally: + # Test deleting the activation profile (also cleanup) + + logger.info("Test: Delete %s activation profile %r on CPC %s", + profile_type, actprof_name, cpc.name) + + # The code to be tested + actprof.delete() + + @pytest.mark.parametrize( "profile_type", ['reset', 'image', 'load'] ) @@ -73,8 +191,8 @@ def test_actprof_find_list(classic_mode_cpcs, profile_type): # noqa: F811 actprof_list = pick_test_resources(actprof_list) for actprof in actprof_list: - print("Testing on CPC {c} with {t} activation profile {p!r}". - format(c=cpc.name, t=profile_type, p=actprof.name)) + print("Testing on CPC {c} with {pt} activation profile {ap!r}". + format(c=cpc.name, pt=profile_type, ap=actprof.name)) if profile_type == 'image': actprof_additional_props = ACTPROF_ADDITIONAL_PROPS else: @@ -113,8 +231,8 @@ def test_actprof_property(classic_mode_cpcs, profile_type): # noqa: F811 actprof_list = pick_test_resources(actprof_list) for actprof in actprof_list: - print("Testing on CPC {c} with {t} activation profile {p!r}". - format(c=cpc.name, t=profile_type, p=actprof.name)) + print("Testing on CPC {c} with {pt} activation profile {ap!r}". + format(c=cpc.name, pt=profile_type, ap=actprof.name)) # Select a property that is not returned by list() non_list_prop = 'description' diff --git a/zhmcclient/_activation_profile.py b/zhmcclient/_activation_profile.py index 0cbe31b6..b4a76fc7 100644 --- a/zhmcclient/_activation_profile.py +++ b/zhmcclient/_activation_profile.py @@ -47,6 +47,7 @@ from __future__ import absolute_import import copy +import warnings from ._manager import BaseManager from ._resource import BaseResource @@ -221,6 +222,67 @@ def list(self, full_properties=False, filter_args=None, list_uri, result_prop, full_properties, filter_args, additional_properties) + @logged_api_call + def create(self, properties): + """ + Create and configure an Activation Profiles on this CPC, of the profile + type managed by this object. + + Supported only on z16 and later CPCs. + + Authorization requirements: + + * Object-access permission to this CPC. + * Task permission to the "Customize/Delete Activation Profiles" task. + + Parameters: + + properties (dict): Initial property values. + Allowable properties are defined in section 'Request body contents' + in section 'Create Reset/Image/Load Activation Profile' in the + :term:`HMC API` book. + + Note that the input profile name for creation must be provided in + property 'profile-name', even though it shows up on the created + resource in property 'name'. This applies to all three types of + activation profiles. + + Returns: + + ActivationProfile: + The resource object for the new Activation Profile. + The object will have its 'element-uri' property set, and will also + have the input properties set. + + Raises: + + :exc:`~zhmcclient.HTTPError` + :exc:`~zhmcclient.ParseError` + :exc:`~zhmcclient.AuthError` + :exc:`~zhmcclient.ConnectionError` + """ + ap_selector = self._profile_type + '-activation-profiles' + uri = '{}/{}'.format(self.cpc.uri, ap_selector) + + result = self.session.post(uri, body=properties) + + # The "Create ... Activation Profile" operations do not return the + # resource URI, so we construct it ourselves. Also, these operations + # specify the profile name in input property 'profile-name'. + if result is not None: + warnings.warn( + "The Create {pt} Activation Profile operation now has " + "response data with properties: {pl!r}". + format(pt=self._profile_type, pl=result.keys()), UserWarning) + name = properties['profile-name'] + uri = '{}/{}'.format(uri, name) + + props = copy.deepcopy(properties) + props[self._uri_prop] = uri + profile = ActivationProfile(self, uri, name, props) + self._name_uri_cache.update(name, uri) + return profile + class ActivationProfile(BaseResource): """ @@ -250,6 +312,29 @@ def __init__(self, manager, uri, name=None, properties=None): .format(ActivationProfileManager, type(manager)) super(ActivationProfile, self).__init__(manager, uri, name, properties) + @logged_api_call + def delete(self): + """ + Delete this Activation Profile. + + Supported only on z16 and later CPCs. + + Authorization requirements: + + * Task permission to the "Customize/Delete Activation Profiles" task. + + Raises: + + :exc:`~zhmcclient.HTTPError` + :exc:`~zhmcclient.ParseError` + :exc:`~zhmcclient.AuthError` + :exc:`~zhmcclient.ConnectionError` + """ + # pylint: disable=protected-access + self.manager.session.delete(self.uri, resource=self) + self.manager._name_uri_cache.delete( + self.get_properties_local(self.manager._name_prop, None)) + @logged_api_call def update_properties(self, properties): """