blob: 42b7680782c91ad7d21f11916ef7ef7950b74b43 [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 Hallfc915882015-07-14 13:33:17 -070023
Jon Hallfc915882015-07-14 13:33:17 -070024from drivers.common.api.controllerdriver import Controller
25
26
27class OnosRestDriver( Controller ):
28
29 def __init__( self ):
30 super( Controller, self ).__init__()
31 self.ip_address = "localhost"
32 self.port = "8080"
33 self.user_name = "user"
34 self.password = "CHANGEME"
35
36 def connect( self, **connectargs ):
37 try:
38 for key in connectargs:
39 vars( self )[ key ] = connectargs[ key ]
40 self.name = self.options[ 'name' ]
41 except Exception as e:
42 main.log.exception( e )
43 try:
44 if os.getenv( str( self.ip_address ) ) != None:
45 self.ip_address = os.getenv( str( self.ip_address ) )
46 else:
kelvin-onlab03eb88d2015-07-22 10:29:02 -070047 main.log.info( self.name + ": ip set to " + self.ip_address )
Jon Hallfc915882015-07-14 13:33:17 -070048 except KeyError:
49 main.log.info( "Invalid host name," +
50 "defaulting to 'localhost' instead" )
51 self.ip_address = 'localhost'
52 except Exception as inst:
53 main.log.error( "Uncaught exception: " + str( inst ) )
54
55 self.handle = super( OnosRestDriver, self ).connect()
56 return self.handle
57
58 def send( self, ip, port, url, base="/onos/v1", method="GET",
59 query=None, data=None ):
60 """
61 Arguments:
62 str ip: ONOS IP Address
63 str port: ONOS REST Port
64 str url: ONOS REST url path.
65 NOTE that this is is only the relative path. IE "/devices"
66 str base: The base url for the given REST api. Applications could
67 potentially have their own base url
68 str method: HTTP method type
kelvin-onlab03eb88d2015-07-22 10:29:02 -070069 dict query: Dictionary to be sent in the query string for
Jon Hallfc915882015-07-14 13:33:17 -070070 the request
71 dict data: Dictionary to be sent in the body of the request
72 """
73 # TODO: Authentication - simple http (user,pass) tuple
74 # TODO: should we maybe just pass kwargs straight to response?
75 # TODO: Do we need to allow for other protocols besides http?
76 # ANSWER: Not yet, but potentially https with certificates
77 try:
78 path = "http://" + str( ip ) + ":" + str( port ) + base + url
79 main.log.info( "Sending request " + path + " using " +
80 method.upper() + " method." )
81 response = requests.request( method.upper(),
82 path,
83 params=query,
84 data=data )
85 return ( response.status_code, response.text.encode( 'utf8' ) )
86 except requests.exceptions:
87 main.log.exception( "Error sending request." )
88 return None
89 except Exception as e:
90 main.log.exception( e )
91 return None
kelvin-onlab03eb88d2015-07-22 10:29:02 -070092 # FIXME: add other exceptions
Jon Hallfc915882015-07-14 13:33:17 -070093
94 def intents( self, ip="DEFAULT", port="DEFAULT" ):
95 """
kelvin-onlab03eb88d2015-07-22 10:29:02 -070096 Description:
97 Gets a list of dictionary of all intents in the system
98 Returns:
99 A list of dictionary of intents in string type to match the cli
100 version for now; Returns main.FALSE if error on request;
101 Returns None for exception
Jon Hallfc915882015-07-14 13:33:17 -0700102 """
103 try:
104 output = None
105 if ip == "DEFAULT":
106 main.log.warn( "No ip given, reverting to ip from topo file" )
107 ip = self.ip_address
108 if port == "DEFAULT":
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700109 main.log.warn( "No port given, reverting to port " +
110 "from topo file" )
Jon Hallfc915882015-07-14 13:33:17 -0700111 port = self.port
112 response = self.send( ip, port, url="/intents" )
113 if response:
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700114 if 200 <= response[ 0 ] <= 299:
115 output = response[ 1 ]
116 a = json.loads( output ).get( 'intents' )
117 b = json.dumps( a )
118 return b
Jon Hallfc915882015-07-14 13:33:17 -0700119 else:
120 main.log.error( "Error with REST request, response was: " +
121 str( response ) )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700122 return main.FALSE
Jon Hallfc915882015-07-14 13:33:17 -0700123 except Exception as e:
124 main.log.exception( e )
125 return None
126
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700127 def intent( self, intentId, appId="org.onosproject.cli",
128 ip="DEFAULT", port="DEFAULT" ):
Jon Hallfc915882015-07-14 13:33:17 -0700129 """
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700130 Description:
131 Get the specific intent information of the given application ID and
132 intent ID
133 Required:
134 str intentId - Intent id in hexadecimal form
135 Optional:
136 str appId - application id of intent
137 Returns:
138 Returns an information dictionary of the given intent;
139 Returns main.FALSE if error on requests; Returns None for exception
140 NOTE:
141 The GET /intents REST api command accepts application id but the
142 api will get updated to accept application name instead
Jon Hallfc915882015-07-14 13:33:17 -0700143 """
144 try:
145 output = None
146 if ip == "DEFAULT":
147 main.log.warn( "No ip given, reverting to ip from topo file" )
148 ip = self.ip_address
149 if port == "DEFAULT":
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700150 main.log.warn( "No port given, reverting to port" +
151 "from topo file" )
Jon Hallfc915882015-07-14 13:33:17 -0700152 port = self.port
153 # NOTE: REST url requires the intent id to be in decimal form
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700154 query = "/" + str( appId ) + "/" + str( intentId )
Jon Hallfc915882015-07-14 13:33:17 -0700155 response = self.send( ip, port, url="/intents" + query )
156 if response:
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700157 if 200 <= response[ 0 ] <= 299:
158 output = response[ 1 ]
159 a = json.loads( output )
160 return a
Jon Hallfc915882015-07-14 13:33:17 -0700161 else:
162 main.log.error( "Error with REST request, response was: " +
163 str( response ) )
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700164 return main.FALSE
Jon Hallfc915882015-07-14 13:33:17 -0700165 except Exception as e:
166 main.log.exception( e )
167 return None
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700168
169 def getIntentsId( self, ip="DEFAULT", port="DEFAULT" ):
170 """
171 Description:
172 Gets all intents ID using intents function
173 Returns:
174 List of intents ID;Returns None for exception; Returns None for
175 exception; Returns None for exception
176 """
177 try:
178 intentsDict = {}
179 intentsIdList = []
180 intentsDict = json.loads( self.intents( ip=ip, port=port ) )
181 for intent in intentsDict:
182 intentsIdList.append( intent.get( 'id' ) )
183
184 if not intentsIdList:
185 main.log.debug( "Cannot find any intents" )
186 return main.FALSE
187 else:
188 main.log.info( "Found intents: " + str( intentsIdList ) )
189 return main.TRUE
190
191 except Exception as e:
192 main.log.exception( e )
193 return None
194
195
196 def apps( self, ip="DEFAULT", port="DEFAULT" ):
197 """
198 Description:
199 Returns all the current application installed in the system
200 Returns:
201 List of dictionary of installed application; Returns main.FALSE for
202 error on request; Returns None for exception
203 """
204 try:
205 output = None
206 if ip == "DEFAULT":
207 main.log.warn( "No ip given, reverting to ip from topo file" )
208 ip = self.ip_address
209 if port == "DEFAULT":
210 main.log.warn( "No port given, reverting to port \
211 from topo file" )
212 port = self.port
213 response = self.send( ip, port, url="/applications" )
214 if response:
215 if 200 <= response[ 0 ] <= 299:
216 output = response[ 1 ]
217 a = json.loads( output ).get( 'applications' )
218 b = json.dumps( a )
219 return b
220 else:
221 main.log.error( "Error with REST request, response was: " +
222 str( response ) )
223 return main.FALSE
224 except Exception as e:
225 main.log.exception( e )
226 return None
227
228 def activateApp( self, appName, ip="DEFAULT", port="DEFAULT", check=True ):
229 """
230 Decription:
231 Activate an app that is already installed in ONOS
232 Optional:
233 bool check - If check is True, method will check the status
234 of the app after the command is issued
235 Returns:
236 Returns main.TRUE if the command was successfully or main.FALSE
237 if the REST responded with an error or given incorrect input;
238 Returns None for exception
239
240 """
241 try:
242 output = None
243 if ip == "DEFAULT":
244 main.log.warn( "No ip given, reverting to ip from topo file" )
245 ip = self.ip_address
246 if port == "DEFAULT":
247 main.log.warn( "No port given, reverting to port" +
248 "from topo file" )
249 port = self.port
250 query = "/" + str( appName ) + "/active"
251 response = self.send( ip, port, method="POST",
252 url="/applications" + query )
253 if response:
254 output = response[ 1 ]
255 app = json.loads( output )
256 if 200 <= response[ 0 ] <= 299:
257 if check:
258 if app.get( 'state' ) == 'ACTIVE':
259 main.log.info( self.name + ": " + appName +
260 " application" +
261 " is in ACTIVE state" )
262 return main.TRUE
263 else:
264 main.log.error( self.name + ": " + appName +
265 " application" + " is in " +
266 app.get( 'state' ) + " state" )
267 return main.FALSE
268 else:
269 main.log.warn( "Skipping " + appName +
270 "application check" )
271 return main.TRUE
272 else:
273 main.log.error( "Error with REST request, response was: " +
274 str( response ) )
275 return main.FALSE
276 except Exception as e:
277 main.log.exception( e )
278 return None
279
280 def deactivateApp( self, appName, ip="DEFAULT", port="DEFAULT",
281 check=True ):
282 """
283 Required:
284 Deactivate an app that is already activated in ONOS
285 Optional:
286 bool check - If check is True, method will check the status of the
287 app after the command is issued
288 Returns:
289 Returns main.TRUE if the command was successfully sent
290 main.FALSE if the REST responded with an error or given
291 incorrect input; Returns None for exception
292 """
293 try:
294 output = None
295 if ip == "DEFAULT":
296 main.log.warn( "No ip given, reverting to ip from topo file" )
297 ip = self.ip_address
298 if port == "DEFAULT":
299 main.log.warn( "No port given, reverting to port" +
300 "from topo file" )
301 port = self.port
302 query = "/" + str( appName ) + "/active"
303 response = self.send( ip, port, method="DELETE",
304 url="/applications" + query )
305 if response:
306 output = response[ 1 ]
307 app = json.loads( output )
308 if 200 <= response[ 0 ] <= 299:
309 if check:
310 if app.get( 'state' ) == 'INSTALLED':
311 main.log.info( self.name + ": " + appName +
312 " application" +
313 " is in INSTALLED state" )
314 return main.TRUE
315 else:
316 main.log.error( self.name + ": " + appName +
317 " application" + " is in " +
318 app.get( 'state' ) + " state" )
319 return main.FALSE
320 else:
321 main.log.warn( "Skipping " + appName +
322 "application check" )
323 return main.TRUE
324 else:
325 main.log.error( "Error with REST request, response was: " +
326 str( response ) )
327 return main.FALSE
328 except Exception as e:
329 main.log.exception( e )
330 return None
331
332 def getApp( self, appName, project="org.onosproject.", ip="DEFAULT",
333 port="DEFAULT" ):
334 """
335 Decription:
336 Gets the informaion of the given application
337 Required:
338 str name - Name of onos application
339 Returns:
340 Returns a dictionary of information ONOS application in string type;
341 Returns main.FALSE if error on requests; Returns None for exception
342 """
343 try:
344 output = None
345 if ip == "DEFAULT":
346 main.log.warn( "No ip given, reverting to ip from topo file" )
347 ip = self.ip_address
348 if port == "DEFAULT":
349 main.log.warn( "No port given, reverting to port" +
350 "from topo file" )
351 port = self.port
352 query = "/" + project + str( appName )
353 response = self.send( ip, port, url="/applications" + query )
354 if response:
355 if 200 <= response[ 0 ] <= 299:
356 output = response[ 1 ]
357 a = json.loads( output )
358 return a
359 else:
360 main.log.error( "Error with REST request, response was: " +
361 str( response ) )
362 return main.FALSE
363 except Exception as e:
364 main.log.exception( e )
365 return None
366
367 def addHostIntent( self, hostIdOne, hostIdTwo, appId='org.onosproject.cli',
368 ip="DEFAULT", port="DEFAULT" ):
369 """
370 Description:
371 Adds a host-to-host intent ( bidirectional ) by
372 specifying the two hosts.
373 Required:
374 * hostIdOne: ONOS host id for host1
375 * hostIdTwo: ONOS host id for host2
376 Optional:
377 str appId - Application name of intent identifier
378 Returns:
kelvin-onlabb50074f2015-07-27 16:18:32 -0700379 Returns main.TRUE for successful requests; Returns main.FALSE if
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700380 error on requests; Returns None for exceptions
381 """
382 try:
383 intentJson = {"two": str( hostIdTwo ),
384 "selector": {"criteria": []}, "priority": 7,
385 "treatment": {"deferred": [], "instructions": []},
386 "appId": appId, "one": str( hostIdOne ),
387 "type": "HostToHostIntent",
388 "constraints": [{"type": "LinkTypeConstraint",
389 "types": ["OPTICAL"],
390 "inclusive": 'false' }]}
391 output = None
392 if ip == "DEFAULT":
393 main.log.warn( "No ip given, reverting to ip from topo file" )
394 ip = self.ip_address
395 if port == "DEFAULT":
396 main.log.warn( "No port given, reverting to port " +
397 "from topo file" )
398 port = self.port
399 response = self.send( ip,
400 port,
401 method="POST",
402 url="/intents",
403 data=json.dumps( intentJson ) )
404 if response:
405 if 201:
406 main.log.info( self.name + ": Successfully POST host" +
407 " intent between host: " + hostIdOne +
408 " and host: " + hostIdTwo )
409 return main.TRUE
410 else:
411 main.log.error( "Error with REST request, response was: " +
412 str( response ) )
413 return main.FALSE
414
415 except Exception as e:
416 main.log.exception( e )
417 return None
418
kelvin-onlabb50074f2015-07-27 16:18:32 -0700419 def addPointIntent( self,
420 ingressDevice,
421 egressDevice,
kelvin-onlabb50074f2015-07-27 16:18:32 -0700422 appId='org.onosproject.cli',
423 ingressPort="",
424 egressPort="",
425 ethType="",
426 ethSrc="",
427 ethDst="",
428 bandwidth="",
429 lambdaAlloc=False,
430 ipProto="",
431 ipSrc="",
432 ipDst="",
433 tcpSrc="",
kelvin-onlab9b42b0a2015-08-05 14:43:58 -0700434 tcpDst="",
435 ip="DEFAULT",
436 port="DEFAULT" ):
kelvin-onlabb50074f2015-07-27 16:18:32 -0700437 """
438 Description:
439 Adds a point-to-point intent ( uni-directional ) by
440 specifying device id's and optional fields
441 Required:
442 * ingressDevice: device id of ingress device
443 * egressDevice: device id of egress device
444 Optional:
445 * ethType: specify ethType
446 * ethSrc: specify ethSrc ( i.e. src mac addr )
447 * ethDst: specify ethDst ( i.e. dst mac addr )
448 * bandwidth: specify bandwidth capacity of link (TODO)
449 * lambdaAlloc: if True, intent will allocate lambda
450 for the specified intent (TODO)
451 * ipProto: specify ip protocol
452 * ipSrc: specify ip source address with mask eg. ip#/24
453 * ipDst: specify ip destination address eg. ip#/24
454 * tcpSrc: specify tcp source port
455 * tcpDst: specify tcp destination port
456 Returns:
457 Returns main.TRUE for successful requests; Returns main.FALSE if
458 no ingress|egress port found and if error on requests;
459 Returns None for exceptions
460 NOTE:
461 The ip and port option are for the requests input's ip and port
462 of the ONOS node
463 """
464 try:
465 if "/" in ingressDevice:
466 if not ingressPort:
467 ingressPort = ingressDevice.split( "/" )[ 1 ]
468 ingressDevice = ingressDevice.split( "/" )[ 0 ]
469 else:
470 if not ingressPort:
471 main.log.debug( self.name + ": Ingress port not specified" )
472 return main.FALSE
473
474 if "/" in egressDevice:
475 if not egressPort:
476 egressPort = egressDevice.split( "/" )[ 1 ]
477 egressDevice = egressDevice.split( "/" )[ 0 ]
478 else:
479 if not egressPort:
480 main.log.debug( self.name + ": Egress port not specified" )
481 return main.FALSE
482
483 intentJson ={ "ingressPoint": { "device": ingressDevice,
484 "port": ingressPort },
485 "selector": { "criteria": [] },
486 "priority": 55,
487 "treatment": { "deferred": [],
488 "instructions": [] },
489 "egressPoint": { "device": egressDevice,
490 "port": egressPort },
491 "appId": appId,
492 "type": "PointToPointIntent",
493 "constraints": [ { "type": "LinkTypeConstraint",
494 "types": [ "OPTICAL" ],
495 "inclusive": "false" } ] }
496
497 if ethType == "IPV4":
498 intentJson[ 'selector' ][ 'criteria' ].append( {
499 "type":"ETH_TYPE",
500 "ethType":2048 } )
kelvin-onlab9b42b0a2015-08-05 14:43:58 -0700501 elif ethType:
502 intentJson[ 'selector' ][ 'criteria' ].append( {
503 "type":"ETH_TYPE",
504 "ethType":ethType } )
505
kelvin-onlabb50074f2015-07-27 16:18:32 -0700506 if ethSrc:
507 intentJson[ 'selector' ][ 'criteria' ].append(
508 { "type":"ETH_SRC",
509 "mac":ethSrc } )
510 if ethDst:
511 intentJson[ 'selector' ][ 'criteria' ].append(
512 { "type":"ETH_DST",
513 "mac":ethDst } )
514 if ipSrc:
515 intentJson[ 'selector' ][ 'criteria' ].append(
516 { "type":"IPV4_SRC",
517 "ip":ipSrc } )
518 if ipDst:
519 intentJson[ 'selector' ][ 'criteria' ].append(
520 { "type":"IPV4_DST",
521 "ip":ipDst } )
522 if tcpSrc:
523 intentJson[ 'selector' ][ 'criteria' ].append(
524 { "type":"TCP_SRC",
525 "tcpPort": tcpSrc } )
526 if tcpDst:
527 intentJson[ 'selector' ][ 'criteria' ].append(
528 { "type":"TCP_DST",
529 "tcpPort": tcpDst } )
530 if ipProto:
531 intentJson[ 'selector' ][ 'criteria' ].append(
532 { "type":"IP_PROTO",
533 "protocol": ipProto } )
534
535 # TODO: Bandwidth and Lambda will be implemented if needed
536
537 main.log.debug( intentJson )
538
539 output = None
540 if ip == "DEFAULT":
541 main.log.warn( "No ip given, reverting to ip from topo file" )
542 ip = self.ip_address
543 if port == "DEFAULT":
544 main.log.warn( "No port given, reverting to port " +
545 "from topo file" )
546 port = self.port
547 response = self.send( ip,
548 port,
549 method="POST",
550 url="/intents",
551 data=json.dumps( intentJson ) )
552 if response:
553 if 201:
554 main.log.info( self.name + ": Successfully POST point" +
555 " intent between ingress: " + ingressDevice +
556 " and egress: " + egressDevice + " devices" )
557 return main.TRUE
558 else:
559 main.log.error( "Error with REST request, response was: " +
560 str( response ) )
561 return main.FALSE
562
563 except Exception as e:
564 main.log.exception( e )
565 return None
566
kelvin-onlab03eb88d2015-07-22 10:29:02 -0700567
568 def removeIntent( self, intentId, appId='org.onosproject.cli',
569 ip="DEFAULT", port="DEFAULT" ):
570 """
571 Remove intent for specified application id and intent id;
572 Returns None for exception
573 """
574 try:
575 output = None
576 if ip == "DEFAULT":
577 main.log.warn( "No ip given, reverting to ip from topo file" )
578 ip = self.ip_address
579 if port == "DEFAULT":
580 main.log.warn( "No port given, reverting to port " +
581 "from topo file" )
582 port = self.port
583 # NOTE: REST url requires the intent id to be in decimal form
584 query = "/" + str( appId ) + "/" + str( int( intentId, 16 ) )
585 response = self.send( ip,
586 port,
587 method="DELETE",
588 url="/intents" + query )
589 if response:
590 if 200 <= response[ 0 ] <= 299:
591 return main.TRUE
592 else:
593 main.log.error( "Error with REST request, response was: " +
594 str( response ) )
595 return main.FALSE
596 except Exception as e:
597 main.log.exception( e )
598 return None
599
600 def getIntentsId( self, ip="DEFAULT", port="DEFAULT" ):
601 """
602 Returns a list of intents id; Returns None for exception
603 """
604 try:
605 intentIdList = []
606 intentsJson = json.loads( self.intents() )
607 print intentsJson
608 for intent in intentsJson:
609 intentIdList.append( intent.get( 'id' ) )
610 print intentIdList
611 return intentIdList
612 except Exception as e:
613 main.log.exception( e )
614 return None
615
616 def removeAllIntents( self, intentIdList ='ALL',appId='org.onosproject.cli',
617 ip="DEFAULT", port="DEFAULT", delay=5 ):
618 """
619 Description:
620 Remove all the intents
621 Returns:
622 Returns main.TRUE if all intents are removed, otherwise returns
623 main.FALSE; Returns None for exception
624 """
625 try:
626 results = []
627 if intentIdList == 'ALL':
628 intentIdList = self.getIntentsId( ip=ip, port=port )
629
630 main.log.info( self.name + ": Removing intents " +
631 str( intentIdList ) )
632
633 if isinstance( intentIdList, types.ListType ):
634 for intent in intentIdList:
635 results.append( self.removeIntent( intentId=intent,
636 appId=appId,
637 ip=ip,
638 port=port ) )
639 # Check for remaining intents
640 # NOTE: Noticing some delay on Deleting the intents so i put
641 # this time out
642 import time
643 time.sleep( delay )
644 intentRemain = len( json.loads( self.intents() ) )
645 if all( result==main.TRUE for result in results ) and \
646 intentRemain == 0:
647 main.log.info( self.name + ": All intents are removed " )
648 return main.TRUE
649 else:
650 main.log.error( self.name + ": Did not removed all intents,"
651 + " there are " + str( intentRemain )
652 + " intents remaining" )
653 return main.FALSE
654 else:
655 main.log.debug( self.name + ": There is no intents ID list" )
656 except Exception as e:
657 main.log.exception( e )
658 return None
659
660
661 def hosts( self, ip="DEFAULT", port="DEFAULT" ):
662 """
663 Description:
664 Get a list of dictionary of all discovered hosts
665 Returns:
666 Returns a list of dictionary of information of the hosts currently
667 discovered by ONOS; Returns main.FALSE if error on requests;
668 Returns None for exception
669 """
670 try:
671 output = None
672 if ip == "DEFAULT":
673 main.log.warn( "No ip given, reverting to ip from topo file" )
674 ip = self.ip_address
675 if port == "DEFAULT":
676 main.log.warn( "No port given, reverting to port \
677 from topo file" )
678 port = self.port
679 response = self.send( ip, port, url="/hosts" )
680 if response:
681 if 200 <= response[ 0 ] <= 299:
682 output = response[ 1 ]
683 a = json.loads( output ).get( 'hosts' )
684 b = json.dumps( a )
685 return b
686 else:
687 main.log.error( "Error with REST request, response was: " +
688 str( response ) )
689 return main.FALSE
690 except Exception as e:
691 main.log.exception( e )
692 return None
693
694 def getHost( self, mac, vlan="-1", ip="DEFAULT", port="DEFAULT" ):
695 """
696 Description:
697 Gets the information from the given host
698 Required:
699 str mac - MAC address of the host
700 Optional:
701 str vlan - VLAN tag of the host, defaults to -1
702 Returns:
703 Return the host id from the hosts/mac/vlan output in REST api
704 whose 'id' contains mac/vlan; Returns None for exception;
705 Returns main.FALSE if error on requests
706
707 NOTE:
708 Not sure what this function should do, any suggestion?
709 """
710 try:
711 output = None
712 if ip == "DEFAULT":
713 main.log.warn( "No ip given, reverting to ip from topo file" )
714 ip = self.ip_address
715 if port == "DEFAULT":
716 main.log.warn( "No port given, reverting to port \
717 from topo file" )
718 port = self.port
719 query = "/" + mac + "/" + vlan
720 response = self.send( ip, port, url="/hosts" + query )
721 if response:
722 # NOTE: What if the person wants other values? would it be better
723 # to have a function that gets a key and return a value instead?
724 # This function requires mac and vlan and returns an ID which
725 # makes this current function useless
726 if 200 <= response[ 0 ] <= 299:
727 output = response[ 1 ]
728 hostId = json.loads( output ).get( 'id' )
729 return hostId
730 else:
731 main.log.error( "Error with REST request, response was: " +
732 str( response ) )
733 return main.FALSE
734 except Exception as e:
735 main.log.exception( e )
736 return None
737
738 def topology( self, ip="DEFAULT", port="DEFAULT" ):
739 """
740 Description:
741 Gets the overview of network topology
742 Returns:
743 Returns a dictionary containing information about network topology;
744 Returns None for exception
745 """
746 try:
747 output = None
748 if ip == "DEFAULT":
749 main.log.warn( "No ip given, reverting to ip from topo file" )
750 ip = self.ip_address
751 if port == "DEFAULT":
752 main.log.warn( "No port given, reverting to port \
753 from topo file" )
754 port = self.port
755 response = self.send( ip, port, url="/topology" )
756 if response:
757 if 200 <= response[ 0 ] <= 299:
758 output = response[ 1 ]
759 a = json.loads( output )
760 b = json.dumps( a )
761 return b
762 else:
763 main.log.error( "Error with REST request, response was: " +
764 str( response ) )
765 return main.FALSE
766 except Exception as e:
767 main.log.exception( e )
768 return None
769
770 def getIntentState( self, intentsId, intentsJson=None,
771 ip="DEFAULT", port="DEFAULT" ):
772 """
773 Description:
774 Get intent state.
775 Accepts a single intent ID (string type) or a list of intent IDs.
776 Returns the state(string type) of the id if a single intent ID is
777 accepted.
778 Required:
779 intentId: intent ID (string type)
780 intentsJson: parsed json object from the onos:intents api
781 Returns:
782 Returns a dictionary with intent IDs as the key and its
783 corresponding states as the values; Returns None for invalid IDs or
784 Type error and any exceptions
785 NOTE:
786 An intent's state consist of INSTALLED,WITHDRAWN etc.
787 """
788 try:
789 state = "State is Undefined"
790 if not intentsJson:
791 intentsJsonTemp = json.loads( self.intents() )
792 else:
793 intentsJsonTemp = json.loads( intentsJson )
794 if isinstance( intentsId, types.StringType ):
795 for intent in intentsJsonTemp:
796 if intentsId == intent[ 'id' ]:
797 state = intent[ 'state' ]
798 return state
799 main.log.info( "Cannot find intent ID" + str( intentsId ) +
800 " on the list" )
801 return state
802 elif isinstance( intentsId, types.ListType ):
803 dictList = []
804 for i in xrange( len( intentsId ) ):
805 stateDict = {}
806 for intents in intentsJsonTemp:
807 if intentsId[ i ] == intents[ 'id' ]:
808 stateDict[ 'state' ] = intents[ 'state' ]
809 stateDict[ 'id' ] = intentsId[ i ]
810 dictList.append( stateDict )
811 break
812 if len( intentsId ) != len( dictList ):
813 main.log.info( "Cannot find some of the intent ID state" )
814 return dictList
815 else:
816 main.log.info( "Invalid intents ID entry" )
817 return None
818
819 except TypeError:
820 main.log.exception( self.name + ": Object Type not as expected" )
821 return None
822 except Exception as e:
823 main.log.exception( e )
824 return None
825
826 def checkIntentState( self, intentsId="ALL", expectedState='INSTALLED',
827 ip="DEFAULT", port="DEFAULT"):
828 """
829 Description:
830 Check intents state based on expected state which defaults to
831 INSTALLED state
832 Required:
833 intentsId - List of intents ID to be checked
834 Optional:
835 expectedState - Check the expected state(s) of each intents
836 state in the list.
837 *NOTE: You can pass in a list of expected state,
838 Eg: expectedState = [ 'INSTALLED' , 'INSTALLING' ]
839 Return:
840 Returns main.TRUE only if all intent are the same as expected states
841 , otherwise, returns main.FALSE; Returns None for general exception
842 """
843 try:
844 # Generating a dictionary: intent id as a key and state as value
845 returnValue = main.TRUE
846 if intentsId == "ALL":
847 intentsId = self.getIntentsId( ip=ip, port=port )
848 intentsDict = self.getIntentState( intentsId, ip=ip, port=port )
849
850 #print "len of intentsDict ", str( len( intentsDict ) )
851 if len( intentsId ) != len( intentsDict ):
852 main.log.error( self.name + ": There is something wrong " +
853 "getting intents state" )
854 return main.FALSE
855
856 if isinstance( expectedState, types.StringType ):
857 for intents in intentsDict:
858 if intents.get( 'state' ) != expectedState:
859 main.log.debug( self.name + " : Intent ID - " +
860 intents.get( 'id' ) +
861 " actual state = " +
862 intents.get( 'state' )
863 + " does not equal expected state = "
864 + expectedState )
865 returnValue = main.FALSE
866
867 elif isinstance( expectedState, types.ListType ):
868 for intents in intentsDict:
869 if not any( state == intents.get( 'state' ) for state in
870 expectedState ):
871 main.log.debug( self.name + " : Intent ID - " +
872 intents.get( 'id' ) +
873 " actual state = " +
874 intents.get( 'state' ) +
875 " does not equal expected states = "
876 + str( expectedState ) )
877 returnValue = main.FALSE
878
879 if returnValue == main.TRUE:
880 main.log.info( self.name + ": All " +
881 str( len( intentsDict ) ) +
882 " intents are in " + str( expectedState ) +
883 " state" )
884 return returnValue
885 except TypeError:
886 main.log.exception( self.name + ": Object not as expected" )
887 return main.FALSE
888 except Exception as e:
889 main.log.exception( e )
890 return None
891
892 def flows( self, ip="DEFAULT", port="DEFAULT" ):
893 """
894 Description:
895 Get flows currently added to the system
896 NOTE:
897 The flows -j cli command has completely different format than
898 the REST output; Returns None for exception
899 """
900 try:
901 output = None
902 if ip == "DEFAULT":
903 main.log.warn( "No ip given, reverting to ip from topo file" )
904 ip = self.ip_address
905 if port == "DEFAULT":
906 main.log.warn( "No port given, reverting to port \
907 from topo file" )
908 port = self.port
909 response = self.send( ip, port, url="/flows" )
910 if response:
911 if 200 <= response[ 0 ] <= 299:
912 output = response[ 1 ]
913 a = json.loads( output ).get( 'flows' )
914 b = json.dumps( a )
915 return b
916 else:
917 main.log.error( "Error with REST request, response was: " +
918 str( response ) )
919 return main.FALSE
920 except Exception as e:
921 main.log.exception( e )
922 return None
923
kelvin-onlab9b42b0a2015-08-05 14:43:58 -0700924 def getFlows( self, device, flowId=0, ip="DEFAULT", port="DEFAULT" ):
925 """
926 Description:
927 Gets all the flows of the device or get a specific flow in the
928 device by giving its flow ID
929 Required:
930 str device - device/switch Id
931 Optional:
932 int/hex flowId - ID of the flow
933 """
934 try:
935 output = None
936 if ip == "DEFAULT":
937 main.log.warn( "No ip given, reverting to ip from topo file" )
938 ip = self.ip_address
939 if port == "DEFAULT":
940 main.log.warn( "No port given, reverting to port \
941 from topo file" )
942 port = self.port
943 url = "/flows/" + device
944 if flowId:
945 url += "/" + str( int( flowId ) )
946 print url
947 response = self.send( ip, port, url=url )
948 if response:
949 if 200 <= response[ 0 ] <= 299:
950 output = response[ 1 ]
951 a = json.loads( output ).get( 'flows' )
952 b = json.dumps( a )
953 return b
954 else:
955 main.log.error( "Error with REST request, response was: " +
956 str( response ) )
957 return main.FALSE
958 except Exception as e:
959 main.log.exception( e )
960 return None
961
962
963 def addFlow( self,
964 deviceId,
965 appId=0,
966 ingressPort="",
967 egressPort="",
968 ethType="",
969 ethSrc="",
970 ethDst="",
971 bandwidth="",
972 lambdaAlloc=False,
973 ipProto="",
974 ipSrc="",
975 ipDst="",
976 tcpSrc="",
977 tcpDst="",
978 ip="DEFAULT",
979 port="DEFAULT" ):
980 """
981 Description:
982 Creates a single flow in the specified device
983 Required:
984 * deviceId: id of the device
985 Optional:
986 * ingressPort: port ingress device
987 * egressPort: port of egress device
988 * ethType: specify ethType
989 * ethSrc: specify ethSrc ( i.e. src mac addr )
990 * ethDst: specify ethDst ( i.e. dst mac addr )
991 * ipProto: specify ip protocol
992 * ipSrc: specify ip source address with mask eg. ip#/24
993 * ipDst: specify ip destination address eg. ip#/24
994 * tcpSrc: specify tcp source port
995 * tcpDst: specify tcp destination port
996 Returns:
997 Returns main.TRUE for successful requests; Returns main.FALSE
998 if error on requests;
999 Returns None for exceptions
1000 NOTE:
1001 The ip and port option are for the requests input's ip and port
1002 of the ONOS node
1003 """
1004 try:
1005
1006 flowJson = { "priority":100,
1007 "isPermanent":"true",
1008 "timeout":0,
1009 "deviceId":deviceId,
1010 "treatment":{"instructions":[]},
1011 "selector": {"criteria":[]}}
1012
1013 if appId:
1014 flowJson[ "appId" ] = appId
1015
1016 if egressPort:
1017 flowJson[ 'treatment' ][ 'instructions' ].append(
1018 { "type":"OUTPUT",
1019 "port":egressPort } )
1020 if ingressPort:
1021 flowJson[ 'selector' ][ 'criteria' ].append(
1022 { "type":"IN_PORT",
1023 "port":ingressPort } )
1024 if ethType == "IPV4":
1025 flowJson[ 'selector' ][ 'criteria' ].append( {
1026 "type":"ETH_TYPE",
1027 "ethType":2048 } )
1028 elif ethType:
1029 flowJson[ 'selector' ][ 'criteria' ].append( {
1030 "type":"ETH_TYPE",
1031 "ethType":ethType } )
1032 if ethSrc:
1033 flowJson[ 'selector' ][ 'criteria' ].append(
1034 { "type":"ETH_SRC",
1035 "mac":ethSrc } )
1036 if ethDst:
1037 flowJson[ 'selector' ][ 'criteria' ].append(
1038 { "type":"ETH_DST",
1039 "mac":ethDst } )
1040 if ipSrc:
1041 flowJson[ 'selector' ][ 'criteria' ].append(
1042 { "type":"IPV4_SRC",
1043 "ip":ipSrc } )
1044 if ipDst:
1045 flowJson[ 'selector' ][ 'criteria' ].append(
1046 { "type":"IPV4_DST",
1047 "ip":ipDst } )
1048 if tcpSrc:
1049 flowJson[ 'selector' ][ 'criteria' ].append(
1050 { "type":"TCP_SRC",
1051 "tcpPort": tcpSrc } )
1052 if tcpDst:
1053 flowJson[ 'selector' ][ 'criteria' ].append(
1054 { "type":"TCP_DST",
1055 "tcpPort": tcpDst } )
1056 if ipProto:
1057 flowJson[ 'selector' ][ 'criteria' ].append(
1058 { "type":"IP_PROTO",
1059 "protocol": ipProto } )
1060
1061 # TODO: Bandwidth and Lambda will be implemented if needed
1062
1063 main.log.debug( flowJson )
1064
1065 output = None
1066 if ip == "DEFAULT":
1067 main.log.warn( "No ip given, reverting to ip from topo file" )
1068 ip = self.ip_address
1069 if port == "DEFAULT":
1070 main.log.warn( "No port given, reverting to port " +
1071 "from topo file" )
1072 port = self.port
1073 url = "/flows/" + deviceId
1074 response = self.send( ip,
1075 port,
1076 method="POST",
1077 url=url,
1078 data=json.dumps( flowJson ) )
1079 if response:
1080 if 201:
1081 main.log.info( self.name + ": Successfully POST flow" +
1082 "in device: " + str( deviceId ) )
1083 return main.TRUE
1084 else:
1085 main.log.error( "Error with REST request, response was: " +
1086 str( response ) )
1087 return main.FALSE
1088
1089 except Exception as e:
1090 main.log.exception( e )
1091 return None
1092
1093 def removeFlow( self, deviceId, flowId,
1094 ip="DEFAULT", port="DEFAULT" ):
1095 """
1096 Description:
1097 Remove specific device flow
1098 Required:
1099 str deviceId - id of the device
1100 str flowId - id of the flow
1101 Return:
1102 Returns main.TRUE if successfully deletes flows, otherwise
1103 Returns main.FALSE, Returns None on error
1104 """
1105 try:
1106 output = None
1107 if ip == "DEFAULT":
1108 main.log.warn( "No ip given, reverting to ip from topo file" )
1109 ip = self.ip_address
1110 if port == "DEFAULT":
1111 main.log.warn( "No port given, reverting to port " +
1112 "from topo file" )
1113 port = self.port
1114 # NOTE: REST url requires the intent id to be in decimal form
1115 query = "/" + str( deviceId ) + "/" + str( int( flowId ) )
1116 response = self.send( ip,
1117 port,
1118 method="DELETE",
1119 url="/flows" + query )
1120 if response:
1121 if 200 <= response[ 0 ] <= 299:
1122 return main.TRUE
1123 else:
1124 main.log.error( "Error with REST request, response was: " +
1125 str( response ) )
1126 return main.FALSE
1127 except Exception as e:
1128 main.log.exception( e )
1129 return None
1130
kelvin-onlab03eb88d2015-07-22 10:29:02 -07001131 def checkFlowsState( self , ip="DEFAULT", port="DEFAULT" ):
1132 """
1133 Description:
1134 Check if all the current flows are in ADDED state
1135 Return:
1136 returnValue - Returns main.TRUE only if all flows are in
1137 return main.FALSE otherwise;
1138 Returns None for exception
1139 """
1140 try:
1141 tempFlows = json.loads( self.flows( ip=ip, port=port ) )
1142 returnValue = main.TRUE
1143 for flow in tempFlows:
1144 if flow.get( 'state' ) != 'ADDED':
1145 main.log.info( self.name + ": flow Id: " +
1146 str( flow.get( 'groupId' ) ) +
1147 " | state:" +
1148 str( flow.get( 'state' ) ) )
1149 returnValue = main.FALSE
1150 return returnValue
1151 except TypeError:
1152 main.log.exception( self.name + ": Object not as expected" )
1153 return main.FALSE
1154 except Exception as e:
1155 main.log.exception( e )
1156 return None
1157 except Exception:
1158 main.log.exception( self.name + ": Uncaught exception!" )
1159 main.cleanup()
1160 main.exit()