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):
234 ws.set_path('/restconf/operational/opendaylight-inventory:nodes')
236 j=json.loads(content[2])
239 def cableflow_list(self):
240 ws.set_path('/config/opendaylight-inventory:nodes/node/%d/flow-node-inventory:table/0/flow')
242 j=json.loads(content[2])
247 def cableflow_update(self, flow):
248 ws.set_path('/restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"' % flow['node']['id'], flow['id'] )
249 content = ws.post(flow)
250 j=json.loads(content[2])
252 def cableflow_list(self):
253 ws.set_path('/restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d')
255 j=json.loads(content[2])
259 def cableflow_add(self, flow):
260 # PUT http://localhost:8181/restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"
261 ws.set_path('/restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"' % flow['node']['id'], flow['id'] )
263 content = ws.put(flow)
265 flowadd_response_codes = {
266 201:"Flow Config processed successfully",
267 400:"Failed to create Static Flow entry due to invalid flow configuration",
268 401:"User not authorized to perform this operation",
269 404:"The Container Name or nodeId is not found",
270 406:"Cannot operate on Default Container when other Containers are active",
271 409:"Failed to create Static Flow entry due to Conflicting Name or configuration",
272 500:"Failed to create Static Flow entry. Failure Reason included in HTTP Error response",
273 503:"One or more of Controller services are unavailable",
275 msg=flowadd_response_codes.get(content[0])
276 print content[0], content[1], msg
278 def cableflow_remove(self, flow):
279 ws.set_path('/restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"' % flow['node']['id'], flow['id'] )
280 content = ws.remove("", flow)
282 flowdelete_reponse_codes = {
283 204:"Flow Config deleted successfully",
284 401:"User not authorized to perform this operation",
285 404:"The Container Name or Node-id or Flow Name passed is not found",
286 406:"Failed to delete Flow config due to invalid operation. Failure details included in HTTP Error response",
287 500:"Failed to delete Flow config. Failure Reason included in HTTP Error response",
288 503:"One or more of Controller service is unavailable",
290 msg=flowdelete_reponse_codes.get(content[0])
291 print content[0], content[1], msg
293 def cableflow_remove_all(self):
294 allFlowConfigs = self.cableflow_list()
295 flowConfigs = allFlowConfigs['flowConfig']
296 for fl in flowConfigs:
297 print "Removing ", fl['name']
298 self.cableflow_remove(fl)
302 def statistics_flows(self):
303 ws.set_path('/controller/nb/v2/statistics/default/flow')
305 allFlowStats = json.loads(content[2])
307 flowStats = allFlowStats['flowStatistics']
308 # These JSON dumps were handy when trying to parse the responses
309 #print json.dumps(flowStats[0]['flowStat'][1], indent = 2)
310 #print json.dumps(flowStats[4], indent = 2)
312 print "\nSwitch ID : " + fs['node']['id']
313 print '{0:8} {1:8} {2:5} {3:15}'.format('Count', 'Action', 'Port', 'DestIP')
314 if not 'flowStatistic' in fs.values():
317 for aFlow in fs['flowStatistic']:
318 #print "*", aFlow, "*", " ", len(aFlow), " ", not aFlow
319 count = aFlow['packetCount']
320 actions = aFlow['flow']['actions']
324 if(type(actions) == type(list())):
325 actionType = actions[1]['type']
326 actionPort = actions[1]['port']['id']
328 actionType = actions['type']
329 actionPort = actions['port']['id']
330 dst = aFlow['flow']['match']['matchField'][0]['value']
331 print '{0:8} {1:8} {2:5} {3:15}'.format(count, actionType, actionPort, dst)
334 def cableflow_remove_all(self):
335 allFlowConfigs = self.cableflow_list()
336 flowConfigs = allFlowConfigs['flowConfig']
337 for fl in flowConfigs:
338 print "Removing ", fl['name']
339 self.cableflow_remove(fl)
343 class CableflowTests(object):
356 odl.cmts_remove(cmts1)
360 odl.cmts_remove(cmts2)
364 odl.cableflow_add(flow1)
369 odl.cableflow_add(flow2)
371 def flow_add_several():
372 print "Add Flow Several "
373 odl.cableflow_add(flow1)
374 odl.cableflow_add(flow2)
375 odl.cableflow_add(flow3)
376 odl.cableflow_add(flow4)
377 odl.cableflow_add(flow5)
381 print "Remove Flow 1 "
382 odl.cableflow_remove(flow1)
385 print "Remove Flow 2 "
386 odl.cableflow_remove(flow2)
388 def flow_remove_all():
389 print "Remove All Flows "
390 odl.cableflow_remove_all()
392 def flow_list_stats():
393 print "List Flow Stats"
394 odl.statistics_flows()
397 print "List Topology "
405 def flows_read(self, content_type='json'):
406 #print "content_type = %s" % content_type
407 for path, dirs, files in os.walk('.'):
408 for filename in files:
409 if filename.endswith(".%s" % content_type):
410 base_filename = basename(filename)
413 with open(filename) as fp:
416 # jdata = yaml.load ( data )
418 self.flows[filename]=data #equivalent to: self.varname= 'something'
419 if content_type == "xml":
420 pprint (self.flows[filename], width=4)
422 # pprint (self.flows[filename], width=4)
423 json.dumps(json.loads(data), indent=4)
429 ws = RestfulAPI('127.0.0.1')
431 if __name__ == "__main__":
432 ws.credentials('admin', 'admin')
433 odl = ODLCableflowRestconf()
434 tests = CableflowTests()