10 from urlparse import urlparse
11 from pprint import pprint
12 from os.path import basename
16 # consider refectoring with request http://docs.python-requests.org/en/latest/index.html
19 # indicates an HTTP error
20 def __init__(self, url, errcode, errmsg, headers):
22 self.errcode = errcode
24 self.headers = headers
27 "<Error for %s: %s %s>" %
28 (self.url, self.errcode, self.errmsg)
32 class RestfulAPI(object):
33 def __init__(self, server):
35 self.path = '/wm/staticflowentrypusher/json'
42 def set_server(self, server):
46 def set_path(self, path):
50 # def set_path(self, path, port):
54 def set_port(self, port):
59 u = self.auth is not None and len(self.auth) > 0
60 # p = self.password is not None and len(self.password) > 0
63 def credentials(self, username, password):
64 self.auth = base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
66 def get(self, data=''):
67 ret = self.rest_call({}, 'GET')
68 #return json.loads(ret[2])
72 #ret = self.rest_call(data, 'PUT')
73 ret = self.rest_call2(data, 'PUT')
75 # return ret[0] == 200
79 ret = self.rest_call(data, 'POST')
80 #ret = self.rest_call2(data, 'POST')
85 ret = self.rest_call(data, 'PUT')
90 def remove(self, objtype, data):
91 ret = self.rest_call(data, 'DELETE')
97 print json.dumps(data, indent=4, sort_keys=True)
98 # print 'DATA:', repr(data)
101 # data_string = json.dumps(data)
102 # print 'JSON:', data_string
105 # data_string = json.dumps(data)
106 # print 'ENCODED:', data_string
109 # decoded = json.loads(data_string)
110 # print 'DECODED:', decoded
113 def rest_call2(self, data, action, content_type='json'):
115 #conn = httplib.HTTPConnection(self.server, self.port)
116 conn = httplib.HTTP(self.server, self.port)
117 conn.putrequest(action, self.path)
118 conn.putheader("Host", self.server+':%s'%self.port)
119 conn.putheader("User-Agent", "Python HTTP Auth")
120 conn.putheader('Content-type', 'application/%s' % content_type)
121 body = json.dumps(data)
122 #conn.putheader("Content-length", "%d" % len(data))
123 conn.putheader("Content-length", "%d" % len(body))
125 # print "using creds"
126 conn.putheader("Authorization", "Basic %s" % self.auth)
130 errcode, errmsg, headers = conn.getreply()
131 ret = (errcode, errmsg, headers)
134 # raise Error(self.path, errcode, errmsg, headers)
137 #response = conn.getresponse()
138 #headers = response.read()
139 #ret = (response.status, response.reason, headers)
140 #if response.status != 200:
141 # raise Error(self.path, response.status, response.reason, headers)
145 def rest_call(self, data, action, content_type='json'):
147 putheaders = {'content-type': 'application/json'}
148 getheaders = {'Accept': 'application/json'}
149 body = json.dumps(data)
151 # print "using creds"
153 'Content-type': 'application/%s' % content_type,
154 'Accept': 'application/%s' % content_type,
155 'Content-length': "%d" % len(body),
156 'Authorization': "Basic %s" % self.auth,
160 'Content-type': 'application/%s' % content_type,
161 'Accept': 'application/%s' % content_type,
162 'Content-length': "%d" % len(body),
165 print self.server+':',self.port, self.path
166 conn = httplib.HTTPConnection(self.server, self.port)
167 conn.request(action, self.path, body, headers)
168 response = conn.getresponse()
169 data = response.read()
170 ret = (response.status, response.reason, data)
171 #print "status %d %s" % (response.status,response.reason)
180 def print_menu(self):
182 print (" CABLEFLOW ")
184 print ("1. Add CMTS 1 ")
185 print ("2. Add CMTS 2 ")
186 print ("3. Add Flow 1 CMTS 1 ")
187 print ("4. Add Flow 2 CMTS 2 ")
188 print ("5. Remove Flow 1 CMTS 1")
189 print ("6. Remove Flow 2 CMTS 2")
190 print ("7. Remove All Flows ")
191 print ("8. List Flow Stats ")
192 print ("9. List Topology ")
193 print ("10. List Flows ")
194 print ("11. Remove CMTS 1 ")
195 print ("12. Remove CMTS 2 ")
200 def no_such_action(self):
201 print "Invalid option!"
206 "1": tests.flow_add_1,
207 "2": tests.flow_add_2,
208 "3": tests.flow_add_several,
209 "4": tests.flow_remove_1,
210 "5": tests.flow_remove_2,
211 "6": tests.flow_remove_all,
212 "8": tests.flow_list_stats,
213 "9": tests.topology_list,
214 "10":tests.flow_list,
220 selection = raw_input("Enter selection: ")
221 if "quit" == selection:
223 toDo = actions.get(selection, self.no_such_action)
229 class ODLCableflowRestconf(object):
230 def __init__(self, ws):
232 self.ws.set_port(8181)
236 self.ws.set_path('/restconf/operational/opendaylight-inventory:nodes')
237 content = self.ws.get()
238 j=json.loads(content[2])
241 def cableflow_list(self):
242 self.ws.set_path('/config/opendaylight-inventory:nodes/node/%d/flow-node-inventory:table/0/flow')
243 content = self.ws.get()
244 j=json.loads(content[2])
249 def cableflow_update(self, flow):
250 self.ws.set_path('/restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"' % flow['node']['id'], flow['id'] )
251 content = self.ws.post(flow)
252 j=json.loads(content[2])
254 def cableflow_list(self):
255 self.ws.set_path('/restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d')
256 content = self.ws.get()
257 j=json.loads(content[2])
261 def cableflow_add(self, flow):
262 # PUT http://localhost:8181/restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"
263 self.ws.set_path('/restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"' % flow['node']['id'], flow['id'] )
265 content = self.ws.put(flow)
267 flowadd_response_codes = {
268 201:"Flow Config processed successfully",
269 400:"Failed to create Static Flow entry due to invalid flow configuration",
270 401:"User not authorized to perform this operation",
271 404:"The Container Name or nodeId is not found",
272 406:"Cannot operate on Default Container when other Containers are active",
273 409:"Failed to create Static Flow entry due to Conflicting Name or configuration",
274 500:"Failed to create Static Flow entry. Failure Reason included in HTTP Error response",
275 503:"One or more of Controller services are unavailable",
277 msg=flowadd_response_codes.get(content[0])
278 print content[0], content[1], msg
280 def cableflow_remove(self, flow):
281 self.ws.set_path('/restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"' % flow['node']['id'], flow['id'] )
282 content = self.ws.remove("", flow)
284 flowdelete_reponse_codes = {
285 204:"Flow Config deleted successfully",
286 401:"User not authorized to perform this operation",
287 404:"The Container Name or Node-id or Flow Name passed is not found",
288 406:"Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response",
289 500:"Failed to delete Flow config. Failure Reason included in HTTP Error response",
290 503:"One or more of Controller service is unavailable",
292 msg=flowdelete_reponse_codes.get(content[0])
293 print content[0], content[1], msg
295 def cableflow_remove_all(self):
296 allFlowConfigs = self.cableflow_list()
297 flowConfigs = allFlowConfigs['flowConfig']
298 for fl in flowConfigs:
299 print "Removing ", fl['name']
300 self.cableflow_remove(fl)
304 def statistics_flows(self):
305 self.ws.set_path('/controller/nb/v2/statistics/default/flow')
306 content = self.ws.get()
307 allFlowStats = json.loads(content[2])
309 flowStats = allFlowStats['flowStatistics']
310 # These JSON dumps were handy when trying to parse the responses
311 #print json.dumps(flowStats[0]['flowStat'][1], indent = 2)
312 #print json.dumps(flowStats[4], indent = 2)
314 print "\nSwitch ID : " + fs['node']['id']
315 print '{0:8} {1:8} {2:5} {3:15}'.format('Count', 'Action', 'Port', 'DestIP')
316 if not 'flowStatistic' in fs.values():
319 for aFlow in fs['flowStatistic']:
320 #print "*", aFlow, "*", " ", len(aFlow), " ", not aFlow
321 count = aFlow['packetCount']
322 actions = aFlow['flow']['actions']
326 if(type(actions) == type(list())):
327 actionType = actions[1]['type']
328 actionPort = actions[1]['port']['id']
330 actionType = actions['type']
331 actionPort = actions['port']['id']
332 dst = aFlow['flow']['match']['matchField'][0]['value']
333 print '{0:8} {1:8} {2:5} {3:15}'.format(count, actionType, actionPort, dst)
336 def cableflow_remove_all(self):
337 allFlowConfigs = self.cableflow_list()
338 flowConfigs = allFlowConfigs['flowConfig']
339 for fl in flowConfigs:
340 print "Removing ", fl['name']
341 self.cableflow_remove(fl)
345 class CableflowTests(object):
346 def __init__(self, odl):
351 self.odl.cmts_add(cmts1)
355 self.odl.cmts_add(cmts2)
359 self.odl.cmts_remove(cmts1)
363 self.odl.cmts_remove(cmts2)
367 self.odl.cableflow_add(flow1)
372 self.odl.cableflow_add(flow2)
374 def flow_add_several():
375 print "Add Flow Several "
376 self.odl.cableflow_add(flow1)
377 self.odl.cableflow_add(flow2)
378 self.odl.cableflow_add(flow3)
379 self.odl.cableflow_add(flow4)
380 self.odl.cableflow_add(flow5)
384 print "Remove Flow 1 "
385 self.odl.cableflow_remove(flow1)
388 print "Remove Flow 2 "
389 self.odl.cableflow_remove(flow2)
391 def flow_remove_all():
392 print "Remove All Flows "
393 self.odl.cableflow_remove_all()
395 def flow_list_stats():
396 print "List Flow Stats"
397 self.odl.statistics_flows()
400 print "List Topology "
405 self.odl.cableflow_list()
408 def flows_read(self, content_type='json'):
409 #print "content_type = %s" % content_type
410 for path, dirs, files in os.walk('.'):
411 for filename in files:
412 if filename.endswith(".%s" % content_type):
413 base_filename = basename(filename)
414 # print base_filename
415 fn = os.path.splitext(os.path.basename(filename))[0]
417 with open(filename) as fp:
420 # jdata = yaml.load ( data )
422 self.flows[fn]=data #equivalent to: self.varname= 'something'
423 if content_type == "xml":
424 pprint (self.flows[fn], width=4)
427 #pprint (self.flows[fn], width=4)
428 json.dumps(json.loads(data), indent=4)
430 def flows_print(self):
431 # print flow dictionary
432 l = self.flows.items()
434 #for k,v in self.flows.items():
443 if __name__ == "__main__":
444 ws = RestfulAPI('127.0.0.1')
445 ws.credentials('admin', 'admin')
446 odl = ODLCableflowRestconf(ws)
447 tests = CableflowTests(odl)