From 5ef0b823429b5d6bc8f2ebf248a66da84be7e00e Mon Sep 17 00:00:00 2001 From: Araslanov Egor Date: Fri, 27 May 2022 16:18:24 +0500 Subject: [PATCH] ADCM-2859 modify `config_set_diff` to always update `attr` and pass full config to `config_set` (#116) --- src/adcm_client/objects.py | 66 +++++++++++++++------------------- src/adcm_client/util/config.py | 32 +++++++++++++++++ 2 files changed, 60 insertions(+), 38 deletions(-) create mode 100644 src/adcm_client/util/config.py diff --git a/src/adcm_client/objects.py b/src/adcm_client/objects.py index f7e1675..68cc09f 100644 --- a/src/adcm_client/objects.py +++ b/src/adcm_client/objects.py @@ -13,7 +13,7 @@ import logging import warnings -from collections import abc, namedtuple +from collections import namedtuple from io import BytesIO from json import dumps from typing import List, Union, Optional @@ -38,6 +38,7 @@ NoSuchEndpointOrAccessIsDenied, ) from adcm_client.util import stream +from adcm_client.util.config import update from adcm_client.wrappers.api import ADCMApiWrapper # Init logger @@ -322,6 +323,29 @@ class HostPrototypeList(BaseAPIListObject): ################################################## # B A S E O B J E C T ################################################## + + +def _config_set_diff(object_with_config, data: dict, attach_to_allure: bool = True) -> dict: + """ + General method to use when config should be updated without passing the full config. + + :param object_with_config: Object with methods `config(..., full: bool) -> dict` + and `config_set(data, attach_to_allure, ...) -> dict` + :param data: Dictionary with new config fields + (may or may not contain "config" and "attr" fields) + :param attach_to_allure: Flag to decide whether attach + changed fields and original config or not + """ + if attach_to_allure: + allure_attach_json(data, name="Changed fields") + if "attr" not in data: + data = {"config": {**data.get("config", data)}, "attr": {}} + config = object_with_config.config(full=True) + if attach_to_allure: + allure_attach_json(config, name="Original config") + return object_with_config.config_set(update(config, data), attach_to_allure=attach_to_allure) + + class _BaseObject(BaseAPIObject): """ Base class 'BaseObject' for adcm_client objects @@ -400,25 +424,7 @@ def config_set(self, data, attach_to_allure=True): @allure_step("Save config") def config_set_diff(self, data, attach_to_allure=True): """Save the difference between old and new config in history""" - - def update(d, u): - """If the old and new values are dictionaries, we try to update, otherwise we replace""" - for key, value in u.items(): - if isinstance(value, abc.Mapping) and key in d and isinstance(d[key], abc.Mapping): - d[key] = update(d[key], value) - continue - d[key] = value - return d - - # this check is incomplete, cases of presence of keys "config" and "attr" in config - # are not considered - if attach_to_allure: - allure_attach_json(data, name="Changed fields") - is_full = "config" in data and "attr" in data - config = self.config(full=is_full) - if attach_to_allure: - allure_attach_json(config, name="Original config") - return self.config_set(update(config, data), attach_to_allure=attach_to_allure) + return _config_set_diff(self, data, attach_to_allure) def config_prototype(self): return self.prototype().config @@ -1447,25 +1453,9 @@ def config_set(self, data, attach_to_allure=True): return current_config["config"] @allure_step("Save group config") - def config_set_diff(self, data, attach_to_allure=True): + def config_set_diff(self, data: dict, attach_to_allure: bool = True) -> dict: """Partial config update""" - - def update(d, u): - """If the old and new values are dictionaries, we try to update, otherwise we replace""" - for key, value in u.items(): - if isinstance(value, abc.Mapping) and key in d and isinstance(d[key], abc.Mapping): - d[key] = update(d[key], value) - continue - d[key] = value - return d - - if attach_to_allure: - allure_attach_json(data, name="Changed fields") - is_full = "config" in data and "attr" in data - config = self.config(full=is_full) - if attach_to_allure: - allure_attach_json(config, name="Original config") - return self.config_set(update(config, data), attach_to_allure=attach_to_allure) + return _config_set_diff(self, data, attach_to_allure) def host_candidate(self, paging=None, **kwargs) -> "HostList": return HostList( diff --git a/src/adcm_client/util/config.py b/src/adcm_client/util/config.py new file mode 100644 index 0000000..c0abbbc --- /dev/null +++ b/src/adcm_client/util/config.py @@ -0,0 +1,32 @@ +# 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. + +"""Utilities to work with objects' configurations""" + +from collections.abc import Mapping + + +def update(current_config: dict, changes: dict) -> dict: + """ + If the old and new values are dictionaries, we try to update, otherwise we replace. + Current config is updated, not copied. + """ + for key, value in changes.items(): + if ( + isinstance(value, Mapping) + and key in current_config + and isinstance(current_config[key], Mapping) + ): + current_config[key] = update(current_config[key], value) + continue + current_config[key] = value + return current_config