1 #!/usr/local/bin/python
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
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
21 Class for keeping track of host stats or affinity link stats, depending.
25 # TODO: Each stat should probably be a thread, and handle its
26 # own output and refreshing for the EWMA
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/"
38 print "incorrect stat type", stat_type
43 self.http = httplib2.Http(".cache")
44 self.http.add_credentials('admin', 'admin')
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
53 return "Unknown Stats type"
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()
65 # EWMA calculation for bit rate
66 def handle_rate_ewma(self):
68 anomaly_threshold = 2.0
69 new_bitrate = self.get_bit_rate()
71 if self.rate_ewma == None:
72 self.rate_ewma = new_bitrate
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
80 def check_large_flow(self):
81 if (self.get_bytes() > 5 * (10**6)):
82 print "!! Large flow detected on %s" % self
87 bytes = long(self.stats["byteCount"])
88 except Exception as e:
93 def get_bit_rate(self):
95 bitrate = float(self.stats["bitRate"])
96 except Exception as e:
101 class AffinityControl:
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/"
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
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)
120 self.groups.append(group_name)
121 print "AffinityGroup %s added successfully. IPs are %s" % (group_name, ips)
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)
129 self.links.append(link_name)
130 print "AffinityLink %s added between %s and %s" % (link_name, src_group, dst_group)
134 Class for controlling subnets. Right now, just adds subnets and
135 checks whether they exist, because that's all we need.
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/"
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"
152 data = json.loads(content)
154 for key in data["subnetConfig"]:
155 if (key["subnet"] == subnet):
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"
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"
170 print "subnet", subnet, "could not be added"
173 def run_interactive_mode():
175 print "Usage: [host | link] [bytes | rate] [src dst | link-name]"
179 request = raw_input("> ")
181 request = request.split()
182 request_type = request[0]
184 if (request_type == "quit"):
187 if (request_type == "host"):
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))
199 elif (request_type == "link"):
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))
208 print "wrong action 2"
211 elif (request_type == "prefix"):
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"
222 print "something else"
224 except Exception as e:
231 h = httplib2.Http(".cache")
232 h.add_credentials("admin", "admin")
234 resp, content = h.request("http://localhost:8080/controller/nb/v2/hosttracker/default/hosts/active", "GET")
235 host_content = json.loads(content)
237 # Even if there are no active hosts, host_content['hostConfig']
238 # still exists (and is empty)
240 for host_data in host_content['hostConfig']:
241 active_hosts.append(host_data['networkAddress'])
245 def run_passive_mode(affinity_links):
246 # TODO: Get affinity_links automatically
247 affinity_link_stats = {}
249 # Go through all affinity link stats
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]
256 print "%d bytes (%1.1f Mbit/s) on %s" % (stat.get_bytes(), (stat.get_bit_rate() / (10**6)), al)
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")
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]" )
272 interactive_mode = True
275 run_interactive_mode()
277 run_passive_mode(["testAL"])
279 if __name__ == "__main__":