Support random host intent add/delete event in CHOTestMonkey

Change-Id: I56e9e9fdb3bea31376dcfb6897aa79c2923bb244
diff --git a/TestON/tests/CHOTestMonkey/CHOTestMonkey.params b/TestON/tests/CHOTestMonkey/CHOTestMonkey.params
index c3a7346..848e4cc 100644
--- a/TestON/tests/CHOTestMonkey/CHOTestMonkey.params
+++ b/TestON/tests/CHOTestMonkey/CHOTestMonkey.params
@@ -15,18 +15,19 @@
     # 50. Set FlowObjective to True
     # 51. Set FlowObjective to False
     # 60. Rebalance devices across controllers
+    # 70. Randomly generate events
     # 90. Sleep for some time
     # 100. Do something else
     # Sample sequence: 0,1,2,3,[10,30,21,31,10,32,21,33,50,10,30,21,31,10,32,21,33,51,40,60,10,30,21,31,10,32,21,33,50,10,30,21,31,10,32,21,33,51,41,60]*500,100
     <testcases>
-        0,1,2,3,10,[30,21,31,32,21,33,50,30,21,31,32,21,33,51]*500,100
+        0,1,2,3,70,[30,21,31,32,21,33]*500,100
     </testcases>
 
     <TEST>
         <topo>1</topo>
         <IPv6>on</IPv6>
         <numCtrl>3</numCtrl>
-        <pauseTest>on</pauseTest>
+        <pauseTest>off</pauseTest>
         <caseSleep>0</caseSleep>
         <setIPv6CfgSleep>5</setIPv6CfgSleep>
         <loadTopoSleep>5</loadTopoSleep>
@@ -382,6 +383,14 @@
         <linkDownUpInterval>1</linkDownUpInterval>
     </CASE21>
 
+    <CASE70>
+        <sleepSec>2</sleepSec>
+        <addHostIntentWeight>3</addHostIntentWeight>
+        <addPointIntentWeight>3</addPointIntentWeight>
+        <linkDownWeight>3</linkDownWeight>
+        <deviceDownWeight>2</deviceDownWeight>
+    </CASE70>
+
     <CASE90>
         <sleepSec>60</sleepSec>
     </CASE90>
diff --git a/TestON/tests/CHOTestMonkey/CHOTestMonkey.py b/TestON/tests/CHOTestMonkey/CHOTestMonkey.py
index 7b07758..11459b3 100644
--- a/TestON/tests/CHOTestMonkey/CHOTestMonkey.py
+++ b/TestON/tests/CHOTestMonkey/CHOTestMonkey.py
@@ -762,6 +762,104 @@
                                  onfail="Balance masters test failed" )
         time.sleep( main.caseSleep )
 
