- Add more error handling
- Refactor clm code
- Keep raising exceptions for existing python methods
---
 python/pyosaf/utils/__init__.py     | 267 ++++++++++++++---
 python/pyosaf/utils/clm/__init__.py | 579 ++++++++++++++++++++++++++++++------
 2 files changed, 716 insertions(+), 130 deletions(-)

diff --git a/python/pyosaf/utils/__init__.py b/python/pyosaf/utils/__init__.py
index 7a1c21b79..fac3c5b80 100644
--- a/python/pyosaf/utils/__init__.py
+++ b/python/pyosaf/utils/__init__.py
@@ -16,17 +16,29 @@
 #
 ############################################################################
 """ pyosaf common utils """
+import os
+import syslog
 import time
+import warnings
 from copy import deepcopy
+from ctypes import POINTER
 
-from pyosaf.saAis import eSaAisErrorT
+from pyosaf import saImmOm
+from pyosaf.saAis import eSaAisErrorT, SaStringT
 
 
-TRY_AGAIN_COUNT = 60
+# The 'MAX_RETRY_TIME' and 'RETRY_INTERVAL' environment variables shall be set
+# with user-defined values BEFORE importing the pyosaf 'utils' module;
+# Otherwise, the default values for MAX_RETRY_TIME(60s) and RETRY_INTERVAL(1s)
+# will be used throughout.
+MAX_RETRY_TIME = int(os.environ.get("MAX_RETRY_TIME")) \
+    if "MAX_RETRY_TIME" in os.environ else 60
+RETRY_INTERVAL = int(os.environ.get("RETRY_INTERVAL")) \
+    if "RETRY_INTERVAL" in os.environ else 1
 
 
 class SafException(Exception):
-    """ SAF Exception that can be printed """
+    """ SAF Exception for error during executing SAF functions """
     def __init__(self, value, msg=None):
         Exception.__init__(self)
         self.value = value
@@ -44,73 +56,244 @@ def raise_saf_exception(func, error):
     raise SafException(error, error_string)
 
 
+def check_resource_abort(ccb_handle):
+    """ Get error strings from IMM and check if it is a resource abort case
+
+    Args:
+        ccb_handle (SaImmCcbHandleT): CCB handle
+
+    Returns:
+        bool: 'True' if it is a resource abort case. 'False', otherwise.
+    """
+    c_error_strings = POINTER(SaStringT)()
+    saImmOmCcbGetErrorStrings = decorate(saImmOm.saImmOmCcbGetErrorStrings)
+
+    # Get error strings
+    # As soon as the ccb_handle is finalized, the strings are freed
+    rc = saImmOmCcbGetErrorStrings(ccb_handle, c_error_strings)
+
+    if rc == eSaAisErrorT.SA_AIS_OK:
+        if c_error_strings:
+            for c_error_string in c_error_strings:
+                if c_error_string.startswith("IMM: Resource abort: "):
+                    return True
+
+    return False
+
+
 def decorate(func):
     """ Decorate the given SAF function so that it retries a fixed number of
-    times if needed and raises an exception if it encounters any fault other
-    than SA_AIS_ERR_TRY_AGAIN.
+    times for certain returned error codes during execution
+
+    Args:
+        func (function): The decorated SAF function
+
+    Returns:
+        SaAisErrorT: Return code of the decorated SAF function
     """
 
     def inner(*args):
