#!/usr/bin/python """ Cluster Monitor Tool Author: Phillip Shea Updated: 2015-May-07 This tool provides real-time visualization of the cluster member roles for all shards in the config datastore. A file named 'cluster.json' contaning a list of the IP addresses of the controllers is required. This resides in the same directory as monitor.py. "user" and "pass" are not required for monitor.py, but they may be needed for other apps in this folder. The file should look like this: { "cluster": { "controllers": [ "172.17.10.93", "172.17.10.94", "172.17.10.95" ], "user": "username", "pass": "password" } } Usage:python monitor.py """ from io import BytesIO import time import pprint import curses import sys import json import pycurl import string def rest_get(restURL): rest_buffer = BytesIO() c = pycurl.Curl() c.setopt(c.TIMEOUT, 2) c.setopt(c.CONNECTTIMEOUT, 1) c.setopt(c.FAILONERROR, False) c.setopt(c.URL, str(restURL)) c.setopt(c.HTTPGET, 0) c.setopt(c.WRITEFUNCTION, rest_buffer.write) c.perform() c.close() return json.loads(rest_buffer.getvalue()) def getClusterRolesWithCurl(shardName, *args): ips = args[0] names = args[1] controller_state = {} for i, ip in enumerate(ips): controller_state[ip] = None url = 'http://' + ip + ':' + '8181/jolokia/read/org.opendaylight.controller:' url += 'Category=Shards,name=' + names[i] url += '-shard-' + shardName + '-config,type=DistributedConfigDatastore' try: resp = rest_get(url) if resp['status'] != 200: controller_state[ip] = 'HTTP ' + str(resp['status']) if 'value' in resp: data_value = resp['value'] controller_state[ip] = data_value['RaftState'] except: if 'timed out' in str(sys.exc_info()[1]): controller_state[ip] = 'timeout' elif 'JSON' in str(sys.exc_info()): controller_state[ip] = 'JSON error' elif 'connect to host' in str(sys.exc_info()): controller_state[ip] = 'no connection' else: controller_state[ip] = 'down' return controller_state def size_and_color(cluster_roles, field_length, ip_addr): status_dict = {} status_dict['txt'] = string.center(str(cluster_roles[ip_addr]), field_length) if cluster_roles[ip_addr] == "Leader": status_dict['color'] = curses.color_pair(2) elif cluster_roles[ip_addr] == "Follower": status_dict['color'] = curses.color_pair(3) elif cluster_roles[ip_addr] == "Candidate": status_dict['color'] = curses.color_pair(5) else: status_dict['color'] = curses.color_pair(0) return status_dict try: with open('cluster.json') as cluster_file: data = json.load(cluster_file) except: print str(sys.exc_info()) print 'Unable to open the file cluster.json' exit(1) try: controllers = data["cluster"]["controllers"] except: print str(sys.exc_info()) print 'Error reading the file cluster.json' exit(1) controller_names = [] Shards = set() # Retrieve controller names and shard names. for controller in controllers: url = "http://" + controller + ":8181/jolokia/read/org.opendaylight.controller:" url += "Category=ShardManager,name=shard-manager-config,type=DistributedConfigDatastore" try: data = rest_get(url) except: print 'Unable to retrieve shard names from ' + controller print 'Are all controllers up?' print str(sys.exc_info()[1]) exit(1) print 'shards from the first controller' pprint.pprint(data) # grab the controller name from the first shard name = data['value']['LocalShards'][0] print name pos = name.find('-shard-') print pos print name[:8] controller_names.append(name[:name.find('-shard-')]) # collect shards found in any controller; does not require all controllers to have the same shards for localShard in data['value']['LocalShards']: shardName = localShard[(localShard.find("-shard-")+7):localShard.find("-config")] Shards.add(shardName) print controller_names print Shards field_len = max(map(len, Shards))+2 stdscr = curses.initscr() curses.noecho() curses.cbreak() curses.curs_set(0) stdscr.keypad(1) stdscr.nodelay(1) curses.start_color() curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_GREEN) curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_BLUE) curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_YELLOW) curses.init_pair(5, curses.COLOR_BLACK, curses.COLOR_YELLOW) # display controller and shard headers for row, controller in enumerate(controller_names): stdscr.addstr(row + 1, 0, string.center(controller, field_len), curses.color_pair(1)) for data_column, shard in enumerate(Shards): stdscr.addstr(0, (field_len+1) * (data_column + 1), string.center(shard, field_len), curses.color_pair(1)) stdscr.addstr(len(Shards) + 2, 0, 'Press q to quit.', curses.color_pair(1)) stdscr.refresh() # display shard status odd_or_even = 0 key = '' while key != ord('q') and key != ord('Q'): odd_or_even += 1 key = stdscr.getch() for data_column, shard_name in enumerate(Shards): cluster_stat = getClusterRolesWithCurl(shard_name, controllers, controller_names) for row, controller in enumerate(controllers): status = size_and_color(cluster_stat, field_len, controller) stdscr.addstr(row + 1, (field_len+1) * (data_column + 1), status['txt'], status['color']) time.sleep(0.5) if odd_or_even % 2 == 0: stdscr.addstr(0, field_len/2 - 2, " <3 ", curses.color_pair(5)) else: stdscr.addstr(0, field_len/2 - 2, " <3 ", curses.color_pair(0)) stdscr.refresh() # clean up curses.nocbreak() stdscr.keypad(0) curses.echo() curses.endwin()