Merge "added scp from ONOScli"
diff --git a/TestON/core/graph.py b/TestON/core/graph.py
new file mode 100644
index 0000000..c893adc
--- /dev/null
+++ b/TestON/core/graph.py
@@ -0,0 +1,317 @@
+#!/usr/bin/env python
+import time
+import random
+
+class Graph:
+    """
+    Graph class provides implementations of graph algorithms.
+    The functions currently supported include:
+    - Comparing two graphs with specified attributes for vertices and edges
+    - Getting DFI (Depth First Index) and back edges during a DFS
+    - Chain decomposition of a graph
+    - Finding (non-)cut-edges and vertices
+    """
+
+    def __init__( self ):
+        # We use a dictionary to store all information about the graph
+        self.graphDict = {}
+        # Depth-first index of each vertex
+        self.DFI = {}
+        self.currentDFI = 0
+        # Parent vertex (and edge to that vertex) of each vertex in depth-first search tree
+        self.parentVertexInDFS = {}
+        self.parentEdgeInDFS = {}
+        # Back edges of the graph generated during DFS
+        self.backEdges = {}
+        # All chains in chain decomposition algorithm
+        self.chains = []
+
+    def update( self, graphDict ):
+        """
+        Update the graph data. The current graph dictionary will be replaced by the
+        new one.
+        graphDict is in a dictionary which maps each vertex to a list of attributes.
+        An example of graphDict:
+        { vertex1: { 'edges': ..., 'name': ..., 'protocol': ... },
+          vertex2: { 'edges': ..., 'name': ..., 'protocol': ... } }
+        Each vertex should at least have an 'edges' attribute which describes the
+        adjacency information. The value of 'edges' attribute is also represented by
+        a dictionary, which maps each edge (identified by the neighbor vertex) to a
+        list of attributes.
+        An example of the edges dictionary:
+        'edges': { vertex2: { 'port': ..., 'type': ... },
+                   vertex3: { 'port': ..., 'type': ... } }
+        """
+        self.graphDict = graphDict
+        return main.TRUE
+
+    def compareGraphs( self, graphDictA, graphDictB, vertexAttributes=['edges'], edgeAttributes=['port'] ):
+        """
+        Compare two graphs.
+        By default only the adjacency relationship, i.e. 'port' attribute in
+        'edges' attribute for each vertex, is compared, To get other attributes
+        included, attribute name needs to be specified in the args, e.g.
+        vertexAttributes=[ 'edges', 'protocol' ] or
+        edgeAttributes=[ 'port', 'type' ]
+        Return main.TRUE if two graphs are equal, otherwise main.FALSE
+        """
+        try:
+            result = main.TRUE
+            for vertex in set( graphDictA ).difference( graphDictB ):
+                result = main.FALSE
+                main.log.warn( "Graph: graph B: vertex {} not found".format( vertex ) )
+            for vertex in set( graphDictB ).difference( graphDictA ):
+                result = main.FALSE
+                main.log.warn( "Graph: graph A: vertex {} not found".format( vertex ) )
+            for vertex in set( graphDictA ).intersection( graphDictB ):
+                for vertexAttribute in vertexAttributes:
+                    attributeFound = True
+                    if vertexAttribute not in graphDictA[ vertex ]:
+                        main.log.warn( "Graph: graph A -> vertex {}: attribute {} not found".format( vertex, vertexAttribute ) )
+                        attributeFound = False
+                    if vertexAttribute not in graphDictB[ vertex ]:
+                        attributeFound = False
+                        main.log.warn( "Graph: graph B -> vertex {}: attribute {} not found".format( vertex, vertexAttribute ) )
+                    if not attributeFound:
+                        result = main.FALSE
+                        continue
+                    else:
+                        # Compare two attributes
+                        attributeValueA = graphDictA[ vertex ][ vertexAttribute ]
+                        attributeValueB = graphDictB[ vertex ][ vertexAttribute ]
+                        # FIXME: the comparison may not work for (sub)attribute values that are of list type
+                        # For attributes except for 'edges', we just rely on '==' for comparison
+                        if not vertexAttribute == 'edges':
+                            if not attributeValueA == attributeValueB:
+                                result = main.FALSE
+                                main.log.warn( "Graph: vertex {}: {} does not match: {} and {}".format( vertex,
+                                                                                                        vertexAttribute,
+                                                                                                        attributeValueA,
+                                                                                                        attributeValueB ) )
+                        # The structure of 'edges' is similar to that of graphs, so we use the same method for comparison
+                        else:
+                            edgeDictA = attributeValueA
+                            edgeDictB = attributeValueB
+                            for neighbor in set( edgeDictA ).difference( edgeDictB ):
+                                result = main.FALSE
+                                main.log.warn( "Graph: graph B -> vertex {}: neighbor {} not found".format( vertex, neighbor ) )
+                            for neighbor in set( edgeDictB ).difference( edgeDictA ):
+                                result = main.FALSE
+                                main.log.warn( "Graph: graph A -> vertex {}: neighbor {} not found".format( vertex, neighbor ) )
+                            for neighbor in set( edgeDictA ).intersection( edgeDictB ):
+                                for edgeAttribute in edgeAttributes:
+                                    attributeFound = True
+                                    if edgeAttribute not in edgeDictA[ neighbor ]:
+                                        attributeFound = False
+                                        main.log.warn( "Graph: graph A -> vertex {} -> neighbor {}: attribute {} not found".format( vertex,
+                                                                                                                                    neighbor,
+                                                                                                                                    edgeAttribute ) )
+                                    if edgeAttribute not in edgeDictB[ neighbor ]:
+                                        attributeFound = False
+                                        main.log.warn( "Graph: graph B -> vertex {} -> neighbor {}: attribute {} not found".format( vertex,
+                                                                                                                                    neighbor,
+                                                                                                                                    edgeAttribute ) )
+                                    if not attributeFound:
+                                        result = main.FALSE
+                                        continue
+                                    else:
+                                        # Compare two attributes
+                                        attributeValueA = edgeDictA[ neighbor ][ edgeAttribute ]
+                                        attributeValueB = edgeDictB[ neighbor ][ edgeAttribute ]
+                                        if not attributeValueA == attributeValueB:
+                                            result = main.FALSE
+                                            main.log.warn( "Graph: vertex {} -> neighbor {}: {} does not match: {} and {}".format( vertex,
+                                                                                                                                   neighbor,
+                                                                                                                                   edgeAttribute,
+                                                                                                                                   attributeValueA,
+                                                                                                                                   attributeValueB ) )
+            if not result:
+                main.log.debug( "Graph: graphDictA: {}".format( graphDictA ) )
+                main.log.debug( "Graph: graphDictB: {}".format( graphDictB ) )
+            return result
+        except TypeError:
+            main.log.exception( "Graph: TypeError exception found" )
+            return main.ERROR
+        except KeyError:
+            main.log.exception( "Graph: KeyError exception found" )
+            return main.ERROR
+        except Exception:
+            main.log.exception( "Graph: Uncaught exception" )
+            return main.ERROR
+
+    def getNonCutEdges( self ):
+        """
+        Get a list of non-cut-edges (non-bridges).
+        The definition of a cut-edge (bridge) is: the deletion of a cut-edge will
+        increase the number of connected component of a graph.
+        The function is realized by impelementing Schmidt's algorithm based on
+        chain decomposition.
+        Returns a list of edges, e.g.
+        [ [ vertex1, vertex2 ], [ vertex2, vertex3 ] ]
+        """
+        try:
+            if not self.depthFirstSearch():
+                return None
+            if not self.findChains():
+                return None
+            nonCutEdges = []
+            for chain in self.chains:
+                for edge in chain:
+                    nonCutEdges.append( edge )
+            main.log.debug( 'Non-cut-edges: {}'.format( nonCutEdges ) )
+            return nonCutEdges
+        except Exception:
+            main.log.exception( "Graph: Uncaught exception" )
+            return None
+
+    def getNonCutVertices( self ):
+        """
+        Get a list of non-cut-vertices.
+        The definition of a cut-vertex is: the deletion of a cut-vertex will
+        increase the number of connected component of a graph.
+        The function is realized by impelementing Schmidt's algorithm based on
+        chain decomposition.
+        Returns a list of vertices, e.g. [ vertex1, vertex2, vertex3 ]
+        """
+        try:
+            nonCutEdges = self.getNonCutEdges()
+            # find all cycle chains
+            cycleChains = []
+            for chain in self.chains:
+                # if the source vertex of the first chain equals to the destination vertex of the last
+                # chain, the chain is a cycle chain
+                if chain[ 0 ][ 0 ] == chain[ -1 ][ 1 ]:
+                    cycleChains.append( chain )
+            main.log.debug( 'Cycle chains: {}'.format( cycleChains ) )
+            # Get a set of vertices which are the first vertices of a cycle chain (excluding the first
+            # cycle chain), and these vertices are a subset of all cut-vertices
+            subsetOfCutVertices = []
+            if len( cycleChains ) > 1:
+                for cycleChain in cycleChains[ 1: ]:
+                    subsetOfCutVertices.append( cycleChain[ 0 ][ 0 ] )
+            main.log.debug( 'Subset of cut vertices: {}'.format( subsetOfCutVertices ) )
+            nonCutVertices = []
+            assert nonCutEdges != None
+            for vertex in self.graphDict.keys():
+                if vertex in subsetOfCutVertices:
+                    continue
+                vertexIsNonCut = True
+                for neighbor in self.graphDict[ vertex ][ 'edges' ].keys():
+                    edge = [ vertex, neighbor ]
+                    backwardEdge = [ neighbor, vertex ]
+                    if not edge in nonCutEdges and not backwardEdge in nonCutEdges:
+                        vertexIsNonCut = False
+                        break
+                if vertexIsNonCut:
+                    nonCutVertices.append( vertex )
+            main.log.debug( 'Non-cut-vertices: {}'.format( nonCutVertices ) )
+            return nonCutVertices
+        except KeyError:
+            main.log.exception( "Graph: KeyError exception found" )
+            return None
+        except AssertionError:
+            main.log.exception( "Graph: AssertionError exception found" )
+            return None
+        except Exception:
+            main.log.exception( "Graph: Uncaught exception" )
+            return None
+
+    def depthFirstSearch( self ):
+        """
+        This function runs a depth-first search and gets DFI of each vertex as well
+        as generates the back edges
+        """
+        try:
+            assert self.graphDict != None and len( self.graphDict ) != 0
+            for vertex in self.graphDict.keys():
+                self.DFI[ vertex ] = -1
+                self.parentVertexInDFS[ vertex ] = ''
+                self.parentEdgeInDFS[ vertex ] = None
+            firstVertex = self.graphDict.keys()[ 0 ]
+            self.currentDFI = 0
+            self.backEdges = {}
+            if not self.depthFirstSearchRecursive( firstVertex ):
+                return main.ERROR
+            return main.TRUE
+        except KeyError:
+            main.log.exception( "Graph: KeyError exception found" )
+            return main.ERROR
+        except AssertionError:
+            main.log.exception( "Graph: AssertionError exception found" )
+            return main.ERROR
+        except Exception:
+            main.log.exception( "Graph: Uncaught exception" )
+            return main.ERROR
+
+    def depthFirstSearchRecursive( self, vertex ):
+        """
+        Recursive function for depth-first search
+        """
+        try:
+            self.DFI[ vertex ] = self.currentDFI
+            self.currentDFI += 1
+            for neighbor in self.graphDict[ vertex ][ 'edges' ].keys():
+                edge = [ vertex, neighbor ]
+                backwardEdge = [ neighbor, vertex ]
+                if neighbor == self.parentVertexInDFS[ vertex ]:
+                    continue
+                elif self.DFI[ neighbor ] == -1:
+                    self.parentVertexInDFS[ neighbor ] = vertex
+                    self.parentEdgeInDFS[ neighbor ] = backwardEdge
+                    if not self.depthFirstSearchRecursive( neighbor ):
+                        return main.ERROR
+                else:
+                    key = self.DFI[ neighbor ]
+                    if key in self.backEdges.keys():
+                        if not edge in self.backEdges[ key ] and\
+                        not backwardEdge in self.backEdges[ key ]:
+                            self.backEdges[ key ].append( backwardEdge )
+                    else:
+                        tempKey = self.DFI[ vertex ]
+                        if tempKey in self.backEdges.keys():
+                            if not edge in self.backEdges[ tempKey ] and\
+                            not backwardEdge in self.backEdges[ tempKey ]:
+                                self.backEdges[ key ] = [ backwardEdge ]
+                        else:
+                            self.backEdges[ key ] = [ backwardEdge ]
+            return main.TRUE
+        except KeyError:
+            main.log.exception( "Graph: KeyError exception found" )
+            return main.ERROR
+        except Exception:
+            main.log.exception( "Graph: Uncaught exception" )
+            return main.ERROR
+
+    def findChains( self ):
+        """
+        This function finds all the chains in chain-decomposition algorithm
+        """
+        keyList = self.backEdges.keys()
+        keyList.sort()
+        vertexIsVisited = {}
+        self.chains = []
+        for vertex in self.graphDict.keys():
+            vertexIsVisited[ vertex ] = False
+        try:
+            for key in keyList:
+                backEdgeList = self.backEdges[ key ]
+                for edge in backEdgeList:
+                    chain = []
+                    currentEdge = edge
+                    sourceVertex = edge[ 0 ]
+                    while True:
+                        currentVertex = currentEdge[ 0 ]
+                        nextVertex = currentEdge[ 1 ]
+                        vertexIsVisited[ currentVertex ] = True
+                        chain.append( currentEdge )
+                        if nextVertex == sourceVertex or vertexIsVisited[ nextVertex ] == True:
+                            break
+                        currentEdge = self.parentEdgeInDFS[ nextVertex ]
+                    self.chains.append( chain )
+            return main.TRUE
+        except KeyError:
+            main.log.exception( "Graph: KeyError exception found" )
+            return main.ERROR
+        except Exception:
+            main.log.exception( "Graph: Uncaught exception" )
+            return main.ERROR
diff --git a/TestON/drivers/common/cli/emulator/mininetclidriver.py b/TestON/drivers/common/cli/emulator/mininetclidriver.py
index 3eb5577..b38e257 100644
--- a/TestON/drivers/common/cli/emulator/mininetclidriver.py
+++ b/TestON/drivers/common/cli/emulator/mininetclidriver.py
@@ -2321,6 +2321,8 @@
         try:
             main.log.info( "Getting flows from Mininet" )
             flows = self.getFlowTable( sw, version, debug )