-        """ Call "function" in the lexical scope in a retry loop and raise an
-        exception if it encounters any fault other than TRY_AGAIN
+        """ Call the decorated function in the lexical scope in a retry loop
+
+        Args:
+            args (tuple): Arguments of the decorated SAF function
         """
-        one_sec_sleeps = 0
-        error = func(*args)
+        count_sec_sleeps = 0
 
-        while error == eSaAisErrorT.SA_AIS_ERR_TRY_AGAIN:
-            if one_sec_sleeps == TRY_AGAIN_COUNT:
+        rc = func(*args)
+        while rc != eSaAisErrorT.SA_AIS_OK:
+
+            if count_sec_sleeps >= MAX_RETRY_TIME:
                 break
-            time.sleep(1)
-            one_sec_sleeps += 1
 
-            error = func(*args)
+            if rc == eSaAisErrorT.SA_AIS_ERR_TRY_AGAIN:
+                sleep_time_interval = RETRY_INTERVAL
+            elif rc == eSaAisErrorT.SA_AIS_ERR_NO_RESOURCES:
+                sleep_time_interval = RETRY_INTERVAL
+            elif rc == eSaAisErrorT.SA_AIS_ERR_BUSY:
+                sleep_time_interval = 3 * RETRY_INTERVAL
+            elif rc == eSaAisErrorT.SA_AIS_ERR_FAILED_OPERATION:
+                # Retry on getting FAILED_OPERATION only applies to IMM
+                # CCB-related operations in case of a resource abort;
+                ccb_handle = args[0]
+                resource_abort = check_resource_abort(ccb_handle)
+                if resource_abort:
+                    sleep_time_interval = RETRY_INTERVAL
+            else:
+                break  # Break out of the retry loop
 
-        if error != eSaAisErrorT.SA_AIS_OK:
-            raise_saf_exception(func, error)
+            # Check sleep_time_interval to sleep and retry the function
+            if sleep_time_interval > 0:
+                time.sleep(sleep_time_interval)
+                count_sec_sleeps += sleep_time_interval
+                rc = func(*args)
 
-        return error
+        return rc
 
     return inner
 
 
-def initialize_decorate(func):
+def initialize_decorate(init_func):
     """ Decorate the given SAF sa<Service>Initialize() function so that it
-    retries a fixed number of times if needed with the same arguments and
-    raises an exception if it encounters any fault other than
-    SA_AIS_ERR_TRY_AGAIN.
+    retries a fixed number of times with the same arguments for certain
+    returned error codes during execution
     """
 
     def inner(*args):
-        """ Call "function" in the lexical scope in a retry loop and raise an
-        exception if it encounters any fault other than TRY_AGAIN
+        """ Call the decorated Initialize() function in the lexical scope in a
+        retry loop
 
         Args:
-            args (tuple): Arguments of SAF Initialize() function with format
-                (handle, callbacks, version)
+            args (tuple): Arguments of the SAF Initialize() function with
+                format (handle, callbacks, version)
         """
-        # Backup current version
+        count_sec_sleeps = 0
+
+        # Backup the current version
         backup_version = deepcopy(args[2])
 
-        one_sec_sleeps = 0
-        error = func(*args)
+        rc = init_func(*args)
+        while rc != eSaAisErrorT.SA_AIS_OK:
 
-        while error == eSaAisErrorT.SA_AIS_ERR_TRY_AGAIN:
-            if one_sec_sleeps == TRY_AGAIN_COUNT:
+            if count_sec_sleeps >= MAX_RETRY_TIME:
                 break
-            time.sleep(1)
-            one_sec_sleeps += 1
 
-            # If the SAF Initialize() function returns ERR_TRY_AGAIN, the
-            # version (as output argument) will still be updated to the latest
-            # supported service API version; thus we need to restore the
-            # original backed-up version before next retry of initialization.
-            version = deepcopy(backup_version)
-            args = args[:2] + (version,)
-            error = func(*args)
+            if rc == eSaAisErrorT.SA_AIS_ERR_TRY_AGAIN:
+                sleep_time_interval = RETRY_INTERVAL
+            elif rc == eSaAisErrorT.SA_AIS_ERR_NO_RESOURCES:
+                sleep_time_interval = RETRY_INTERVAL
+            elif rc == eSaAisErrorT.SA_AIS_ERR_BUSY:
+                sleep_time_interval = 3 * RETRY_INTERVAL
+            else:
+                break  # Break out of the retry loop
+
+            # Check sleep_time_interval to sleep and retry the function
+            if sleep_time_interval > 0:
+                time.sleep(sleep_time_interval)
+                count_sec_sleeps += sleep_time_interval
+                # If the SAF Initialize() function returns ERR_TRY_AGAIN, the
+                # version (as output argument) will still get updated to the
+                # latest supported service API version; thus we need to restore
+                # the original backed-up version before the next retry of
+                # initialization.
+                version = deepcopy(backup_version)
+                args = args[:2] + (version,)
+                rc = init_func(*args)
 
-        if error != eSaAisErrorT.SA_AIS_OK:
-            raise_saf_exception(func, error)
+        return rc
 