+    def CASE70( self, main ):
+        """
+        Randomly generate events
+        """
+        import time
+        import random
+        from tests.CHOTestMonkey.dependencies.events.Event import EventType
+        from tests.CHOTestMonkey.dependencies.EventScheduler import EventScheduleMethod
+
+        main.log.report( "Randomly generate events" )
+        main.log.report( "__________________________________________________" )
+        main.case( "Randomly generate events" )
+        main.step( "Randomly generate events" )
+        main.caseResult = main.TRUE
+        sleepSec = int( main.params[ 'CASE70' ][ 'sleepSec' ] )
+        hostIntentNum = 0
+        pointIntentNum = 0
+        downDeviceNum = 0
+        downLinkNum = 0
+        upControllers = [ 1, 2, 3 ]
+        while True:
+            events = []
+            for i in range( int( main.params[ 'CASE70' ][ 'addHostIntentWeight' ] ) ):
+                events.append( 'add-host-intent' )
+            for i in range( int( main.params[ 'CASE70' ][ 'addPointIntentWeight' ] ) ):
+                events.append( 'add-point-intent' )
+            for i in range( int( main.params[ 'CASE70' ][ 'linkDownWeight' ] ) ):
+                events.append( 'link-down' )
+            for i in range( int( main.params[ 'CASE70' ][ 'deviceDownWeight' ] ) ):
+                events.append( 'device-down' )
+            for i in range( int( pow( hostIntentNum, 1.5 ) / 100 ) ):
+                events.append( 'del-host-intent' )
+            for i in range( int( pow( pointIntentNum/2, 1.5 ) / 100 ) ):
+                events.append( 'del-point-intent' )
+            for i in range( pow( 2, downLinkNum ) - 1 ):
+                events.append( 'link-up' )
+            for i in range( pow( 5, downDeviceNum ) - 1 ):
+                events.append( 'device-up' )
+            main.log.debug( events )
+            event = random.sample( events, 1 )[ 0 ]
+            if event == 'add-host-intent':
+                n = random.randint( 5, 50 )
+                for i in range( n ):
+                    cliIndex = random.sample( upControllers, 1 )[ 0 ]
+                    main.eventGenerator.triggerEvent( EventType().APP_INTENT_HOST_ADD, EventScheduleMethod().RUN_NON_BLOCK, 'random', 'random', cliIndex )
+                    hostIntentNum += 1
+                main.eventGenerator.triggerEvent( EventType().NULL, EventScheduleMethod().RUN_BLOCK )
+            elif event == 'del-host-intent':
+                n = random.randint( 5, hostIntentNum )
+                for i in range( n ):
+                    cliIndex = random.sample( upControllers, 1 )[ 0 ]
+                    main.eventGenerator.triggerEvent( EventType().APP_INTENT_HOST_DEL, EventScheduleMethod().RUN_NON_BLOCK, 'random', 'random', cliIndex )
+                    hostIntentNum -= 1
+                main.eventGenerator.triggerEvent( EventType().NULL, EventScheduleMethod().RUN_BLOCK )
+            elif event == 'add-point-intent':
+                n = random.randint( 5, 50 )
+                for i in range( n ):
+                    cliIndex = random.sample( upControllers, 1 )[ 0 ]
+                    main.eventGenerator.triggerEvent( EventType().APP_INTENT_POINT_ADD, EventScheduleMethod().RUN_NON_BLOCK, 'random', 'random', cliIndex, 'bidirectional' )
+                    pointIntentNum += 1
+                main.eventGenerator.triggerEvent( EventType().NULL, EventScheduleMethod().RUN_BLOCK )
+            elif event == 'del-point-intent':
+                n = random.randint( 5, pointIntentNum )
+                for i in range( n ):
+                    cliIndex = random.sample( upControllers, 1 )[ 0 ]
+                    main.eventGenerator.triggerEvent( EventType().APP_INTENT_POINT_DEL, EventScheduleMethod().RUN_NON_BLOCK, 'random', 'random', cliIndex, 'bidirectional' )
+                    pointIntentNum -= 1
+                main.eventGenerator.triggerEvent( EventType().NULL, EventScheduleMethod().RUN_BLOCK )
+            elif event == 'link-down':
+                main.eventGenerator.triggerEvent( EventType().NETWORK_LINK_DOWN, EventScheduleMethod().RUN_BLOCK, 'random', 'random' )
+                downLinkNum += 1
+            elif event == 'link-up':
+                main.eventGenerator.triggerEvent( EventType().NETWORK_LINK_UP, EventScheduleMethod().RUN_BLOCK, 'random', 'random' )
+                downLinkNum -= 1
+            elif event == 'device-down':
+                main.eventGenerator.triggerEvent( EventType().NETWORK_DEVICE_DOWN, EventScheduleMethod().RUN_BLOCK, 'random' )
+                downDeviceNum += 1
+            elif event == 'device-up':
+                main.eventGenerator.triggerEvent( EventType().NETWORK_DEVICE_UP, EventScheduleMethod().RUN_BLOCK, 'random' )
+                downDeviceNum -= 1
+            else:
+                pass
+            main.eventGenerator.triggerEvent( EventType().CHECK_TOPO, EventScheduleMethod().RUN_NON_BLOCK )
+            main.eventGenerator.triggerEvent( EventType().CHECK_ONOS, EventScheduleMethod().RUN_NON_BLOCK )
+            main.eventGenerator.triggerEvent( EventType().CHECK_TRAFFIC, EventScheduleMethod().RUN_NON_BLOCK )
+            main.eventGenerator.triggerEvent( EventType().CHECK_FLOW, EventScheduleMethod().RUN_NON_BLOCK )
+            main.eventGenerator.triggerEvent( EventType().CHECK_INTENT, EventScheduleMethod().RUN_NON_BLOCK )
+            main.eventGenerator.triggerEvent( EventType().NULL, EventScheduleMethod().RUN_BLOCK )
+            with main.eventScheduler.idleCondition:
+                while not main.eventScheduler.isIdle():
+                    main.eventScheduler.idleCondition.wait()
+            #time.sleep( sleepSec )
+        utilities.assert_equals( expect=main.TRUE,
+                                 actual=main.caseResult,
+                                 onpass="Randomly generate events test passed",
+                                 onfail="Randomly generate events test failed" )
+        time.sleep( main.caseSleep )
+
     def CASE90( self, main ):
         """
         Sleep for some time
diff --git a/TestON/tests/CHOTestMonkey/dependencies/cli.py b/TestON/tests/CHOTestMonkey/dependencies/cli.py
index d1a8448..a3fb18e 100644
--- a/TestON/tests/CHOTestMonkey/dependencies/cli.py
+++ b/TestON/tests/CHOTestMonkey/dependencies/cli.py
@@ -64,26 +64,28 @@
             if cmdList[ 0 ] in commandMap.keys():
                 num = paramNum[ cmdList[ 0 ] ]
                 name = commandMap[ cmdList[ 0 ] ]
+                '''
                 if len( cmdList ) < num + 1:
                     print 'not enough arguments'
                 elif len( cmdList ) > num + 1:
                     print 'Too many arguments'
                 else:
-                    result = triggerEvent( debugMode, name, 'RUN_BLOCK', cmdList[ 1: ] )
-                    if result == 10:
-                        pass
-                    elif result == 11:
-                        print "Scheduler busy...Try later or use debugging mode by entering \'debug\'"
-                    elif result == 20:
-                        print "Unknown message to server"
-                    elif result == 21:
-                        print "Unknown event type to server"
-                    elif result == 22:
-                        print "Unknown schedule method to server"
-                    elif result == 23:
-                        print "Not enough argument"
-                    else:
-                        print "Unknown response from server"
+                '''
+                result = triggerEvent( debugMode, name, 'RUN_BLOCK', cmdList[ 1: ] )
+                if result == 10:
+                    pass
+                elif result == 11:
+                    print "Scheduler busy...Try later or use debugging mode by entering \'debug\'"
+                elif result == 20:
+                    print "Unknown message to server"
+                elif result == 21:
+                    print "Unknown event type to server"
+                elif result == 22:
+                    print "Unknown schedule method to server"
+                elif result == 23:
+                    print "Not enough argument"
+                else:
+                    print "Unknown response from server"
             else:
                 print 'Unknown command'
 
diff --git a/TestON/tests/CHOTestMonkey/dependencies/elements/NetworkElement.py b/TestON/tests/CHOTestMonkey/dependencies/elements/NetworkElement.py
index b3618dd..31a4084 100644
--- a/TestON/tests/CHOTestMonkey/dependencies/elements/NetworkElement.py
+++ b/TestON/tests/CHOTestMonkey/dependencies/elements/NetworkElement.py
@@ -37,7 +37,7 @@
         self.outgoingLinks = []
 
     def __str__( self ):
-        return "name: " + self.name + ", dpid: " + self.dpid
+        return "name: " + self.name + ", dpid: " + self.dpid + ", status: " + self.status
 
 class Host( NetworkElement ):
     def __init__( self, index, name, id, mac, device, devicePort, vlan, ipAddresses ):
@@ -53,7 +53,7 @@
         self.handle = None
 
     def __str__( self ):
-        return "name: " + self.name + ", mac: " + self.mac + ", device: " + self.device.dpid + ", ipAddresses: " + str( self.ipAddresses )
+        return "name: " + self.name + ", mac: " + self.mac + ", device: " + self.device.dpid + ", ipAddresses: " + str( self.ipAddresses ) + ", status: " + self.status
 
     def setHandle( self, handle ):
         self.handle = handle
@@ -71,7 +71,7 @@
         self.portB = portB
 
     def __str__( self ):
-        return self.deviceA.dpid + "/" + self.portA + " - " + self.deviceB.dpid + "/" + self.portB
+        return self.deviceA.dpid + "/" + self.portA + " - " + self.deviceB.dpid + "/" + self.portB + ", status: " + self.status
 
     def setBackwardLink( self, link ):
         self.backwardLink = link
diff --git a/TestON/tests/CHOTestMonkey/dependencies/events/AppEvent.py b/TestON/tests/CHOTestMonkey/dependencies/events/AppEvent.py
index de5fd48..8113b09 100644
--- a/TestON/tests/CHOTestMonkey/dependencies/events/AppEvent.py
+++ b/TestON/tests/CHOTestMonkey/dependencies/events/AppEvent.py
@@ -11,6 +11,47 @@
         # The index of the ONOS CLI that is going to run the command
         self.CLIIndex = 0
 
+    def getRandomCorrespondent( self, hostA, connected=True ):
+        import random
+        if connected:
+            candidates = hostA.correspondents
+        else:
+            candidates = [ host for host in main.hosts if host not in hostA.correspondents and host != hostA ]
+        if len( candidates ) == 0:
+            return None
+        hostB = random.sample( candidates, 1 )[0]
+        return hostB
+
+    def getRandomHostPair( self, connected=True ):
+        import random
+        candidateDict = {}
+        with main.variableLock:
+            for host in main.hosts:
+                correspondent = self.getRandomCorrespondent( host, connected=connected )
+                if correspondent != None:
+                    candidateDict[ host ] = correspondent
+            if candidateDict == {}:
+                return None
+            hostA = random.sample( candidateDict.keys(), 1 )[0]
+            hostB = candidateDict[ hostA ]
+            return [ hostA, hostB ]
+
+    def getIntentsByType( self, intentType ):
+        intents = []
+        with main.variableLock:
+            for intent in main.intents:
+                if intent.type == intentType:
+                    intents.append( intent )
+        return intents
+
+    def getRandomIntentByType( self, intentType ):
+        import random
+        intents = self.getIntentsByType( intentType )
+        if len( intents ) == 0:
+            return None
+        intent = random.sample( intents, 1 )[ 0 ]
+        return intent
+
 class HostIntentEvent( IntentEvent ):
     def __init__( self ):
         IntentEvent.__init__( self )
@@ -30,10 +71,25 @@
                 elif len( args ) > 3:
                     main.log.warn( "%s - Too many arguments: %s" % ( self.typeString, args ) )
                     return EventStates().ABORT
+                if args[ 0 ] == 'random' or args[ 1 ] == 'random':
+                    if self.typeIndex == EventType().APP_INTENT_HOST_ADD:
+                        hostPairRandom = self.getRandomHostPair( connected=False )
+                        if hostPairRandom == None:
+                            main.log.warn( "All host pairs are connected, aborting event" )
+                            return EventStates().ABORT
+                        self.hostA = hostPairRandom[ 0 ]
+                        self.hostB = hostPairRandom[ 1 ]
+                    elif self.typeIndex == EventType().APP_INTENT_HOST_DEL:
+                        intent = self.getRandomIntentByType( 'INTENT_HOST' )
+                        if intent == None:
+                            main.log.warn( "No host intent for deletion, aborting event" )
+                            return EventStates().ABORT
+                        self.hostA = intent.hostA
+                        self.hostB = intent.hostB
+                elif args[ 0 ] == args[ 1 ]:
+                    main.log.warn( "%s - invalid argument: %s, %s" % ( self.typeString, args[ 0 ], args[ 1 ] ) )
+                    return EventStates().ABORT
                 else:
-                    if args[ 0 ] == args[ 1 ]:
-                        main.log.warn( "%s - invalid argument: %s" % ( self.typeString, index ) )
-                        return EventStates().ABORT
                     for host in main.hosts:
                         if host.name == args[ 0 ]:
                             self.hostA = host
@@ -47,15 +103,15 @@
                     if self.hostB == None:
                         main.log.warn( "Host %s does not exist: " % ( args[ 1 ] ) )
                         return EventStates().ABORT
-                    index = int( args[ 2 ] )
-                    if index < 1 or index > int( main.numCtrls ):
-                        main.log.warn( "%s - invalid argument: %s" % ( self.typeString, index ) )
-                        return EventStates().ABORT
-                    if not main.controllers[ index - 1 ].isUp():
-                        main.log.warn( self.typeString + " - invalid argument: onos %s is down" % ( controller.index ) )
-                        return EventStates().ABORT
-                    self.CLIIndex = index
-                    return self.startHostIntentEvent()
+                index = int( args[ 2 ] )
+                if index < 1 or index > int( main.numCtrls ):
+                    main.log.warn( "%s - invalid argument: %s" % ( self.typeString, index ) )
+                    return EventStates().ABORT
+                if not main.controllers[ index - 1 ].isUp():
+                    main.log.warn( self.typeString + " - invalid argument: onos %s is down" % ( controller.index ) )
+                    return EventStates().ABORT
+                self.CLIIndex = index
+                return self.startHostIntentEvent()
 
 class AddHostIntent( HostIntentEvent ):
     """
