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