-        return error
+    return inner
+
+
+def bad_handle_retry(func):
+    """ Decorate the given function so that it retries a fixed number of times
+    if getting the error code SA_AIS_ERR_BAD_HANDLE during execution
+
+    Args:
+        func (function): The decorated function
+
+    Returns:
+        Return code/output of the decorated function
+    """
+    def inner(*args, **kwargs):
+        """ Call the decorated function in the lexical scope in a retry loop
+        if it gets the returned error code SA_AIS_ERR_BAD_HANDLE
+
+        Args:
+            args (tuple): Arguments of the decorated function
+        """
+        count_sec_sleeps = 0
+        sleep_time_interval = 10 * RETRY_INTERVAL
+
+        result = func(*args, **kwargs)
+        rc = result[0] if type(result) is tuple else result
+        while rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+            if count_sec_sleeps >= MAX_RETRY_TIME:
+                break
+
+            time.sleep(sleep_time_interval)
+            count_sec_sleeps += sleep_time_interval
+            result = func(*args, **kwargs)
+            rc = result[0] if type(result) is tuple else result
+
+        return result
 
     return inner
+
+
+def deprecate(func):
+    """ Decorate the given function as deprecated
+
+    A warning message to notify the users about the function deprecation will
+    be displayed if the users have enabled the filter for this kind of warning
+
+    Args:
+        func (function): The deprecated function
+
+    Returns:
+        Return code/output of the decorated function
+    """
+    def inner(*args, **kwargs):
+        """ Call the deprecated function in the lexical scope """
+        warnings.warn("This function will be deprecated in future release. "
+                      "Please consider using its OOP counterpart.",
+                      PendingDeprecationWarning)
+        return func(*args, **kwargs)
+
+    return inner
+
+
+###############################
+# Common system logging utils #
+###############################
+def log_err(message):
+    """ Print a message to syslog at ERROR level
+
+    Args:
+        message (str): Message to be printed to syslog
+    """
+    syslog.syslog(syslog.LOG_ERR, "ER " + message)
+
+
+def log_warn(message):
+    """ Print a message to syslog at WARNING level
+
+    Args:
+        message (str): Message to be printed to syslog
+    """
+    syslog.syslog(syslog.LOG_WARNING, "WA " + message)
+
+
+def log_notice(message):
+    """ Print a message to syslog at NOTICE level
+    Args:
+        message (str): Message to be printed to syslog
+    """
+    syslog.syslog(syslog.LOG_NOTICE, "NO " + message)
+
+
+def log_info(message):
+    """ Print a message to syslog at INFO level
+
+    Args:
+        message (str): Message to be printed to syslog
+    """
+    syslog.syslog(syslog.LOG_INFO, "IN " + message)
+
+
+def log_debug(message):
+    """ Print a message to syslog at DEBUG level
+
+    Args:
+        message (str): Message to be printed to syslog
+    """
+    syslog.syslog(syslog.LOG_DEBUG, "DB " + message)
+
+
+def log_init(ident):
+    """ Initialize system logging function
+
+    Args:
+        ident(str): A string to be prepended to each message
+    """
+    syslog.openlog(ident, syslog.LOG_PID, syslog.LOG_USER)
diff --git a/python/pyosaf/utils/clm/__init__.py 
b/python/pyosaf/utils/clm/__init__.py
index 53b86261f..fa9460182 100644
--- a/python/pyosaf/utils/clm/__init__.py
+++ b/python/pyosaf/utils/clm/__init__.py
@@ -17,10 +17,14 @@
 ############################################################################
 # pylint: disable=unused-argument
 """ CLM common utilities """
-from pyosaf.saAis import saAis, SaVersionT, eSaDispatchFlagsT
+from copy import deepcopy
+
+from pyosaf.saAis import saAis, SaVersionT, eSaAisErrorT, eSaDispatchFlagsT
 from pyosaf import saClm
-from pyosaf.utils import decorate, initialize_decorate
+from pyosaf.utils import decorate, initialize_decorate, deprecate, \
+    bad_handle_retry, log_err, SafException
 
+_clm_agent = None
 
 # Decorate pure saClm* API's with error-handling retry and exception raising
 saClmInitialize = initialize_decorate(saClm.saClmInitialize)
@@ -30,93 +34,372 @@ saClmSelectionObjectGet = 
decorate(saClm.saClmSelectionObjectGet)
 saClmDispatch = decorate(saClm.saClmDispatch)
 saClmFinalize = decorate(saClm.saClmFinalize)
 saClmClusterTrack = decorate(saClm.saClmClusterTrack)
-saClmClusterNodeGet = decorate(saClm.saClmClusterNodeGet)
-saClmClusterNotificationFree = decorate(saClm.saClmClusterNotificationFree)
 saClmClusterTrack_4 = decorate(saClm.saClmClusterTrack_4)
 saClmClusterTrackStop = decorate(saClm.saClmClusterTrackStop)