+            if flows == None:
+                return main.ERROR
 
             if debug: print "flow ids:\n{}\n\n".format(flowId)
 
diff --git a/TestON/drivers/common/cli/onosclidriver.py b/TestON/drivers/common/cli/onosclidriver.py
index 61c38b7..832fe29 100644
--- a/TestON/drivers/common/cli/onosclidriver.py
+++ b/TestON/drivers/common/cli/onosclidriver.py
@@ -2205,6 +2205,9 @@
             if returnValue == main.TRUE:
                 main.log.info( self.name + ": all intent IDs and states match that in ONOS" )
             return returnValue
+        except KeyError:
+            main.log.exception( self.name + ": KeyError exception found" )
+            return main.ERROR
         except ( TypeError, ValueError ):
             main.log.exception( "{}: Object not as expected: {!r}".format( self.name, intentsRaw ) )
             return main.ERROR
diff --git a/TestON/tests/FUNC/FUNCintent/FUNCintent.params b/TestON/tests/FUNC/FUNCintent/FUNCintent.params
index d0fcfa8..c201411 100644
--- a/TestON/tests/FUNC/FUNCintent/FUNCintent.params
+++ b/TestON/tests/FUNC/FUNCintent/FUNCintent.params
@@ -13,6 +13,7 @@
     # 16 - Balance ownership of switches
     # 17 - Activate Flow Objectives
     # 18 - Stop Mininet
