fixing pep8 problems for test verify tox job
[integration/test.git] / tools / clustering / cluster-monitor / monitor.py
1 #!/usr/bin/python
2 """
3 Cluster Monitor Tool
4 Author: Phillip Shea
5 Updated: 2016-Mar-07
6
7 This tool provides real-time visualization of the cluster member roles for all
8 shards in either the config or operational datastore.
9
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:
14
15     {
16         "cluster": {
17             "controllers": [
18                 {"ip": "172.17.10.93", "port": "8181"},
19                 {"ip": "172.17.10.93", "port": "8181"},
20                 {"ip": "172.17.10.93", "port": "8181"}
21             ],
22             "user": "username",
23             "pass": "password",
24             "shards_to_exclude": []  # list of shard names to omit from output
25         }
26     }
27
28 Usage:python monitor.py [-d data_store_name]
29 """
30 from io import BytesIO
31 import time
32 import pprint
33 import curses
34 import sys
35 import json
36 import pycurl
37 import string
38 import argparse
39
40
41 def rest_get(restURL, username, password):
42     rest_buffer = BytesIO()
43     c = pycurl.Curl()
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)))
51     c.perform()
52     c.close()
53     return json.loads(rest_buffer.getvalue())
54
55
56 def getClusterRolesWithCurl(shardName, *args):
57     controllers = args[0]
58     names = args[1]
59     controller_state = {}
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'
65         try:
66             resp = rest_get(url, username, password)
67             if resp['status'] != 200:
68                 controller_state[controller["ip"]] = 'HTTP ' + str(resp['status'])
69             if 'value' in resp:
70                 data_value = resp['value']
71                 controller_state[controller["ip"]] = data_value['RaftState']
72         except:
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'
79             else:
80                 controller_state[controller["ip"]] = 'down'
81     return controller_state
82
83
84 def size_and_color(cluster_roles, field_length, ip_addr):
85     status_dict = {}
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)
93     else:
94         status_dict['color'] = curses.color_pair(0)
95     return status_dict
96
97
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'
105     exit(1)
106
107 try:
108     with open('cluster.json') as cluster_file:
109         data = json.load(cluster_file)
110 except:
111     print str(sys.exc_info())
112     print 'Unable to open the file cluster.json'
113     exit(1)
114 try:
115     controllers = data["cluster"]["controllers"]
116     shards_to_exclude = data["cluster"]["shards_to_exclude"]
117     username = data["cluster"]["user"]
118     password = data["cluster"]["pass"]
119 except:
120     print str(sys.exc_info())
121     print 'Error reading the file cluster.json'
122     exit(1)
123
124 controller_names = []
125 Shards = set()
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)
132     try:
133         data = rest_get(url, username, password)
134     except:
135         print 'Unable to retrieve shard names from ' + str(controller)
136         print 'Are all controllers up?'
137         print str(sys.exc_info()[1])
138         exit(1)
139     print 'shards from the first controller'
140     pprint.pprint(data)
141     # grab the controller name from the first shard
142     name = data['value']['LocalShards'][0]
143     print name
144     pos = name.find('-shard-')
145     print pos
146     print name[:8]
147     controller_names.append(name[:name.find('-shard-')])
148
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
155 print Shards
156 field_len = max(map(len, Shards)) + 2
157
158 stdscr = curses.initscr()
159 curses.noecho()
160 curses.cbreak()
161 curses.curs_set(0)
162 stdscr.keypad(1)
163 stdscr.nodelay(1)
164
165 curses.start_color()
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)
171
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))
178 stdscr.refresh()
179
180 # display shard status
181 odd_or_even = 0
182 key = ''
183 while key != ord('q') and key != ord('Q'):
184     odd_or_even += 1
185     key = stdscr.getch()
186
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'])
193         time.sleep(0.5)
194         if odd_or_even % 2 == 0:
195             stdscr.addstr(0, field_len / 2 - 2, " <3 ", curses.color_pair(5))
196         else:
197             stdscr.addstr(0, field_len / 2 - 2, " <3 ", curses.color_pair(0))
198         stdscr.refresh()
199
200 # clean up
201 curses.nocbreak()
202 stdscr.keypad(0)
203 curses.echo()
204 curses.endwin()