@@ -67,30 +123,36 @@
         self.typeIndex= int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
 
     def startHostIntentEvent( self ):
-        assert self.hostA != None and self.hostB != None
-        # Check whether there already exists some intent for the host pair
-        # For now we should avoid installing overlapping intents
-        for intent in main.intents:
-            if not intent.type == 'INTENT_HOST':
-                continue
-            if intent.hostA == self.hostA and intent.hostB == self.hostB or\
-            intent.hostB == self.hostA and intent.hostA == self.hostB:
-                main.log.warn( self.typeString + " - find an exiting intent for the host pair, abort installation" )
-                return EventStates().ABORT
-        controller = main.controllers[ self.CLIIndex - 1 ]
-        with controller.CLILock:
-            id = controller.CLI.addHostIntent( self.hostA.id, self.hostB.id )
-        if id == None:
-            main.log.warn( self.typeString + " - add host intent failed" )
-            return EventStates().FAIL
-        with main.variableLock:
-            newHostIntent = HostIntent( id, self.hostA, self.hostB )
-            main.intents.append( newHostIntent )
-            # Update host connectivity status
-            # TODO: should we check whether hostA and hostB are already correspondents?
-            self.hostB.correspondents.append( self.hostA )
-            self.hostA.correspondents.append( self.hostB )
-        return EventStates().PASS
+        try:
+            assert self.hostA != None and self.hostB != None
+            # Check whether there already exists some intent for the host pair
+            # For now we should avoid installing overlapping intents
+            for intent in main.intents:
+                if not intent.type == 'INTENT_HOST':
+                    continue
+                if intent.hostA == self.hostA and intent.hostB == self.hostB or\
+                intent.hostB == self.hostA and intent.hostA == self.hostB:
+                    main.log.warn( self.typeString + " - find an exiting intent for the host pair, abort installation" )
+                    return EventStates().ABORT
+            controller = main.controllers[ self.CLIIndex - 1 ]
+            with controller.CLILock:
+                id = controller.CLI.addHostIntent( self.hostA.id, self.hostB.id )
+            if id == None:
+                main.log.warn( self.typeString + " - add host intent failed" )
+                return EventStates().FAIL
+            with main.variableLock:
+                newHostIntent = HostIntent( id, self.hostA, self.hostB )
+                if self.hostA.isDown() or self.hostA.isRemoved() or self.hostB.isDown() or self.hostB.isRemoved():
+                    newHostIntent.setFailed()
+                main.intents.append( newHostIntent )
+                # Update host connectivity status
+                # TODO: should we check whether hostA and hostB are already correspondents?
+                self.hostB.correspondents.append( self.hostA )
+                self.hostA.correspondents.append( self.hostB )
+            return EventStates().PASS
+        except Exception:
+            main.log.warn( "Caught exception, aborting event" )
+            return EventStates().ABORT
 
 class DelHostIntent( HostIntentEvent ):
     """
@@ -102,30 +164,34 @@
         self.typeIndex= int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
 
     def startHostIntentEvent( self ):
-        assert self.hostA != None and self.hostB != None
-        targetIntent = None
-        for intent in main.intents:
-            if not intent.type == 'INTENT_HOST':
-                continue
-            if intent.hostA == self.hostA and intent.hostB == self.hostB or\
-            intent.hostB == self.hostA and intent.hostA == self.hostB:
-                targetIntent = intent
-                break
-        if targetIntent == None:
-            main.log.warn( self.typeString + " - intent does not exist" )
-            return EventStates().FAIL
-        controller = main.controllers[ self.CLIIndex - 1 ]
-        with controller.CLILock:
-            result = controller.CLI.removeIntent( targetIntent.id, purge=True )
-        if result == None or result == main.FALSE:
-            main.log.warn( self.typeString + " - delete host intent failed" )
-            return EventStates().FAIL
-        with main.variableLock:
-            main.intents.remove( targetIntent )
-            # Update host connectivity status
-            self.hostB.correspondents.remove( self.hostA )
-            self.hostA.correspondents.remove( self.hostB )
-        return EventStates().PASS
+        try:
+            assert self.hostA != None and self.hostB != None
+            targetIntent = None
+            for intent in main.intents:
+                if not intent.type == 'INTENT_HOST':
+                    continue
+                if intent.hostA == self.hostA and intent.hostB == self.hostB or\
+                intent.hostB == self.hostA and intent.hostA == self.hostB:
+                    targetIntent = intent
+                    break
+            if targetIntent == None:
+                main.log.warn( self.typeString + " - intent does not exist" )
+                return EventStates().FAIL
+            controller = main.controllers[ self.CLIIndex - 1 ]
+            with controller.CLILock:
+                result = controller.CLI.removeIntent( targetIntent.id, purge=True )
+            if result == None or result == main.FALSE:
+                main.log.warn( self.typeString + " - delete host intent failed" )
+                return EventStates().FAIL
+            with main.variableLock:
+                main.intents.remove( targetIntent )
+                # Update host connectivity status
+                self.hostB.correspondents.remove( self.hostA )
+                self.hostA.correspondents.remove( self.hostB )
+            return EventStates().PASS
+        except Exception:
+            main.log.warn( "Caught exception, aborting event" )
+            return EventStates().ABORT
 
 class PointIntentEvent( IntentEvent ):
     def __init__( self ):
@@ -143,9 +209,27 @@
                 if len( args ) < 3:
                     main.log.warn( "%s - Not enough arguments: %s" % ( self.typeString, args ) )
                     return EventStates().ABORT
-                elif len( args ) > 3:
+                elif len( args ) > 4:
                     main.log.warn( "%s - Too many arguments: %s" % ( self.typeString, args ) )
                     return EventStates().ABORT
+                if args[ 0 ] == 'random' or args[ 1 ] == 'random':
+                    if self.typeIndex == EventType().APP_INTENT_POINT_ADD:
+                        hostPairRandom = self.getRandomHostPair( connected=False )
+                        if hostPairRandom == None:
+                            main.log.warn( "All host pairs are connected, aborting event" )
+                            return EventStates().ABORT
+                        self.deviceA = hostPairRandom[ 0 ].device
+                        self.deviceB = hostPairRandom[ 1 ].device
+                    elif self.typeIndex == EventType().APP_INTENT_POINT_DEL:
+                        intent = self.getRandomIntentByType( 'INTENT_POINT' )
+                        if intent == None:
+                            main.log.warn( "No point intent for deletion, aborting event" )
+                            return EventStates().ABORT
+                        self.deviceA = intent.deviceA
+                        self.deviceB = intent.deviceB
+                elif args[ 0 ] == args[ 1 ]:
+                    main.log.warn( "%s - invalid argument: %s" % ( self.typeString, args[ 0 ], args[ 1 ] ) )
+                    return EventStates().ABORT
                 else:
                     for device in main.devices:
                         if device.name == args[ 0 ]:
@@ -160,14 +244,24 @@
                     if self.deviceB == None:
                         main.log.warn( "Device %s does not exist: " % ( args[ 1 ] ) )
                         return EventStates().ABORT
-                    index = int( args[ 2 ] )
-                    if index < 1 or index > int( main.numCtrls ):
-                        main.log.warn( "%s - invalid argument: %s" % ( self.typeString, index ) )
-                        return EventStates().ABORT
-                    if not main.controllers[ index - 1 ].isUp():
-                        main.log.warn( self.typeString + " - invalid argument: onos %s is down" % ( controller.index ) )
-                        return EventStates().ABORT
-                    self.CLIIndex = index
+                index = int( args[ 2 ] )
+                if index < 1 or index > int( main.numCtrls ):
+                    main.log.warn( "%s - invalid argument: %s" % ( self.typeString, index ) )
+                    return EventStates().ABORT
+                if not main.controllers[ index - 1 ].isUp():
+                    main.log.warn( self.typeString + " - invalid argument: onos %s is down" % ( controller.index ) )
+                    return EventStates().ABORT
+                self.CLIIndex = index
+                if len( args ) == 4 and args[ 3 ] == 'bidirectional':
+                    # Install point intents for both directions
+                    resultA = self.startPointIntentEvent()
+                    [ self.deviceA, self.deviceB ] = [ self.deviceB, self.deviceA ]
+                    resultB = self.startPointIntentEvent()
+                    if resultA == EventStates().PASS and resultB == EventStates().PASS:
+                        return EventStates().PASS
+                    else:
+                        return EventStates().FAIL
+                else:
                     return self.startPointIntentEvent()
 
 class AddPointIntent( PointIntentEvent ):
@@ -180,39 +274,45 @@
         self.typeIndex= int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
 
     def startPointIntentEvent( self ):
-        assert self.deviceA != None and self.deviceB != None
-        controller = main.controllers[ self.CLIIndex - 1 ]
-        # TODO: the following check only work when we use default port number for point intents
-        # Check whether there already exists some intent for the device pair
-        # For now we should avoid installing overlapping intents
-        for intent in main.intents:
-            if not intent.type == 'INTENT_POINT':
-                continue
-            if intent.deviceA == self.deviceA and intent.deviceB == self.deviceB:
-                main.log.warn( self.typeString + " - find an exiting intent for the device pair, abort installation" )
-                return EventStates().ABORT
-        controller = main.controllers[ self.CLIIndex - 1 ]
-        with controller.CLILock:
-            # TODO: handle the case that multiple hosts attach to one device
-            srcMAC = ""
-            dstMAC = ""
-            if len( self.deviceA.hosts ) > 0:
-                srcMAC = self.deviceA.hosts[ 0 ].mac
-            if len( self.deviceB.hosts ) > 0:
-                dstMAC = self.deviceB.hosts[ 0 ].mac
-            id = controller.CLI.addPointIntent( self.deviceA.dpid, self.deviceB.dpid,
-                                                1, 1, '', srcMAC, dstMAC )
-        if id == None:
-            main.log.warn( self.typeString + " - add point intent failed" )
-            return EventStates().FAIL
-        with main.variableLock:
-            newPointIntent = PointIntent( id, self.deviceA, self.deviceB )
-            main.intents.append( newPointIntent )
-            # Update host connectivity status
-            for hostA in self.deviceA.hosts:
-                for hostB in self.deviceB.hosts:
-                    hostA.correspondents.append( hostB )
-        return EventStates().PASS
+        try:
+            assert self.deviceA != None and self.deviceB != None
+            controller = main.controllers[ self.CLIIndex - 1 ]
+            # TODO: the following check only work when we use default port number for point intents
+            # Check whether there already exists some intent for the device pair
+            # For now we should avoid installing overlapping intents
+            for intent in main.intents:
+                if not intent.type == 'INTENT_POINT':
+                    continue
+                if intent.deviceA == self.deviceA and intent.deviceB == self.deviceB:
+                    main.log.warn( self.typeString + " - find an exiting intent for the device pair, abort installation" )
+                    return EventStates().ABORT
+            controller = main.controllers[ self.CLIIndex - 1 ]
+            with controller.CLILock:
+                # TODO: handle the case that multiple hosts attach to one device
+                srcMAC = ""
+                dstMAC = ""
+                if len( self.deviceA.hosts ) > 0:
+                    srcMAC = self.deviceA.hosts[ 0 ].mac
+                if len( self.deviceB.hosts ) > 0:
+                    dstMAC = self.deviceB.hosts[ 0 ].mac
+                id = controller.CLI.addPointIntent( self.deviceA.dpid, self.deviceB.dpid,
+                                                    1, 1, '', srcMAC, dstMAC )
+            if id == None:
+                main.log.warn( self.typeString + " - add point intent failed" )
+                return EventStates().FAIL
+            with main.variableLock:
+                newPointIntent = PointIntent( id, self.deviceA, self.deviceB )
+                if self.deviceA.isDown() or self.deviceB.isDown() or self.deviceA.isRemoved() or self.deviceB.isRemoved():
+                    newPointIntent.setFailed()
+                main.intents.append( newPointIntent )
+                # Update host connectivity status
+                for hostA in self.deviceA.hosts:
+                    for hostB in self.deviceB.hosts:
+                        hostA.correspondents.append( hostB )
+            return EventStates().PASS
+        except Exception:
+            main.log.warn( "Caught exception, aborting event" )
+            return EventStates().ABORT
 
 class DelPointIntent( PointIntentEvent ):
     """