+    # 19 - Copy karaf logs from ONOS nodes to TestON log directory
     # 1000 - Test host intents
     # 2000 - Test point intents
     # 3000 - Test single to multi point intents
@@ -20,7 +21,7 @@
     # 5000 - Test host mobility
     # 6000 - Test Multi Point intent End Point Failure
 
-    <testcases>1,[2,10,12,13,15,16,1000,2000,3000,4000,5000,6000,18]*2,[2,10,12,13,15,16,17,1000,2000,3000,4000,5000,6000,18]*2,[2,11,12,13,15,16,1000,2000,3000,4000,5000,6000,18]*2,[2,11,12,13,15,16,17,1000,2000,3000,4000,5000,6000,18]*2</testcases>
+    <testcases>1,[2,10,12,13,15,16,1000,2000,3000,4000,5000,6000,18,19]*2,[2,10,12,13,15,16,17,1000,2000,3000,4000,5000,6000,18,19]*2,[2,11,12,13,15,16,1000,2000,3000,4000,5000,6000,18,19]*2,[2,11,12,13,15,16,17,1000,2000,3000,4000,5000,6000,18,19]*2</testcases>
 
     <SCALE>
         <size>1,3,1,3,1,3,1,3</size>
diff --git a/TestON/tests/FUNC/FUNCintent/FUNCintent.py b/TestON/tests/FUNC/FUNCintent/FUNCintent.py
index e81b5fa..7dc6fb3 100644
--- a/TestON/tests/FUNC/FUNCintent/FUNCintent.py
+++ b/TestON/tests/FUNC/FUNCintent/FUNCintent.py
@@ -59,6 +59,7 @@
             main.scapyHostNames = main.params[ 'SCAPY' ][ 'HOSTNAMES' ].split( ',' )
             main.scapyHosts = []  # List of scapy hosts for iterating
             main.assertReturnString = ''  # Assembled assert return string
