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')
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()
56 # EWMA calculation for bit rate
57 def handle_rate_ewma(self):
59 anomaly_threshold = 2.0
60 new_bitrate = self.get_bit_rate()
62 if self.rate_ewma == None:
63 self.rate_ewma = new_bitrate
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
77 bytes = long(self.stats["byteCount"])
78 except Exception as e:
83 def get_bit_rate(self):
85 bitrate = float(self.stats["bitRate"])
86 except Exception as e:
91 class AffinityControl:
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/"
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
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)
110 self.groups.append(group_name)
111 print "AffinityGroup %s added successfully. IPs are %s" % (group_name, ips)
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)
119 self.links.append(link_name)
120 print "AffinityLink %s added between %s and %s" % (link_name, src_group, dst_group)
124 Class for controlling subnets. Right now, just adds subnets and
125 checks whether they exist, because that's all we need.
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/"
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"
142 data = json.loads(content)
144 for key in data["subnetConfig"]:
145 if (key["subnet"] == subnet):
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"
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"
160 print "subnet", subnet, "could not be added"
163 def run_interactive_mode():
165 print "Usage: [host | link] [bytes | rate] [src dst | link-name]"
169 request = raw_input("> ")
171 request = request.split()
172 request_type = request[0]
174 if (request_type == "quit"):
177 if (request_type == "host"):
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))
189 elif (request_type == "link"):
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))
198 print "wrong action 2"
201 elif (request_type == "prefix"):
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"
212 print "something else"
214 except Exception as e:
221 h = httplib2.Http(".cache")
222 h.add_credentials("admin", "admin")
224 resp, content = h.request("http://localhost:8080/controller/nb/v2/hosttracker/default/hosts/active", "GET")
225 host_content = json.loads(content)
227 # Even if there are no active hosts, host_content['hostConfig']
228 # still exists (and is empty)
230 for host_data in host_content['hostConfig']:
231 active_hosts.append(host_data['networkAddress'])
235 def run_passive_mode(affinity_links):
236 # TODO: Get affinity_links automatically
237 affinity_link_stats = {}
239 # Go through all affinity link stats
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]
246 print "%d bytes (%1.1f Mbit/s) on %s" % (stat.get_bytes(), (stat.get_bit_rate() / (10**6)), al)
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")
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]" )
262 interactive_mode = True
265 run_interactive_mode()
267 run_passive_mode(["testAL"])
269 if __name__ == "__main__":