@@ -224,27 +324,31 @@
         self.typeIndex= int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
 
     def startPointIntentEvent( self ):
-        assert self.deviceA != None and self.deviceB != None
-        targetIntent = None
-        for intent in main.intents:
-            if not intent.type == 'INTENT_POINT':
-                continue
-            if intent.deviceA == self.deviceA and intent.deviceB == self.deviceB:
-                targetIntent = intent
-                break
-        if targetIntent == None:
-            main.log.warn( self.typeString + " - intent does not exist" )
-            return EventStates().FAIL
-        controller = main.controllers[ self.CLIIndex - 1 ]
-        with controller.CLILock:
-            result = controller.CLI.removeIntent( targetIntent.id, purge=True )
-        if result == None or result == main.FALSE:
-            main.log.warn( self.typeString + " - delete host intent failed" )
-            return EventStates().FAIL
-        with main.variableLock:
-            main.intents.remove( targetIntent )
-            # Update host connectivity status
-            for hostA in self.deviceA.hosts:
-                for hostB in self.deviceB.hosts:
-                    hostA.correspondents.remove( hostB )
-        return EventStates().PASS
+        try:
+            assert self.deviceA != None and self.deviceB != None
+            targetIntent = None
+            for intent in main.intents:
+                if not intent.type == 'INTENT_POINT':
+                    continue
+                if intent.deviceA == self.deviceA and intent.deviceB == self.deviceB:
+                    targetIntent = intent
+                    break
+            if targetIntent == None:
+                main.log.warn( self.typeString + " - intent does not exist" )
+                return EventStates().FAIL
+            controller = main.controllers[ self.CLIIndex - 1 ]
+            with controller.CLILock:
+                result = controller.CLI.removeIntent( targetIntent.id, purge=True )
+            if result == None or result == main.FALSE:
+                main.log.warn( self.typeString + " - delete point intent failed" )
+                return EventStates().FAIL
+            with main.variableLock:
+                main.intents.remove( targetIntent )
+                # Update host connectivity status
+                for hostA in self.deviceA.hosts:
+                    for hostB in self.deviceB.hosts:
+                        hostA.correspondents.remove( hostB )
+            return EventStates().PASS
+        except Exception:
+            main.log.warn( "Caught exception, aborting event" )
+            return EventStates().ABORT