+            main.cycle = 0 # How many times FUNCintent has run through its tests
 
             main.ONOSip = main.ONOSbench.getOnosIps()
             print main.ONOSip
@@ -132,6 +133,8 @@
         - Connect to cli
         """
 
+        main.cycle += 1
+
         # main.scale[ 0 ] determines the current number of ONOS controller
         main.numCtrls = int( main.scale[ 0 ] )
         main.flowCompiler = "Flow Rules"
@@ -655,6 +658,28 @@
             main.cleanup()
             main.exit()
 
+    def CASE19( self, main ):
+        """
+            Copy the karaf.log files after each testcase cycle
+        """
+        main.log.report( "Copy karaf logs" )
+        main.case( "Copy karaf logs" )
+        main.caseExplanation = "Copying the karaf logs to preserve them through" +\
+                               "reinstalling ONOS"
+        main.step( "Copying karaf logs" )
+        i = 0
+        for cli in main.CLIs:
+            main.node = cli
+            ip = main.ONOSip[ i ]
+            main.node.ip_address = ip
+            main.ONOSbench.scp( main.node ,
+                                "/opt/onos/log/karaf.log",
+                                "/tmp/karaf.log",
+                                direction="from" )
+            main.ONOSbench.cpLogsToDir( "/tmp/karaf.log", main.logdir,
+                                        copyFileName=( "karaf.log.node{0}.cycle{1}".format( str( i + 1 ), str( main.cycle ) ) ) )
+            i += 1
+
     def CASE1000( self, main ):
         """
             Add host intents between 2 host:
diff --git a/TestON/tests/FUNC/FUNCintentRest/FUNCintentRest.params b/TestON/tests/FUNC/FUNCintentRest/FUNCintentRest.params
index b3d3df3..d91559d 100644
--- a/TestON/tests/FUNC/FUNCintentRest/FUNCintentRest.params
+++ b/TestON/tests/FUNC/FUNCintentRest/FUNCintentRest.params
@@ -13,13 +13,14 @@
     # 16 - Balance ownership of switches
     # 17 - Activate Flow Objectives
     # 18 - Stop Mininet
+    # 19 - Copy karaf logs from ONOS nodes to TestON log directory
     # 1000 - Test host intents
     # 2000 - Test point intents
     # 3000 - Test single to multi point intents
     # 4000 - Test multi to single point intents
     # 5000 - Test host mobility
 
-    <testcases>1,[2,10,12,13,15,16,1000,2000,5000,18]*2,[2,10,12,13,15,16,17,1000,2000,5000,18]*2,[2,11,12,13,15,16,1000,2000,5000,18]*2,[2,11,12,13,15,16,17,1000,2000,5000,18]*2</testcases>
+    <testcases>1,[2,10,12,13,15,16,1000,2000,5000,18,19]*2,[2,10,12,13,15,16,17,1000,2000,5000,18,19]*2,[2,11,12,13,15,16,1000,2000,5000,18,19]*2,[2,11,12,13,15,16,17,1000,2000,5000,18,19]*2</testcases>
 
     <SCALE>
         <size>1,3,1,3,1,3,1,3</size>
