7 This tool provides real-time visualization of the cluster member roles for all
8 shards in the config 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
30 from io import BytesIO
40 def rest_get(restURL, username, password):
41 rest_buffer = BytesIO()
43 c.setopt(c.TIMEOUT, 2)
44 c.setopt(c.CONNECTTIMEOUT, 1)
45 c.setopt(c.FAILONERROR, False)
46 c.setopt(c.URL, str(restURL))
47 c.setopt(c.HTTPGET, 0)
48 c.setopt(c.WRITEFUNCTION, rest_buffer.write)
49 c.setopt(pycurl.USERPWD, "%s:%s" % (str(username), str(password)))
52 return json.loads(rest_buffer.getvalue())
55 def getClusterRolesWithCurl(shardName, *args):
59 for i, controller in enumerate(controllers):
60 controller_state[controller["ip"]] = None
61 url = "http://" + controller["ip"] + ":" + controller["port"] + "/jolokia/read/org.opendaylight.controller:"
62 url += 'Category=Shards,name=' + names[i]
63 url += '-shard-' + shardName + '-config,type=DistributedConfigDatastore'
65 resp = rest_get(url, username, password)
66 if resp['status'] != 200:
67 controller_state[controller["ip"]] = 'HTTP ' + str(resp['status'])
69 data_value = resp['value']
70 controller_state[controller["ip"]] = data_value['RaftState']
72 if 'timed out' in str(sys.exc_info()[1]):
73 controller_state[controller["ip"]] = 'timeout'
74 elif 'JSON' in str(sys.exc_info()):
75 controller_state[controller["ip"]] = 'JSON error'
76 elif 'connect to host' in str(sys.exc_info()):
77 controller_state[controller["ip"]] = 'no connection'
79 controller_state[controller["ip"]] = 'down'
80 return controller_state
83 def size_and_color(cluster_roles, field_length, ip_addr):
85 status_dict['txt'] = string.center(str(cluster_roles[ip_addr]), field_length)
86 if cluster_roles[ip_addr] == "Leader":
87 status_dict['color'] = curses.color_pair(2)
88 elif cluster_roles[ip_addr] == "Follower":
89 status_dict['color'] = curses.color_pair(3)
90 elif cluster_roles[ip_addr] == "Candidate":
91 status_dict['color'] = curses.color_pair(5)
93 status_dict['color'] = curses.color_pair(0)
98 with open('cluster.json') as cluster_file:
99 data = json.load(cluster_file)
101 print str(sys.exc_info())
102 print 'Unable to open the file cluster.json'
105 controllers = data["cluster"]["controllers"]
106 shards_to_exclude = data["cluster"]["shards_to_exclude"]
107 username = data["cluster"]["user"]
108 password = data["cluster"]["pass"]
110 print str(sys.exc_info())
111 print 'Error reading the file cluster.json'
114 controller_names = []
116 # Retrieve controller names and shard names.
117 for controller in controllers:
118 url = "http://" + controller["ip"] + ":" + controller["port"] + "/jolokia/read/org.opendaylight.controller:"
119 url += "Category=ShardManager,name=shard-manager-config,type=DistributedConfigDatastore"
120 rest_get(url, username, password)
122 data = rest_get(url, username, password)
124 print 'Unable to retrieve shard names from ' + str(controller)
125 print 'Are all controllers up?'
126 print str(sys.exc_info()[1])
128 print 'shards from the first controller'
130 # grab the controller name from the first shard
131 name = data['value']['LocalShards'][0]
133 pos = name.find('-shard-')
136 controller_names.append(name[:name.find('-shard-')])
138 # collect shards found in any controller; does not require all controllers to have the same shards
139 for localShard in data['value']['LocalShards']:
140 shardName = localShard[(localShard.find("-shard-") + 7):localShard.find("-config")]
141 if shardName not in shards_to_exclude:
142 Shards.add(shardName)
143 print controller_names
145 field_len = max(map(len, Shards)) + 2
147 stdscr = curses.initscr()
155 curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
156 curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_GREEN)
157 curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_BLUE)
158 curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_YELLOW)
159 curses.init_pair(5, curses.COLOR_BLACK, curses.COLOR_YELLOW)
161 # display controller and shard headers
162 for row, controller in enumerate(controller_names):
163 stdscr.addstr(row + 1, 0, string.center(controller, field_len), curses.color_pair(1))
164 for data_column, shard in enumerate(Shards):
165 stdscr.addstr(0, (field_len + 1) * (data_column + 1), string.center(shard, field_len), curses.color_pair(1))
166 stdscr.addstr(len(Shards) + 2, 0, 'Press q to quit.', curses.color_pair(1))
169 # display shard status
172 while key != ord('q') and key != ord('Q'):
176 for data_column, shard_name in enumerate(Shards):
177 if shard_name not in shards_to_exclude:
178 cluster_stat = getClusterRolesWithCurl(shard_name, controllers, controller_names)
179 for row, controller in enumerate(controllers):
180 status = size_and_color(cluster_stat, field_len, controller["ip"])
181 stdscr.addstr(row + 1, (field_len + 1) * (data_column + 1), status['txt'], status['color'])
183 if odd_or_even % 2 == 0:
184 stdscr.addstr(0, field_len / 2 - 2, " <3 ", curses.color_pair(5))
186 stdscr.addstr(0, field_len / 2 - 2, " <3 ", curses.color_pair(0))