+saClmClusterNotificationFree = decorate(saClm.saClmClusterNotificationFree)
 saClmClusterNotificationFree_4 = decorate(saClm.saClmClusterNotificationFree_4)
+saClmClusterNodeGet = decorate(saClm.saClmClusterNodeGet)
 saClmClusterNodeGet_4 = decorate(saClm.saClmClusterNodeGet_4)
 saClmClusterNodeGetAsync = decorate(saClm.saClmClusterNodeGetAsync)
 saClmResponse_4 = decorate(saClm.saClmResponse_4)
 
-# Create the handle
-handle = saClm.SaClmHandleT()
-track_function = None
 
+class ClmAgentManager(object):
+    """ This class manages the life cycle of a CLM agent, and also acts as
+    a proxy handler for CLM callbacks """
 
-def track_callback(c_notification_buffer, c_number_of_members, c_invocation_id,
-                   c_root_cause_entity, c_correlation_ids, c_step,
-                   c_time_supervision, c_error):
-    """ This callback is invoked to get information about cluster membership
-    changes in the structure to which the notificationBuffer parameter points.
+    def __init__(self, version=None):
+        """ Constructor for ClmAgentManager class
 
-    Args:
-        c_notification_buffer (SaClmClusterNotificationBufferT_4): Notification
-            buffer
-        c_number_of_members (SaUint32T): Number of members
-        c_invocation_id (SaInvocationT): Invocation id
-        c_root_cause_entity (SaNameT): Root cause entity
-        c_correlation_ids (SaNtfCorrelationIdsT): Correlation ids
-        c_step (SaClmChangeStepT): Change step
-        c_time_supervision (SaTimeT): Time supervision
-        c_error (SaAisErrorT): Return code
-    """
+        Args:
+            version (SaVersionT): CLM API version
+        """
+        self.init_version = version
+        self.version = None
+        self.handle = None
+        self.callbacks = None
+        self.sel_obj = saClm.SaSelectionObjectT()
+        self.track_function = None
+        self.node_get_function = None
+
+    def track_callback(self, c_notification_buffer, c_number_of_members,
+                       c_invocation_id, c_root_cause_entity, c_correlation_ids,
+                       c_step, c_time_supervision, c_error):
+        """ This callback is invoked by CLM to notify the subscribed cluster
+        membership tracker about changes in the cluster membership, along with
+        detailed information of the changes.
 
-    if track_function:
-        added = []
-        removed = []
-        step = c_step
+        Args:
+            c_notification_buffer (SaClmClusterNotificationBufferT_4):
+                A pointer to a structure containing pointer to an array of
+                information structures about current member nodes and their
+                membership changes if any
+            c_number_of_members (SaUint32T): The current number of member nodes
+            c_invocation_id (SaInvocationT): Invocation id identifying this
+                particular callback invocation, and which shall be used in
+                saClmResponse_4() to respond to CLM in certain cases
+            c_root_cause_entity (SaNameT): A pointer to the DN of the CLM node
+                directly targeted by the action or event that caused the
+                membership change
+            c_correlation_ids (SaNtfCorrelationIdsT): A pointer to the
+                correlation identifiers associated with the root cause
+            c_step (SaClmChangeStepT): The tracking step in which this callback
+                is invoked
+            c_time_supervision (SaTimeT): The time specifying how long CLM will
+                wait for the process to provide the response for the callback
+                by invoking the saClmResponse_4() function
+            c_error (SaAisErrorT): Return code to indicate whether CLM was able
+                to perform the requested operation
+        """
+        if self.track_function:
+            invocation = c_invocation_id
+            step = c_step
+            num_of_members = c_number_of_members
+            error = c_error
+            # List of tuples (ClusterNode, clusterChange)
+            node_list = []
 
-        if step == saClm.eSaClmChangeStepT.SA_CLM_CHANGE_COMPLETED:
             notification_buffer = c_notification_buffer.contents
-
-            i = 0
-            for notification in notification_buffer.notification:
-                if i == notification_buffer.numberOfItems:
-                    break
-                else:
-                    i = i + 1
-
+            for i in range(notification_buffer.numberOfItems):
+                notification = notification_buffer.notification[i]
                 clm_cluster_node = notification.clusterNode
                 cluster_node = \
                     create_cluster_node_instance(clm_cluster_node)
+                node_state = (cluster_node, notification.clusterChange)
 
