Initial Sketch of REST driver for ONOS

Intents function - returns all intents
Intent fucntion - returns a single intent

Change-Id: I718bedfd60603d0930f5c69a6664c92bbf8a74ba
diff --git a/TestON/drivers/common/api/controller/onosrestdriver.py b/TestON/drivers/common/api/controller/onosrestdriver.py
new file mode 100644
index 0000000..a38b073
--- /dev/null
+++ b/TestON/drivers/common/api/controller/onosrestdriver.py
@@ -0,0 +1,199 @@
+#!/usr/bin/env python
+"""
+Created on 07-08-2015
+
+    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 sys
+
+sys.path.append( "../" )
+from drivers.common.api.controllerdriver import Controller
+
+
+class OnosRestDriver( Controller ):
+
+    def __init__( self ):
+        super( Controller, self ).__init__()
+        self.ip_address = "localhost"
+        self.port = "8080"
+        self.user_name = "user"
+        self.password = "CHANGEME"
+
+    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 ) ) != 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( "Invalid host name," +
+                           "defaulting to 'localhost' instead" )
+            self.ip_address = 'localhost'
+        except Exception as inst:
+            main.log.error( "Uncaught exception: " + str( inst ) )
+
+        self.handle = super( OnosRestDriver, self ).connect()
+        return self.handle
+
+    def send( self, ip, port, url, base="/onos/v1", method="GET",
+              query=None, data=None ):
+        """
+        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 params: 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
+        try:
+            path = "http://" + str( ip ) + ":" + str( port ) + base + url
+            main.log.info( "Sending request " + path + " using " +
+                           method.upper() + " method." )
+            response = requests.request( method.upper(),
+                                         path,
+                                         params=query,
+                                         data=data )
+            return ( response.status_code, response.text.encode( 'utf8' ) )
+        except requests.exceptions:
+            main.log.exception( "Error sending request." )
+            return None
+        except Exception as e:
+            main.log.exception( e )
+            return None
+        # FIXME: add other exceptions  
+
+    def intents( self, ip="DEFAULT", port="DEFAULT" ):
+        """
+        """
+        try:
+            output = None
+            if ip == "DEFAULT":
+                main.log.warn( "No ip given, reverting to ip from topo file" )
+                ip = self.ip_address
+            if port == "DEFAULT":
+                main.log.warn( "No port given, reverting to port from topo file" )
+                port = self.port
+            response = self.send( ip, port, url="/intents" )
+            if response:
+                main.log.debug( response )
+                # We can do some verification on the return code
+                # NOTE: Not all requests return 200 on success, usually it will be a 2XX code
+                #       3XX is a redirction, which may be given on a post to show where the new resource can be found
+                #       4XX is usually a bad request on our side
+                #       5XX will usually mean an ONOS bug
+                if response[0] == 200:
+                    main.log.debug("all ok")
+                else:
+                    main.log.error( "Error with REST request, response was: " +
+                                    str( response ) )
+                    # return main.FALSE
+                output = response[1]
+
+            # NOTE: The output is slightly differen from the cli.
+            # if data = cli's json output
+            # then rest output is dict( 'intents' : data )
+
+            # We have some options here:
+            # 1) we can return the whole thing as a string as we do with cli,
+            #    then change our tests to correctly parse the data
+            #
+            # 2) We could return just the data of the response, but we would
+            #    have to decide if we return:
+            # 2a) the data as a dictionary or
+            # 2b) as a json string.
+            #
+            # If we return the dict, we probably want to change the
+            #    cli drivers to match.
+
+            # Ideally, we would be able to switch between using the api driver
+            # and the cli driver by simply changing the .topo file. I don't
+            # know if we will achive that right away, but we should try to plan
+            # for that.
+
+
+            # 1)
+            '''
+            return output
+            '''
+
+            # 2a)
+            '''
+            import json
+            a = json.loads( output )
+            return a.get( 'intents' )
+            '''
+
+            # 2b)
+            import json
+            a = json.loads( output ).get( 'intents' )
+            b = json.dumps(a)
+            main.log.debug( b )
+            return b
+        except Exception as e:
+            main.log.exception( e )
+            return None
+
+    def intent( self, appId, intentId,  ip="DEFAULT", port="DEFAULT" ):
+        """
+        Returns a single intent in dictionary form
+        """
+        try:
+            output = None
+            if ip == "DEFAULT":
+                main.log.warn( "No ip given, reverting to ip from topo file" )
+                ip = self.ip_address
+            if port == "DEFAULT":
+                main.log.warn( "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( ip, port, url="/intents" + query )
+            if response:
+                main.log.debug( response )
+                # We can do some verification on the return code
+                # NOTE: Not all requests return 200 on success, usually it will be a 2XX code
+                #       3XX is a redirction, which may be given on a post to show where the new resource can be found
+                #       4XX is usually a bad request on our side
+                #       5XX will usually mean an ONOS bug
+                if response[0] == 200:
+                    main.log.debug("all ok")
+                else:
+                    main.log.error( "Error with REST request, response was: " +
+                                    str( response ) )
+                    # return main.FALSE
+                output = response[1]
+            a = json.loads( output )
+            return a
+        except Exception as e:
+            main.log.exception( e )
+            return None