diff --git a/TestON/tests/FUNC/FUNCintentRest/FUNCintentRest.py b/TestON/tests/FUNC/FUNCintentRest/FUNCintentRest.py
index 60fc47f..ec39ceb 100644
--- a/TestON/tests/FUNC/FUNCintentRest/FUNCintentRest.py
+++ b/TestON/tests/FUNC/FUNCintentRest/FUNCintentRest.py
@@ -70,6 +70,7 @@
             main.scapyHostNames = main.params[ 'SCAPY' ][ 'HOSTNAMES' ].split( ',' )
             main.scapyHosts = []  # List of scapy hosts for iterating
             main.assertReturnString = ''  # Assembled assert return string
+            main.cycle = 0 # How many times FUNCintent has run through its tests
 
             main.ONOSip = main.ONOSbench.getOnosIps()
             print main.ONOSip
@@ -152,6 +153,8 @@
         - Connect to cli
         """
 
+        main.cycle += 1
+
         # main.scale[ 0 ] determines the current number of ONOS controller
         main.numCtrls = int( main.scale[ 0 ] )
         main.flowCompiler = "Flow Rules"
@@ -770,6 +773,28 @@
             main.cleanup()
             main.exit()
 
+    def CASE19( self, main ):
+        """
+            Copy the karaf.log files after each testcase cycle
+        """
+        main.log.report( "Copy karaf logs" )
+        main.case( "Copy karaf logs" )
+        main.caseExplanation = "Copying the karaf logs to preserve them through" +\
+                               "reinstalling ONOS"
+        main.step( "Copying karaf logs" )
+        i = 0
+        for cli in main.CLIs:
+            main.node = cli
+            ip = main.ONOSip[ i ]
+            main.node.ip_address = ip
+            main.ONOSbench.scp( main.node ,
+                                "/opt/onos/log/karaf.log",
+                                "/tmp/karaf.log",
+                                direction="from" )
+            main.ONOSbench.cpLogsToDir( "/tmp/karaf.log", main.logdir,
+                                        copyFileName=( "karaf.log.node{0}.cycle{1}".format( str( i + 1 ), str( main.cycle ) ) ) )
+            i += 1
+
     def CASE1000( self, main ):
         """
             Add host intents between 2 host:
diff --git a/TestON/tests/FUNC/FUNCnetconf/FUNCnetconf.params b/TestON/tests/FUNC/FUNCnetconf/FUNCnetconf.params
index fc296cc..f5339f2 100644
--- a/TestON/tests/FUNC/FUNCnetconf/FUNCnetconf.params
+++ b/TestON/tests/FUNC/FUNCnetconf/FUNCnetconf.params
@@ -2,12 +2,13 @@
     # CASE - Description
     # 1 - Variable initialization and optional pull and build ONOS package
     # 2 - Install ONOS
+    # 19 - Copy karaf logs from ONOS nodes to TestON log directory
     # 100 - Ensure netconf app is running
     # 200 - Create or modify a configuration file
     # 300 - Push a configuration file to bring up a device
     # 400 - Bring down a device (not yet possible)
 
-    <testcases>1,[2,100,200,300]*2</testcases>
+    <testcases>1,[2,100,200,300,19]*2</testcases>
 
     <SCALE>
         <size>1,3</size>
diff --git a/TestON/tests/FUNC/FUNCnetconf/FUNCnetconf.py b/TestON/tests/FUNC/FUNCnetconf/FUNCnetconf.py
index ddd1cd8..6e4ae5a 100644
--- a/TestON/tests/FUNC/FUNCnetconf/FUNCnetconf.py
+++ b/TestON/tests/FUNC/FUNCnetconf/FUNCnetconf.py
@@ -55,6 +55,7 @@
             main.configName = main.params[ 'CONFIGURE' ][ 'cfgName' ]
             main.configPass = main.params[ 'CONFIGURE' ][ 'cfgPass' ]
             main.configPort = main.params[ 'CONFIGURE' ][ 'cfgAppPort' ]
