Framework of the new CHOtest

Change-Id: Ie5b58bfa2ed487386443692cbea0d469d7419c24
diff --git a/TestON/tests/CHOTestMonkey/dependencies/events/AppEvent.py b/TestON/tests/CHOTestMonkey/dependencies/events/AppEvent.py
new file mode 100644
index 0000000..7f0ca7c
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/events/AppEvent.py
@@ -0,0 +1,246 @@
+"""
+This file contains classes for CHOTestMonkey that are related to application event
+Author: you@onlab.us
+"""
+from tests.CHOTestMonkey.dependencies.events.Event import EventType, EventStates, Event
+from tests.CHOTestMonkey.dependencies.elements.ONOSElement import HostIntent, PointIntent
+
+class IntentEvent( Event ):
+    def __init__( self ):
+        Event.__init__( self )
+        # The index of the ONOS CLI that is going to run the command
+        self.CLIIndex = 0
+
+class HostIntentEvent( IntentEvent ):
+    def __init__( self ):
+        IntentEvent.__init__( self )
+        self.hostA = None
+        self.hostB = None
+
+    def startHostIntentEvent( self ):
+        return EventStates().PASS
+
+    def startEvent( self, args ):
+        with self.eventLock:
+            main.log.info( "%s - starting event" % ( self.typeString ) )
+            if self.typeIndex == EventType().APP_INTENT_HOST_ADD or self.typeIndex == EventType().APP_INTENT_HOST_DEL:
+                if len( args ) < 3:
+                    main.log.warn( "%s - Not enough arguments: %s" % ( self.typeString, args ) )
+                    return EventStates().ABORT
+                elif len( args ) > 3:
+                    main.log.warn( "%s - Too many arguments: %s" % ( self.typeString, args ) )
+                    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
+                        elif host.name == args[ 1 ]:
+                            self.hostB = host
+                        if self.hostA != None and self.hostB != None:
+                            break
+                    if self.hostA == None:
+                        main.log.warn( "Host %s does not exist: " % ( args[ 0 ] ) )
+                        return EventStates().ABORT
+                    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()
+
+class AddHostIntent( HostIntentEvent ):
+    """
+    Add a host-to-host intent (bidirectional)
+    """
+    def __init__( self ):
+        HostIntentEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        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
+
+class DelHostIntent( HostIntentEvent ):
+    """
+    Delete a host-to-host intent (bidirectional)
+    """
+    def __init__( self ):
+        HostIntentEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        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
+
+class PointIntentEvent( IntentEvent ):
+    def __init__( self ):
+        IntentEvent.__init__( self )
+        self.deviceA = None
+        self.deviceB = None
+
+    def startPointIntentEvent( self ):
+        return EventStates().PASS
+
+    def startEvent( self, args ):
+        with self.eventLock:
+            main.log.info( "%s - starting event" % ( self.typeString ) )
+            if self.typeIndex == EventType().APP_INTENT_POINT_ADD or self.typeIndex == EventType().APP_INTENT_POINT_DEL:
+                if len( args ) < 3:
+                    main.log.warn( "%s - Not enough arguments: %s" % ( self.typeString, args ) )
+                    return EventStates().ABORT
+                elif len( args ) > 3:
+                    main.log.warn( "%s - Too many arguments: %s" % ( self.typeString, args ) )
+                    return EventStates().ABORT
+                else:
+                    for device in main.devices:
+                        if device.name == args[ 0 ]:
+                            self.deviceA = device
+                        elif device.name == args[ 1 ]:
+                            self.deviceB = device
+                        if self.deviceA != None and self.deviceB != None:
+                            break
+                    if self.deviceA == None:
+                        main.log.warn( "Device %s does not exist: " % ( args[ 0 ] ) )
+                        return EventStates().ABORT
+                    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
+                    return self.startPointIntentEvent()
+
+class AddPointIntent( PointIntentEvent ):
+    """
+    Add a point-to-point intent
+    """
+    def __init__( self ):
+        PointIntentEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        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
+            id = controller.CLI.addPointIntent( self.deviceA.dpid, self.deviceB.dpid,
+                                                1, 1, '',
+                                                self.deviceA.hosts[ 0 ].mac,
+                                                self.deviceB.hosts[ 0 ].mac )
+        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
+
+class DelPointIntent( PointIntentEvent ):
+    """
+    Delete a point-to-point intent
+    """
+    def __init__( self ):
+        PointIntentEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        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
diff --git a/TestON/tests/CHOTestMonkey/dependencies/events/CheckEvent.py b/TestON/tests/CHOTestMonkey/dependencies/events/CheckEvent.py
new file mode 100644
index 0000000..e48f674
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/events/CheckEvent.py
@@ -0,0 +1,235 @@
+"""
+This file contains classes for CHOTestMonkey that are related to check event
+Author: you@onlab.us
+"""
+from tests.CHOTestMonkey.dependencies.events.Event import EventType, EventStates, Event
+
+class CheckEvent( Event ):
+    def __init__( self ):
+        Event.__init__( self )
+
+    def startCheckEvent( self ):
+        return EventStates().PASS
+
+    def startEvent( self, args ):
+        with self.eventLock:
+            main.log.info( "%s - starting event" % ( self.typeString ) )
+            result = self.startCheckEvent()
+            return result
+
+class IntentCheck( CheckEvent ):
+    def __init__( self ):
+        CheckEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex = int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startCheckEvent( self, args=None ):
+        checkResult = EventStates().PASS
+        intentIDs = []
+        for intent in main.intents:
+            if intent.isHostIntent():
+                deviceA = intent.hostA.device
+                deviceB = intent.hostB.device
+            elif intent.isPointIntent():
+                deviceA = intent.deviceA
+                deviceB = intent.deviceB
+            # Exclude the intents that are to or from removed devices/hosts
+            if not deviceA.isRemoved() and not deviceB.isRemoved():
+                intentIDs.append( intent.id )
+        for controller in main.controllers:
+            if controller.isUp():
+                with controller.CLILock:
+                    intentState = controller.CLI.checkIntentState( intentsId=intentIDs )
+                if not intentState:
+                    main.log.warn( "Intent Check - Not all intents are in INSTALLED state on ONOS%s" % ( controller.index ) )
+                    checkResult = EventStates().FAIL
+        #TODO: check flows?
+        return checkResult
+
+class TopoCheck( CheckEvent ):
+    def __init__( self ):
+        CheckEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex = int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startCheckEvent( self, args=None ):
+        import json
+        checkResult = EventStates().PASS
+        upLinkNum = 0
+        upDeviceNum = 0
+        upHostNum = 0
+        with main.variableLock:
+            for link in main.links:
+                if not link.isDown() and not link.isRemoved():
+                    upLinkNum += 1
+            for device in main.devices:
+                if not device.isDown() and not device.isRemoved():
+                    upDeviceNum += 1
+            for host in main.hosts:
+                if not host.isDown() and not host.isRemoved():
+                    upHostNum += 1
+        clusterNum = 1
+        for controller in main.controllers:
+            if controller.isUp():
+                with controller.CLILock:
+                    topologyOutput = controller.CLI.topology()
+                    topoState = controller.CLI.checkStatus( topologyOutput, upDeviceNum, upLinkNum )
+                    #if not topoState:
+                    #    main.log.warn( "Topo Check - link or device number discoverd by ONOS%s is incorrect" % ( controller.index ) )
+                    #    checkResult = EventStates().FAIL
+                    # Check links
+                    links = controller.CLI.links()
+                    links = json.loads( links )
+                    if not len( links ) == upLinkNum:
+                        checkResult = EventStates().FAIL
+                        main.log.warn( "Topo Check - link number discoverd by ONOS%s is incorrect: %s expected and %s actual" % ( controller.index, upLinkNum, len( links ) ) )
+                    # Check devices
+                    devices = controller.CLI.devices()
+                    devices = json.loads( devices )
+                    availableDeviceNum = 0
+                    for device in devices:
+                        if device[ 'available' ] == True:
+                            availableDeviceNum += 1
+                    if not availableDeviceNum == upDeviceNum:
+                        checkResult = EventStates().FAIL
+                        main.log.warn( "Topo Check - device number discoverd by ONOS%s is incorrect: %s expected and %s actual" % ( controller.index, upDeviceNum, availableDeviceNum ) )
+                    # Check hosts
+                    hosts = controller.CLI.hosts()
+                    hosts = json.loads( hosts )
+                    if not len( hosts ) == upHostNum:
+                        checkResult = EventStates().FAIL
+                        main.log.warn( "Topo Check - host number discoverd by ONOS%s is incorrect: %s expected and %s actual" % ( controller.index, upHostNum, len( hosts ) ) )
+                    # Check clusters
+                    clusters = controller.CLI.clusters()
+                    clusters = json.loads( clusters )
+                    if not len( clusters ) == clusterNum:
+                        checkResult = EventStates().FAIL
+                        main.log.warn( "Topo Check - cluster number discoverd by ONOS%s is incorrect: %s expected and %s actual" % ( controller.index, clusterNum, len( clusters ) ) )
+        return checkResult
+
+class ONOSCheck( CheckEvent ):
+    def __init__( self ):
+        CheckEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex= int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startCheckEvent( self, args=None ):
+        import json
+        checkResult = EventStates().PASS
+        topics = []
+        # TODO: Other topics?
+        for i in range( 14 ):
+            topics.append( "intent-partition-" + str( i ) )
+        dpidToAvailability = {}
+        dpidToMaster = {}
+        for device in main.devices:
+            if device.isDown() or device.isRemoved():
+                dpidToAvailability[ device.dpid ] = False
+            else:
+                dpidToAvailability[ device.dpid ] = True
+            dpidToMaster[ device.dpid ] = 'unknown'
+        # Check mastership, leaders and node states on each controller node
+        for controller in main.controllers:
+            if controller.isUp():
+                # Check mastership
+                with controller.CLILock:
+                    roles = controller.CLI.roles()
+                roles = json.loads( roles )
+                for device in roles:
+                    dpid = device[ 'id' ]
+                    if dpidToMaster[ dpid ] == 'unknown':
+                        dpidToMaster[ dpid ] = device[ 'master' ]
+                    elif dpidToMaster[ dpid ] != device[ 'master' ]:
+                        checkResult = EventStates().FAIL
+                        main.log.warn( "ONOS Check - Mastership of %s on ONOS%s is inconsistent with that on ONOS1" % ( device.name, controller.index ) )
+                    if dpidToAvailability[ dpid ] and device[ 'master' ] == "none":
+                        checkResult = EventStates().FAIL
+                        main.log.warn( "ONOS Check - Device %s has no master on ONOS%s" % ( device.name, controller.index ) )
+                # Check leaders
+                with controller.CLILock:
+                    leaders = controller.CLI.leaders()
+                leaders = json.loads( leaders )
+                ONOSTopics = [ j['topic'] for j in leaders ]
+                for topic in topics:
+                    if topic not in ONOSTopics:
+                        checkResult = EventStates().FAIL
+                        main.log.warn( "ONOS Check - Topic %s not in leaders on ONOS%s" % ( topic, controller.index ) )
+                # Check node state
+                with controller.CLILock:
+                    nodes = controller.CLI.nodes()
+                nodes = json.loads( nodes )
+                ipToState = {}
+                for node in nodes:
+                    ipToState[ node[ 'ip' ] ] = node[ 'state' ]
+                for c in main.controllers:
+                    if c.isUp() and ipToState[ c.ip ] == 'READY':
+                        pass
+                    elif not c.isUp() and ipToState[ c.ip ] == 'INACTIVE':
+                        pass
+                    else:
+                        checkResult = EventStates().FAIL
+                        main.log.warn( "ONOS Check - ONOS%s shows wrong node state: ONOS%s is %s but state is %s" % ( controller.index, c.index, c.status, ipToState[ c.ip ] ) )
+                # TODO: check partitions?
+        return checkResult
+
+class TrafficCheck( CheckEvent ):
+    def __init__( self ):
+        CheckEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex= int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startCheckEvent( self, args=None ):
+        checkResult = EventStates().PASS
+        pool = []
+        wait = int( main.params[ 'EVENT' ][ 'TrafficCheck' ][ 'pingWait' ] )
+        timeout = int( main.params[ 'EVENT' ][ 'TrafficCheck' ][ 'pingTimeout' ] )
+        dstIPv4List = {}
+        dstIPv6List = {}
+        upHosts = []
+        for host in main.hosts:
+            if host.isUp():
+                upHosts.append( host )
+        for host in upHosts:
+            dstIPv4List[ host.index ] = []
+            dstIPv6List[ host.index ] = []
+            for correspondent in host.correspondents:
+                if not correspondent in upHosts:
+                    continue
+                for ipAddress in correspondent.ipAddresses:
+                    if ipAddress.startswith( str( main.params[ 'TEST' ][ 'ipv6Prefix' ] ) ):
+                        dstIPv6List[ host.index ].append( ipAddress )
+                    elif ipAddress.startswith( str( main.params[ 'TEST' ][ 'ipv4Prefix' ] ) ):
+                        dstIPv4List[ host.index ].append( ipAddress )
+            thread = main.Thread( target=host.handle.pingHostSetAlternative,
+                                  threadID=main.threadID,
+                                  name="pingHostSetAlternative",
+                                  args=[ dstIPv4List[ host.index ], 1 ] )
+            pool.append( thread )
+            thread.start()
+            with main.variableLock:
+                main.threadID += 1
+        for thread in pool:
+            thread.join( 10 )
+            if not thread.result:
+                checkResult = EventStates().FAIL
+                main.log.warn( "Traffic Check - ping failed" )
+
+        if not main.enableIPv6:
+            return checkResult
+        # Check ipv6 ping
+        for host in upHosts:
+            thread = main.Thread( target=host.handle.pingHostSetAlternative,
+                                  threadID=main.threadID,
+                                  name="pingHostSetAlternative",
+                                  args=[ dstIPv6List[ host.index ], 1, True ] )
+            pool.append( thread )
+            thread.start()
+            with main.variableLock:
+                main.threadID += 1
+        for thread in pool:
+            thread.join( 10 )
+            if not thread.result:
+                checkResult = EventStates().FAIL
+                main.log.warn( "Traffic Check - ping6 failed" )
+        return checkResult
+
diff --git a/TestON/tests/CHOTestMonkey/dependencies/events/Event.py b/TestON/tests/CHOTestMonkey/dependencies/events/Event.py
new file mode 100644
index 0000000..2abd77f
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/events/Event.py
@@ -0,0 +1,43 @@
+"""
+This file contains the Event class for CHOTestMonkey
+Author: you@onlab.us
+"""
+from threading import Lock
+
+class EventType:
+    def __init__( self ):
+        self.map = {}
+        # Group events (>100) should be divided into individual events by the generator before going to the scheduler
+        self.NULL = 0
+        for eventName in main.params[ 'EVENT' ].keys():
+            typeString = main.params[ 'EVENT' ][ eventName ][ 'typeString' ]
+            typeIndex = int( main.params[ 'EVENT' ][ eventName ][ 'typeIndex' ] )
+            setattr( self, typeString, typeIndex )
+            self.map[ typeIndex ] = typeString
+
+class EventStates:
+    def __init__( self ):
+        self.map = {}
+        self.FAIL = 0
+        self.map[ 0 ] = 'FAIL'
+        self.PASS = 1
+        self.map[ 1 ] = 'PASS'
+        self.ABORT = -1
+        self.map[ -1 ] = 'ABORT'
+
+class Event:
+    """
+    Event class for CHOTestMonkey
+    It is the super class for CheckEvent and NetworkEvent
+    """
+    def __init__( self ):
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex = int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+        self.eventLock = Lock()
+        self.variableLock = Lock()
+
+    def startEvent( self, args=None ):
+        """
+        Start running the event
+        """
+        return EventStates().PASS
diff --git a/TestON/tests/CHOTestMonkey/dependencies/events/NetworkEvent.py b/TestON/tests/CHOTestMonkey/dependencies/events/NetworkEvent.py
new file mode 100644
index 0000000..46b37e7
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/events/NetworkEvent.py
@@ -0,0 +1,289 @@
+"""
+This file contains classes for CHOTestMonkey that are related to network event
+Author: you@onlab.us
+"""
+from tests.CHOTestMonkey.dependencies.events.Event import EventType, EventStates, Event
+from tests.CHOTestMonkey.dependencies.elements.NetworkElement import NetworkElement, Device, Host, Link
+from tests.CHOTestMonkey.dependencies.GraphHelper import GraphHelper
+
+class LinkEvent( Event ):
+    def __init__( self ):
+        Event.__init__( self )
+        self.linkA = None
+        self.linkB = None
+
+    def startLinkEvent( self ):
+        return EventStates().PASS
+
+    def startEvent( self, args ):
+        """
+        args are the names of the two link ends, e.g. ['s1', 's2']
+        """
+        with self.eventLock:
+            main.log.info( "%s - starting event" % ( self.typeString ) )
+            if len( args ) < 2:
+                main.log.warn( "%s - Not enough arguments: %s" % ( self.typeString, args ) )
+                return EventStates().ABORT
+            elif len( args ) > 2:
+                main.log.warn( "%s - Too many arguments: %s" % ( self.typeString, args ) )
+                return EventStates().ABORT
+            if args[ 0 ] == 'random' or args[ 1 ] == 'random':
+                import random
+                if self.typeIndex == EventType().NETWORK_LINK_DOWN:
+                    with main.variableLock:
+                        graphHelper = GraphHelper()
+                        availableLinks = graphHelper.getNonCutEdges()
+                        if len( availableLinks ) == 0:
+                            main.log.warn( "All links are cut edges, aborting event" )
+                            return EventStates().ABORT
+                        linkList = random.sample( availableLinks, 1 )
+                        self.linkA = linkList[ 0 ]
+                        self.linkB = linkList[ 0 ].backwardLink
+                elif self.typeIndex == EventType().NETWORK_LINK_UP:
+                    with main.variableLock:
+                        downLinks = []
+                        for link in main.links:
+                            if link.isDown():
+                                downLinks.append( link )
+                        if len( downLinks ) == 0:
+                            main.log.warn( "None of the links are in 'down' state, aborting event" )
+                            return EventStates().ABORT
+                        linkList = random.sample( downLinks, 1 )
+                        self.linkA = linkList[ 0 ]
+                        self.linkB = linkList[ 0 ].backwardLink
+            elif args[ 0 ] == args[ 1 ]:
+                main.log.warn( "%s - invalid arguments: %s" % ( self.typeString, args ) )
+                return EventStates().ABORT
+            else:
+                for link in main.links:
+                    if link.deviceA.name == args[ 0 ] and link.deviceB.name == args[ 1 ]:
+                        self.linkA = link
+                    elif link.deviceA.name == args[ 1 ] and link.deviceB.name == args[ 0 ]:
+                        self.linkB = link
+                    if self.linkA != None and self.linkB != None:
+                        break
+                if self.linkA == None or self.linkB == None:
+                    main.log.warn( "Bidirectional link %s - %s does not exist: " % ( args[ 0 ], args[ 1 ] ) )
+                    return EventStates().ABORT
+            main.log.debug( "%s - %s" % ( self.typeString, self.linkA ) )
+            return self.startLinkEvent()
+
+class LinkDown( LinkEvent ):
+    """
+    Generate a link down event giving the two ends of the link
+    """
+    def __init__( self ):
+        LinkEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex = int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startLinkEvent( self ):
+        # TODO: do we need to handle a unidirectional link?
+        assert self.linkA != None and self.linkB != None
+        with main.variableLock:
+            if self.linkA.isDown() or self.linkB.isDown():
+                main.log.warn( "Link Down - link already down" )
+                return EventStates().ABORT
+            elif self.linkA.isRemoved() or self.linkB.isRemoved():
+                main.log.warn( "Link Down - link has been removed" )
+                return EventStates().ABORT
+        with main.mininetLock:
+            result = main.Mininet1.link( END1=self.linkA.deviceA.name,
+                                         END2=self.linkA.deviceB.name,
+                                         OPTION="down")
+        if not result:
+            main.log.warn( "%s - failed to bring down link" % ( self.typeString ) )
+            return EventStates().FAIL
+        with main.variableLock:
+            self.linkA.bringDown()
+            self.linkB.bringDown()
+        return EventStates().PASS
+
+class LinkUp( LinkEvent ):
+    """
+    Generate a link up event giving the two ends of the link
+    """
+    def __init__( self ):
+        LinkEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex = int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startLinkEvent( self ):
+        assert self.linkA != None and self.linkB != None
+        with main.variableLock:
+            if self.linkA.isUp() or self.linkB.isUp():
+                main.log.warn( "Link Up - link already up" )
+                return EventStates().ABORT
+            if self.linkA.isRemoved() or self.linkB.isRemoved():
+                main.log.warn( "Link Up - link has been removed" )
+                return EventStates().ABORT
+        with main.mininetLock:
+            result = main.Mininet1.link( END1=self.linkA.deviceA.name,
+                                         END2=self.linkA.deviceB.name,
+                                         OPTION="up")
+        if not result:
+            main.log.warn( "%s - failed to bring up link" % ( self.typeString ) )
+            return EventStates().FAIL
+        with main.variableLock:
+            self.linkA.bringUp()
+            self.linkB.bringUp()
+        return EventStates().PASS
+
+class DeviceEvent( Event ):
+    def __init__( self ):
+        Event.__init__( self )
+        self.device = None
+
+    def startDeviceEvent( self ):
+        return EventStates().PASS
+
+    def startEvent( self, args ):
+        """
+        args are the names of the device, e.g. 's1'
+        """
+        with self.eventLock:
+            main.log.info( "%s - starting event" % ( self.typeString ) )
+            if len( args ) < 1:
+                main.log.warn( "%s - Not enough arguments: %s" % ( self.typeString, args ) )
+                return EventStates().ABORT
+            elif len( args ) > 1:
+                main.log.warn( "%s - Too many arguments: %s" % ( self.typeString, args ) )
+                return EventStates().ABORT
+            if args[ 0 ] == 'random':
+                import random
+                if self.typeIndex == EventType().NETWORK_DEVICE_DOWN:
+                    with main.variableLock:
+                        graphHelper = GraphHelper()
+                        availableDevices = graphHelper.getNonCutVertices()
+                        if len( availableDevices ) == 0:
+                            main.log.warn( "All devices are cut vertices, aborting event" )
+                            return EventStates().ABORT
+                        deviceList = random.sample( availableDevices, 1 )
+                        self.device = deviceList[ 0 ]
+                elif self.typeIndex == EventType().NETWORK_DEVICE_UP:
+                    with main.variableLock:
+                        removedDevices = []
+                        for device in main.devices:
+                            if device.isRemoved():
+                                removedDevices.append( device )
+                        if len( removedDevices ) == 0:
+                            main.log.warn( "None of the devices are removed, aborting event" )
+                            return EventStates().ABORT
+                        deviceList = random.sample( removedDevices, 1 )
+                        self.device = deviceList[ 0 ]
+            else:
+                for device in main.devices:
+                    if device.name == args[ 0 ]:
+                        self.device = device
+                if self.device == None:
+                    main.log.warn( "Device %s does not exist: " % ( args[ 0 ] ) )
+                    return EventStates().ABORT
+            main.log.debug( "%s - %s" % ( self.typeString, self.device ) )
+            return self.startDeviceEvent()
+
+class DeviceDown( DeviceEvent ):
+    """
+    Generate a device down event (which actually removes this device for now) giving its name
+    """
+    def __init__( self ):
+        DeviceEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex = int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startDeviceEvent( self ):
+        assert self.device != None
+        with main.variableLock:
+            if self.device.isRemoved():
+                main.log.warn( "Device Down - device has been removed" )
+                return EventStates().ABORT
+        with main.mininetLock:
+            result = main.Mininet1.delSwitch( self.device.name )
+        if not result:
+            main.log.warn( "%s - failed to bring down device" % ( self.typeString ) )
+            return EventStates().FAIL
+        with main.variableLock:
+            self.device.setRemoved()
+            for link in self.device.outgoingLinks:
+                link.setRemoved()
+                link.backwardLink.setRemoved()
+            for host in self.device.hosts:
+                host.setRemoved()
+        return EventStates().PASS
+
+class DeviceUp( DeviceEvent ):
+    """
+    Generate a device up event (which re-adds this device in case the device is removed) giving its name
+    """
+    def __init__( self ):
+        DeviceEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex = int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startDeviceEvent( self ):
+        assert self.device != None
+        with main.variableLock:
+            if self.device.isUp():
+                main.log.warn( "Device Up - device already up" )
+                return EventStates().ABORT
+        # Re-add the device
+        with main.mininetLock:
+            result = main.Mininet1.addSwitch( self.device.name, dpid=self.device.dpid[3:] )
+        if not result:
+            main.log.warn( "%s - failed to re-add device" % ( self.typeString ) )
+            return EventStates().FAIL
+        with main.variableLock:
+            self.device.bringUp()
+        # Re-add links
+        # We add host-device links first since we did the same in mininet topology file
+        # TODO: a more rubust way is to add links according to the port info of the device
+        for host in self.device.hosts:
+            # Add host-device link
+            with main.mininetLock:
+                result = main.Mininet1.addLink( self.device.name, host.name )
+            if not result:
+                main.log.warn( "%s - failed to re-connect host %s to device" % ( self.typeString, host.name ) )
+                return EventStates().FAIL
+        for link in self.device.outgoingLinks:
+            neighbor = link.deviceB
+            # Skip bringing up any link that connecting this device to a removed neighbor
+            if neighbor.isRemoved():
+                continue
+            with main.mininetLock:
+                result = main.Mininet1.addLink( self.device.name, neighbor.name )
+            if not result:
+                main.log.warn( "%s - failed to re-add link to %s" % ( self.typeString, neighbor.name ) )
+                return EventStates().FAIL
+            with main.variableLock:
+                link.bringUp()
+                link.backwardLink.bringUp()
+        # Re-assign mastership for the device
+        with main.mininetLock:
+            main.Mininet1.assignSwController( sw=self.device.name, ip=main.onosIPs )
+        # Re-discover hosts
+        for host in self.device.hosts:
+            correspondent = None
+            for h in main.hosts:
+                if h.isUp() and h != host:
+                    correspondent = h
+                    break
+            if correspondent == None:
+                with main.mininetLock:
+                    main.Mininet1.pingall()
+                    if main.enableIPv6:
+                        main.Mininet1.pingall( protocol="IPv6" )
+            else:
+                ipv4Addr = None
+                ipv6Addr = None
+                for ipAddress in correspondent.ipAddresses:
+                    if ipAddress.startswith( str( main.params[ 'TEST' ][ 'ipv6Prefix' ] ) ) and ipv6Addr == None:
+                        ipv6Addr = ipAddress
+                    elif ipAddress.startswith( str( main.params[ 'TEST' ][ 'ipv4Prefix' ] ) ) and ipv4Addr == None:
+                        ipv4Addr = ipAddress
+                assert ipv4Addr != None
+                host.handle.pingHostSetAlternative( [ ipv4Addr ], 1 )
+                if main.enableIPv6:
+                    assert ipv6Addr != None
+                    host.handle.pingHostSetAlternative( [ ipv6Addr ], 1, True )
+            with main.variableLock:
+                host.bringUp()
+        return EventStates().PASS
diff --git a/TestON/tests/CHOTestMonkey/dependencies/events/ONOSEvent.py b/TestON/tests/CHOTestMonkey/dependencies/events/ONOSEvent.py
new file mode 100644
index 0000000..f4e2a89
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/events/ONOSEvent.py
@@ -0,0 +1,194 @@
+"""
+This file contains classes for CHOTestMonkey that are related to application event
+Author: you@onlab.us
+"""
+from tests.CHOTestMonkey.dependencies.events.Event import EventType, EventStates, Event
+
+class ONOSEvent( Event ):
+    def __init__( self ):
+        Event.__init__( self )
+        self.ONOSIndex = -1
+
+    def startEvent( self, args ):
+        with self.eventLock:
+            main.log.info( "%s - starting event" % ( self.typeString ) )
+            result = EventStates().PASS
+            if self.typeIndex == EventType().ONOS_ONOS_DOWN or self.typeIndex == EventType().ONOS_ONOS_UP:
+                if len( args ) < 1:
+                    main.log.warn( "%s - Not enough arguments: %s" % ( self.typeString, args ) )
+                    result = EventStates().ABORT
+                elif len( args ) > 1:
+                    main.log.warn( "%s - Too many arguments: %s" % ( self.typeString, args ) )
+                    result = EventStates().ABORT
+                else:
+                    index = int( args[ 0 ] )
+                    if index < 1 or index > int( main.numCtrls ):
+                        main.log.warn( "%s - invalid argument: %s" % ( self.typeString, index ) )
+                        result = EventStates().ABORT
+                    else:
+                        self.ONOSIndex = index
+                        result = self.startONOSEvent()
+            return result
+
+class ONOSDown( ONOSEvent ):
+    def __init__( self ):
+        ONOSEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex= int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startONOSEvent( self ):
+        assert self.ONOSIndex != -1
+        with main.variableLock:
+            if not main.controllers[ self.ONOSIndex - 1 ].isUp():
+                main.log.warn( "ONOS Down - ONOS already down" )
+                return EventStates().ABORT
+        with main.ONOSbenchLock:
+            result = main.ONOSbench.onosStop( main.controllers[ self.ONOSIndex - 1 ].ip )
+        if not result:
+            main.log.warn( "%s - failed to bring down ONOS" % ( self.typeString ) )
+            return EventStates().FAIL
+        with main.variableLock:
+            main.controllers[ self.ONOSIndex - 1 ].bringDown()
+        return EventStates().PASS
+
+class ONOSUp( ONOSEvent ):
+    def __init__( self ):
+        ONOSEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex= int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startONOSEvent( self ):
+        assert self.ONOSIndex != -1
+        with main.variableLock:
+            if main.controllers[ self.ONOSIndex - 1 ].isUp():
+                main.log.warn( "ONOS Up - ONOS already up" )
+                return EventStates().ABORT
+        with main.ONOSbenchLock:
+            startResult = main.ONOSbench.onosStart( main.controllers[ self.ONOSIndex - 1 ].ip )
+        if not startResult:
+            main.log.warn( "%s - failed to bring up ONOS" % ( self.typeString ) )
+            return EventStates().FAIL
+        else:
+            ONOSState = main.ONOSbench.isup( main.controllers[ self.ONOSIndex - 1 ].ip )
+            if not ONOSState:
+                main.log.warn( "%s - ONOS is not up" % ( self.typeString ) )
+                return EventStates().FAIL
+            else:
+                cliResult = main.controllers[ self.ONOSIndex - 1 ].startCLI()
+                if not cliResult:
+                    main.log.warn( "%s - failed to start ONOS cli" % ( self.typeString ) )
+                    return EventStates().FAIL
+                else:
+                    with main.variableLock:
+                        main.controllers[ self.ONOSIndex - 1 ].bringUp()
+        return EventStates().PASS
+
+class CfgEvent( Event ):
+    def __init__( self ):
+        Event.__init__( self )
+        self.component = ''
+        self.propName = ''
+        self.value = ''
+
+    def startEvent( self, args ):
+        with self.eventLock:
+            main.log.info( "%s - starting event" % ( self.typeString ) )
+            result = self.startCfgEvent( args )
+            return result
+
+class SetCfg( CfgEvent ):
+    def __init__( self ):
+        CfgEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex= int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startCfgEvent( self, args ):
+        if len( args ) < 3:
+            main.log.warn( "%s - Not enough arguments: %s" % ( self.typeString, args ) )
+            return EventStates().ABORT
+        elif len( args ) > 3:
+            main.log.warn( "%s - Too many arguments: %s" % ( self.typeString, args ) )
+            return EventStates().ABORT
+        else:
+            self.component = str( args[ 0 ] )
+            self.propName = str( args[ 1 ] )
+            self.value = str( args[ 2 ] )
+        assert self.component != '' and self.propName != '' and self.value != ''
+        index = -1
+        for controller in main.controllers:
+            if controller.isUp():
+                index = controller.index
+        if index == -1:
+            main.log.warn( "%s - No available controllers" %s ( self.typeString ) )
+            return EventStates().ABORT
+        controller = main.controllers[ index - 1 ]
+        with controller.CLILock:
+            result = controller.CLI.setCfg( component=self.component,
+                                            propName=self.propName,
+                                            value=self.value )
+        if not result:
+            main.log.warn( "%s - failed to set configuration" % ( self.typeString ) )
+            return EventStates().FAIL
+        return EventStates().PASS
+
+class SetFlowObj( CfgEvent ):
+    def __init__( self ):
+        CfgEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex= int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startCfgEvent( self, args ):
+        if len( args ) < 1:
+            main.log.warn( "%s - Not enough arguments: %s" % ( self.typeString, args ) )
+            return EventStates().ABORT
+        elif len( args ) > 1:
+            main.log.warn( "%s - Too many arguments: %s" % ( self.typeString, args ) )
+            return EventStates().ABORT
+        elif args[ 0 ] != 'true' and args[ 0 ] != 'false':
+            main.log.warn( "%s - Invalid arguments: %s" % ( self.typeString, args ) )
+            return EventStates().ABORT
+        else:
+            self.component = 'org.onosproject.net.intent.impl.compiler.IntentConfigurableRegistrator'
+            self.propName = 'useFlowObjectives'
+            self.value = str( args[ 0 ] )
+        index = -1
+        for controller in main.controllers:
+            if controller.isUp():
+                index = controller.index
+        if index == -1:
+            main.log.warn( "%s - No available controllers" %s ( self.typeString ) )
+            return EventStates().ABORT
+        controller = main.controllers[ index - 1 ]
+        with controller.CLILock:
+            result = controller.CLI.setCfg( component=self.component,
+                                            propName=self.propName,
+                                            value=self.value )
+        if not result:
+            main.log.warn( "%s - failed to set configuration" % ( self.typeString ) )
+            return EventStates().FAIL
+        return EventStates().PASS
+
+class BalanceMasters( Event ):
+    def __init__( self ):
+        Event.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex= int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startEvent( self, args=None ):
+        with self.eventLock:
+            main.log.info( "%s - starting event" % ( self.typeString ) )
+            index = -1
+            for controller in main.controllers:
+                if controller.isUp():
+                    index = controller.index
+            if index == -1:
+                main.log.warn( "%s - No available controllers" %s ( self.typeString ) )
+                return EventStates().ABORT
+            controller = main.controllers[ index - 1 ]
+            with controller.CLILock:
+                result = controller.CLI.balanceMasters()
+            if not result:
+                main.log.warn( "%s - failed to balance masters" % ( self.typeString ) )
+                return EventStates().FAIL
+            return EventStates().PASS
+
diff --git a/TestON/tests/CHOTestMonkey/dependencies/events/TestEvent.py b/TestON/tests/CHOTestMonkey/dependencies/events/TestEvent.py
new file mode 100644
index 0000000..605e43f
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/events/TestEvent.py
@@ -0,0 +1,59 @@
+"""
+This file contains classes for CHOTestMonkey that are related to check event
+Author: you@onlab.us
+"""
+from tests.CHOTestMonkey.dependencies.events.Event import EventType, EventStates, Event
+
+class TestEvent( Event ):
+    def __init__( self ):
+        Event.__init__( self )
+
+    def startTestEvent( self ):
+        return EventStates().PASS
+
+    def startEvent( self, args ):
+        with self.eventLock:
+            main.log.info( "%s - starting event" % ( self.typeString ) )
+            result = self.startTestEvent( args )
+            return result
+
+class TestPause( TestEvent ):
+    def __init__( self ):
+        TestEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex = int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startTestEvent( self, args=None ):
+        result = EventStates().PASS
+        main.eventScheduler.setRunningState( False )
+        return result
+
+class TestResume( TestEvent ):
+    def __init__( self ):
+        TestEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex = int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startTestEvent( self, args=None ):
+        result = EventStates().PASS
+        main.eventScheduler.setRunningState( True )
+        return result
+
+class TestSleep( TestEvent ):
+    def __init__( self ):
+        TestEvent.__init__( self )
+        self.typeString = main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeString' ]
+        self.typeIndex = int( main.params[ 'EVENT' ][ self.__class__.__name__ ][ 'typeIndex' ] )
+
+    def startTestEvent( self, args ):
+        import time
+        result = EventStates().PASS
+        if len( args ) < 1:
+            main.log.warn( "%s - Not enough arguments: %s" % ( self.typeString, args ) )
+            result = EventStates().ABORT
+        elif len( args ) > 1:
+            main.log.warn( "%s - Too many arguments: %s" % ( self.typeString, args ) )
+            result = EventStates().ABORT
+        sleepTime = int( args[ 0 ] )
+        time.sleep( sleepTime )
+        return result
diff --git a/TestON/tests/CHOTestMonkey/dependencies/events/__init__.py b/TestON/tests/CHOTestMonkey/dependencies/events/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/TestON/tests/CHOTestMonkey/dependencies/events/__init__.py