blob: ebccce0303977573d3700766b82b1b4fe14de050 [file] [log] [blame]
Jon Hallfc915882015-07-14 13:33:17 -07001#!/usr/bin/env python
2"""
3Created on 07-08-2015
4
5 TestON is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 2 of the License, or
8 ( at your option ) any later version.
9
10 TestON is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with TestON. If not, see <http://www.gnu.org/licenses/>.
17
18"""
19import json
20import os
21import requests
kelvin-onlab03eb88d2015-07-22 10:29:02 -070022import types
Jon Halle401b092015-09-23 13:34:24 -070023import sys
Jon Hallfc915882015-07-14 13:33:17 -070024
Jon Hallfc915882015-07-14 13:33:17 -070025from drivers.common.api.controllerdriver import Controller
26
27
28class OnosRestDriver( Controller ):
29
30 def __init__( self ):
Jon Hallf7234882015-08-28 13:16:31 -070031 self.pwd = None
32 self.user_name = "user"
Jon Hallfc915882015-07-14 13:33:17 -070033 super( Controller, self ).__init__()
34 self.ip_address = "localhost"
35 self.port = "8080"
Jon Halle401b092015-09-23 13:34:24 -070036 self.wrapped = sys.modules[ __name__ ]
Jon Hallfc915882015-07-14 13:33:17 -070037
38 def connect( self, **connectargs ):
39 try:
40 for key in connectargs:
41 vars( self )[ key ] = connectargs[ key ]
42 self.name = self.options[ 'name' ]
43 except Exception as e:
44 main.log.exception( e )
45 try:
46 if os.getenv( str( self.ip_address ) ) != None:
47 self.ip_address = os.getenv( str( self.ip_address ) )
48 else:
kelvin-onlab03eb88d2015-07-22 10:29:02 -070049 main.log.info( self.name + ": ip set to " + self.ip_address )
Jon Hallfc915882015-07-14 13:33:17 -070050 except KeyError:
51 main.log.info( "Invalid host name," +
52 "defaulting to 'localhost' instead" )
53 self.ip_address = 'localhost'
54 except Exception as inst:
55 main.log.error( "Uncaught exception: " + str( inst ) )
56
57 self.handle = super( OnosRestDriver, self ).connect()
58 return self.handle
59
Jon Halle401b092015-09-23 13:34:24 -070060 def pprint( self, jsonObject ):
61 """
62 Pretty Prints a json object
63
64 arguments:
65 jsonObject - a parsed json object
66 returns:
67 A formatted string for printing or None on error
68 """
69 try:
70 if isinstance( jsonObject, str ):
71 jsonObject = json.loads( jsonObject )
72 return json.dumps( jsonObject, sort_keys=True,
73 indent=4, separators=(',', ': '))
74 except ( TypeError, ValueError ):
75 main.log.exception( "Error parsing jsonObject" )
76 return None
77
Jon Hallfc915882015-07-14 13:33:17 -070078 def send( self, ip, port, url, base="/onos/v1", method="GET",
Jon Hallf7234882015-08-28 13:16:31 -070079 query=None, data=None, debug=False ):
Jon Hallfc915882015-07-14 13:33:17 -070080 """
81 Arguments:
82 str ip: ONOS IP Address
83 str port: ONOS REST Port
84 str url: ONOS REST url path.
85 NOTE that this is is only the relative path. IE "/devices"
86 str base: The base url for the given REST api. Applications could
87 potentially have their own base url
88 str method: HTTP method type
kelvin-onlab03eb88d2015-07-22 10:29:02 -070089 dict query: Dictionary to be sent in the query string for
Jon Hallfc915882015-07-14 13:33:17 -070090 the request
91 dict data: Dictionary to be sent in the body of the request
92 """
93 # TODO: Authentication - simple http (user,pass) tuple
94 # TODO: should we maybe just pass kwargs straight to response?
95 # TODO: Do we need to allow for other protocols besides http?
96 # ANSWER: Not yet, but potentially https with certificates
97 try:
98 path = "http://" + str( ip ) + ":" + str( port ) + base + url
Jon Hallf7234882015-08-28 13:16:31 -070099 if self.user_name and self.pwd:
suibin zhangeb121c02015-11-04 12:06:38 -0800100 main.log.info("user/passwd is: " + self.user_name + "/" + self.pwd)
Jon Hallf7234882015-08-28 13:16:31 -0700101 auth = (self.user_name, self.pwd)
102 else:
103 auth=None
Jon Hallfc915882015-07-14 13:33:17 -0700104 main.log.info( "Sending request " + path + " using " +
105 method.upper() + " method." )
106 response = requests.request( method.upper(),
107 path,
108 params=query,
Jon Hallf7234882015-08-28 13:16:31 -0700109 data=data,
110 auth=auth )
111 if debug:
112 main.log.debug( response )
Jon Hallfc915882015-07-14 13:33:17 -0700113 return ( response.status_code, response.text.encode( 'utf8' ) )
114 except requests.exceptions:
115 main.log.exception( "Error sending request." )
116 return None
Jon Halle401b092015-09-23 13:34:24 -0700117 except Exception:
118 main.log.exception( self.name + ": Uncaught exception!" )
119 main.cleanup()
120 main.exit()
Jon Hallfc915882015-07-14 13:33:17 -0700121
122 def intents( self, ip="DEFAULT", port="DEFAULT" ):
123 """
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700124 Description:
125 Gets a list of dictionary of all intents in the system
126 Returns:
127 A list of dictionary of intents in string type to match the cli
128 version for now; Returns main.FALSE if error on request;
129 Returns None for exception
Jon Hallfc915882015-07-14 13:33:17 -0700130 """
131 try:
132 output = None
133 if ip == "DEFAULT":
134 main.log.warn( "No ip given, reverting to ip from topo file" )
135 ip = self.ip_address
136 if port == "DEFAULT":
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700137 main.log.warn( "No port given, reverting to port " +
138 "from topo file" )
Jon Hallfc915882015-07-14 13:33:17 -0700139 port = self.port
140 response = self.send( ip, port, url="/intents" )
141 if response:
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700142 if 200 <= response[ 0 ] <= 299:
143 output = response[ 1 ]
144 a = json.loads( output ).get( 'intents' )
Jon Halle401b092015-09-23 13:34:24 -0700145 assert a is not None, "Error parsing json object"
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700146 b = json.dumps( a )
147 return b
Jon Hallfc915882015-07-14 13:33:17 -0700148 else:
149 main.log.error( "Error with REST request, response was: " +
150 str( response ) )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700151 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -0700152 except ( AttributeError, AssertionError, TypeError ):
153 main.log.exception( self.name + ": Object not as expected" )
Jon Hallfc915882015-07-14 13:33:17 -0700154 return None
Jon Halle401b092015-09-23 13:34:24 -0700155 except Exception:
156 main.log.exception( self.name + ": Uncaught exception!" )
157 main.cleanup()
158 main.exit()
Jon Hallfc915882015-07-14 13:33:17 -0700159
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700160 def intent( self, intentId, appId="org.onosproject.cli",
161 ip="DEFAULT", port="DEFAULT" ):
Jon Hallfc915882015-07-14 13:33:17 -0700162 """
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700163 Description:
164 Get the specific intent information of the given application ID and
165 intent ID
166 Required:
167 str intentId - Intent id in hexadecimal form
168 Optional:
169 str appId - application id of intent
170 Returns:
171 Returns an information dictionary of the given intent;
172 Returns main.FALSE if error on requests; Returns None for exception
173 NOTE:
174 The GET /intents REST api command accepts application id but the
175 api will get updated to accept application name instead
Jon Hallfc915882015-07-14 13:33:17 -0700176 """
177 try:
178 output = None
179 if ip == "DEFAULT":
180 main.log.warn( "No ip given, reverting to ip from topo file" )
181 ip = self.ip_address
182 if port == "DEFAULT":
Jon Hallf7234882015-08-28 13:16:31 -0700183 main.log.warn( "No port given, reverting to port " +
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700184 "from topo file" )
Jon Hallfc915882015-07-14 13:33:17 -0700185 port = self.port
186 # NOTE: REST url requires the intent id to be in decimal form
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700187 query = "/" + str( appId ) + "/" + str( intentId )
Jon Hallfc915882015-07-14 13:33:17 -0700188 response = self.send( ip, port, url="/intents" + query )
189 if response:
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700190 if 200 <= response[ 0 ] <= 299:
191 output = response[ 1 ]
192 a = json.loads( output )
193 return a
Jon Hallfc915882015-07-14 13:33:17 -0700194 else:
195 main.log.error( "Error with REST request, response was: " +
196 str( response ) )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700197 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -0700198 except ( AttributeError, TypeError ):
199 main.log.exception( self.name + ": Object not as expected" )
Jon Hallfc915882015-07-14 13:33:17 -0700200 return None
Jon Halle401b092015-09-23 13:34:24 -0700201 except Exception:
202 main.log.exception( self.name + ": Uncaught exception!" )
203 main.cleanup()
204 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700205
206 def getIntentsId( self, ip="DEFAULT", port="DEFAULT" ):
207 """
208 Description:
209 Gets all intents ID using intents function
210 Returns:
211 List of intents ID;Returns None for exception; Returns None for
212 exception; Returns None for exception
213 """
214 try:
215 intentsDict = {}
216 intentsIdList = []
217 intentsDict = json.loads( self.intents( ip=ip, port=port ) )
218 for intent in intentsDict:
219 intentsIdList.append( intent.get( 'id' ) )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700220 if not intentsIdList:
221 main.log.debug( "Cannot find any intents" )
222 return main.FALSE
223 else:
224 main.log.info( "Found intents: " + str( intentsIdList ) )
225 return main.TRUE
Jon Halle401b092015-09-23 13:34:24 -0700226 except ( AttributeError, TypeError ):
227 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700228 return None
Jon Halle401b092015-09-23 13:34:24 -0700229 except Exception:
230 main.log.exception( self.name + ": Uncaught exception!" )
231 main.cleanup()
232 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700233
234 def apps( self, ip="DEFAULT", port="DEFAULT" ):
235 """
236 Description:
237 Returns all the current application installed in the system
238 Returns:
239 List of dictionary of installed application; Returns main.FALSE for
240 error on request; Returns None for exception
241 """
242 try:
243 output = None
244 if ip == "DEFAULT":
245 main.log.warn( "No ip given, reverting to ip from topo file" )
246 ip = self.ip_address
247 if port == "DEFAULT":
Jon Hallf7234882015-08-28 13:16:31 -0700248 main.log.warn( "No port given, reverting to port " +
249 "from topo file" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700250 port = self.port
251 response = self.send( ip, port, url="/applications" )
252 if response:
253 if 200 <= response[ 0 ] <= 299:
254 output = response[ 1 ]
255 a = json.loads( output ).get( 'applications' )
Jon Halle401b092015-09-23 13:34:24 -0700256 assert a is not None, "Error parsing json object"
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700257 b = json.dumps( a )
258 return b
259 else:
260 main.log.error( "Error with REST request, response was: " +
261 str( response ) )
262 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -0700263 except ( AttributeError, AssertionError, TypeError ):
264 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700265 return None
Jon Halle401b092015-09-23 13:34:24 -0700266 except Exception:
267 main.log.exception( self.name + ": Uncaught exception!" )
268 main.cleanup()
269 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700270
271 def activateApp( self, appName, ip="DEFAULT", port="DEFAULT", check=True ):
272 """
273 Decription:
274 Activate an app that is already installed in ONOS
275 Optional:
276 bool check - If check is True, method will check the status
277 of the app after the command is issued
278 Returns:
279 Returns main.TRUE if the command was successfully or main.FALSE
280 if the REST responded with an error or given incorrect input;
281 Returns None for exception
282
283 """
284 try:
285 output = None
286 if ip == "DEFAULT":
287 main.log.warn( "No ip given, reverting to ip from topo file" )
288 ip = self.ip_address
289 if port == "DEFAULT":
Jon Hallf7234882015-08-28 13:16:31 -0700290 main.log.warn( "No port given, reverting to port " +
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700291 "from topo file" )
292 port = self.port
293 query = "/" + str( appName ) + "/active"
294 response = self.send( ip, port, method="POST",
295 url="/applications" + query )
296 if response:
297 output = response[ 1 ]
298 app = json.loads( output )
299 if 200 <= response[ 0 ] <= 299:
300 if check:
301 if app.get( 'state' ) == 'ACTIVE':
302 main.log.info( self.name + ": " + appName +
303 " application" +
304 " is in ACTIVE state" )
305 return main.TRUE
306 else:
307 main.log.error( self.name + ": " + appName +
308 " application" + " is in " +
309 app.get( 'state' ) + " state" )
310 return main.FALSE
311 else:
312 main.log.warn( "Skipping " + appName +
313 "application check" )
314 return main.TRUE
315 else:
316 main.log.error( "Error with REST request, response was: " +
317 str( response ) )
318 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -0700319 except ( AttributeError, TypeError ):
320 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700321 return None
Jon Halle401b092015-09-23 13:34:24 -0700322 except Exception:
323 main.log.exception( self.name + ": Uncaught exception!" )
324 main.cleanup()
325 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700326
327 def deactivateApp( self, appName, ip="DEFAULT", port="DEFAULT",
328 check=True ):
329 """
330 Required:
331 Deactivate an app that is already activated in ONOS
332 Optional:
333 bool check - If check is True, method will check the status of the
334 app after the command is issued
335 Returns:
336 Returns main.TRUE if the command was successfully sent
337 main.FALSE if the REST responded with an error or given
338 incorrect input; Returns None for exception
339 """
340 try:
341 output = None
342 if ip == "DEFAULT":
343 main.log.warn( "No ip given, reverting to ip from topo file" )
344 ip = self.ip_address
345 if port == "DEFAULT":
Jon Hallf7234882015-08-28 13:16:31 -0700346 main.log.warn( "No port given, reverting to port " +
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700347 "from topo file" )
348 port = self.port
349 query = "/" + str( appName ) + "/active"
350 response = self.send( ip, port, method="DELETE",
351 url="/applications" + query )
352 if response:
353 output = response[ 1 ]
354 app = json.loads( output )
355 if 200 <= response[ 0 ] <= 299:
356 if check:
357 if app.get( 'state' ) == 'INSTALLED':
358 main.log.info( self.name + ": " + appName +
359 " application" +
360 " is in INSTALLED state" )
361 return main.TRUE
362 else:
363 main.log.error( self.name + ": " + appName +
364 " application" + " is in " +
365 app.get( 'state' ) + " state" )
366 return main.FALSE
367 else:
368 main.log.warn( "Skipping " + appName +
369 "application check" )
370 return main.TRUE
371 else:
372 main.log.error( "Error with REST request, response was: " +
373 str( response ) )
374 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -0700375 except ( AttributeError, TypeError ):
376 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700377 return None
Jon Halle401b092015-09-23 13:34:24 -0700378 except Exception:
379 main.log.exception( self.name + ": Uncaught exception!" )
380 main.cleanup()
381 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700382
383 def getApp( self, appName, project="org.onosproject.", ip="DEFAULT",
384 port="DEFAULT" ):
385 """
386 Decription:
387 Gets the informaion of the given application
388 Required:
389 str name - Name of onos application
390 Returns:
391 Returns a dictionary of information ONOS application in string type;
392 Returns main.FALSE if error on requests; Returns None for exception
393 """
394 try:
395 output = None
396 if ip == "DEFAULT":
397 main.log.warn( "No ip given, reverting to ip from topo file" )
398 ip = self.ip_address
399 if port == "DEFAULT":
Jon Hallf7234882015-08-28 13:16:31 -0700400 main.log.warn( "No port given, reverting to port " +
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700401 "from topo file" )
402 port = self.port
403 query = "/" + project + str( appName )
404 response = self.send( ip, port, url="/applications" + query )
405 if response:
406 if 200 <= response[ 0 ] <= 299:
407 output = response[ 1 ]
408 a = json.loads( output )
409 return a
410 else:
411 main.log.error( "Error with REST request, response was: " +
412 str( response ) )
413 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -0700414 except ( AttributeError, TypeError ):
415 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700416 return None
Jon Halle401b092015-09-23 13:34:24 -0700417 except Exception:
418 main.log.exception( self.name + ": Uncaught exception!" )
419 main.cleanup()
420 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700421
422 def addHostIntent( self, hostIdOne, hostIdTwo, appId='org.onosproject.cli',
423 ip="DEFAULT", port="DEFAULT" ):
424 """
425 Description:
426 Adds a host-to-host intent ( bidirectional ) by
427 specifying the two hosts.
428 Required:
429 * hostIdOne: ONOS host id for host1
430 * hostIdTwo: ONOS host id for host2
431 Optional:
432 str appId - Application name of intent identifier
433 Returns:
kelvin-onlabb50074f2015-07-27 16:18:32 -0700434 Returns main.TRUE for successful requests; Returns main.FALSE if
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700435 error on requests; Returns None for exceptions
436 """
437 try:
438 intentJson = {"two": str( hostIdTwo ),
439 "selector": {"criteria": []}, "priority": 7,
440 "treatment": {"deferred": [], "instructions": []},
441 "appId": appId, "one": str( hostIdOne ),
442 "type": "HostToHostIntent",
443 "constraints": [{"type": "LinkTypeConstraint",
444 "types": ["OPTICAL"],
445 "inclusive": 'false' }]}
446 output = None
447 if ip == "DEFAULT":
448 main.log.warn( "No ip given, reverting to ip from topo file" )
449 ip = self.ip_address
450 if port == "DEFAULT":
451 main.log.warn( "No port given, reverting to port " +
452 "from topo file" )
453 port = self.port
454 response = self.send( ip,
455 port,
456 method="POST",
457 url="/intents",
458 data=json.dumps( intentJson ) )
459 if response:
460 if 201:
461 main.log.info( self.name + ": Successfully POST host" +
462 " intent between host: " + hostIdOne +
463 " and host: " + hostIdTwo )
464 return main.TRUE
465 else:
466 main.log.error( "Error with REST request, response was: " +
467 str( response ) )
468 return main.FALSE
469
Jon Halle401b092015-09-23 13:34:24 -0700470 except ( AttributeError, TypeError ):
471 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700472 return None
Jon Halle401b092015-09-23 13:34:24 -0700473 except Exception:
474 main.log.exception( self.name + ": Uncaught exception!" )
475 main.cleanup()
476 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700477
kelvin-onlabb50074f2015-07-27 16:18:32 -0700478 def addPointIntent( self,
479 ingressDevice,
480 egressDevice,
kelvin-onlabb50074f2015-07-27 16:18:32 -0700481 appId='org.onosproject.cli',
482 ingressPort="",
483 egressPort="",
484 ethType="",
485 ethSrc="",
486 ethDst="",
487 bandwidth="",
488 lambdaAlloc=False,
489 ipProto="",
490 ipSrc="",
491 ipDst="",
492 tcpSrc="",
kelvin-onlab9b42b0a2015-08-05 14:43:58 -0700493 tcpDst="",
494 ip="DEFAULT",
495 port="DEFAULT" ):
kelvin-onlabb50074f2015-07-27 16:18:32 -0700496 """
497 Description:
498 Adds a point-to-point intent ( uni-directional ) by
499 specifying device id's and optional fields
500 Required:
501 * ingressDevice: device id of ingress device
502 * egressDevice: device id of egress device
503 Optional:
504 * ethType: specify ethType
505 * ethSrc: specify ethSrc ( i.e. src mac addr )
506 * ethDst: specify ethDst ( i.e. dst mac addr )
507 * bandwidth: specify bandwidth capacity of link (TODO)
508 * lambdaAlloc: if True, intent will allocate lambda
509 for the specified intent (TODO)
510 * ipProto: specify ip protocol
511 * ipSrc: specify ip source address with mask eg. ip#/24
512 * ipDst: specify ip destination address eg. ip#/24
513 * tcpSrc: specify tcp source port
514 * tcpDst: specify tcp destination port
515 Returns:
516 Returns main.TRUE for successful requests; Returns main.FALSE if
517 no ingress|egress port found and if error on requests;
518 Returns None for exceptions
519 NOTE:
520 The ip and port option are for the requests input's ip and port
521 of the ONOS node
522 """
523 try:
524 if "/" in ingressDevice:
525 if not ingressPort:
526 ingressPort = ingressDevice.split( "/" )[ 1 ]
527 ingressDevice = ingressDevice.split( "/" )[ 0 ]
528 else:
529 if not ingressPort:
530 main.log.debug( self.name + ": Ingress port not specified" )
531 return main.FALSE
532
533 if "/" in egressDevice:
534 if not egressPort:
535 egressPort = egressDevice.split( "/" )[ 1 ]
536 egressDevice = egressDevice.split( "/" )[ 0 ]
537 else:
538 if not egressPort:
539 main.log.debug( self.name + ": Egress port not specified" )
540 return main.FALSE
541
542 intentJson ={ "ingressPoint": { "device": ingressDevice,
543 "port": ingressPort },
544 "selector": { "criteria": [] },
545 "priority": 55,
546 "treatment": { "deferred": [],
547 "instructions": [] },
548 "egressPoint": { "device": egressDevice,
549 "port": egressPort },
550 "appId": appId,
551 "type": "PointToPointIntent",
552 "constraints": [ { "type": "LinkTypeConstraint",
553 "types": [ "OPTICAL" ],
554 "inclusive": "false" } ] }
555
556 if ethType == "IPV4":
557 intentJson[ 'selector' ][ 'criteria' ].append( {
558 "type":"ETH_TYPE",
559 "ethType":2048 } )
kelvin-onlab9b42b0a2015-08-05 14:43:58 -0700560 elif ethType:
561 intentJson[ 'selector' ][ 'criteria' ].append( {
562 "type":"ETH_TYPE",
563 "ethType":ethType } )
564
kelvin-onlabb50074f2015-07-27 16:18:32 -0700565 if ethSrc:
566 intentJson[ 'selector' ][ 'criteria' ].append(
567 { "type":"ETH_SRC",
568 "mac":ethSrc } )
569 if ethDst:
570 intentJson[ 'selector' ][ 'criteria' ].append(
571 { "type":"ETH_DST",
572 "mac":ethDst } )
573 if ipSrc:
574 intentJson[ 'selector' ][ 'criteria' ].append(
575 { "type":"IPV4_SRC",
576 "ip":ipSrc } )
577 if ipDst:
578 intentJson[ 'selector' ][ 'criteria' ].append(
579 { "type":"IPV4_DST",
580 "ip":ipDst } )
581 if tcpSrc:
582 intentJson[ 'selector' ][ 'criteria' ].append(
583 { "type":"TCP_SRC",
584 "tcpPort": tcpSrc } )
585 if tcpDst:
586 intentJson[ 'selector' ][ 'criteria' ].append(
587 { "type":"TCP_DST",
588 "tcpPort": tcpDst } )
589 if ipProto:
590 intentJson[ 'selector' ][ 'criteria' ].append(
591 { "type":"IP_PROTO",
592 "protocol": ipProto } )
593
594 # TODO: Bandwidth and Lambda will be implemented if needed
595
596 main.log.debug( intentJson )
597
598 output = None
599 if ip == "DEFAULT":
600 main.log.warn( "No ip given, reverting to ip from topo file" )
601 ip = self.ip_address
602 if port == "DEFAULT":
603 main.log.warn( "No port given, reverting to port " +
604 "from topo file" )
605 port = self.port
606 response = self.send( ip,
607 port,
608 method="POST",
609 url="/intents",
610 data=json.dumps( intentJson ) )
611 if response:
612 if 201:
613 main.log.info( self.name + ": Successfully POST point" +
614 " intent between ingress: " + ingressDevice +
615 " and egress: " + egressDevice + " devices" )
616 return main.TRUE
617 else:
618 main.log.error( "Error with REST request, response was: " +
619 str( response ) )
620 return main.FALSE
621
Jon Halle401b092015-09-23 13:34:24 -0700622 except ( AttributeError, TypeError ):
623 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlabb50074f2015-07-27 16:18:32 -0700624 return None
Jon Halle401b092015-09-23 13:34:24 -0700625 except Exception:
626 main.log.exception( self.name + ": Uncaught exception!" )
627 main.cleanup()
628 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700629
630 def removeIntent( self, intentId, appId='org.onosproject.cli',
631 ip="DEFAULT", port="DEFAULT" ):
632 """
633 Remove intent for specified application id and intent id;
634 Returns None for exception
635 """
636 try:
637 output = None
638 if ip == "DEFAULT":
639 main.log.warn( "No ip given, reverting to ip from topo file" )
640 ip = self.ip_address
641 if port == "DEFAULT":
642 main.log.warn( "No port given, reverting to port " +
643 "from topo file" )
644 port = self.port
645 # NOTE: REST url requires the intent id to be in decimal form
646 query = "/" + str( appId ) + "/" + str( int( intentId, 16 ) )
647 response = self.send( ip,
648 port,
649 method="DELETE",
650 url="/intents" + query )
651 if response:
652 if 200 <= response[ 0 ] <= 299:
653 return main.TRUE
654 else:
655 main.log.error( "Error with REST request, response was: " +
656 str( response ) )
657 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -0700658 except ( AttributeError, TypeError ):
659 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700660 return None
Jon Halle401b092015-09-23 13:34:24 -0700661 except Exception:
662 main.log.exception( self.name + ": Uncaught exception!" )
663 main.cleanup()
664 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700665
666 def getIntentsId( self, ip="DEFAULT", port="DEFAULT" ):
667 """
668 Returns a list of intents id; Returns None for exception
669 """
670 try:
671 intentIdList = []
672 intentsJson = json.loads( self.intents() )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700673 for intent in intentsJson:
674 intentIdList.append( intent.get( 'id' ) )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700675 return intentIdList
Jon Halle401b092015-09-23 13:34:24 -0700676 except ( AttributeError, TypeError ):
677 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700678 return None
Jon Halle401b092015-09-23 13:34:24 -0700679 except Exception:
680 main.log.exception( self.name + ": Uncaught exception!" )
681 main.cleanup()
682 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700683
684 def removeAllIntents( self, intentIdList ='ALL',appId='org.onosproject.cli',
685 ip="DEFAULT", port="DEFAULT", delay=5 ):
686 """
687 Description:
688 Remove all the intents
689 Returns:
690 Returns main.TRUE if all intents are removed, otherwise returns
691 main.FALSE; Returns None for exception
692 """
693 try:
694 results = []
695 if intentIdList == 'ALL':
696 intentIdList = self.getIntentsId( ip=ip, port=port )
697
698 main.log.info( self.name + ": Removing intents " +
699 str( intentIdList ) )
700
701 if isinstance( intentIdList, types.ListType ):
702 for intent in intentIdList:
703 results.append( self.removeIntent( intentId=intent,
704 appId=appId,
705 ip=ip,
706 port=port ) )
707 # Check for remaining intents
708 # NOTE: Noticing some delay on Deleting the intents so i put
709 # this time out
710 import time
711 time.sleep( delay )
712 intentRemain = len( json.loads( self.intents() ) )
713 if all( result==main.TRUE for result in results ) and \
714 intentRemain == 0:
715 main.log.info( self.name + ": All intents are removed " )
716 return main.TRUE
717 else:
718 main.log.error( self.name + ": Did not removed all intents,"
719 + " there are " + str( intentRemain )
720 + " intents remaining" )
721 return main.FALSE
722 else:
723 main.log.debug( self.name + ": There is no intents ID list" )
Jon Halle401b092015-09-23 13:34:24 -0700724 except ( AttributeError, TypeError ):
725 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700726 return None
Jon Halle401b092015-09-23 13:34:24 -0700727 except Exception:
728 main.log.exception( self.name + ": Uncaught exception!" )
729 main.cleanup()
730 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700731
732 def hosts( self, ip="DEFAULT", port="DEFAULT" ):
733 """
734 Description:
735 Get a list of dictionary of all discovered hosts
736 Returns:
737 Returns a list of dictionary of information of the hosts currently
738 discovered by ONOS; Returns main.FALSE if error on requests;
739 Returns None for exception
740 """
741 try:
742 output = None
743 if ip == "DEFAULT":
744 main.log.warn( "No ip given, reverting to ip from topo file" )
745 ip = self.ip_address
746 if port == "DEFAULT":
Jon Hallf7234882015-08-28 13:16:31 -0700747 main.log.warn( "No port given, reverting to port " +
748 "from topo file" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700749 port = self.port
750 response = self.send( ip, port, url="/hosts" )
751 if response:
752 if 200 <= response[ 0 ] <= 299:
753 output = response[ 1 ]
754 a = json.loads( output ).get( 'hosts' )
Jon Halle401b092015-09-23 13:34:24 -0700755 assert a is not None, "Error parsing json object"
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700756 b = json.dumps( a )
757 return b
758 else:
759 main.log.error( "Error with REST request, response was: " +
760 str( response ) )
761 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -0700762 except ( AttributeError, AssertionError, TypeError ):
763 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700764 return None
Jon Halle401b092015-09-23 13:34:24 -0700765 except Exception:
766 main.log.exception( self.name + ": Uncaught exception!" )
767 main.cleanup()
768 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700769
770 def getHost( self, mac, vlan="-1", ip="DEFAULT", port="DEFAULT" ):
771 """
772 Description:
773 Gets the information from the given host
774 Required:
775 str mac - MAC address of the host
776 Optional:
777 str vlan - VLAN tag of the host, defaults to -1
778 Returns:
779 Return the host id from the hosts/mac/vlan output in REST api
780 whose 'id' contains mac/vlan; Returns None for exception;
781 Returns main.FALSE if error on requests
782
783 NOTE:
784 Not sure what this function should do, any suggestion?
785 """
786 try:
787 output = None
788 if ip == "DEFAULT":
789 main.log.warn( "No ip given, reverting to ip from topo file" )
790 ip = self.ip_address
791 if port == "DEFAULT":
Jon Hallf7234882015-08-28 13:16:31 -0700792 main.log.warn( "No port given, reverting to port " +
793 "from topo file" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700794 port = self.port
795 query = "/" + mac + "/" + vlan
796 response = self.send( ip, port, url="/hosts" + query )
797 if response:
798 # NOTE: What if the person wants other values? would it be better
799 # to have a function that gets a key and return a value instead?
800 # This function requires mac and vlan and returns an ID which
801 # makes this current function useless
802 if 200 <= response[ 0 ] <= 299:
803 output = response[ 1 ]
804 hostId = json.loads( output ).get( 'id' )
805 return hostId
806 else:
807 main.log.error( "Error with REST request, response was: " +
808 str( response ) )
809 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -0700810 except ( AttributeError, TypeError ):
811 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700812 return None
Jon Halle401b092015-09-23 13:34:24 -0700813 except Exception:
814 main.log.exception( self.name + ": Uncaught exception!" )
815 main.cleanup()
816 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700817
818 def topology( self, ip="DEFAULT", port="DEFAULT" ):
819 """
820 Description:
821 Gets the overview of network topology
822 Returns:
823 Returns a dictionary containing information about network topology;
824 Returns None for exception
825 """
826 try:
827 output = None
828 if ip == "DEFAULT":
829 main.log.warn( "No ip given, reverting to ip from topo file" )
830 ip = self.ip_address
831 if port == "DEFAULT":
Jon Hallf7234882015-08-28 13:16:31 -0700832 main.log.warn( "No port given, reverting to port " +
833 "from topo file" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700834 port = self.port
835 response = self.send( ip, port, url="/topology" )
836 if response:
837 if 200 <= response[ 0 ] <= 299:
838 output = response[ 1 ]
839 a = json.loads( output )
840 b = json.dumps( a )
841 return b
842 else:
843 main.log.error( "Error with REST request, response was: " +
844 str( response ) )
845 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -0700846 except ( AttributeError, TypeError ):
847 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700848 return None
Jon Halle401b092015-09-23 13:34:24 -0700849 except Exception:
850 main.log.exception( self.name + ": Uncaught exception!" )
851 main.cleanup()
852 main.exit()
853
854 def devices( self, ip="DEFAULT", port="DEFAULT" ):
855 """
856 Description:
857 Get the devices discovered by ONOS is json string format
858 Returns:
859 a json string of the devices currently discovered by ONOS OR
860 main.FALSE if there is an error in the request OR
861 Returns None for exception
862 """
863 try:
864 output = None
865 if ip == "DEFAULT":
866 main.log.warn( "No ip given, reverting to ip from topo file" )
867 ip = self.ip_address
868 if port == "DEFAULT":
869 main.log.warn( "No port given, reverting to port " +
870 "from topo file" )
871 port = self.port
872 response = self.send( ip, port, url="/devices" )
873 if response:
874 if 200 <= response[ 0 ] <= 299:
875 output = response[ 1 ]
876 a = json.loads( output ).get( 'devices' )
877 assert a is not None, "Error parsing json object"
878 b = json.dumps( a )
879 return b
880 else:
881 main.log.error( "Error with REST request, response was: " +
882 str( response ) )
883 return main.FALSE
884 except ( AttributeError, AssertionError, TypeError ):
885 main.log.exception( self.name + ": Object not as expected" )
886 return None
887 except Exception:
888 main.log.exception( self.name + ": Uncaught exception!" )
889 main.cleanup()
890 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700891
892 def getIntentState( self, intentsId, intentsJson=None,
893 ip="DEFAULT", port="DEFAULT" ):
894 """
895 Description:
896 Get intent state.
897 Accepts a single intent ID (string type) or a list of intent IDs.
898 Returns the state(string type) of the id if a single intent ID is
899 accepted.
900 Required:
901 intentId: intent ID (string type)
902 intentsJson: parsed json object from the onos:intents api
903 Returns:
904 Returns a dictionary with intent IDs as the key and its
905 corresponding states as the values; Returns None for invalid IDs or
906 Type error and any exceptions
907 NOTE:
908 An intent's state consist of INSTALLED,WITHDRAWN etc.
909 """
910 try:
911 state = "State is Undefined"
912 if not intentsJson:
913 intentsJsonTemp = json.loads( self.intents() )
914 else:
915 intentsJsonTemp = json.loads( intentsJson )
916 if isinstance( intentsId, types.StringType ):
917 for intent in intentsJsonTemp:
918 if intentsId == intent[ 'id' ]:
919 state = intent[ 'state' ]
920 return state
921 main.log.info( "Cannot find intent ID" + str( intentsId ) +
922 " on the list" )
923 return state
924 elif isinstance( intentsId, types.ListType ):
925 dictList = []
926 for i in xrange( len( intentsId ) ):
927 stateDict = {}
928 for intents in intentsJsonTemp:
929 if intentsId[ i ] == intents[ 'id' ]:
930 stateDict[ 'state' ] = intents[ 'state' ]
931 stateDict[ 'id' ] = intentsId[ i ]
932 dictList.append( stateDict )
933 break
934 if len( intentsId ) != len( dictList ):
935 main.log.info( "Cannot find some of the intent ID state" )
936 return dictList
937 else:
938 main.log.info( "Invalid intents ID entry" )
939 return None
940
Jon Halle401b092015-09-23 13:34:24 -0700941 except ( AttributeError, TypeError ):
942 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700943 return None
Jon Halle401b092015-09-23 13:34:24 -0700944 except Exception:
945 main.log.exception( self.name + ": Uncaught exception!" )
946 main.cleanup()
947 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700948
949 def checkIntentState( self, intentsId="ALL", expectedState='INSTALLED',
950 ip="DEFAULT", port="DEFAULT"):
951 """
952 Description:
953 Check intents state based on expected state which defaults to
954 INSTALLED state
955 Required:
956 intentsId - List of intents ID to be checked
957 Optional:
958 expectedState - Check the expected state(s) of each intents
959 state in the list.
960 *NOTE: You can pass in a list of expected state,
961 Eg: expectedState = [ 'INSTALLED' , 'INSTALLING' ]
962 Return:
963 Returns main.TRUE only if all intent are the same as expected states
964 , otherwise, returns main.FALSE; Returns None for general exception
965 """
966 try:
967 # Generating a dictionary: intent id as a key and state as value
968 returnValue = main.TRUE
969 if intentsId == "ALL":
970 intentsId = self.getIntentsId( ip=ip, port=port )
971 intentsDict = self.getIntentState( intentsId, ip=ip, port=port )
972
973 #print "len of intentsDict ", str( len( intentsDict ) )
974 if len( intentsId ) != len( intentsDict ):
975 main.log.error( self.name + ": There is something wrong " +
976 "getting intents state" )
977 return main.FALSE
978
979 if isinstance( expectedState, types.StringType ):
980 for intents in intentsDict:
981 if intents.get( 'state' ) != expectedState:
982 main.log.debug( self.name + " : Intent ID - " +
983 intents.get( 'id' ) +
984 " actual state = " +
985 intents.get( 'state' )
986 + " does not equal expected state = "
987 + expectedState )
988 returnValue = main.FALSE
989
990 elif isinstance( expectedState, types.ListType ):
991 for intents in intentsDict:
992 if not any( state == intents.get( 'state' ) for state in
993 expectedState ):
994 main.log.debug( self.name + " : Intent ID - " +
995 intents.get( 'id' ) +
996 " actual state = " +
997 intents.get( 'state' ) +
998 " does not equal expected states = "
999 + str( expectedState ) )
1000 returnValue = main.FALSE
1001
1002 if returnValue == main.TRUE:
1003 main.log.info( self.name + ": All " +
1004 str( len( intentsDict ) ) +
1005 " intents are in " + str( expectedState ) +
1006 " state" )
1007 return returnValue
Jon Halle401b092015-09-23 13:34:24 -07001008 except ( AttributeError, TypeError ):
kelvin-onlab03eb88d2015-07-22 10:29:02 -07001009 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -07001010 return None
Jon Halle401b092015-09-23 13:34:24 -07001011 except Exception:
1012 main.log.exception( self.name + ": Uncaught exception!" )
1013 main.cleanup()
1014 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -07001015
1016 def flows( self, ip="DEFAULT", port="DEFAULT" ):
1017 """
1018 Description:
1019 Get flows currently added to the system
1020 NOTE:
1021 The flows -j cli command has completely different format than
Jon Halle401b092015-09-23 13:34:24 -07001022 the REST output
1023
1024 Returns None for exception
kelvin-onlab03eb88d2015-07-22 10:29:02 -07001025 """
1026 try:
1027 output = None
1028 if ip == "DEFAULT":
1029 main.log.warn( "No ip given, reverting to ip from topo file" )
1030 ip = self.ip_address
1031 if port == "DEFAULT":
Jon Hallf7234882015-08-28 13:16:31 -07001032 main.log.warn( "No port given, reverting to port " +
1033 "from topo file" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -07001034 port = self.port
1035 response = self.send( ip, port, url="/flows" )
1036 if response:
1037 if 200 <= response[ 0 ] <= 299:
1038 output = response[ 1 ]
1039 a = json.loads( output ).get( 'flows' )
Jon Halle401b092015-09-23 13:34:24 -07001040 assert a is not None, "Error parsing json object"
kelvin-onlab03eb88d2015-07-22 10:29:02 -07001041 b = json.dumps( a )
1042 return b
1043 else:
1044 main.log.error( "Error with REST request, response was: " +
1045 str( response ) )
1046 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -07001047 except ( AttributeError, AssertionError, TypeError ):
1048 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -07001049 return None
Jon Halle401b092015-09-23 13:34:24 -07001050 except Exception:
1051 main.log.exception( self.name + ": Uncaught exception!" )
1052 main.cleanup()
1053 main.exit()
kelvin-onlab03eb88d2015-07-22 10:29:02 -07001054
Jon Halle401b092015-09-23 13:34:24 -07001055 def getFlows( self, deviceId, flowId=None, ip="DEFAULT", port="DEFAULT" ):
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001056 """
1057 Description:
1058 Gets all the flows of the device or get a specific flow in the
1059 device by giving its flow ID
1060 Required:
Jon Halle401b092015-09-23 13:34:24 -07001061 str deviceId - device/switch Id
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001062 Optional:
1063 int/hex flowId - ID of the flow
1064 """
1065 try:
1066 output = None
1067 if ip == "DEFAULT":
1068 main.log.warn( "No ip given, reverting to ip from topo file" )
1069 ip = self.ip_address
1070 if port == "DEFAULT":
Jon Hallf7234882015-08-28 13:16:31 -07001071 main.log.warn( "No port given, reverting to port " +
1072 "from topo file" )
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001073 port = self.port
Jon Halle401b092015-09-23 13:34:24 -07001074 url = "/flows/" + deviceId
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001075 if flowId:
1076 url += "/" + str( int( flowId ) )
1077 print url
1078 response = self.send( ip, port, url=url )
1079 if response:
1080 if 200 <= response[ 0 ] <= 299:
1081 output = response[ 1 ]
1082 a = json.loads( output ).get( 'flows' )
Jon Halle401b092015-09-23 13:34:24 -07001083 assert a is not None, "Error parsing json object"
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001084 b = json.dumps( a )
1085 return b
1086 else:
1087 main.log.error( "Error with REST request, response was: " +
1088 str( response ) )
1089 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -07001090 except ( AttributeError, AssertionError, TypeError ):
1091 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001092 return None
Jon Halle401b092015-09-23 13:34:24 -07001093 except Exception:
1094 main.log.exception( self.name + ": Uncaught exception!" )
1095 main.cleanup()
1096 main.exit()
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001097
GlennRC073e8bc2015-10-27 17:11:28 -07001098 def sendFlow( self, deviceId, flowJson, ip="DEFAULT", port="DEFAULT", debug=False ):
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001099 """
1100 Description:
GlennRC073e8bc2015-10-27 17:11:28 -07001101 Sends a single flow to the specified device. This function exists
1102 so you can bypass the addFLow driver and send your own custom flow.
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001103 Required:
GlennRC073e8bc2015-10-27 17:11:28 -07001104 * The flow in json
1105 * the device id to add the flow to
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001106 Returns:
1107 Returns main.TRUE for successful requests; Returns main.FALSE
1108 if error on requests;
1109 Returns None for exceptions
1110 NOTE:
1111 The ip and port option are for the requests input's ip and port
1112 of the ONOS node
1113 """
GlennRC073e8bc2015-10-27 17:11:28 -07001114
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001115 try:
GlennRC073e8bc2015-10-27 17:11:28 -07001116 if debug: main.log.debug( "Adding flow: " + self.pprint( flowJson ) )
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001117 output = None
1118 if ip == "DEFAULT":
1119 main.log.warn( "No ip given, reverting to ip from topo file" )
1120 ip = self.ip_address
1121 if port == "DEFAULT":
1122 main.log.warn( "No port given, reverting to port " +
1123 "from topo file" )
1124 port = self.port
1125 url = "/flows/" + deviceId
1126 response = self.send( ip,
1127 port,
1128 method="POST",
1129 url=url,
1130 data=json.dumps( flowJson ) )
1131 if response:
1132 if 201:
1133 main.log.info( self.name + ": Successfully POST flow" +
1134 "in device: " + str( deviceId ) )
1135 return main.TRUE
1136 else:
1137 main.log.error( "Error with REST request, response was: " +
1138 str( response ) )
1139 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -07001140 except NotImplementedError as e:
1141 raise e # Inform the caller
1142 except ( AttributeError, TypeError ):
1143 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001144 return None
Jon Halle401b092015-09-23 13:34:24 -07001145 except Exception:
1146 main.log.exception( self.name + ": Uncaught exception!" )
1147 main.cleanup()
1148 main.exit()
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001149
GlennRC073e8bc2015-10-27 17:11:28 -07001150 def addFlow( self,
1151 deviceId,
1152 appId=0,
1153 ingressPort="",
1154 egressPort="",
1155 ethType="",
1156 ethSrc="",
1157 ethDst="",
1158 vlan="",
1159 ipProto="",
1160 ipSrc=(),
1161 ipDst=(),
1162 tcpSrc="",
1163 tcpDst="",
GlennRC956ea742015-11-05 16:14:15 -08001164 udpDst="",
1165 udpSrc="",
1166 mpls="",
GlennRC073e8bc2015-10-27 17:11:28 -07001167 ip="DEFAULT",
1168 port="DEFAULT",
1169 debug=False ):
1170 """
1171 Description:
1172 Creates a single flow in the specified device
1173 Required:
1174 * deviceId: id of the device
1175 Optional:
1176 * ingressPort: port ingress device
1177 * egressPort: port of egress device
1178 * ethType: specify ethType
1179 * ethSrc: specify ethSrc ( i.e. src mac addr )
1180 * ethDst: specify ethDst ( i.e. dst mac addr )
1181 * ipProto: specify ip protocol
1182 * ipSrc: specify ip source address with mask eg. ip#/24
1183 as a tuple (type, ip#)
1184 * ipDst: specify ip destination address eg. ip#/24
1185 as a tuple (type, ip#)
1186 * tcpSrc: specify tcp source port
1187 * tcpDst: specify tcp destination port
1188 Returns:
1189 Returns main.TRUE for successful requests; Returns main.FALSE
1190 if error on requests;
1191 Returns None for exceptions
1192 NOTE:
1193 The ip and port option are for the requests input's ip and port
1194 of the ONOS node
1195 """
1196 try:
1197 flowJson = { "priority":100,
1198 "isPermanent":"true",
1199 "timeout":0,
1200 "deviceId":deviceId,
1201 "treatment":{"instructions":[]},
1202 "selector": {"criteria":[]}}
1203 if appId:
1204 flowJson[ "appId" ] = appId
1205 if egressPort:
1206 flowJson[ 'treatment' ][ 'instructions' ].append( {
1207 "type":"OUTPUT",
1208 "port":egressPort } )
1209 if ingressPort:
1210 flowJson[ 'selector' ][ 'criteria' ].append( {
1211 "type":"IN_PORT",
1212 "port":ingressPort } )
1213 if ethType:
1214 flowJson[ 'selector' ][ 'criteria' ].append( {
1215 "type":"ETH_TYPE",
1216 "ethType":ethType } )
1217 if ethSrc:
1218 flowJson[ 'selector' ][ 'criteria' ].append( {
1219 "type":"ETH_SRC",
1220 "mac":ethSrc } )
1221 if ethDst:
1222 flowJson[ 'selector' ][ 'criteria' ].append( {
1223 "type":"ETH_DST",
1224 "mac":ethDst } )
1225 if vlan:
1226 flowJson[ 'selector' ][ 'criteria' ].append( {
1227 "type":"VLAN_VID",
1228 "vlanId":vlan } )
GlennRC956ea742015-11-05 16:14:15 -08001229 if mpls:
1230 flowJson[ 'selector' ][ 'criteria' ].append( {
1231 "type":"MPLS_LABEL",
1232 "label":mpls } )
GlennRC073e8bc2015-10-27 17:11:28 -07001233 if ipSrc:
1234 flowJson[ 'selector' ][ 'criteria' ].append( {
1235 "type":ipSrc[0],
1236 "ip":ipSrc[1] } )
1237 if ipDst:
1238 flowJson[ 'selector' ][ 'criteria' ].append( {
1239 "type":ipDst[0],
1240 "ip":ipDst[1] } )
1241 if tcpSrc:
1242 flowJson[ 'selector' ][ 'criteria' ].append( {
1243 "type":"TCP_SRC",
1244 "tcpPort": tcpSrc } )
1245 if tcpDst:
1246 flowJson[ 'selector' ][ 'criteria' ].append( {
1247 "type":"TCP_DST",
1248 "tcpPort": tcpDst } )
GlennRC956ea742015-11-05 16:14:15 -08001249 if udpSrc:
1250 flowJson[ 'selector' ][ 'criteria' ].append( {
1251 "type":"UDP_SRC",
1252 "udpPort": udpSrc } )
1253 if udpDst:
1254 flowJson[ 'selector' ][ 'criteria' ].append( {
1255 "type":"UDP_DST",
1256 "udpPort": udpDst } )
GlennRC073e8bc2015-10-27 17:11:28 -07001257 if ipProto:
1258 flowJson[ 'selector' ][ 'criteria' ].append( {
1259 "type":"IP_PROTO",
1260 "protocol": ipProto } )
1261
1262 return self.sendFlow( deviceId=deviceId, flowJson=flowJson, debug=debug )
1263
1264 except ( AttributeError, TypeError ):
1265 main.log.exception( self.name + ": Object not as expected" )
1266 return None
1267 except Exception:
1268 main.log.exception( self.name + ": Uncaught exception!" )
1269 main.cleanup()
1270 main.exit()
1271
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001272 def removeFlow( self, deviceId, flowId,
1273 ip="DEFAULT", port="DEFAULT" ):
1274 """
1275 Description:
1276 Remove specific device flow
1277 Required:
1278 str deviceId - id of the device
1279 str flowId - id of the flow
1280 Return:
1281 Returns main.TRUE if successfully deletes flows, otherwise
1282 Returns main.FALSE, Returns None on error
1283 """
1284 try:
1285 output = None
1286 if ip == "DEFAULT":
1287 main.log.warn( "No ip given, reverting to ip from topo file" )
1288 ip = self.ip_address
1289 if port == "DEFAULT":
1290 main.log.warn( "No port given, reverting to port " +
1291 "from topo file" )
1292 port = self.port
1293 # NOTE: REST url requires the intent id to be in decimal form
1294 query = "/" + str( deviceId ) + "/" + str( int( flowId ) )
1295 response = self.send( ip,
1296 port,
1297 method="DELETE",
1298 url="/flows" + query )
1299 if response:
1300 if 200 <= response[ 0 ] <= 299:
1301 return main.TRUE
1302 else:
1303 main.log.error( "Error with REST request, response was: " +
1304 str( response ) )
1305 return main.FALSE
Jon Halle401b092015-09-23 13:34:24 -07001306 except ( AttributeError, TypeError ):
1307 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001308 return None
Jon Halle401b092015-09-23 13:34:24 -07001309 except Exception:
1310 main.log.exception( self.name + ": Uncaught exception!" )
1311 main.cleanup()
1312 main.exit()
kelvin-onlab9b42b0a2015-08-05 14:43:58 -07001313
kelvin-onlab03eb88d2015-07-22 10:29:02 -07001314 def checkFlowsState( self , ip="DEFAULT", port="DEFAULT" ):
1315 """
1316 Description:
1317 Check if all the current flows are in ADDED state
1318 Return:
1319 returnValue - Returns main.TRUE only if all flows are in
1320 return main.FALSE otherwise;
1321 Returns None for exception
1322 """
1323 try:
1324 tempFlows = json.loads( self.flows( ip=ip, port=port ) )
1325 returnValue = main.TRUE
1326 for flow in tempFlows:
1327 if flow.get( 'state' ) != 'ADDED':
1328 main.log.info( self.name + ": flow Id: " +
1329 str( flow.get( 'groupId' ) ) +
1330 " | state:" +
1331 str( flow.get( 'state' ) ) )
1332 returnValue = main.FALSE
1333 return returnValue
Jon Halle401b092015-09-23 13:34:24 -07001334 except ( AttributeError, TypeError ):
kelvin-onlab03eb88d2015-07-22 10:29:02 -07001335 main.log.exception( self.name + ": Object not as expected" )
kelvin-onlab03eb88d2015-07-22 10:29:02 -07001336 return None
1337 except Exception:
1338 main.log.exception( self.name + ": Uncaught exception!" )
1339 main.cleanup()
1340 main.exit()
Jon Hall66e001c2015-11-12 09:45:10 -08001341
1342 def getNetCfg( self, ip="DEFAULT", port="DEFAULT",
1343 subjectClass=None, subjectKey=None, configKey=None ):
1344 """
1345 Description:
1346 Get a json object with the ONOS network configurations
1347 Returns:
1348 A json object containing the network configuration in
1349 ONOS; Returns main.FALSE if error on requests;
1350 Returns None for exception
1351 """
1352 try:
1353 output = None
1354 if ip == "DEFAULT":
1355 main.log.warn( "No ip given, reverting to ip from topo file" )
1356 ip = self.ip_address
1357 if port == "DEFAULT":
1358 main.log.warn( "No port given, reverting to port " +
1359 "from topo file" )
1360 port = self.port
1361 url = "/network/configuration"
1362 if subjectClass:
1363 url += "/" + subjectClass
1364 if subjectKey:
1365 url += "/" + subjectKey
1366 if configKey:
1367 url += "/" + configKey
1368 response = self.send( ip, port, url=url )
1369 if response:
1370 if 200 <= response[ 0 ] <= 299:
1371 output = response[ 1 ]
1372 a = json.loads( output )
1373 b = json.dumps( a )
1374 return b
1375 elif response[ 0 ] == 404:
1376 main.log.error( "Requested configuration doesn't exist: " +
1377 str( response ) )
1378 return {}
1379 else:
1380 main.log.error( "Error with REST request, response was: " +
1381 str( response ) )
1382 return main.FALSE
1383 except ( AttributeError, TypeError ):
1384 main.log.exception( self.name + ": Object not as expected" )
1385 return None
1386 except Exception:
1387 main.log.exception( self.name + ": Uncaught exception!" )
1388 main.cleanup()
1389 main.exit()
1390
1391 def setNetCfg( self, cfgJson, ip="DEFAULT", port="DEFAULT",
1392 subjectClass=None, subjectKey=None, configKey=None ):
1393 """
1394 Description:
1395 Set a json object with the ONOS network configurations
1396 Returns:
1397 Returns main.TRUE for successful requests; Returns main.FALSE
1398 if error on requests;
1399 Returns None for exceptions
1400
1401 """
1402 try:
1403 output = None
1404 if ip == "DEFAULT":
1405 main.log.warn( "No ip given, reverting to ip from topo file" )
1406 ip = self.ip_address
1407 if port == "DEFAULT":
1408 main.log.warn( "No port given, reverting to port " +
1409 "from topo file" )
1410 port = self.port
1411 url = "/network/configuration"
1412 if subjectClass:
1413 url += "/" + subjectClass
1414 if subjectKey:
1415 url += "/" + subjectKey
1416 if configKey:
1417 url += "/" + configKey
1418 response = self.send( ip, port,
1419 method="POST",
1420 url=url,
1421 data=json.dumps( cfgJson ) )
1422 if response:
1423 if 200 <= response[ 0 ] <= 299:
1424 main.log.info( self.name + ": Successfully POST cfg" )
1425 return main.TRUE
1426 else:
1427 main.log.error( "Error with REST request, response was: " +
1428 str( response ) )
1429 return main.FALSE
1430 except ( AttributeError, TypeError ):
1431 main.log.exception( self.name + ": Object not as expected" )
1432 return None
1433 except Exception:
1434 main.log.exception( self.name + ": Uncaught exception!" )
1435 main.cleanup()
1436 main.exit()
1437
1438 def removeNetCfg( self, ip="DEFAULT", port="DEFAULT",
1439 subjectClass=None, subjectKey=None, configKey=None ):
1440 """
1441 Description:
1442 Remove a json object from the ONOS network configurations
1443 Returns:
1444 Returns main.TRUE for successful requests; Returns main.FALSE
1445 if error on requests;
1446 Returns None for exceptions
1447
1448 """
1449 try:
1450 output = None
1451 if ip == "DEFAULT":
1452 main.log.warn( "No ip given, reverting to ip from topo file" )
1453 ip = self.ip_address
1454 if port == "DEFAULT":
1455 main.log.warn( "No port given, reverting to port " +
1456 "from topo file" )
1457 port = self.port
1458 url = "/network/configuration"
1459 if subjectClass:
1460 url += "/" + subjectClass
1461 if subjectKey:
1462 url += "/" + subjectKey
1463 if configKey:
1464 url += "/" + configKey
1465 response = self.send( ip, port,
1466 method="DELETE",
1467 url=url )
1468 if response:
1469 if 200 <= response[ 0 ] <= 299:
1470 main.log.info( self.name + ": Successfully delete cfg" )
1471 return main.TRUE
1472 else:
1473 main.log.error( "Error with REST request, response was: " +
1474 str( response ) )
1475 return main.FALSE
1476 except ( AttributeError, TypeError ):
1477 main.log.exception( self.name + ": Object not as expected" )
1478 return None
1479 except Exception:
1480 main.log.exception( self.name + ": Uncaught exception!" )
1481 main.cleanup()
1482 main.exit()
suibin zhang17308622016-04-14 15:45:30 -07001483
1484 def createFlowBatch( self,
1485 numSw = 1,
1486 swIndex = 1,
1487 batchSize = 1,
1488 batchIndex = 1,
1489 deviceIdpreFix = "of:",
1490 appId=0,
1491 deviceID="",
1492 ingressPort="",
1493 egressPort="",
1494 ethType="",
1495 ethSrc="",
1496 ethDst="",
1497 vlan="",
1498 ipProto="",
1499 ipSrc=(),
1500 ipDst=(),
1501 tcpSrc="",
1502 tcpDst="",
1503 udpDst="",
1504 udpSrc="",
1505 mpls="",
1506 ip="DEFAULT",
1507 port="DEFAULT",
1508 debug=False ):
1509 """
1510 Description:
1511 Creates batches of MAC-rule flows for POST.
1512 Predefined MAC: 2 MS Hex digit for iterating devices
1513 Next 5 Hex digit for iterating batch numbers
1514 Next 5 Hex digit for iterating flows within a batch
1515 Required:
1516 * deviceId: id of the device
1517 Optional:
1518 * ingressPort: port ingress device
1519 * egressPort: port of egress device
1520 * ethType: specify ethType
1521 * ethSrc: specify ethSrc ( i.e. src mac addr )
1522 * ethDst: specify ethDst ( i.e. dst mac addr )
1523 * ipProto: specify ip protocol
1524 * ipSrc: specify ip source address with mask eg. ip#/24
1525 as a tuple (type, ip#)
1526 * ipDst: specify ip destination address eg. ip#/24
1527 as a tuple (type, ip#)
1528 * tcpSrc: specify tcp source port
1529 * tcpDst: specify tcp destination port
1530 Returns:
1531 Returns main.TRUE for successful requests; Returns main.FALSE
1532 if error on requests;
1533 Returns None for exceptions
1534 NOTE:
1535 The ip and port option are for the requests input's ip and port
1536 of the ONOS node
1537 """
1538 #from pprint import pprint
1539
1540 flowJsonList = []
1541 flowJsonBatch = {"flows":flowJsonList}
1542 dev = swIndex
1543
1544 for fl in range(1, batchSize + 1):
1545 flowJson = { "priority":100,
1546 "deviceId":"",
1547 "isPermanent":"true",
1548 "timeout":0,
1549 "treatment":{"instructions":[]},
1550 "selector": {"criteria":[]}}
1551
1552 #main.log.info("fl: " + str(fl))
1553 if dev <= numSw:
1554 deviceId = deviceIdpreFix + "{0:0{1}x}".format(dev,16)
1555 #print deviceId
1556 flowJson['deviceId'] = deviceId
1557 dev += 1
1558 else:
1559 dev = 1
1560 deviceId = deviceIdpreFix + "{0:0{1}x}".format(dev,16)
1561 #print deviceId
1562 flowJson['deviceId'] = deviceId
1563 dev += 1
1564
1565 # ethSrc starts with "0"; ethDst starts with "1"
1566 # 2 Hex digit of device number; 5 digits of batch index number; 5 digits of batch size
1567 ethS = "%02X" %int( "0" + "{0:0{1}b}".format(dev,7), 2 ) + \
1568 "{0:0{1}x}".format(batchIndex,5) + "{0:0{1}x}".format(fl,5)
1569 ethSrc = ':'.join(ethS[i:i+2] for i in range(0,len(ethS),2))
1570 ethD = "%02X" %int( "1" + "{0:0{1}b}".format(dev,7), 2 ) + \
1571 "{0:0{1}x}".format(batchIndex,5) + "{0:0{1}x}".format(fl,5)
1572 ethDst = ':'.join(ethD[i:i+2] for i in range(0,len(ethD),2))
1573
1574 if appId:
1575 flowJson[ "appId" ] = appId
1576
1577 if egressPort:
1578 flowJson[ 'treatment' ][ 'instructions' ].append( {
1579 "type":"OUTPUT",
1580 "port":egressPort } )
1581 if ingressPort:
1582 flowJson[ 'selector' ][ 'criteria' ].append( {
1583 "type":"IN_PORT",
1584 "port":ingressPort } )
1585 if ethType:
1586 flowJson[ 'selector' ][ 'criteria' ].append( {
1587 "type":"ETH_TYPE",
1588 "ethType":ethType } )
1589 if ethSrc:
1590 flowJson[ 'selector' ][ 'criteria' ].append( {
1591 "type":"ETH_SRC",
1592 "mac":ethSrc } )
1593 if ethDst:
1594 flowJson[ 'selector' ][ 'criteria' ].append( {
1595 "type":"ETH_DST",
1596 "mac":ethDst } )
1597 if vlan:
1598 flowJson[ 'selector' ][ 'criteria' ].append( {
1599 "type":"VLAN_VID",
1600 "vlanId":vlan } )
1601 if mpls:
1602 flowJson[ 'selector' ][ 'criteria' ].append( {
1603 "type":"MPLS_LABEL",
1604 "label":mpls } )
1605 if ipSrc:
1606 flowJson[ 'selector' ][ 'criteria' ].append( {
1607 "type":ipSrc[0],
1608 "ip":ipSrc[1] } )
1609 if ipDst:
1610 flowJson[ 'selector' ][ 'criteria' ].append( {
1611 "type":ipDst[0],
1612 "ip":ipDst[1] } )
1613 if tcpSrc:
1614 flowJson[ 'selector' ][ 'criteria' ].append( {
1615 "type":"TCP_SRC",
1616 "tcpPort": tcpSrc } )
1617 if tcpDst:
1618 flowJson[ 'selector' ][ 'criteria' ].append( {
1619 "type":"TCP_DST",
1620 "tcpPort": tcpDst } )
1621 if udpSrc:
1622 flowJson[ 'selector' ][ 'criteria' ].append( {
1623 "type":"UDP_SRC",
1624 "udpPort": udpSrc } )
1625 if udpDst:
1626 flowJson[ 'selector' ][ 'criteria' ].append( {
1627 "type":"UDP_DST",
1628 "udpPort": udpDst } )
1629 if ipProto:
1630 flowJson[ 'selector' ][ 'criteria' ].append( {
1631 "type":"IP_PROTO",
1632 "protocol": ipProto } )
1633 #pprint(flowJson)
1634 flowJsonList.append(flowJson)
1635
1636 main.log.info("Number of flows in batch: " + str( len(flowJsonList) ) )
1637 flowJsonBatch['flows'] = flowJsonList
1638 #pprint(flowJsonBatch)
1639
1640 return flowJsonBatch
1641
1642
1643 def sendFlowBatch( self, batch={}, ip="DEFAULT", port="DEFAULT", debug=False ):
1644 """
1645 Description:
1646 Sends a single flow batch through /flows REST API.
1647 Required:
1648 * The batch of flows
1649 Returns:
1650 Returns main.TRUE for successful requests; Returns main.FALSE
1651 if error on requests;
1652 Returns None for exceptions
1653 NOTE:
1654 The ip and port option are for the requests input's ip and port
1655 of the ONOS node
1656 """
1657 import time
1658
1659 try:
1660 if debug: main.log.debug( "Adding flow: " + self.pprint( batch ) )
1661 output = None
1662 if ip == "DEFAULT":
1663 main.log.warn( "No ip given, reverting to ip from topo file" )
1664 ip = self.ip_address
1665 if port == "DEFAULT":
1666 main.log.warn( "No port given, reverting to port " +
1667 "from topo file" )
1668 port = self.port
1669 url = "/flows/"
1670 response = self.send( ip,
1671 port,
1672 method="POST",
1673 url=url,
1674 data=json.dumps( batch ) )
1675 #main.log.info("Post response is: ", str(response[0]))
1676 if response[0] == 200:
1677 main.log.info( self.name + ": Successfully POST flow batch" )
1678 return main.TRUE, response
1679 else:
1680 main.log.error( "Error with REST request, response was: " +
1681 str( response ) )
1682 return main.FALSE
1683 except NotImplementedError as e:
1684 raise e # Inform the caller
1685 except ( AttributeError, TypeError ):
1686 main.log.exception( self.name + ": Object not as expected" )
1687 return None
1688 except Exception:
1689 main.log.exception( self.name + ": Uncaught exception!" )
1690 main.cleanup()
1691 main.exit()
1692
1693 def removeFlowBatch( self, batch={},
1694 ip="DEFAULT", port="DEFAULT" ):
1695 """
1696 Description:
1697 Remove a batch of flows
1698 Required:
1699 flow batch
1700 Return:
1701 Returns main.TRUE if successfully deletes flows, otherwise
1702 Returns main.FALSE, Returns None on error
1703 """
1704 try:
1705 output = None
1706 if ip == "DEFAULT":
1707 main.log.warn( "No ip given, reverting to ip from topo file" )
1708 ip = self.ip_address
1709 if port == "DEFAULT":
1710 main.log.warn( "No port given, reverting to port " +
1711 "from topo file" )
1712 port = self.port
1713 # NOTE: REST url requires the intent id to be in decimal form
1714
1715 response = self.send( ip,
1716 port,
1717 method="DELETE",
1718 url="/flows/",
1719 data = json.dumps(batch) )
1720 if response:
1721 if 200 <= response[ 0 ] <= 299:
1722 return main.TRUE
1723 else:
1724 main.log.error( "Error with REST request, response was: " +
1725 str( response ) )
1726 return main.FALSE
1727 except ( AttributeError, TypeError ):
1728 main.log.exception( self.name + ": Object not as expected" )
1729 return None
1730 except Exception:
1731 main.log.exception( self.name + ": Uncaught exception!" )
1732 main.cleanup()
1733 main.exit()