7 This tool provides real-time visualization of the cluster member roles for all
8 shards in either the config or operational datastore.
10 A file named 'cluster.json' contaning a list of the IP addresses and port numbers
11 of the controllers is required. This resides in the same directory as monitor.py.
12 "user" and "pass" are not required for monitor.py, but they may be
13 needed for other apps in this folder. The file should look like this:
18 {"ip": "172.17.10.93", "port": "8181"},
19 {"ip": "172.17.10.93", "port": "8181"},
20 {"ip": "172.17.10.93", "port": "8181"}
24 "shards_to_exclude": [] # list of shard names to omit from output
28 Usage:python monitor.py [-d data_store_name]
30 from io import BytesIO
41 def rest_get(restURL, username, password):
42 rest_buffer = BytesIO()
44 c.setopt(c.TIMEOUT, 2)
45 c.setopt(c.CONNECTTIMEOUT, 1)
46 c.setopt(c.FAILONERROR, False)
47 c.setopt(c.URL, str(restURL))
48 c.setopt(c.HTTPGET, 0)
49 c.setopt(c.WRITEFUNCTION, rest_buffer.write)
50 c.setopt(pycurl.USERPWD, "%s:%s" % (str(username), str(password)))
53 return json.loads(rest_buffer.getvalue())
56 def getClusterRolesWithCurl(shardName, *args):
60 for i, controller in enumerate(controllers):
61 controller_state[controller["ip"]] = None
62 url = "http://" + controller["ip"] + ":" + controller["port"] + "/jolokia/read/org.opendaylight.controller:"
63 url += 'Category=Shards,name=' + names[i]
64 url += '-shard-' + shardName + '-' + data_store.lower() + ',type=Distributed' + data_store + 'Datastore'
66 resp = rest_get(url, username, password)
67 if resp['status'] != 200:
68 controller_state[controller["ip"]] = 'HTTP ' + str(resp['status'])
70 data_value = resp['value']
71 controller_state[controller["ip"]] = data_value['RaftState']
73 if 'timed out' in str(sys.exc_info()[1]):
74 controller_state[controller["ip"]] = 'timeout'
75 elif 'JSON' in str(sys.exc_info()):
76 controller_state[controller["ip"]] = 'JSON error'
77 elif 'connect to host' in str(sys.exc_info()):
78 controller_state[controller["ip"]] = 'no connection'
80 controller_state[controller["ip"]] = 'down'
81 return controller_state
84 def size_and_color(cluster_roles, field_length, ip_addr):
86 status_dict['txt'] = string.center(str(cluster_roles[ip_addr]), field_length)
87 if cluster_roles[ip_addr] == "Leader":
88 status_dict['color'] = curses.color_pair(2)
89 elif cluster_roles[ip_addr] == "Follower":
90 status_dict['color'] = curses.color_pair(3)
91 elif cluster_roles[ip_addr] == "Candidate":
92 status_dict['color'] = curses.color_pair(5)
94 status_dict['color'] = curses.color_pair(0)
98 parser = argparse.ArgumentParser()
99 parser.add_argument('-d', '--datastore', default='Config', type=str,
100 help='polling can be done on "Config" or "Operational" data stores')
101 args = parser.parse_args()
102 data_store = args.datastore
103 if data_store != 'Config' and data_store != 'Operational':
104 print 'Only "Config" or "Operational" data store is available for polling'
108 with open('cluster.json') as cluster_file:
109 data = json.load(cluster_file)
111 print str(sys.exc_info())
112 print 'Unable to open the file cluster.json'
115 controllers = data["cluster"]["controllers"]
116 shards_to_exclude = data["cluster"]["shards_to_exclude"]
117 username = data["cluster"]["user"]
118 password = data["cluster"]["pass"]
120 print str(sys.exc_info())
121 print 'Error reading the file cluster.json'
124 controller_names = []
126 # Retrieve controller names and shard names.
127 for controller in controllers:
128 url = "http://" + controller["ip"] + ":" + controller["port"] + "/jolokia/read/org.opendaylight.controller:"
129 url += "Category=ShardManager,name=shard-manager-" + data_store.lower()\
130 + ",type=Distributed" + data_store + "Datastore"
131 rest_get(url, username, password)
133 data = rest_get(url, username, password)
135 print 'Unable to retrieve shard names from ' + str(controller)
136 print 'Are all controllers up?'
137 print str(sys.exc_info()[1])
139 print 'shards from the first controller'
141 # grab the controller name from the first shard
142 name = data['value']['LocalShards'][0]
144 pos = name.find('-shard-')
147 controller_names.append(name[:name.find('-shard-')])
149 # collect shards found in any controller; does not require all controllers to have the same shards
150 for localShard in data['value']['LocalShards']:
151 shardName = localShard[(localShard.find("-shard-") + 7):localShard.find("-" + data_store.lower())]
152 if shardName not in shards_to_exclude:
153 Shards.add(shardName)
154 print controller_names
156 field_len = max(map(len, Shards)) + 2
158 stdscr = curses.initscr()
166 curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
167 curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_GREEN)
168 curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_BLUE)
169 curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_YELLOW)
170 curses.init_pair(5, curses.COLOR_BLACK, curses.COLOR_YELLOW)
172 # display controller and shard headers
173 for row, controller in enumerate(controller_names):
174 stdscr.addstr(row + 1, 0, string.center(controller, field_len), curses.color_pair(1))
175 for data_column, shard in enumerate(Shards):
176 stdscr.addstr(0, (field_len + 1) * (data_column + 1), string.center(shard, field_len), curses.color_pair(1))
177 stdscr.addstr(len(Shards) + 2, 0, 'Press q to quit.', curses.color_pair(1))
180 # display shard status
183 while key != ord('q') and key != ord('Q'):
187 for data_column, shard_name in enumerate(Shards):
188 if shard_name not in shards_to_exclude:
189 cluster_stat = getClusterRolesWithCurl(shard_name, controllers, controller_names)
190 for row, controller in enumerate(controllers):
191 status = size_and_color(cluster_stat, field_len, controller["ip"])
192 stdscr.addstr(row + 1, (field_len + 1) * (data_column + 1), status['txt'], status['color'])
194 if odd_or_even % 2 == 0:
195 stdscr.addstr(0, field_len / 2 - 2, " <3 ", curses.color_pair(5))
197 stdscr.addstr(0, field_len / 2 - 2, " <3 ", curses.color_pair(0))