- Refactor clm-tool.
- Add new function in clm util for sending node get.
---
python/pyosaf/utils/clm/__init__.py | 125 +++++++++------
python/samples/clm-tool | 309
++++++++++++++++++++++++++++--------
2 files changed, 322 insertions(+), 112 deletions(-)
diff --git a/python/pyosaf/utils/clm/__init__.py
b/python/pyosaf/utils/clm/__init__.py
index 533a4b5..5461d4a 100644
--- a/python/pyosaf/utils/clm/__init__.py
+++ b/python/pyosaf/utils/clm/__init__.py
@@ -44,6 +44,38 @@ saClmClusterNodeGetAsync =
decorate(saClm.saClmClusterNodeGetAsync)
saClmResponse_4 = decorate(saClm.saClmResponse_4)
+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
+
+ Args:
+ node_id (SaClmNodeIdT): Node identifier
+ node_address (SaClmNodeAddressT): Node network communication
+ address
+ node_name (SaNameT): Node name
+ execution_environment (SaNameT): DN of the PLM execution
+ environment that hosts the node
+ member (SaBoolT): Indicate whether this is a CLM member node (True)
+ or not (False)
+ boot_timestamp (SaTimeT): Timestamp at which the node was last
+ booted
+ initial_view_number (SaUint64T): View number of the latest
+ membership transition when this configured node joined the
+ cluster membership
+ """
+ self.node_id = node_id
+ self.node_address_value = node_address.value
+ self.node_address_family = node_address.family
+ self.node_name = node_name.value
+ self.execution_environment = execution_environment
+ self.member = member
+ self.boot_timestamp = boot_timestamp
+ self.initial_view_number = initial_view_number
+
+
class ClmAgentManager(object):
""" This class manages the life cycle of a CLM agent, and also acts as
a proxy handler for CLM callbacks """
@@ -105,7 +137,7 @@ class ClmAgentManager(object):
notification = notification_buffer.notification[i]
clm_cluster_node = notification.clusterNode
cluster_node = \
- create_cluster_node_instance(clm_cluster_node)
+
+ self.create_cluster_node_instance(clm_cluster_node)
node_state = (cluster_node,
notification.clusterChange)
node_list.append(node_state) @@ -133,7 +165,7 @@
class ClmAgentManager(object):
error = c_error
cluster_node = \
- create_cluster_node_instance(c_cluster_node.contents)
+
+ self.create_cluster_node_instance(c_cluster_node.contents)
# Send the node info to user's callback function
self.node_get_function(invocation, cluster_node, error)
@@ -221,7 +253,7 @@ class ClmAgentManager(object):
rc = saClmFinalize(self.handle)
if rc != eSaAisErrorT.SA_AIS_OK:
log_err("saClmFinalize FAILED - %s" %
eSaAisErrorT.whatis(rc))
- elif rc == eSaAisErrorT.SA_AIS_OK \
+ if 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.
@@ -229,6 +261,25 @@ class ClmAgentManager(object):
return rc
+ @staticmethod
+ 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)
+
class ClmAgent(ClmAgentManager):
""" This class acts as a high-level CLM agent, providing CLM
functions to @@ -327,7 +378,8 @@ class ClmAgent(ClmAgentManager):
notification = notification_buffer.notification[i]
clm_cluster_node = notification.clusterNode
- cluster_node = create_cluster_node_instance(clm_cluster_node)
+ cluster_node = \
+
+ self.create_cluster_node_instance(clm_cluster_node)
cluster_nodes.append(cluster_node)
else:
@@ -379,7 +431,7 @@ class ClmAgent(ClmAgentManager):
"""
rc = saClmClusterTrackStop(self.handle)
if rc != eSaAisErrorT.SA_AIS_OK:
- log_err("saClmClusterTrack_4 FAILED - %s" %
+ log_err("saClmClusterTrackStop FAILED - %s" %
eSaAisErrorT.whatis(rc))
if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
@@ -415,56 +467,29 @@ class ClmAgent(ClmAgentManager):
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
+ def get_node_info(self, node_id, time_out):
+ """ Get information of a specific cluster member node.
Args:
- node_id (SaClmNodeIdT): Node identifier
- node_address (SaClmNodeAddressT): Node network communication
- address
- node_name (SaNameT): Node name
- execution_environment (SaNameT): DN of the PLM execution
- environment that hosts the node
- member (SaBoolT): Indicate whether this is a CLM member node (True)
- or not (False)
- boot_timestamp (SaTimeT): Timestamp at which the node was last
- booted
- initial_view_number (SaUint64T): View number of the latest
- membership transition when this configured node joined the
- cluster membership
- """
- self.node_id = node_id
- self.node_address_value = node_address.value
- self.node_address_family = node_address.family
- self.node_name = node_name.value
- self.execution_environment = execution_environment
- self.member = member
- self.boot_timestamp = boot_timestamp
- self.initial_view_number = initial_view_number
-
+ node_id (SaClmNodeIdT): Node id
+ time_out (SaTimeT): Time-out (in nanosecond) for
+ saClmClusterNodeGet_4 operation
-def create_cluster_node_instance(clm_cluster_node):
- """ Create ClusterNode object from cluster node information
+ Returns:
+ SaAisErrorT: Return code of the saClmClusterNodeGet_4() API call.
+ ClusterNode: Node info or None if node with `node id` is not found.
+ """
+ clm_cluster_node = saClm.SaClmClusterNodeT_4()
- Args:
- clm_cluster_node (SaClmClusterNodeT): Cluster node information
+ rc = saClmClusterNodeGet_4(self.handle, node_id, time_out,
+ clm_cluster_node)
+ if rc != eSaAisErrorT.SA_AIS_OK:
+ log_err("saClmClusterNodeGet() FAILED - %s" %
+ eSaAisErrorT.whatis(rc))
+ return rc, None
- 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)
+ cluster_node = self.create_cluster_node_instance(clm_cluster_node)
+ return rc, cluster_node
@deprecate
diff --git a/python/samples/clm-tool b/python/samples/clm-tool index
89bf0d3..60dcc8f 100755
--- a/python/samples/clm-tool
+++ b/python/samples/clm-tool
@@ -1,67 +1,252 @@
#!/usr/bin/env python
-
+#####################################################################
+#######
+#
+# (C) Copyright 2015 The OpenSAF Foundation # (C) Copyright 2017
+Ericsson AB. All rights reserved.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. This file and
+program are licensed # under the GNU Lesser General Public License Version
2.1, February 1999.
+# The complete license can be accessed from the following location:
+# http://opensource.org/licenses/lgpl-license.php
+# See the Copying file included with the OpenSAF distribution for
+full # licensing terms.
+#
+# Author(s): Ericsson
+#
+#####################################################################
+#######
+"""
+This is a SAF CLM utility which supports options to:
+ - Retrieve information about one or all CLM cluster member nodes.
+ - Track CLM cluster membership changes.
+ - Run clm-tool --help/-h for more detail on usage.
+"""
+from __future__ import print_function
import argparse
import sys
+from select import select
-from pyosaf import saAis, saClm, saImm
-
+from pyosaf.saAis import saAis, eSaAisErrorT, eSaDispatchFlagsT
from pyosaf.utils import clm
-
-def dn_to_vm_name(vm_dn):
- return vm_dn.split(',')[0].split('=')[1]
-
-def print_members(members):
-
- for node in members:
- if node.member:
- vm_name = dn_to_vm_name(node.node_name)
- ip_address = node.node_address_value
-
- print " - %s %s" % (vm_name, ip_address)
-
-def membership_change(added, removed):
- print "Info: Received cluster membership update"
-
- for node in added:
- vm_name = dn_to_vm_name(node.node_name)
-
- print "INFO: %s joined the cluster" % vm_name
-
- for node in removed:
- vm_name = dn_to_vm_name(node.node_name)
-
- print "INFO: %s left the cluster" % vm_name
-
-
-if __name__ == "__main__":
-
- # Parse command line arguments
- parser = argparse.ArgumentParser(
- description='Listens to changes to objects of the given classes')
-
- group = parser.add_mutually_exclusive_group(required=True)
-
- group.add_argument('--snapshot', action="store_true",
- help='shows a snapshot of the current membership list')
- group.add_argument('--listen', action="store_true", help='listens to
changes to all classes')
-
- args = parser.parse_args()
-
- # Initialize the CLM service
- clm.initialize(track_fn=membership_change)
-
- if args.snapshot:
- print "-" * 10 + " Members " + "-"*10
-
- members = clm.get_members()
-
- print_members(members)
-
- sys.exit(0)
-
- # Start tracking
- clm.track()
-
- # Do dispatch forever
- while True:
- clm.saClmDispatch(clm.HANDLE,
saAis.eSaDispatchFlagsT.SA_DISPATCH_BLOCKING)
+from pyosaf.utils.immom.accessor import ImmOmAccessor
+
+
+class ClmTool(object):
+ """ This class provides functions of the clm-tool. """
+ def __init__(self):
+ self.clm_agent = clm.ClmAgent()
+ self.clm_agent.init(self.membership_change)
+ self.sel_obj = self.clm_agent.get_selection_object()
+
+ def print_members(self):
+ """ Print out the name and the IP address of all current member nodes.
+
+ Returns:
+ Boolean: True if cluster members info is printed successfully.
+ Otherwise, it is False.
+
+ """
+ print("-" * 10 + " Members " + "-" * 10)
+ rc, members = self.clm_agent.get_members()
+ if rc != eSaAisErrorT.SA_AIS_OK:
+ print("Failed to get current membership. rc:",
+ eSaAisErrorT.whatis(rc))
+ return False
+ else:
+ for node in members:
+ node_name = self.node_dn_to_node_name(node.node_name)
+ ip_address = node.node_address_value
+ print(" - %s %s" % (node_name, ip_address))
+
+ return True
+
+ def request_node_info(self, node_dn, time_out):
+ """ Request the information of a specific member node which is
+ identified by its DN.
+
+ Args:
+ node_dn (str): The DN of SaClmNode class's object.
+ time_out (int): Timeout in nanosecond.
+
+ Returns:
+ ClusterNode: Node info or None if node with `node_dn` is not found.
+ """
+ accessor = ImmOmAccessor()
+ accessor.init()
+
+ rc, clm_node_object = accessor.get(node_dn)
+ if rc != eSaAisErrorT.SA_AIS_OK:
+ print("%s object does not exist" % node_dn)
+ return None
+
+ node_id = clm_node_object["saClmNodeID"]
+ rc, mem_node_info = self.clm_agent.get_node_info(node_id, time_out)
+ if rc != eSaAisErrorT.SA_AIS_OK:
+ print("Failed to request node info. rc:", eSaAisErrorT.whatis(rc))
+ return None
+
+ return mem_node_info
+
+ def start_tracking(self, track_type):
+ """ Start tracking member nodes.
+
+ Args:
+ track_type (SaUint8T): Type of cluster membership
+ tracking
+
+ Returns:
+ Boolean: True if it starts tracking successfully. Otherwise, it is
+ False.
+ """
+ rc = self.clm_agent.track_start(track_type)
+ if rc != eSaAisErrorT.SA_AIS_OK:
+ print("Failed to start tracking. rc:", eSaAisErrorT.whatis(rc))
+ return False
+
+ return True
+
+ def stop_tracking(self):
+ """ Stop tracking member nodes.
+
+ Returns:
+ Boolean: True if it stops tracking successfully. Otherwise, it is
+ False.
+ """
+ rc = self.clm_agent.track_stop()
+ if rc != eSaAisErrorT.SA_AIS_OK:
+ print("Failed to stop tracking. rc:", eSaAisErrorT.whatis(rc))
+ return False
+
+ return True
+
+ def dispatch(self, dispatch_flag):
+ """ Dispatch message to appropriate callback.
+
+ Args:
+ dispatch_flag (eSaDispatchFlagsT): Flags specifying dispatch mode.
+
+ Returns:
+ Boolean: True if it dispatches message successfully. Otherwise, it
+ is False.
+ """
+ rc = self.clm_agent.dispatch(dispatch_flag)
+ if rc != eSaAisErrorT.SA_AIS_OK:
+ print("Failed to dispatch message. rc:", eSaAisErrorT.whatis(rc))
+ return False
+
+ return True
+
+ def finalize(self):
+ """ Finalize the clm agent.
+
+ Returns:
+ Boolean: True if it clm agent finalizes successfully. Otherwise, it
+ is False.
+ """
+ rc = self.clm_agent.finalize()
+ if rc != eSaAisErrorT.SA_AIS_OK:
+ print("Failed to finalize ClmAgent. rc:", eSaAisErrorT.whatis(rc))
+ return False
+
+ return True
+
+ def membership_change(self, node_list, invocation, step,
+ num_of_members, error):
+ """ Callback to handle membership change event.
+
+ Args:
+ node_list (tuple): A list of tuple which contain cluster node info
+ and cluster node change type.
+ invocation (SaInvocationT): A particular invocation of the
+ callback function.
+ step (SaClmChangeStepT): Indicates the tracking step in which
+ the callback is invoked.
+ num_of_members (SaUint32T): The current number of members in the
+ cluster membership.
+ error (SaAisErrorT): indicates if the CLM was able to perform the
+ requested operation.
+ """
+
+ print('-' * 10 + 'Membership Change' + '-' * 10)
+ print("Changed nodes:")
+ for node in node_list:
+ self.print_node_info(node[0])
+ print("\tNode change state:", node[1])
+ print("\t" + "-" * 10)
+
+ print("Invocation:", invocation)
+ print("Step:", step)
+ print("Number of members:", num_of_members)
+ print("Error code:", eSaAisErrorT.whatis(error))
+
+ @staticmethod
+ def print_node_info(cluster_node):
+ """ Print information of a cluster node.
+
+ Args:
+ cluster_node (ClusterNode): ClusterNode object which is printed.
+ """
+ print("\tNode ID:", cluster_node.node_id)
+ print("\tNode Address:", cluster_node.node_address_value)
+ print("\tNode Address Family:", cluster_node.node_address_family)
+ print("\tNode Name:", cluster_node.node_name)
+ print("\tExecution Environment:",
+ 'None' if cluster_node.execution_environment.length == 0
+ else cluster_node.execution_environment.value)
+ print("\tMember:", 'Yes' if cluster_node.member == 1 else 'No')
+ print("\tBoot Timestamp:", cluster_node.boot_timestamp)
+ print("\tView Number:", cluster_node.initial_view_number)
+
+ @staticmethod
+ def node_dn_to_node_name(node_dn):
+ """ Convert from node's DN to node's name.
+
+ Args:
+ node_dn (str): DN of a SaClmNode object.
+
+ Returns:
+ str: Name of node.
+ """
+ return node_dn.split(',')[0].split('=')[1]
+
+
+# Main
+parser = argparse.ArgumentParser(description='This is a SAF CLM utility used '
+ 'to view/track CLM cluster '
+ 'membership.') group =
+parser.add_mutually_exclusive_group(required=True)
+group.add_argument('-s', '--show', action="store_true",
+ help='print info of all current cluster member
+nodes.') group.add_argument('-n', '--nodeinfo', type=str,
+ help="print info of a specific cluster member
+node.") group.add_argument('-t', '--track', action="store_true",
+ help='track changes in cluster membership.')
+
+clm_tool = ClmTool()
+result = True
+
+args = parser.parse_args()
+if args.show:
+ result = clm_tool.print_members() elif args.nodeinfo:
+ node_info = clm_tool.request_node_info(args.nodeinfo, 6000000000)
+ if node_info is not None:
+ clm_tool.print_node_info(node_info)
+ else:
+ result = False
+elif args.track:
+ fds = [clm_tool.sel_obj.value]
+ result = clm_tool.start_tracking(saAis.SA_TRACK_CHANGES)
+
+ try:
+ while result:
+ ioe = select(fds, [], [])
+ for fd in ioe[0]:
+ if fd == clm_tool.sel_obj.value:
+ result = clm_tool.dispatch(
+ eSaDispatchFlagsT.SA_DISPATCH_ALL)
+ except KeyboardInterrupt:
+ clm_tool.stop_tracking()
+
+clm_tool.finalize()
+sys.exit(0 if result else 1)