-                if notification.clusterChange == \
-                        saClm.eSaClmClusterChangesT.SA_CLM_NODE_JOINED:
-                    added.append(cluster_node)
-                elif notification.clusterChange == \
-                        saClm.eSaClmClusterChangesT.SA_CLM_NODE_LEFT:
-                    removed.append(cluster_node)
+                node_list.append(node_state)
 
-        track_function(added, removed)
+            # Send the node list to user's callback function
+            self.track_function(node_list, invocation, step, num_of_members,
+                                error)
 
+    def node_get_callback(self, c_invocation_id, c_cluster_node, c_error):
+        """ This callback is invoked by CLM to return information about the
+        requested member node to a registered client that had previously called
+        saClmClusterNodeGetAsync().
 
-def node_get_callback(*args):
-    """ Dummy function used as a callback when no proper callbacks are set """
-    pass
+        Args:
+            c_invocation_id(SaInvocationT): Invocation id associating this
+                callback invocation with the corresponding previous invocation
+                of saClmClusterNodeGetAsync()
+            c_cluster_node(SaClmClusterNodeT_4): A pointer to the structure
+                that contains information about the requested member node
+            c_error(SaAisErrorT): Return code to indicate whether the
+                saClmClusterNodeGetAsync() function was successful
+        """
+        if self.node_get_function:
+            invocation = c_invocation_id
+            error = c_error
 
+            cluster_node = \
+                create_cluster_node_instance(c_cluster_node.contents)
 
-def create_cluster_node_instance(clm_cluster_node):
-    """ Create ClusterNode object from cluster node information
+            # Send the node info to user's callback function
+            self.node_get_function(invocation, cluster_node, error)
 
-    Args:
-        clm_cluster_node (SaClmClusterNodeT): Cluster node information
+    def initialize(self, track_func=None, node_get_func=None):
+        """ Initialize the CLM agent library
 