+            main.cycle = 0 # How many times FUNCintent has run through its tests
 
             gitPull = main.params[ 'GIT' ][ 'pull' ]
             main.cellData = {} # for creating cell file
@@ -139,6 +140,8 @@
         - Connect to cli
         """
 
+        main.cycle += 1
+
         # main.scale[ 0 ] determines the current number of ONOS controller
         main.numCtrls = int( main.scale[ 0 ] )
 
@@ -254,6 +257,28 @@
         # Remove the first element in main.scale list
         main.scale.remove( main.scale[ 0 ] )
 
+    def CASE19( self, main ):
+        """
+            Copy the karaf.log files after each testcase cycle
+        """
+        main.log.report( "Copy karaf logs" )
+        main.case( "Copy karaf logs" )
+        main.caseExplanation = "Copying the karaf logs to preserve them through" +\
+                               "reinstalling ONOS"
+        main.step( "Copying karaf logs" )
+        i = 0
+        for cli in main.CLIs:
+            main.node = cli
+            ip = main.ONOSip[ i ]
+            main.node.ip_address = ip
+            main.ONOSbench.scp( main.node ,
+                                "/opt/onos/log/karaf.log",
+                                "/tmp/karaf.log",
+                                direction="from" )
+            main.ONOSbench.cpLogsToDir( "/tmp/karaf.log", main.logdir,
+                                        copyFileName=( "karaf.log.node{0}.cycle{1}".format( str( i + 1 ), str( main.cycle ) ) ) )
+            i += 1
+
     def CASE100( self, main ):
         """
             Start NETCONF app and OFC-Server or make sure that they are already running
diff --git a/TestON/tests/FUNC/FUNCoptical/FUNCoptical.params b/TestON/tests/FUNC/FUNCoptical/FUNCoptical.params
index a906f12..50bb652 100644
--- a/TestON/tests/FUNC/FUNCoptical/FUNCoptical.params
+++ b/TestON/tests/FUNC/FUNCoptical/FUNCoptical.params
@@ -5,13 +5,14 @@
     # 10 - Start Mininet opticalTest Topology
     # 14 - Stop Mininet
     # 17 - Activate Flow Objectives
+    # 19 - Copy karaf logs from ONOS nodes to TestON log directory
     # 21 - Run pingall to discover all hosts
     # 22 - Send arpings to discover all hosts
     # 23 - Compare ONOS Topology to Mininet Topology
     # 31 - Add and test bidirectional point intents
     # 32 - Add and test bidirectional host intents
 
-    <testcases>1,[2,10,21,22,23,31,32,14,2,10,21,22,23,31,32,14]*1,[2,10,17,21,22,23,31,32,14,2,10,17,21,22,23,31,32,14]*1</testcases>
+    <testcases>1,[2,10,21,22,23,31,32,14,19,2,10,21,22,23,31,32,14,19]*1,[2,10,17,21,22,23,31,32,14,19,2,10,17,21,22,23,31,32,14,19]*1</testcases>
 
     <SCALE>
         <size>1,3,1,3</size>
diff --git a/TestON/tests/FUNC/FUNCoptical/FUNCoptical.py b/TestON/tests/FUNC/FUNCoptical/FUNCoptical.py
index c41a740..f4ac6ce 100644
--- a/TestON/tests/FUNC/FUNCoptical/FUNCoptical.py
+++ b/TestON/tests/FUNC/FUNCoptical/FUNCoptical.py
@@ -57,6 +57,7 @@
             main.ONOSip = []  # List of IPs of active ONOS nodes. CASE 2
             main.activeONOSip = []
             main.assertReturnString = ''  # Assembled assert return string
+            main.cycle = 0 # How many times FUNCintent has run through its tests
 
             main.ONOSip = main.ONOSbench.getOnosIps()
 
