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