-    Returns:
-        ClusterNode: An object containing cluster node information
-    """
-    return ClusterNode(
-        node_id=clm_cluster_node.nodeId,
-        node_address=clm_cluster_node.nodeAddress,
-        node_name=clm_cluster_node.nodeName,
-        execution_environment=clm_cluster_node.executionEnvironment,
-        member=clm_cluster_node.member,
-        boot_timestamp=clm_cluster_node.bootTimestamp,
-        initial_view_number=clm_cluster_node.initialViewNumber)
+        Args:
+            track_func (callback): Cluster track callback function
+            node_get_func (callback): Cluster node get function
+
+        Returns:
+            SaAisErrorT: Return code of the saClmInitialize_4() API call
+        """
+        self.track_function = track_func
+        self.node_get_function = node_get_func
+
+        self.callbacks = saClm.SaClmCallbacksT_4()
+        self.callbacks.saClmClusterTrackCallback = \
+            saClm.SaClmClusterTrackCallbackT_4(self.track_callback)
+        self.callbacks.saClmClusterNodeGetCallback = \
+            saClm.SaClmClusterNodeGetCallbackT_4(
+                self.node_get_callback)
+
+        self.handle = saClm.SaClmHandleT()
+        self.version = deepcopy(self.init_version)
+        rc = saClmInitialize_4(self.handle, self.callbacks, self.version)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saClmInitialize_4 FAILED - %s" % eSaAisErrorT.whatis(rc))
+
+        return rc
+
+    def get_handle(self):
+        """ Return the CLM agent handle successfully initialized
+
+        Returns:
+            SaClmHandleT: CLM agent handle
+        """
+        return self.handle
+
+    def _fetch_sel_obj(self):
+        """ Obtain a selection object (OS file descriptor)
+
+        Returns:
+            SaAisErrorT: Return code of the saClmSelectionObjectGet() API call
+        """
+        rc = saClmSelectionObjectGet(self.handle, self.sel_obj)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saClmSelectionObjectGet FAILED - %s" %
+                    eSaAisErrorT.whatis(rc))
+
+        return rc
+
+    def get_selection_object(self):
+        """ Return the selection object associating with the CLM handle
+
+        Returns:
+            SaSelectionObjectT: Selection object associated with the CLM handle
+        """
+        return self.sel_obj
+
+    def dispatch(self, flags):
+        """ Invoke CLM callbacks for queued events
+
+        Args:
+            flags (eSaDispatchFlagsT): Flags specifying dispatch mode
+
+        Returns:
+            SaAisErrorT: Return code of the saClmDispatch() API call
+        """
+        rc = saClmDispatch(self.handle, flags)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saClmDispatch FAILED - %s" % eSaAisErrorT.whatis(rc))
+
+        return rc
+
+    def finalize(self):
+        """ Finalize the CLM agent handle
+
+        Returns:
+            SaAisErrorT: Return code of the saClmFinalize() API call
+        """
+        rc = eSaAisErrorT.SA_AIS_OK
+        if self.handle:
+            rc = saClmFinalize(self.handle)
+            if rc != eSaAisErrorT.SA_AIS_OK:
+                log_err("saClmFinalize FAILED - %s" % eSaAisErrorT.whatis(rc))
+            elif rc == eSaAisErrorT.SA_AIS_OK \
+                    or rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+                # If the Finalize() call returned BAD_HANDLE, the handle should
+                # already become stale and invalid, so we reset it anyway.
+                self.handle = None
+
+        return rc
+
+
+class ClmAgent(ClmAgentManager):
+    """ This class acts as a high-level CLM agent, providing CLM functions to
+    the users at a higher level, and relieving the users of the need to manage
+    the life cycle of the CLM agent """
+
+    def __init__(self, version=None):
+        """ Constructor for ClmAgent class
+
+        Args:
+            version (SaVersionT): CLM API version
+        """
+        self.init_version = version if version else SaVersionT('B', 4, 1)
+        super(ClmAgent, self).__init__(self.init_version)
+
+    def __exit__(self):
+        """ Destructor for ClmAgent class
+
+        Finalize the CLM agent handle
+        """
+        if self.handle:
+            saClmFinalize(self.handle)
+
+    @bad_handle_retry
+    def _re_init(self):
+        """ Internal function to re-initialize the CLM agent in case of getting
+        BAD_HANDLE during an operation
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding CLM API call(s)
+        """
+        self.finalize()
+        self.handle = saClm.SaClmHandleT()
+        self.version = deepcopy(self.init_version)
+        rc = saClmInitialize_4(self.handle, self.callbacks, self.version)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saClmInitialize_4 FAILED - %s" % eSaAisErrorT.whatis(rc))
+        else:
+            rc = self._fetch_sel_obj()
+
+        return rc
+
+    def init(self, track_func=None, node_get_func=None):
+        """ Initialize the CLM agent and fetch the selection object
+
+        Args:
+            track_func (callback): Cluster track callback function
+            node_get_func (callback): Cluster node get callback function
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding CLM API call(s)
+        """
+        rc = self.initialize(track_func, node_get_func)
+        if rc == eSaAisErrorT.SA_AIS_OK:
+            rc = self._fetch_sel_obj()
+            if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+                rc = self._re_init()
+
+        return rc
+
+    @bad_handle_retry
+    def get_members(self):
+        """ Obtain information of each CLM cluster member node
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding CLM API call(s)
+            list(ClusterNode): The list of ClusterNode structures containing
+                information of each CLM member node
+        """
+        cluster_nodes = []
+
+        notification_buffer = saClm.SaClmClusterNotificationBufferT_4()
+
+        rc = saClmClusterTrack_4(self.handle, saAis.SA_TRACK_CURRENT,
+                                 notification_buffer)
+        if rc == eSaAisErrorT.SA_AIS_OK:
+            for i in range(notification_buffer.numberOfItems):
+                notification = notification_buffer.notification[i]
+                clm_cluster_node = notification.clusterNode
+
+                cluster_node = create_cluster_node_instance(clm_cluster_node)
+
+                cluster_nodes.append(cluster_node)
+        else:
+            log_err("saClmClusterTrack_4 FAILED - %s" %
+                    eSaAisErrorT.whatis(rc))
+
+        if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+            init_rc = self._re_init()
+            # If the re-initialization of agent handle succeeds, we still need
+            # to return BAD_HANDLE to the function decorator, so that it would
+            # re-try the failed operation. Otherwise, the true error code is
+            # returned to the user to decide further actions.
+            if init_rc != eSaAisErrorT.SA_AIS_OK:
+                rc = init_rc
+
+        return rc, cluster_nodes
+
+    @bad_handle_retry
+    def track_start(self, flags):
+        """ Start cluster membership tracking with the specified track flags
+
+        Args:
+            flags (SaUint8T): Type of cluster membership tracking
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding CLM API call(s)
+        """
+        rc = saClmClusterTrack_4(self.handle, flags, None)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saClmClusterTrack_4 FAILED - %s" %
+                    eSaAisErrorT.whatis(rc))
+
+        if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+            init_rc = self._re_init()
+            # If the re-initialization of agent handle succeeds, we still need
+            # to return BAD_HANDLE to the function decorator, so that it would
+            # re-try the failed operation. Otherwise, the true error code is
+            # returned to the user to decide further actions.
+            if init_rc != eSaAisErrorT.SA_AIS_OK:
+                rc = init_rc
+
+        return rc
+
+    def track_stop(self):
+        """ Stop cluster membership tracking
+
+        Returns:
+            SaAisErrorT: Return code of the saClmClusterTrackStop() API call
+        """
+        rc = saClmClusterTrackStop(self.handle)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saClmClusterTrack_4 FAILED - %s" %
+                    eSaAisErrorT.whatis(rc))
+
+        if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+            init_rc = self._re_init()
+            if init_rc != eSaAisErrorT.SA_AIS_OK:
+                rc = init_rc
+        # No need to retry in case of BAD_HANDLE since the tracking should
+        # have already been stopped when the agent disconnected
+        return rc
+
+    def response(self, invocation, result):
+        """ Respond to CLM the result of execution of the requested callback
+
+        Args:
+            invocation (SaInvocationT): Invocation id associated with the
+                callback
+            result (SaAisErrorT): Result of callback execution
+
+        Returns:
+            SaAisErrorT: Return code of the saClmResponse_4() API call
+        """
+        rc = saClmResponse_4(self.handle, invocation, result)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saClmResponse_4 FAILED - %s" % eSaAisErrorT.whatis(rc))
+
+        if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+            init_rc = self._re_init()
+            if init_rc != eSaAisErrorT.SA_AIS_OK:
+                rc = init_rc
+            # No need to retry in case of BAD_HANDLE since the corresponding
+            # callback response will no longer be valid with the new
+            # re-initialized handle
+
+        return rc
 
 
 class ClusterNode(object):
     """ Class representing a CLM cluster node """
