+# Copyright 2019 Nokia
+
+# 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.
+import re
+
+from functools import wraps
+from cmframework import apis as cmapis
+
+
+def handle_exceptions(func):
+ @wraps(func)
+ def wrapper(self, *arg, **kwargs):
+ try:
+ return func(self, *arg, **kwargs)
+ except cmapis.cmerror.CMError as exp:
+ raise
+ except Exception as exp:
+ raise cmapis.cmerror.CMError(str(exp))
+
+ return wrapper
+
+
+class CMClient(object):
+ """
+ Usage Example:
+ class VerboseLogger:
+ def __call__(self, msg):
+ print(msg)
+
+ logger = VerboseLogger()
+
+ client = CMClient(192.128.254.10, 51110, cmclient.CMClientImpl, logger)
+ try:
+ value = client.get_property('controller-1.ntp.servers')
+ except cmapis.cmerror.CMError as error:
+ print('Got exception %s' % str(error))
+ """
+
+ @handle_exceptions
+ def __init__(self, server_ip='config-manager', server_port=61100,
+ client_lib_impl_module='cmframework.lib.CMClientImpl', verbose_logger=None):
+ """ initialize the client interface
+
+ Arguments:
+
+ server_ip: The configuration management server ip address.
+
+ server_port: The configuration management server port number.
+
+ client_lib_impl_module: The module implementing the client library.
+
+ verbose_logger: The verbose logging callable. any callable which
+ takes a string as input argument can be used.
+
+ Raise:
+
+ CMError exception in-case of a failure.
+ """
+
+ import socket
+ try:
+ serverip = socket.gethostbyname(server_ip)
+ except Exception: # pylint: disable=broad-except
+ # use localhost in-case we cannot resolve the provided hostname
+ serverip = '127.0.0.1'
+
+ self.server_ip = serverip
+ self.server_port = server_port
+ self.client_lib = None
+ self.verbose_logger = verbose_logger
+
+ # Separate class path and module name
+ parts = client_lib_impl_module.rsplit('.', 1)
+ module_path = parts[0]
+ class_name = parts[1]
+ self.verbose_log('module_path = %s' % module_path)
+ self.verbose_log('class_name = %s' % class_name)
+ module = __import__(module_path, fromlist=[module_path])
+ classobj = getattr(module, class_name)
+ self.client_lib = classobj(self.server_ip, self.server_port, verbose_logger)
+
+ def verbose_log(self, msg):
+ if self.verbose_logger:
+ self.verbose_logger(msg)
+
+ @handle_exceptions
+ def get_property(self, prop_name, snapshot_name=None):
+ """get the value assoicated with a property.
+
+ This is the API used to read the value associated with a
+ configuration property.
+
+ Arguments:
+
+ prop_name: The property name
+ (optional) snapshot_name: The snapshot name
+
+ Raise:
+
+ CMError in-case of a failure.
+ """
+ result = self.client_lib.get_property(prop_name, snapshot_name)
+ return result
+
+ @handle_exceptions
+ def get_properties(self, prop_filter, snapshot_name=None):
+ """get a set of properties matching a filter.
+
+ This is the API used to read a group of properties matching some
+ filter.
+
+ Arguments:
+
+ prop_filter: A valid python re describing the filter used when
+ matching the returned properties.
+ (optional) snapshot_name: The snapshot name
+
+ Raise:
+
+ CMError is raised in-case of a failure.
+ """
+ self._check_filter(prop_filter)
+ result = self.client_lib.get_properties(prop_filter, snapshot_name)
+ return result
+
+ @handle_exceptions
+ def set_property(self, prop_name, prop_value):
+ """set/update the value of a property.
+
+ This is the API used to set/update the value associated with a
+ property.
+
+ Arguments:
+
+ prop_name: A string representing the property name.
+
+ prop_value: A string representing the property value.
+
+ Raise:
+
+ CMError is raised in-case of failure.
+ """
+ return self.client_lib.set_property(prop_name, prop_value)
+
+ @handle_exceptions
+ def set_properties(self, props, overwrite=False):
+ """set/update a group of properties as a whole
+
+ This API is used to set/update the values associated with a group of
+ properties as a whole, the change is either accepted as a whole or
+ rejected as a whole.
+
+ Arguments:
+
+ props: A dictionary containing the changed properties.
+
+ overwrite: Replace the existing configuration dictionary with the new one.
+
+ Raise:
+
+ CMError is raised in-case of a failure.
+ """
+ return self.client_lib.set_properties(props, overwrite)
+
+ @handle_exceptions
+ def delete_property(self, prop_name):
+ """delete a property
+
+ This is the API used to delete a configuration property.
+
+ Arguments:
+
+ prop_name: The name of the property to be deleted.
+
+ Raise:
+
+ CMError is raised in-case of a failure.
+ """
+ return self.client_lib.delete_property(prop_name)
+
+ @handle_exceptions
+ def delete_properties(self, arg):
+ """delete a group of properties as a whole
+
+ This is the API used to delete a group of properties as whole, if the
+ deletion of one of the properties is rejected then the whole delete
+ operation will fail.
+
+ Arguments:
+
+ arg: This can be either a string representing the re used when
+ matching the properties to be deleted, or it can be a list of
+ properties names to be deleted.
+
+ Raise:
+
+ CMError is raised in-case of a failure.
+ """
+ if isinstance(arg, str):
+ self._check_filter(arg)
+ return self.client_lib.delete_properties(arg)
+
+ @handle_exceptions
+ def get_changes_states(self, change_uuid):
+ """get the config changes states
+
+ This is the API used to get the changes states
+
+ Arguments:
+
+ arg: This can be either a valid change uuid or None.
+
+ Raise:
+
+ CMError is raised in-case of a failure.
+ """
+ return self.client_lib.get_changes_states(change_uuid)
+
+ @handle_exceptions
+ def wait_activation(self, change_uuid):
+ """wait for activation of config changes to finish
+
+ This is the API used to wait for config changes to finish
+
+ Arguments:
+
+ arg: A valid change uuid.
+
+ Raise:
+
+ CMError is raised in-case of a failure.
+ """
+ return self.client_lib.wait_activation(change_uuid)
+
+ # pylint: disable=no-self-use
+ def _check_filter(self, prop_filter):
+ re.compile(prop_filter)