@@ -124,6 +125,8 @@
         - Connect to cli
         """
 
+        main.cycle += 1
+
         # main.scale[ 0 ] determines the current number of ONOS controller
         main.numCtrls = int( main.scale[ 0 ] )
         main.flowCompiler = "Flow Rules"
@@ -302,6 +305,28 @@
                                  onpass="Successfully activated Flow Objectives",
                                  onfail="Failed to activate Flow Objectives" )
 
+    def CASE19( self, main ):
+        """
+            Copy the karaf.log files after each testcase cycle
+        """
+        main.log.report( "Copy karaf logs" )
+        main.case( "Copy karaf logs" )
+        main.caseExplanation = "Copying the karaf logs to preserve them through" +\
+                               "reinstalling ONOS"
+        main.step( "Copying karaf logs" )
+        i = 0
+        for cli in main.CLIs:
+            main.node = cli
+            ip = main.ONOSip[ i ]
+            main.node.ip_address = ip
+            main.ONOSbench.scp( main.node ,
+                                "/opt/onos/log/karaf.log",
+                                "/tmp/karaf.log",
+                                direction="from" )
+            main.ONOSbench.cpLogsToDir( "/tmp/karaf.log", main.logdir,
+                                        copyFileName=( "karaf.log.node{0}.cycle{1}".format( str( i + 1 ), str( main.cycle ) ) ) )
+            i += 1
+
     def CASE21( self,main ):
         """
             Run pingall to discover all hosts
diff --git a/TestON/tests/SCPF/SCPFscalingMaxIntents/SCPFscalingMaxIntents.py b/TestON/tests/SCPF/SCPFscalingMaxIntents/SCPFscalingMaxIntents.py
index 608f0e6..a6f4445 100644
--- a/TestON/tests/SCPF/SCPFscalingMaxIntents/SCPFscalingMaxIntents.py
+++ b/TestON/tests/SCPF/SCPFscalingMaxIntents/SCPFscalingMaxIntents.py
@@ -483,6 +483,7 @@
                 if not intentsState:
                     # If some intents are not installed, grep the previous flows list, and finished this test case
                     main.log.warn( "Some intens did not install" )
+                    verifyTotalIntents = main.CLIs[0].getTotalIntentsNum(timeout=600)
                     main.log.info("Total Intents: {}".format( verifyTotalIntents) )
                     break
 
diff --git a/TestON/tests/SCPF/SCPFscalingMaxIntentsWithFlowObj/SCPFscalingMaxIntentsWithFlowObj.py b/TestON/tests/SCPF/SCPFscalingMaxIntentsWithFlowObj/SCPFscalingMaxIntentsWithFlowObj.py
index 97dc6f3..438338a 100644
--- a/TestON/tests/SCPF/SCPFscalingMaxIntentsWithFlowObj/SCPFscalingMaxIntentsWithFlowObj.py
+++ b/TestON/tests/SCPF/SCPFscalingMaxIntentsWithFlowObj/SCPFscalingMaxIntentsWithFlowObj.py
@@ -486,6 +486,7 @@
                 if not intentsState:
                     # If some intents are not installed, grep the previous flows list, and finished this test case
                     main.log.warn( "Some intens did not install" )
+                    verifyTotalIntents = main.CLIs[0].getTotalIntentsNum(timeout=600)
                     main.log.info("Total Intents: {}".format( totalIntents) )
                     break
 
diff --git a/TestON/tests/USECASE/USECASE_SdnipFunction_fsfw/dependencies/fsfw.xml b/TestON/tests/USECASE/USECASE_SdnipFunction_fsfw/dependencies/fsfw.xml
index e792f37..8fee131 100644
--- a/TestON/tests/USECASE/USECASE_SdnipFunction_fsfw/dependencies/fsfw.xml
+++ b/TestON/tests/USECASE/USECASE_SdnipFunction_fsfw/dependencies/fsfw.xml
@@ -434,7 +434,7 @@
             </port>
         </switch>
 
-        <controller ip_address="10.128.20.11" ssl="false" port="6633"/>
+        <controller ip_address="10.254.1.201" ssl="false" port="6633"/>
     </slice>
 
 </flowspace_firewall>
\ No newline at end of file