+
     def __init__(self, node_id, node_address, node_name, execution_environment,
                  member, boot_timestamp, initial_view_number):
         """ Constructor for ClusterNode class
@@ -146,62 +429,182 @@ class ClusterNode(object):
         self.initial_view_number = initial_view_number
 
 
-def initialize(track_func=None):
-    """ Initialize the CLM library
+def create_cluster_node_instance(clm_cluster_node):
+    """ Create ClusterNode object from cluster node information
+
+    Args:
+        clm_cluster_node (SaClmClusterNodeT): Cluster node information
+
+    Returns:
+        ClusterNode: An object containing cluster node information
+    """
+    return ClusterNode(
+        node_id=clm_cluster_node.nodeId,
+        node_address=clm_cluster_node.nodeAddress,
+        node_name=clm_cluster_node.nodeName,
+        execution_environment=clm_cluster_node.executionEnvironment,
+        member=clm_cluster_node.member,
+        boot_timestamp=clm_cluster_node.bootTimestamp,
+        initial_view_number=clm_cluster_node.initialViewNumber)
+
+
+@deprecate
+def initialize(track_func=None, node_get_func=None, version=None):
+    """ Initialize the CLM agent library
 
     Args:
         track_func (callback): Cluster track callback function
+        node_get_func (callback): Cluster node get function
+        version (SaVersionT): Clm version being initialized
+
+    Raises:
+        SafException: If the return code of the corresponding CLM API call(s)
+            is not SA_AIS_OK
     """
-    global track_function
-    track_function = track_func
+    global _clm_agent
+    _clm_agent = ClmAgent(version)
+
+    rc = _clm_agent.initialize(track_func, node_get_func)
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
 
-    # Set up callbacks for cluster membership tracking
-    callbacks = saClm.SaClmCallbacksT_4()
-    callbacks.saClmClusterNodeGetCallback = \
-        saClm.SaClmClusterNodeGetCallbackT_4(node_get_callback)
-    callbacks.saClmClusterTrackCallback = \
-        saClm.SaClmClusterTrackCallbackT_4(track_callback)
 
-    # Define which version of the CLM API to use
-    version = SaVersionT('B', 4, 1)
+def get_handle():
+    """ Get this CLM agent handle
+
+    Returns:
+        SaClmHandleT: CLM agent handle if one was successfully initialized.
+            Otherwise, 'None' is returned.
+    """
+    if _clm_agent:
+        return _clm_agent.get_handle()
 
