Merge commit 'refs/changes/43/1843/1' of https://git.opendaylight.org/gerrit/affinity
[affinity.git] / scripts / analytics.py
1 #!/usr/local/bin/python
2
3 import httplib2
4 import json
5 import sys
6 import time
7
8 # 1. Start the controller
9 # 2. On the local machine (e.g., your laptop), start this script.
10 #    > python analytics.py
11 # 3. On the mininet VM, run:
12 #    > sudo mn --controller=remote,ip=192.168.56.1 --topo tree,2
13 #    > h1 ping h3
14 # 4. Give commands to analytics.py.  For instance:
15 #    > host bytes 10.0.0.1 10.0.0.3
16 #   (There is a usage prompt that prints at the beginning of analytics.py)
17 # 5. Type 'quit' to exit analytics.py
18
19
20 '''
21 Class for keeping track of host stats or affinity link stats, depending.
22 '''
23 class Stats:
24
25     # TODO: Each stat should probably be a thread, and handle its
26     # own output and refreshing for the EWMA
27
28     def __init__(self, stat_type, **kwargs):
29         self.stat_type = stat_type
30         if stat_type == "host":
31             self.src = kwargs['src']
32             self.dst = kwargs['dst']
33             self.url_prefix = "http://localhost:8080/affinity/nb/v2/analytics/default/hoststats/" 
34         elif stat_type == "affinityLink":
35             self.al = kwargs['al']
36             self.url_prefix = "http://localhost:8080/affinity/nb/v2/analytics/default/affinitylinkstats/"
37         else:
38             print "incorrect stat type", stat_type
39
40         self.stats = {}
41         self.rate_ewma = None
42
43         self.http = httplib2.Http(".cache")
44         self.http.add_credentials('admin', 'admin')
45         self.refresh()
46
47     # Refresh statistics
48     def refresh(self):
49         if (self.stat_type == "host"):
50             resp, content = self.http.request(self.url_prefix + self.src + "/" + self.dst, "GET")
51         elif (self.stat_type == "affinityLink"):
52             resp, content = self.http.request(self.url_prefix + self.al, "GET")
53         self.stats = json.loads(content)
54         self.handle_rate_ewma()
55
56     # EWMA calculation for bit rate
57     def handle_rate_ewma(self):
58         alpha = .25
59         anomaly_threshold = 2.0
60         new_bitrate = self.get_bit_rate()
61
62         if self.rate_ewma == None:
63             self.rate_ewma = new_bitrate
64         else:
65             new_rate_ewma = alpha * new_bitrate + (1 - alpha) * self.rate_ewma
66             if (self.rate_ewma > 0 and new_rate_ewma > anomaly_threshold * self.rate_ewma):
67                 if (self.stat_type == "host"):
68                     print "!! Anomaly detected between %s and %s" % (self.src, self.dst)
69                 elif (self.stat_type == "affinityLink"):
70                     print "!! Anomaly detected on AffinityLink %s" % (self.al)
71                 print "!! Rate rose from %1.1f Mbit/s to %1.1f Mbit/s" % ((self.rate_ewma/10**6), (new_rate_ewma/10**6))
72             self.rate_ewma = new_rate_ewma
73
74     # Bytes
75     def get_bytes(self):
76         try:
77             bytes = long(self.stats["byteCount"])
78         except Exception as e:
79             bytes = 0
80         return bytes
81
82     # Bit Rate
83     def get_bit_rate(self):
84         try:
85             bitrate = float(self.stats["bitRate"])
86         except Exception as e:
87             bitrate = 0.0
88         return bitrate
89
90
91 class AffinityControl:
92
93     def __init__(self):
94         self.http = httplib2.Http(".cache")
95         self.http.add_credentials("admin", "admin")
96         self.url_prefix = "http://localhost:8080/affinity/nb/v2/affinity/default/"
97         self.groups = []
98         self.links = []        
99
100     def add_affinity_group(self, group_name, ips):
101         resp, content = self.http.request(self.url_prefix + "create/group/%s" % group_name, "PUT")
102         if (resp.status != 201):
103             print "AffinityGroup %s could not be created" % group_name
104             return
105         for ip in ips:
106             resp, content = self.http.request(self.url_prefix + "group/%s/add/ip/%s" % (group_name, ip), "PUT")
107             if (resp.status != 201):
108                 print "IP %s could not be added to AffinityGroup %s" % (ip, group_name)
109                 return
110         self.groups.append(group_name)
111         print "AffinityGroup %s added successfully. IPs are %s" % (group_name, ips)
112
113
114     def add_affinity_link(self, link_name, src_group, dst_group):
115         resp, content = self.http.request(self.url_prefix + "create/link/%s/from/%s/to/%s" % (link_name, src_group, dst_group), "PUT")
116         if (resp.status != 201):
117             print "AffinityLink %s could not be added between %s and %s" % (link_name, src_group, dst_group)
118             return
119         self.links.append(link_name)
120         print "AffinityLink %s added between %s and %s" % (link_name, src_group, dst_group)
121
122
123 '''
124 Class for controlling subnets.  Right now, just adds subnets and
125 checks whether they exist, because that's all we need.
126 '''
127 class SubnetControl:
128
129     def __init__(self):
130         self.http = httplib2.Http(".cache")
131         self.http.add_credentials("admin", "admin")
132         self.url_prefix = "http://localhost:8080/controller/nb/v2/subnetservice/default/"
133
134     # Checks whether subnet exists.  Checks against the actual subnet
135     # string (e.g., "10.0.0.255/1"), not the subnet name.  Will not
136     # catch things like overlapping subnets.
137     def exists(self, subnet):
138         resp, content = self.http.request(self.url_prefix + "subnets", "GET")
139         if (resp.status != 200):
140             print "Fatal error - can't check for subnet existence"
141             sys.exit(-1)
142         data = json.loads(content)
143
144         for key in data["subnetConfig"]:
145             if (key["subnet"] == subnet):
146                 return True
147         return False
148
149     # Add a subnet if it doesn't already exist.
150     def add_subnet(self, subnet_name, subnet):
151         if (self.exists(subnet)):
152             print "subnet", subnet, "already exists"
153             return
154         subnet_config = dict(name=subnet_name, subnet=subnet)
155         json_data = json.dumps(subnet_config)
156         resp, content = self.http.request(self.url_prefix + "subnet/" + subnet_name, "POST", json_data, {'Content-Type': 'application/json'})
157         if (resp.status == 201):
158             print "subnet", subnet, "added"
159         else:
160             print "subnet", subnet, "could not be added"
161
162
163 def run_interactive_mode():
164
165     print "Usage: [host | link] [bytes | rate] [src dst | link-name]"
166
167     # Demo mode
168     while True:
169         request = raw_input("> ")
170         try:
171             request = request.split()
172             request_type = request[0]
173
174             if (request_type == "quit"):
175                 sys.exit()
176
177             if (request_type == "host"):
178                 action = request[1]
179                 src, dst = request[2:4]
180                 host_stat = Stats("host", src=src, dst=dst)
181                 if (action == "bytes"):
182                     print("%d bytes between %s and %s" % (host_stat.get_bytes(), src, dst))
183                 elif (action == "rate"):
184                     print("%f bit/s between %s and %s" % (host_stat.get_bit_rate(), src, dst))
185                 else:
186                     print "wrong action"
187                     raise Exception
188
189             elif (request_type == "link"):
190                 action = request[1]
191                 link = request[2]
192                 link_stat = Stats("affinityLink", al=link)
193                 if (action == "bytes"):
194                     print("%d bytes on %s" % (link_stat.get_bytes(), link))
195                 elif (action == "rate"):
196                     print("%f bit/s on %s" % (link_stat.get_bit_rate(), link))
197                 else:
198                     print "wrong action 2"
199                     raise Exception
200
201             elif (request_type == "prefix"):
202                 prefix = request[1]
203                 h = httplib2.Http(".cache")
204                 h.add_credentials("admin", "admin")
205                 url_prefix = "http://localhost:8080/affinity/nb/v2/analytics/default/prefixstats/"
206                 resp, content = h.request(url_prefix + prefix, "GET")
207                 if (resp.status == 200):
208                     data = json.loads(content)
209                     print data['byteCount'], "bytes"
210
211             else:
212                 print "something else"
213                 raise Exception
214         except Exception as e:
215             print "Error"
216             print e
217
218
219 def get_all_hosts():
220
221     h = httplib2.Http(".cache")
222     h.add_credentials("admin", "admin")
223
224     resp, content = h.request("http://localhost:8080/controller/nb/v2/hosttracker/default/hosts/active", "GET")
225     host_content = json.loads(content)
226
227     # Even if there are no active hosts, host_content['hostConfig']
228     # still exists (and is empty)
229     active_hosts = []
230     for host_data in host_content['hostConfig']:
231         active_hosts.append(host_data['networkAddress'])
232     return active_hosts
233
234
235 def run_passive_mode(affinity_links):
236     # TODO: Get affinity_links automatically
237     affinity_link_stats = {}
238
239     # Go through all affinity link stats
240     while True:
241         for al in affinity_links:
242             if al not in affinity_link_stats:
243                 affinity_link_stats[al] = Stats("affinityLink", al=al)
244             stat = affinity_link_stats[al]
245             stat.refresh()
246             print "%d bytes (%1.1f Mbit/s) on %s" % (stat.get_bytes(), (stat.get_bit_rate() / (10**6)), al)
247         time.sleep(2)
248
249 def main():
250
251     # Default subnet is required for the host tracker to work.
252     subnet_control = SubnetControl()
253     subnet_control.add_subnet("defaultSubnet", "10.0.0.254/8")
254
255     # Set up an affinity link
256     affinity_control = AffinityControl()
257     affinity_control.add_affinity_group("testAG1", ["10.0.0.1", "10.0.0.2"])
258     affinity_control.add_affinity_group("testAG2", ["10.0.0.3", "10.0.0.4"])
259     affinity_control.add_affinity_link("testAL", "testAG1", "testAG2")
260     raw_input("[Press enter to continue]" )
261
262     interactive_mode = True
263
264     if interactive_mode:
265         run_interactive_mode()
266     else:
267         run_passive_mode(["testAL"])
268
269 if __name__ == "__main__":
270     main()