blob: 70274089b2bedb3d66994b7ffa1cdeff51db1093 [file] [log] [blame]
#!/usr/bin/env python
"""
Created on 07-08-2015
Copyright 2015 Open Networking Foundation (ONF)
Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>,
the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>,
or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg>
TestON is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
( at your option ) any later version.
TestON is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TestON. If not, see <http://www.gnu.org/licenses/>.
"""
import json
import os
import requests
import types
import sys
import time
from drivers.common.api.controllerdriver import Controller
class OnosRestDriver( Controller ):
def __init__( self ):
self.pwd = None
self.user_name = "user"
super( OnosRestDriver, self ).__init__()
self.ip_address = "localhost"
self.port = "8080"
self.wrapped = sys.modules[ __name__ ]
def connect( self, **connectargs ):
try:
for key in connectargs:
vars( self )[ key ] = connectargs[ key ]
self.name = self.options[ 'name' ]
except Exception as e:
main.log.exception( e )
try:
if os.getenv( str( self.ip_address ) ) is not None:
self.ip_address = os.getenv( str( self.ip_address ) )
else:
main.log.info( self.name + ": ip set to " + self.ip_address )
except KeyError:
main.log.info( self.name + ": Invalid host name," +
"defaulting to 'localhost' instead" )
self.ip_address = 'localhost'
except Exception as inst:
main.log.error( "Uncaught exception: " + str( inst ) )
return super( OnosRestDriver, self ).connect()
def pprint( self, jsonObject ):
"""
Pretty Prints a json object
arguments:
jsonObject - a parsed json object
returns:
A formatted string for printing or None on error
"""
try:
if not jsonObject:
return None
if isinstance( jsonObject, str ):
jsonObject = json.loads( jsonObject )
return json.dumps( jsonObject, sort_keys=True,
indent=4, separators=( ',', ': ' ) )
except ( TypeError, ValueError ):
main.log.exception( "Error parsing jsonObject %s" % repr(jsonObject) )
return None
def send( self, url, ip = "DEFAULT", port = "DEFAULT", base="/onos/v1", method="GET",
query=None, data=None, debug=False ):
"""
Arguments:
str ip: ONOS IP Address
str port: ONOS REST Port
str url: ONOS REST url path.
NOTE that this is is only the relative path. IE "/devices"
str base: The base url for the given REST api. Applications could
potentially have their own base url
str method: HTTP method type
dict query: Dictionary to be sent in the query string for
the request
dict data: Dictionary to be sent in the body of the request
"""
# TODO: Authentication - simple http (user,pass) tuple
# TODO: should we maybe just pass kwargs straight to response?
# TODO: Do we need to allow for other protocols besides http?
# ANSWER: Not yet, but potentially https with certificates
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
try:
path = "http://" + str( ip ) + ":" + str( port ) + base + url
if self.user_name and self.pwd:
main.log.info( self.name + ": user/passwd is: " + self.user_name + "/" + self.pwd )
auth = ( self.user_name, self.pwd )
else:
auth = None
main.log.info( self.name + ": Sending request " + path + " using " +
method.upper() + " method." )
if debug:
main.log.debug( self.name + ": request data: " + str( data ) )
self.log( "Sending %s to %s with %s\n" % ( method.upper(),
path,
self.pprint( data ) ) )
response = requests.request( method.upper(),
path,
params=query,
data=data,
auth=auth )
self.log( "Received %s code with body: %s\n" % ( response.status_code,
self.pprint( response.text.encode( 'utf8' ) ) ) )
if debug:
main.log.debug( response )
return ( response.status_code, response.text.encode( 'utf8' ) )
except requests.ConnectionError:
main.log.exception( "Error sending request." )
return ( None, None )
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def intents( self, ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Gets a list of dictionary of all intents in the system
Returns:
A list of dictionary of intents in string type to match the cli
version for now; Returns main.FALSE if error on request;
Returns None for exception
"""
try:
output = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
response = self.send( url="/intents", ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
output = response[ 1 ]
a = json.loads( output ).get( 'intents' )
assert a is not None, "Error parsing json object"
b = json.dumps( a )
return b
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, AssertionError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def intent( self, intentId, appId="org.onosproject.cli",
ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Get the specific intent information of the given application ID and
intent ID
Required:
str intentId - Intent id in hexadecimal form
Optional:
str appId - application id of intent
Returns:
Returns an information dictionary of the given intent;
Returns main.FALSE if error on requests; Returns None for exception
NOTE:
The GET /intents REST api command accepts application id but the
api will get updated to accept application name instead
"""
try:
output = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
# NOTE: REST url requires the intent id to be in decimal form
query = "/" + str( appId ) + "/" + str( intentId )
response = self.send( url="/intents" + query, ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
output = response[ 1 ]
a = json.loads( output )
return a
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def apps( self, ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Returns all the current application installed in the system
Returns:
List of dictionary of installed application; Returns main.FALSE for
error on request; Returns None for exception
"""
try:
output = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
response = self.send( url="/applications", ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
output = response[ 1 ]
a = json.loads( output ).get( 'applications' )
assert a is not None, "Error parsing json object"
b = json.dumps( a )
return b
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, AssertionError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def activateApp( self, appName, ip="DEFAULT", port="DEFAULT", check=True ):
"""
Decription:
Activate an app that is already installed in ONOS
Optional:
bool check - If check is True, method will check the status
of the app after the command is issued
Returns:
Returns main.TRUE if the command was successfully or main.FALSE
if the REST responded with an error or given incorrect input;
Returns None for exception
"""
try:
output = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
query = "/" + str( appName ) + "/active"
retry = 0
retCode = main.TRUE
while retry < 50:
if retry > 0:
main.log.warn( self.name + ": Retrying " + query + " for the " + str( retry ) + " time" )
retry += 1
response = self.send( method="POST",
debug=True,
url="/applications" + query,
ip = ip, port = port )
if response:
output = response[ 1 ]
if 200 <= response[ 0 ] <= 299:
if check:
app = json.loads( output )
if app.get( 'state' ) == 'ACTIVE':
main.log.info( self.name + ": " + appName +
" application" +
" is in ACTIVE state" )
appHealth = self.getAppHealth( appName=appName, ip=ip, port=port )
if "ready" == json.loads( appHealth[1] ).get( 'message' ):
return main.TRUE
else:
return main.FALSE
else:
main.log.error( self.name + ": " + appName +
" application" + " is in " +
app.get( 'state' ) + " state" )
retCode = main.FALSE
else:
main.log.warn( self.name + ": Skipping " + appName +
"application check" )
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
retCode = main.FALSE
time.sleep( 30 )
return retCode
except ( ValueError ):
main.log.exception( self.name + ": Error parsing json" )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.log.debug( self.name + ": " + response )
main.cleanAndExit()
def deactivateApp( self, appName, ip="DEFAULT", port="DEFAULT",
check=True ):
"""
Required:
Deactivate an app that is already activated in ONOS
Optional:
bool check - If check is True, method will check the status of the
app after the command is issued
Returns:
Returns main.TRUE if the command was successfully sent
main.FALSE if the REST responded with an error or given
incorrect input; Returns None for exception
"""
try:
output = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
query = "/" + str( appName ) + "/active"
self.send( method="DELETE",
url="/applications" + query,
ip = ip, port = port )
response = self.getApp( appName, ip, port )
if response:
output = response[ 1 ]
app = {} if output == "" else json.loads( output )
if 200 <= response[ 0 ] <= 299:
if check:
if app.get( 'state' ) == 'INSTALLED':
main.log.info( self.name + ": " + appName +
" application" +
" is in INSTALLED state" )
return main.TRUE
else:
main.log.error( self.name + ": " + appName +
" application" + " is in " +
app.get( 'state' ) + " state" )
return main.FALSE
else:
main.log.warn( self.name + ": Skipping " + appName +
"application check" )
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def getApp( self, appName, ip="DEFAULT",
port="DEFAULT" ):
"""
Decription:
Gets the informaion of the given application
Required:
str name - Name of onos application
Returns:
Returns a dictionary of information ONOS application in string type;
Returns main.FALSE if error on requests; Returns None for exception
"""
try:
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
query = "/" + str( appName )
response = self.send( url="/applications" + query,
ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
return response
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def getAppHealth( self, appName, ip="DEFAULT",
port="DEFAULT" ):
"""
Decription:
Gets the health of the given application
Required:
str name - Name of onos application
Returns:
Returns a dictionary of information ONOS application in string type;
Returns main.FALSE if error on requests; Returns None for exception
"""
try:
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
response = self.send( url="/applications/%s/health" % str( appName ),
ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
return response
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def getAllAppHealth( self, retries=1, wait=30 , ip="DEFAULT",
port="DEFAULT" ):
"""
Description:
Gets the health of all activated apps
Required:
Optional:
retries - The number of tries to return before returning
wait - Time to wait in between retries
"""
try:
responses = main.TRUE
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
apps = self.apps()
for app in json.loads(apps):
appName = app.get( "name" )
response = self.getAppHealth( appName=appName, ip=ip, port=port )
responses = main.TRUE and "ready" == json.loads( response[1] ).get( "message" )
return responses
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def addHostIntent( self, hostIdOne, hostIdTwo, appId='org.onosproject.cli',
ip="DEFAULT", port="DEFAULT", vlanId="" ):
"""
Description:
Adds a host-to-host intent ( bidirectional ) by
specifying the two hosts.
Required:
* hostIdOne: ONOS host id for host1
* hostIdTwo: ONOS host id for host2
Optional:
str appId - Application name of intent identifier
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE if
error on requests; Returns None for exceptions
"""
try:
intentJson = { "two": str( hostIdTwo ),
"selector": { "criteria": [] }, "priority": 7,
"treatment": { "deferred": [], "instructions": [] },
"appId": appId, "one": str( hostIdOne ),
"type": "HostToHostIntent",
"constraints": [ { "type": "LinkTypeConstraint",
"types": [ "OPTICAL" ],
"inclusive": 'false' } ] }
if vlanId:
intentJson[ 'selector' ][ 'criteria' ].append( { "type": "VLAN_VID",
"vlanId": vlanId } )
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
response = self.send( method="POST",
url="/intents", ip = ip, port = port,
data=json.dumps( intentJson ) )
if response:
if "201" in str( response[ 0 ] ):
main.log.info( self.name + ": Successfully POST host" +
" intent between host: " + hostIdOne +
" and host: " + hostIdTwo )
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def addPointIntent( self,
ingressDevice,
egressDevice,
appId='org.onosproject.cli',
ingressPort="",
egressPort="",
ethType="",
ethSrc="",
ethDst="",
bandwidth="",
protected=False,
lambdaAlloc=False,
ipProto="",
ipSrc="",
ipDst="",
tcpSrc="",
tcpDst="",
ip="DEFAULT",
port="DEFAULT",
vlanId="" ):
"""
Description:
Adds a point-to-point intent ( uni-directional ) by
specifying device id's and optional fields
Required:
* ingressDevice: device id of ingress device
* egressDevice: device id of egress device
Optional:
* ethType: specify ethType
* ethSrc: specify ethSrc ( i.e. src mac addr )
* ethDst: specify ethDst ( i.e. dst mac addr )
* bandwidth: specify bandwidth capacity of link (TODO)
* lambdaAlloc: if True, intent will allocate lambda
for the specified intent (TODO)
* ipProto: specify ip protocol
* ipSrc: specify ip source address with mask eg. ip#/24
* ipDst: specify ip destination address eg. ip#/24
* tcpSrc: specify tcp source port
* tcpDst: specify tcp destination port
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE if
no ingress|egress port found and if error on requests;
Returns None for exceptions
NOTE:
The ip and port option are for the requests input's ip and port
of the ONOS node
"""
try:
if "/" in ingressDevice:
if not ingressPort:
ingressPort = ingressDevice.split( "/" )[ 1 ]
ingressDevice = ingressDevice.split( "/" )[ 0 ]
else:
if not ingressPort:
main.log.debug( self.name + ": Ingress port not specified" )
return main.FALSE
if "/" in egressDevice:
if not egressPort:
egressPort = egressDevice.split( "/" )[ 1 ]
egressDevice = egressDevice.split( "/" )[ 0 ]
else:
if not egressPort:
main.log.debug( self.name + ": Egress port not specified" )
return main.FALSE
intentJson = { "ingressPoint": { "device": ingressDevice,
"port": ingressPort },
"selector": { "criteria": [] },
"priority": 55,
"treatment": { "deferred": [],
"instructions": [] },
"egressPoint": { "device": egressDevice,
"port": egressPort },
"appId": appId,
"type": "PointToPointIntent",
"constraints": [ { "type": "LinkTypeConstraint",
"types": [ "OPTICAL" ],
"inclusive": "false" } ] }
if ethType == "IPV4":
intentJson[ 'selector' ][ 'criteria' ].append( {
"type": "ETH_TYPE",
"ethType": 2048 } )
elif ethType:
intentJson[ 'selector' ][ 'criteria' ].append( {
"type": "ETH_TYPE",
"ethType": ethType } )
if ethSrc:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "ETH_SRC",
"mac": ethSrc } )
if ethDst:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "ETH_DST",
"mac": ethDst } )
if ipSrc:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "IPV4_SRC",
"ip": ipSrc } )
if ipDst:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "IPV4_DST",
"ip": ipDst } )
if tcpSrc:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "TCP_SRC",
"tcpPort": tcpSrc } )
if tcpDst:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "TCP_DST",
"tcpPort": tcpDst } )
if ipProto:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "IP_PROTO",
"protocol": ipProto } )
if vlanId:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "VLAN_VID",
"vlanId": vlanId } )
# TODO: Bandwidth and Lambda will be implemented if needed
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
response = self.send( method="POST",
url="/intents", ip = ip, port = port,
data=json.dumps( intentJson ) )
main.log.debug( intentJson )
if response:
if "201" in str( response[ 0 ] ):
main.log.info( self.name + ": Successfully POST point" +
" intent between ingress: " + ingressDevice +
" and egress: " + egressDevice + " devices" )
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def addSinglepointToMultipointIntent( self,
ingressDevice,
egressDeviceList,
portEgressList,
appId='org.onosproject.cli',
portIngress="",
ethType="",
ethSrc="",
ethDst="",
bandwidth="",
lambdaAlloc=False,
ipProto="",
ipSrc="",
ipDst="",
tcpSrc="",
tcpDst="",
partial=False,
ip="DEFAULT",
port="DEFAULT",
vlanId="" ):
"""
Description:
Adds a point-to-multi point intent ( uni-directional ) by
specifying device id's and optional fields
Required:
* ingressDevice: device id of ingress device
* egressDevice: device id of egress device
* portEgressList: a list of port id of egress device
Optional:
* portIngress: port id of ingress device
* ethType: specify ethType
* ethSrc: specify ethSrc ( i.e. src mac addr )
* ethDst: specify ethDst ( i.e. dst mac addr )
* bandwidth: specify bandwidth capacity of link (TODO)
* lambdaAlloc: if True, intent will allocate lambda
for the specified intent (TODO)
* ipProto: specify ip protocol
* ipSrc: specify ip source address with mask eg. ip#/24
* ipDst: specify ip destination address eg. ip#/24
* tcpSrc: specify tcp source port
* tcpDst: specify tcp destination port
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE if
no ingress|egress port found and if error on requests;
Returns None for exceptions
NOTE:
The ip and port option are for the requests input's ip and port
of the ONOS node
"""
try:
if "/" in ingressDevice:
if not portIngress:
ingressPort = ingressDevice.split( "/" )[ 1 ]
ingressDevice = ingressDevice.split( "/" )[ 0 ]
else:
if not portIngress:
main.log.debug( self.name + ": Ingress port not specified" )
return main.FALSE
index = 0
for egressDevice in egressDeviceList:
if "/" in egressDevice:
portEgressList.append( egressDevice.split( "/" )[ 1 ] )
egressDeviceList[ index ] = egressDevice.split( "/" )[ 0 ]
else:
if not portEgressList:
main.log.debug( self.name + ": Egress port not specified" )
return main.FALSE
index = index + 1
intentJson = { "ingressPoint": { "device": ingressDevice,
"port": ingressPort },
"selector": { "criteria": [] },
"priority": 55,
"treatment": { "deferred": [],
"instructions": [] },
"egressPoint": { "connectPoints": [] },
"appId": appId,
"type": "SinglePointToMultiPointIntent",
"constraints": [ { "type": "LinkTypeConstraint",
"types": [ "OPTICAL" ],
"inclusive": "false" } ] }
index = 0
for ep in portEgressList:
intentJson[ 'egressPoint' ][ 'connectPoints' ].append(
{ "device": egressDeviceList[ index ],
"port": ep } )
index += 1
if ethType == "IPV4":
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "ETH_TYPE",
"ethType": 2048 } )
elif ethType:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "ETH_TYPE",
"ethType": ethType } )
if ethSrc:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "ETH_SRC",
"mac": ethSrc } )
if ethDst:
for dst in ethDst:
if dst:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "ETH_DST",
"mac": dst } )
if tcpSrc:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "TCP_SRC",
"tcpPort": tcpSrc } )
if tcpDst:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "TCP_DST",
"tcpPort": tcpDst } )
if ipProto:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "IP_PROTO",
"protocol": ipProto } )
if vlanId:
intentJson[ 'selector' ][ 'criteria' ].append(
{ "type": "VLAN_VID",
"vlanId": vlanId } )
# TODO: Bandwidth and Lambda will be implemented if needed
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
response = self.send( method="POST",
url="/intents", ip=ip, port=port,
data=json.dumps( intentJson ) )
main.log.debug( intentJson )
if response:
if "201" in str( response[ 0 ] ):
main.log.info( self.name + ": Successfully POST point" +
" intent between ingress: " + ingressDevice +
" and egress: " + str( egressDeviceList ) + " devices" )
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" % ( response[ 0 ], response[ 1 ] ) )
return main.FALSE
else:
main.log.error( "REST request has no response." )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def removeIntent( self, intentId, appId='org.onosproject.cli',
ip="DEFAULT", port="DEFAULT" ):
"""
Remove intent for specified application id and intent id;
Returns None for exception
"""
try:
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
# NOTE: REST url requires the intent id to be in decimal form
query = "/" + str( appId ) + "/" + str( int( intentId, 16 ) )
response = self.send( method="DELETE",
url="/intents" + query, ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def getIntentsId( self, ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Gets all intents ID using intents function
Returns:
List of intents ID if found any intents; Returns main.FALSE for other exceptions
"""
try:
intentIdList = []
intentsJson = json.loads( self.intents( ip=ip, port=port ) )
for intent in intentsJson:
intentIdList.append( intent.get( 'id' ) )
if not intentIdList:
main.log.debug( self.name + ": Cannot find any intents" )
return main.FALSE
else:
return intentIdList
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def removeAllIntents( self, intentIdList ='ALL', appId='org.onosproject.cli',
ip="DEFAULT", port="DEFAULT", delay=5 ):
"""
Description:
Remove all the intents
Returns:
Returns main.TRUE if all intents are removed, otherwise returns
main.FALSE; Returns None for exception
"""
try:
results = []
if intentIdList == 'ALL':
# intentIdList = self.getIntentsId( ip=ip, port=port )
intentIdList = self.getIntentsId()
main.log.info( self.name + ": Removing intents " +
str( intentIdList ) )
if isinstance( intentIdList, types.ListType ):
for intent in intentIdList:
results.append( self.removeIntent( intentId=intent,
appId=appId,
ip=ip,
port=port ) )
# Check for remaining intents
# NOTE: Noticing some delay on Deleting the intents so i put
# this time out
import time
time.sleep( delay )
intentRemain = len( json.loads( self.intents() ) )
if all( result == main.TRUE for result in results ) and \
intentRemain == 0:
main.log.info( self.name + ": All intents are removed " )
return main.TRUE
else:
main.log.error( self.name + ": Did not removed all intents,"
+ " there are " + str( intentRemain )
+ " intents remaining" )
return main.FALSE
else:
main.log.debug( self.name + ": There is no intents ID list" )
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def hosts( self, ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Get a list of dictionary of all discovered hosts
Returns:
Returns a list of dictionary of information of the hosts currently
discovered by ONOS; Returns main.FALSE if error on requests;
Returns None for exception
"""
try:
output = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
response = self.send( url="/hosts", ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
output = response[ 1 ]
a = json.loads( output ).get( 'hosts' )
assert a is not None, "Error parsing json object"
b = json.dumps( a )
return b
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, AssertionError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def getHost( self, mac, vlan="-1", ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Gets the information from the given host
Required:
str mac - MAC address of the host
Optional:
str vlan - VLAN tag of the host, defaults to -1
Returns:
Return the host id from the hosts/mac/vlan output in REST api
whose 'id' contains mac/vlan; Returns None for exception;
Returns main.FALSE if error on requests
NOTE:
Not sure what this function should do, any suggestion?
"""
try:
output = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
query = "/" + mac + "/" + vlan
response = self.send( url="/hosts" + query, ip = ip, port = port )
if response:
# NOTE: What if the person wants other values? would it be better
# to have a function that gets a key and return a value instead?
# This function requires mac and vlan and returns an ID which
# makes this current function useless
if 200 <= response[ 0 ] <= 299:
output = response[ 1 ]
hostId = json.loads( output ).get( 'id' )
return hostId
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def topology( self, ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Gets the overview of network topology
Returns:
Returns a dictionary containing information about network topology;
Returns None for exception
"""
try:
output = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
response = self.send( url="/topology", ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
output = response[ 1 ]
a = json.loads( output )
b = json.dumps( a )
return b
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def devices( self, ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Get the devices discovered by ONOS is json string format
Returns:
a json string of the devices currently discovered by ONOS OR
main.FALSE if there is an error in the request OR
Returns None for exception
"""
try:
output = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
response = self.send( url="/devices", ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
output = response[ 1 ]
a = json.loads( output ).get( 'devices' )
assert a is not None, "Error parsing json object"
b = json.dumps( a )
return b
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, AssertionError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def getIntentState( self, intentsId, intentsJson=None,
ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Get intent state.
Accepts a single intent ID (string type) or a list of intent IDs.
Returns the state(string type) of the id if a single intent ID is
accepted.
Required:
intentId: intent ID (string type)
intentsJson: parsed json object from the onos:intents api
Returns:
Returns a dictionary with intent IDs as the key and its
corresponding states as the values; Returns None for invalid IDs or
Type error and any exceptions
NOTE:
An intent's state consist of INSTALLED,WITHDRAWN etc.
"""
try:
state = "State is Undefined"
if not intentsJson:
intentsJsonTemp = json.loads( self.intents() )
else:
intentsJsonTemp = json.loads( intentsJson )
if isinstance( intentsId, types.StringType ):
for intent in intentsJsonTemp:
if intentsId == intent[ 'id' ]:
state = intent[ 'state' ]
return state
main.log.info( self.name + ": Cannot find intent ID" + str( intentsId ) +
" on the list" )
return state
elif isinstance( intentsId, types.ListType ):
dictList = []
for i in xrange( len( intentsId ) ):
stateDict = {}
for intents in intentsJsonTemp:
if intentsId[ i ] == intents[ 'id' ]:
stateDict[ 'state' ] = intents[ 'state' ]
stateDict[ 'id' ] = intentsId[ i ]
dictList.append( stateDict )
break
if len( intentsId ) != len( dictList ):
main.log.info( self.name + ": Cannot find some of the intent ID state" )
return dictList
else:
main.log.info( self.name + ": Invalid intents ID entry" )
return None
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def checkIntentState( self, intentsId="ALL", expectedState='INSTALLED',
ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Check intents state based on expected state which defaults to
INSTALLED state
Required:
intentsId - List of intents ID to be checked
Optional:
expectedState - Check the expected state(s) of each intents
state in the list.
*NOTE: You can pass in a list of expected state,
Eg: expectedState = [ 'INSTALLED' , 'INSTALLING' ]
Return:
Returns main.TRUE only if all intent are the same as expected states
, otherwise, returns main.FALSE; Returns None for general exception
"""
try:
# Generating a dictionary: intent id as a key and state as value
returnValue = main.TRUE
if intentsId == "ALL":
intentsId = self.getIntentsId( ip=ip, port=port )
intentsDict = self.getIntentState( intentsId, ip=ip, port=port )
if len( intentsId ) != len( intentsDict ):
main.log.error( self.name + ": There is something wrong " +
"getting intents state" )
return main.FALSE
if isinstance( expectedState, types.StringType ):
for intents in intentsDict:
if intents.get( 'state' ) != expectedState:
main.log.debug( self.name + ": Intent ID - " +
intents.get( 'id' ) +
" actual state = " +
intents.get( 'state' )
+ " does not equal expected state = "
+ expectedState )
returnValue = main.FALSE
elif isinstance( expectedState, types.ListType ):
for intents in intentsDict:
if not any( state == intents.get( 'state' ) for state in
expectedState ):
main.log.debug( self.name + ": Intent ID - " +
intents.get( 'id' ) +
" actual state = " +
intents.get( 'state' ) +
" does not equal expected states = "
+ str( expectedState ) )
returnValue = main.FALSE
if returnValue == main.TRUE:
main.log.info( self.name + ": All " +
str( len( intentsDict ) ) +
" intents are in " + str( expectedState ) +
" state" )
return returnValue
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def flows( self, ip="DEFAULT", port="DEFAULT", subjectClass=None, subjectKey=None ):
"""
Description:
Get flows currently added to the system
NOTE:
The flows -j cli command has completely different format than
the REST output
Returns None for exception
"""
try:
output = None
url = "/flows"
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
if subjectKey and not subjectClass:
main.log.warning( "Subject Key provided without Subject Class. Ignoring Subject Key" )
if subjectClass:
url += "/" + subjectClass
if subjectKey:
url += "/" + subjectKey
response = self.send( url=url, ip=ip, port=port )
if response:
if 200 <= response[ 0 ] <= 299:
output = response[ 1 ]
a = json.loads( output ).get( 'flows' )
assert a is not None, "Error parsing json object"
b = json.dumps( a )
return b
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, AssertionError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def getFlows( self, deviceId, flowId=None, ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Gets all the flows of the device or get a specific flow in the
device by giving its flow ID
Required:
str deviceId - device/switch Id
Optional:
int/hex flowId - ID of the flow
"""
try:
output = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
url = "/flows/" + deviceId
if flowId:
url += "/" + str( int( flowId ) )
print url
response = self.send( url=url, ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
output = response[ 1 ]
a = json.loads( output ).get( 'flows' )
assert a is not None, "Error parsing json object"
b = json.dumps( a )
return b
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, AssertionError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def sendFlow( self, deviceId, flowJson, ip="DEFAULT", port="DEFAULT", debug=False ):
"""
Description:
Sends a single flow to the specified device. This function exists
so you can bypass the addFLow driver and send your own custom flow.
Required:
* The flow in json
* the device id to add the flow to
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE
if error on requests;
Returns None for exceptions
NOTE:
The ip and port option are for the requests input's ip and port
of the ONOS node
"""
try:
if debug:
main.log.debug( self.name + ": Adding flow: " + self.pprint( flowJson ) )
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
url = "/flows/" + deviceId
response = self.send( method="POST",
url=url, ip = ip, port = port,
data=json.dumps( flowJson ) )
if response:
if "201" in str( response[ 0 ] ):
main.log.info( self.name + ": Successfully POST flow" +
"in device: " + str( deviceId ) )
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except NotImplementedError as e:
raise e # Inform the caller
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def addFlow( self,
deviceId,
appId=0,
ingressPort="",
egressPort="",
ethType="",
ethSrc="",
ethDst="",
vlan="",
ipProto="",
ipSrc=(),
ipDst=(),
tcpSrc="",
tcpDst="",
udpDst="",
udpSrc="",
mpls="",
priority=100,
groupId="",
ip="DEFAULT",
port="DEFAULT",
debug=False ):
"""
Description:
Creates a single flow in the specified device
Required:
* deviceId: id of the device
Optional:
* ingressPort: port ingress device
* egressPort: port of egress device
* ethType: specify ethType
* ethSrc: specify ethSrc ( i.e. src mac addr )
* ethDst: specify ethDst ( i.e. dst mac addr )
* ipProto: specify ip protocol
* ipSrc: specify ip source address with mask eg. ip#/24
as a tuple (type, ip#)
* ipDst: specify ip destination address eg. ip#/24
as a tuple (type, ip#)
* tcpSrc: specify tcp source port
* tcpDst: specify tcp destination port
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE
if error on requests;
Returns None for exceptions
NOTE:
The ip and port option are for the requests input's ip and port
of the ONOS node
"""
try:
flowJson = { "priority": priority,
"isPermanent": "true",
"timeout": 0,
"deviceId": deviceId,
"treatment": { "instructions": [] },
"selector": { "criteria": [] }}
if appId:
flowJson[ "appId" ] = appId
if groupId:
flowJson[ 'treatment' ][ 'instructions' ].append( {
"type": "GROUP",
"groupId": groupId } )
if egressPort:
flowJson[ 'treatment' ][ 'instructions' ].append( {
"type": "OUTPUT",
"port": egressPort } )
if ingressPort:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "IN_PORT",
"port": ingressPort } )
if ethType:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "ETH_TYPE",
"ethType": ethType } )
if ethSrc:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "ETH_SRC",
"mac": ethSrc } )
if ethDst:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "ETH_DST",
"mac": ethDst } )
if vlan:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "VLAN_VID",
"vlanId": vlan } )
if mpls:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "MPLS_LABEL",
"label": mpls } )
if ipSrc:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": ipSrc[ 0 ],
"ip": ipSrc[ 1 ] } )
if ipDst:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": ipDst[ 0 ],
"ip": ipDst[ 1 ] } )
if tcpSrc:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "TCP_SRC",
"tcpPort": tcpSrc } )
if tcpDst:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "TCP_DST",
"tcpPort": tcpDst } )
if udpSrc:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "UDP_SRC",
"udpPort": udpSrc } )
if udpDst:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "UDP_DST",
"udpPort": udpDst } )
if ipProto:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "IP_PROTO",
"protocol": ipProto } )
return self.sendFlow( deviceId=deviceId, flowJson=flowJson, debug=debug, ip=ip, port=port )
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def removeFlow( self, deviceId, flowId,
ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Remove specific device flow
Required:
str deviceId - id of the device
str flowId - id of the flow
Return:
Returns main.TRUE if successfully deletes flows, otherwise
Returns main.FALSE, Returns None on error
"""
try:
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
# NOTE: REST url requires the intent id to be in decimal form
query = "/" + str( deviceId ) + "/" + str( int( flowId ) )
response = self.send( method="DELETE",
url="/flows" + query, ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def checkFlowsState( self , ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Check if all the current flows are in ADDED state
Return:
returnValue - Returns main.TRUE only if all flows are in
return main.FALSE otherwise;
Returns None for exception
"""
try:
tempFlows = json.loads( self.flows( ip=ip, port=port ) )
returnValue = main.TRUE
for flow in tempFlows:
if flow.get( 'state' ) != 'ADDED':
main.log.info( self.name + ": flow Id: " +
str( flow.get( 'groupId' ) ) +
" | state:" +
str( flow.get( 'state' ) ) )
returnValue = main.FALSE
return returnValue
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def getNetCfg( self, ip="DEFAULT", port="DEFAULT",
subjectClass=None, subjectKey=None, configKey=None ):
"""
Description:
Get a json object with the ONOS network configurations
Returns:
A json object containing the network configuration in
ONOS; Returns main.FALSE if error on requests;
Returns None for exception
"""
try:
output = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
url = "/network/configuration"
if subjectClass:
url += "/" + self.checkRegex( subjectClass )
if subjectKey:
url += "/" + self.checkRegex( subjectKey )
if configKey:
url += "/" + self.checkRegex( configKey )
url = requests.utils.quote( url, safe="%/" )
response = self.send( url=url, ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
output = response[ 1 ]
a = json.loads( output )
b = json.dumps( a )
return b
elif response[ 0 ] == 404:
main.log.error( "Requested configuration doesn't exist: " +
( response[ 0 ], response[ 1 ] ) )
return {}
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def checkRegex( self, inputStr ):
res = inputStr.replace('/', '%2f')
return res
def setNetCfg( self, cfgJson, ip="DEFAULT", port="DEFAULT",
subjectClass=None, subjectKey=None, configKey=None ):
"""
Description:
Set a json object with the ONOS network configurations
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE
if error on requests;
Returns None for exceptions
"""
try:
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
url = "/network/configuration"
if subjectClass:
url += "/" + self.checkRegex( subjectClass )
if subjectKey:
url += "/" + self.checkRegex( subjectKey )
if configKey:
url += "/" + self.checkRegex( configKey )
url = requests.utils.quote( url, safe="%/" )
response = self.send( method="POST",
url=url, ip = ip, port = port,
data=json.dumps( cfgJson ) )
if response:
if 200 <= response[ 0 ] <= 299:
main.log.info( self.name + ": Successfully POST cfg" )
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def removeNetCfg( self, ip="DEFAULT", port="DEFAULT",
subjectClass=None, subjectKey=None, configKey=None ):
"""
Description:
Remove a json object from the ONOS network configurations
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE
if error on requests;
Returns None for exceptions
"""
try:
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
url = "/network/configuration"
if subjectClass:
url += "/" + subjectClass
if subjectKey:
url += "/" + subjectKey
if configKey:
url += "/" + configKey
response = self.send( method="DELETE",
url=url, ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
main.log.info( self.name + ": Successfully delete cfg" )
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def getXconnect( self, ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Get xconnects
Returns:
Return xconnects json object
Returns None for exceptions
"""
try:
base = "/onos/segmentrouting"
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
url = "/xconnect"
response = self.send( method="GET",
base=base,
url=url, ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
main.log.info( self.name + ": Successfully POST cfg" )
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def setXconnect( self, deviceId, vlanId, port1, port2, ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Set xconnects
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE
if error on requests;
Returns None for exceptions
"""
try:
cfgJson = json.loads( '{"deviceId": "%s", "vlanId": "%s", "endpoints":[%s,%s]}' %
( deviceId, vlanId, port1, port2 ) )
response = self.setXconnectJson( cfgJson, ip=ip, port=port )
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def setXconnectJson( self, cfgJson, ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Set xconnects
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE
if error on requests;
Returns None for exceptions
"""
try:
base = "/onos/segmentrouting"
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
url = "/xconnect"
response = self.send( method="POST",
base=base,
url=url, ip = ip, port = port,
data=json.dumps( cfgJson ) )
if response:
if 200 <= response[ 0 ] <= 299:
main.log.info( self.name + ": Successfully POST cfg" )
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def deleteXconnect( self, cfgJson, ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Remove xconnects
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE
if error on requests;
Returns None for exceptions
"""
try:
base = "/onos/segmentrouting"
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
url = "/xconnect"
response = self.send( method="DELETE",
base=base,
url=url, ip = ip, port = port,
data=json.dumps( cfgJson ) )
if response:
if 200 <= response[ 0 ] <= 299:
main.log.info( self.name + ": Successfully POST cfg" )
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def createFlowBatch( self,
numSw = 1,
swIndex = 1,
batchSize = 1,
batchIndex = 1,
deviceIdpreFix = "of:",
appId=0,
deviceID="",
ingressPort="",
egressPort="",
ethType="",
ethSrc="",
ethDst="",
vlan="",
ipProto="",
ipSrc=(),
ipDst=(),
tcpSrc="",
tcpDst="",
udpDst="",
udpSrc="",
mpls="",
ip="DEFAULT",
port="DEFAULT",
debug=False ):
"""
Description:
Creates batches of MAC-rule flows for POST.
Predefined MAC: 2 MS Hex digit for iterating devices
Next 5 Hex digit for iterating batch numbers
Next 5 Hex digit for iterating flows within a batch
Required:
* deviceId: id of the device
Optional:
* ingressPort: port ingress device
* egressPort: port of egress device
* ethType: specify ethType
* ethSrc: specify ethSrc ( i.e. src mac addr )
* ethDst: specify ethDst ( i.e. dst mac addr )
* ipProto: specify ip protocol
* ipSrc: specify ip source address with mask eg. ip#/24
as a tuple (type, ip#)
* ipDst: specify ip destination address eg. ip#/24
as a tuple (type, ip#)
* tcpSrc: specify tcp source port
* tcpDst: specify tcp destination port
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE
if error on requests;
Returns None for exceptions
NOTE:
The ip and port option are for the requests input's ip and port
of the ONOS node
"""
flowJsonList = []
flowJsonBatch = { "flows": flowJsonList }
dev = swIndex
for fl in range( 1, batchSize + 1 ):
flowJson = { "priority": 100,
"deviceId": "",
"isPermanent": "true",
"timeout": 0,
"treatment": { "instructions": [] },
"selector": { "criteria": [] }}
# main.log.info("fl: " + str(fl))
if dev <= numSw:
deviceId = deviceIdpreFix + "{0:0{1}x}".format( dev, 16 )
# print deviceId
flowJson[ 'deviceId' ] = deviceId
dev += 1
else:
dev = 1
deviceId = deviceIdpreFix + "{0:0{1}x}".format( dev, 16 )
# print deviceId
flowJson[ 'deviceId' ] = deviceId
dev += 1
# ethSrc starts with "0"; ethDst starts with "1"
# 2 Hex digit of device number; 5 digits of batch index number; 5 digits of batch size
ethS = "%02X" % int( "0" + "{0:0{1}b}".format( dev, 7 ), 2 ) + \
"{0:0{1}x}".format( batchIndex, 5 ) + "{0:0{1}x}".format( fl, 5 )
ethSrc = ':'.join( ethS[ i: i+2 ] for i in range( 0, len( ethS ), 2 ) )
ethD = "%02X" % int( "1" + "{0:0{1}b}".format( dev, 7 ), 2 ) + \
"{0:0{1}x}".format( batchIndex, 5 ) + "{0:0{1}x}".format( fl, 5 )
ethDst = ':'.join( ethD[ i: i+2 ] for i in range( 0, len( ethD ), 2 ) )
if appId:
flowJson[ "appId" ] = appId
if egressPort:
flowJson[ 'treatment' ][ 'instructions' ].append( {
"type": "OUTPUT",
"port": egressPort } )
if ingressPort:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "IN_PORT",
"port": ingressPort } )
if ethType:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "ETH_TYPE",
"ethType": ethType } )
if ethSrc:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "ETH_SRC",
"mac": ethSrc } )
if ethDst:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "ETH_DST",
"mac": ethDst } )
if vlan:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "VLAN_VID",
"vlanId": vlan } )
if mpls:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "MPLS_LABEL",
"label": mpls } )
if ipSrc:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": ipSrc[ 0 ],
"ip": ipSrc[ 1 ] } )
if ipDst:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": ipDst[ 0 ],
"ip": ipDst[ 1 ] } )
if tcpSrc:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "TCP_SRC",
"tcpPort": tcpSrc } )
if tcpDst:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "TCP_DST",
"tcpPort": tcpDst } )
if udpSrc:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "UDP_SRC",
"udpPort": udpSrc } )
if udpDst:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "UDP_DST",
"udpPort": udpDst } )
if ipProto:
flowJson[ 'selector' ][ 'criteria' ].append( {
"type": "IP_PROTO",
"protocol": ipProto } )
flowJsonList.append( flowJson )
main.log.info( self.name + ": Number of flows in batch: " + str( len( flowJsonList ) ) )
flowJsonBatch[ 'flows' ] = flowJsonList
return flowJsonBatch
def sendFlowBatch( self, batch={}, ip="DEFAULT", port="DEFAULT", debug=False ):
"""
Description:
Sends a single flow batch through /flows REST API.
Required:
* The batch of flows
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE
if error on requests;
Returns None for exceptions
NOTE:
The ip and port option are for the requests input's ip and port
of the ONOS node
"""
try:
if debug:
main.log.debug( self.name + ": Adding flow: " + self.pprint( batch ) )
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
url = "/flows/"
response = self.send( method="POST",
url=url, ip = ip, port = port,
data=json.dumps( batch ) )
# main.log.info("Post response is: ", str(response[0]))
if response[ 0 ] == 200:
main.log.info( self.name + ": Successfully POST flow batch" )
return main.TRUE, response
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE, response
except NotImplementedError as e:
raise e # Inform the caller
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None, None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def removeFlowBatch( self, batch={},
ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Remove a batch of flows
Required:
flow batch
Return:
Returns main.TRUE if successfully deletes flows, otherwise
Returns main.FALSE, Returns None on error
"""
try:
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
# NOTE: REST url requires the intent id to be in decimal form
response = self.send( method="DELETE",
url="/flows/", ip = ip, port = port,
data = json.dumps( batch ) )
if response:
if 200 <= response[ 0 ] <= 299:
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def getTopology( self, topologyOutput ):
"""
Definition:
Loads a json topology output
Return:
topology = current ONOS topology
"""
import json
try:
# either onos:topology or 'topology' will work in CLI
topology = json.loads( topologyOutput )
main.log.debug( topology )
return topology
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def checkStatus(
self,
numswitch,
numlink,
logLevel="info" ):
"""
Checks the number of switches & links that ONOS sees against the
supplied values. By default this will report to main.log, but the
log level can be specific.
Params: numswitch = expected number of switches
numlink = expected number of links
logLevel = level to log to.
Currently accepts 'info', 'warn' and 'report'
Returns: main.TRUE if the number of switches and links are correct,
main.FALSE if the number of switches and links is incorrect,
and main.ERROR otherwise
"""
try:
topology = self.getTopology( self.topology() )
# summary = self.summary()
if topology == {}:
return main.ERROR
output = ""
# Is the number of switches is what we expected
devices = topology.get( 'devices', False )
links = topology.get( 'links', False )
if devices is False or links is False:
return main.ERROR
switchCheck = ( int( devices ) == int( numswitch ) )
# Is the number of links is what we expected
linkCheck = ( int( links ) == int( numlink ) )
if switchCheck and linkCheck:
# We expected the correct numbers
output = output + "The number of links and switches match "\
+ "what was expected"
result = main.TRUE
else:
output = output + \
"The number of links and switches does not match " + \
"what was expected"
result = main.FALSE
output = output + "\n ONOS sees %i devices" % int( devices )
output = output + " (%i expected) " % int( numswitch )
output = output + "and %i links " % int( links )
output = output + "(%i expected)" % int( numlink )
if logLevel == "report":
main.log.report( output )
elif logLevel == "warn":
main.log.warn( output )
else:
main.log.info( output )
return result
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def addGroup( self, deviceId, groupType, bucketList, appCookie, groupId,
ip="DEFAULT", port="DEFAULT", debug=False ):
"""
Description:
Creates a single Group for the specified device.
Required:
* deviceId: id of the device
* type: Type of the Group
* bucketList: Buckets to be added to the group
* appCookie: Cookie for the Group
* groupId: Id of the Group
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE
if error on requests;
Returns None for exceptions
Note:
The ip and port option are for the requests input's ip and port
of the ONOS node
"""
try:
groupJson = { "type": groupType,
"appCookie": appCookie,
"groupId": groupId,
"buckets": bucketList
}
return self.sendGroup( deviceId=deviceId, groupJson=groupJson, ip="DEFAULT", port="DEFAULT", debug=False )
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def sendGroup( self, deviceId, groupJson, ip="DEFAULT", port="DEFAULT", debug=False ):
"""
Description:
Sends a single group to the specified device.
Required:
* deviceId: id of the device
* groupJson: the group in json
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE
if error on requests;
Returns None for exceptions
NOTE:
The ip and port option are for the requests input's ip and port
of the ONOS node
"""
try:
if debug:
main.log.debug( self.name + ": Adding group: " + self.pprint( groupJson ) )
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
url = "/groups/" + deviceId
response = self.send( method="POST",
url=url, ip = ip, port = port,
data=json.dumps( groupJson ) )
if response:
if "201" in str( response[ 0 ] ):
main.log.info( self.name + ": Successfully POST group " +
"in device: " + str( deviceId ) )
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except NotImplementedError as e:
raise e # Inform the caller
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def getGroups( self, deviceId=None, appCookie=None, ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Get all the groups or get a specific group by giving the
deviceId and appCookie
Optional:
* deviceId: id of the Device
* appCookie: Cookie of the Group
Returns:
Returns Groups for successful requests; Returns main.FALSE
if error on requests;
Returns None for exceptions
NOTE:
The ip and port option are for the requests input's ip and port
of the ONOS node
"""
try:
output = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
url = "/groups"
if deviceId:
url += "/" + deviceId
if appCookie:
url += "/" + appCookie
response = self.send( url=url, ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
output = response[ 1 ]
groupsJson = json.loads( output ).get( 'groups' )
assert groupsJson is not None, "Error parsing json object"
groups = json.dumps( groupsJson )
return groups
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, AssertionError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def removeGroup( self, deviceId, appCookie,
ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Removes specific device group
Required:
* deviceId: id of the Device
* appCookie: Cookie of the Group
Returns:
Returns main.TRUE for successful requests; Returns main.FALSE
if error on requests;
Returns None for exceptions
NOTE:
The ip and port option are for the requests input's ip and port
of the ONOS node
"""
try:
response = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
query = "/" + str( deviceId ) + "/" + str( appCookie )
response = self.send( method="DELETE",
url="/groups" + query, ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
return main.TRUE
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()
def portstats( self, ip="DEFAULT", port="DEFAULT" ):
"""
Description:
Gets the portstats for each port in ONOS
Returns:
A list of dicts containing device id and a list of dicts containing the
port statistics for each port.
Returns main.FALSE if error on request;
Returns None for exception
"""
try:
output = None
if ip == "DEFAULT":
main.log.warn( self.name + ": No ip given, reverting to ip from topo file" )
ip = self.ip_address
if port == "DEFAULT":
main.log.warn( self.name + ": No port given, reverting to port " +
"from topo file" )
port = self.port
response = self.send( url="/statistics/ports", ip = ip, port = port )
if response:
if 200 <= response[ 0 ] <= 299:
output = response[ 1 ]
a = json.loads( output ).get( 'statistics' )
assert a is not None, "Error parsing json object"
b = json.dumps( a )
return b
else:
main.log.error( "Error with REST request, response was: %s: %s" %
( response[ 0 ], response[ 1 ] ) )
return main.FALSE
except ( AttributeError, AssertionError, TypeError ):
main.log.exception( self.name + ": Object not as expected" )
return None
except Exception:
main.log.exception( self.name + ": Uncaught exception!" )
main.cleanAndExit()