-    # Initialize the CLM interface
-    saClmInitialize_4(handle, callbacks, version)
+    return None
 
 
+@deprecate
 def track(flags=saAis.SA_TRACK_CHANGES_ONLY):
     """ Start cluster membership tracking with specified flags
 
     Args:
         flags (SaUint8T): Type of cluster membership tracking
+
+    Raises:
+        SafException: If the return code of the corresponding CLM API call(s)
+            is not SA_AIS_OK
+    """
+    if not _clm_agent:
+        # Return SA_AIS_ERR_INIT if user calls this function without first
+        # calling initialize()
+        return eSaAisErrorT.SA_AIS_ERR_INIT
+
+    rc = _clm_agent.track_start(flags)
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
+
+
+@deprecate
+def track_stop():
+    """ Stop cluster membership tracking
+
+    Raises:
+        SafException: If the return code of the corresponding CLM API call(s)
+            is not SA_AIS_OK
     """
-    saClmClusterTrack_4(handle, flags, None)
+    if not _clm_agent:
+        # Return SA_AIS_ERR_INIT if user calls this function without first
+        # calling initialize()
+        raise SafException(eSaAisErrorT.SA_AIS_ERR_INIT)
 
+    rc = _clm_agent.track_stop()
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
 
+
+@deprecate
 def get_members():
-    """ Get member notifications from notification buffer """
-    notification_buffer = saClm.SaClmClusterNotificationBufferT_4()
+    """ Get member notifications from notification buffer
 
-    saClmClusterTrack_4(handle, saAis.SA_TRACK_CURRENT, notification_buffer)
+    Returns:
+        SaAisErrorT: Return code of the corresponding CLM API call(s)
+        ClusterNode: The node information
 
+    Raises:
+        SafException: If the return code of the corresponding CLM API call(s)
+            is not SA_AIS_OK
+    """
     cluster_nodes = []
+    if not _clm_agent:
+        # SA_AIS_ERR_INIT is returned if user calls this function without first
+        # calling initialize()
+        raise SafException(eSaAisErrorT.SA_AIS_ERR_INIT)
+    else:
+        rc, cluster_nodes = _clm_agent.get_members()
+
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
 
-    for i in range(notification_buffer.numberOfItems):
-        notification = notification_buffer.notification[i]
-        clm_cluster_node = notification.clusterNode
+    return rc, cluster_nodes
 
-        cluster_node = create_cluster_node_instance(clm_cluster_node)
 
-        cluster_nodes.append(cluster_node)
+@deprecate
+def get_selection_object():
+    """ Get the selection object associated with this CLM agent
+
+    Raises:
+        SafException: If the return code of the corresponding CLM API call(s)
+            is not SA_AIS_OK
+    """
+    if not _clm_agent:
+        # SA_AIS_ERR_INIT is returned if user calls this function without first
+        # calling initialize()
+        raise SafException(eSaAisErrorT.SA_AIS_ERR_INIT)
 
-    return cluster_nodes
+    rc = _clm_agent.get_selection_object()
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
 
 
+@deprecate
 def dispatch(flags=eSaDispatchFlagsT.SA_DISPATCH_ALL):
-    """ Invoke CLM callbacks for queued events. The default is to dispatch all
-    available events.
+    """ Invoke CLM callbacks for queued events with the given dispatch flag.
+    If no dispatch flag is specified, the default is to dispatch all available
+    events.
 
     Args:
         flags (eSaDispatchFlagsT): Flags specifying dispatch mode
+
+    Raises:
+        SafException: If the return code of the corresponding CLM API call(s)
+            is not SA_AIS_OK
+    """
+    if not _clm_agent:
+        # SA_AIS_ERR_INIT is returned if user calls this function without first
+        # calling initialize()
+        raise SafException(eSaAisErrorT.SA_AIS_ERR_INIT)
+
+    rc = _clm_agent.dispatch(flags)
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
+
+
+@deprecate
+def response(invocation, result):
+    """ Respond to CLM the result of execution of the requested callback
+
+    Args:
+        invocation (SaInvocationT): Invocation id associated with the callback
+        result (SaAisErrorT): Result of callback execution
+
+    Raises:
+        SafException: If the return code of the corresponding CLM API call(s)
+            is not SA_AIS_OK
     """
-    saClmDispatch(handle, flags)
+    if not _clm_agent:
+        # SA_AIS_ERR_INIT is returned if user calls this function without first
+        # calling initialize()
+        raise SafException(eSaAisErrorT.SA_AIS_ERR_INIT)
+
+    rc = _clm_agent.response(invocation, result)
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
-- 
2.11.0


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Opensaf-devel mailing list
Opensaf-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/opensaf-devel

Reply via email to