Tests for L2 load balancing feature.

Change-Id: Ic8fcd217c6ae383fa7097bc1288c0db9b2190e63
diff --git a/accton/accton_util.py b/accton/accton_util.py
index ef13c43..78d183e 100755
--- a/accton/accton_util.py
+++ b/accton/accton_util.py
@@ -160,6 +160,9 @@
 def encode_l2_unfiltered_group_id(id):
     return id + (11 << OFDPA_GROUP_TYPE_SHIFT)
 
+def encode_l2_loadbal_group_id(id):
+    return id + (12 << OFDPA_GROUP_TYPE_SHIFT)
+
 def encode_l2_overlay_group_id(tunnel_id, subtype, index):
     tunnel_id=tunnel_id&0xffff #16 bits
     subtype = subtype&3        #2 bits
@@ -306,6 +309,20 @@
     ctrl.message_send(request)
     return request
 
+def add_l2_flood_group_with_gids(ctrl, gids, vlanid, id):
+    buckets=[]
+    for gid in gids:
+        action=[ofp.action.group(gid)]
+        buckets.append(ofp.bucket(actions=action))
+
+    group_id =encode_l2_flood_group_id(vlanid, id)
+    request = ofp.message.group_add(group_type=ofp.OFPGT_ALL,
+                                    group_id=group_id,
+                                    buckets=buckets
+                                   )
+    ctrl.message_send(request)
+    return request
+
 def mod_l2_flood_group(ctrl, ports, vlanid, id):
     buckets=[]
     for of_port in ports:
@@ -396,6 +413,37 @@
     ctrl.message_send(request)
     return request
 
+
+def add_l2_loadbal_group(ctrl, id, l2_unfil_intf_groups, send_barrier=False):
+    buckets=[]
+    for group in l2_unfil_intf_groups:
+        buckets.append(ofp.bucket(actions=[ofp.action.group(group)]))
+
+    group_id=encode_l2_loadbal_group_id(id)
+    request = ofp.message.group_add(group_type=ofp.OFPGT_SELECT,
+                                   group_id=group_id,
+                                   buckets=buckets
+                                   )
+    ctrl.message_send(request)
+    if send_barrier:
+        do_barrier(ctrl)
+
+    return group_id, request
+
+def mod_l2_loadbal_group(ctrl, id, l2_unfil_intf_groups, send_barrier=False):
+    buckets=[]
+    for group in l2_unfil_intf_groups:
+        buckets.append(ofp.bucket(actions=[ofp.action.group(group)]))
+
+    group_id =encode_l2_loadbal_group_id(id)
+    request = ofp.message.group_modify(group_type=ofp.OFPGT_SELECT,
+                                    group_id=group_id,
+                                    buckets=buckets
+                                   )
+    ctrl.message_send(request)
+    return request
+
+
 def add_l3_ecmp_group(ctrl, id, l3_ucast_groups, send_barrier=False):
     buckets=[]
     for group in l3_ucast_groups:
@@ -2147,3 +2195,4 @@
         print "hard_timeout", obj.hard_timeout
         #obj.actions
         print "packet count: %lx"%obj.packet_count
+
diff --git a/ofdpa/flows.py b/ofdpa/flows.py
index 8bfd70c..004edd9 100755
--- a/ofdpa/flows.py
+++ b/ofdpa/flows.py
@@ -3452,3 +3452,346 @@
             delete_all_groups( self.controller )
 
 
+@disabled
+class Lag( base_tests.SimpleDataPlane ):
+    """
+    Checks the L2 load balancing (LAG) functionality of the ofdpa switches.
+    """
+    def runTest( self ):
+        Groups = Queue.LifoQueue( )
+        try:
+            if len( config[ "port_map" ] ) < 2:
+                logging.info( "Port count less than 2, can't run this case" )
+                return
+
+            ports = sorted(config[ "port_map" ].keys( ))
+            in_port = ports[0]
+            out_port = ports[1]
+
+            # add l2 interface unfiltered group
+            l2_o_gid, l2_o_msg = add_one_l2_unfiltered_group( self.controller, out_port, True)
+
+            # create l2 load-balance group
+            lag_gid, lag_msg = add_l2_loadbal_group(self.controller, 1, [l2_o_gid], True)
+            Groups.put(l2_o_gid, lag_gid)
+
+            # --- TEST 1: bridging with load-bal----
+            vlan_in_port_untagged = 31
+            # table 10 flows and bridging flows for dstMac+vlan -> l2-load-bal group
+            add_one_vlan_table_flow( self.controller, in_port, vlan_id=vlan_in_port_untagged, flag=VLAN_TABLE_FLAG_ONLY_BOTH,
+                                     send_barrier=True)
+            mac_dst = [ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 ]
+            mac_dst_str = '00:11:22:33:44:55'
+            add_bridge_flow( self.controller, mac_dst, vlan_in_port_untagged, lag_gid, True )
+            # untagged packet input becomes tagged packet at output
+            time.sleep(0.1)
+            pkt = str( simple_tcp_packet( pktlen=80, eth_dst=mac_dst_str ) )
+            exp_pkt = str( simple_tcp_packet( pktlen=84, dl_vlan_enable=True, vlan_vid=vlan_in_port_untagged, eth_dst=mac_dst_str ) )
+            self.dataplane.send( in_port, pkt )
+            verify_packet( self, exp_pkt, out_port )
+            verify_no_other_packets( self )
+
+            # --- TEST 2: flooding with load-bal ----
+            # flooding group --> load-bal group --> l2 unfiltered interface
+            floodmsg = add_l2_flood_group_with_gids( self.controller, [lag_gid] , vlan_in_port_untagged, id=0 )
+            Groups.put( floodmsg.group_id )
+            # add bridging flows for flooding groups
+            add_bridge_flow( self.controller, dst_mac=None, vlanid=vlan_in_port_untagged, group_id=floodmsg.group_id )
+            # unknown dstmac  packet input to hit flood group
+            un_mac_dst_str = '00:11:22:ff:ff:ff'
+            pkt = str( simple_tcp_packet( pktlen=80, eth_dst=un_mac_dst_str ) )
+            exp_pkt = str( simple_tcp_packet( pktlen=84, dl_vlan_enable=True, vlan_vid=vlan_in_port_untagged, eth_dst=un_mac_dst_str ) )
+            time.sleep(0.1)
+            self.dataplane.send( in_port, pkt )
+            verify_packet( self, exp_pkt, out_port )
+            verify_no_other_packets( self )
+
+            # --- TEST 3: editing load-bal ----
+            # create and add another l2 unfiltered interface group to the load-balancing group
+            l2_i_gid, l2_i_msg = add_one_l2_unfiltered_group( self.controller, in_port, True)
+            msg = mod_l2_loadbal_group(self.controller, 1, [l2_o_gid, l2_i_gid], True)
+            self.dataplane.send( in_port, pkt )
+            verify_packet( self, exp_pkt, out_port )
+
+            # delete all buckets in loadbal group
+            msg = mod_l2_loadbal_group(self.controller, 1, [], True)
+            time.sleep(0.1)
+            self.dataplane.send( in_port, pkt )
+            verify_no_other_packets( self ) # no buckets
+
+            # only input port in load balancing group
+            msg = mod_l2_loadbal_group(self.controller, 1, [l2_i_gid], True)
+            self.dataplane.send( in_port, pkt )
+            verify_no_other_packets( self ) # packet should not be sent out of in port
+
+            # remove input port and add output port in lag group
+            msg = mod_l2_loadbal_group(self.controller, 1, [l2_o_gid], True)
+            time.sleep(0.1)
+            self.dataplane.send( in_port, pkt )
+            verify_packet( self, exp_pkt, out_port )
+            verify_no_other_packets( self )
+
+        finally:
+            #print("done")
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
+
+@disabled
+class FloodMix( base_tests.SimpleDataPlane ):
+    """
+    Checks the combination of L2 filtered and L2 load balancing (LAG) groups
+    in a flooding group
+    """
+    def runTest( self ):
+        Groups = Queue.LifoQueue( )
+        try:
+            if len( config[ "port_map" ] ) < 2:
+                logging.info( "Port count less than 2, can't run this case" )
+                return
+
+            ports = sorted(config[ "port_map" ].keys( ))
+            in_port = ports[0]
+            out_port = ports[1]
+            vlan_in_port_untagged = 31
+
+            # add l2 unfiltered interface group for outport
+            l2_o_gid, l2_o_msg = add_one_l2_unfiltered_group( self.controller, out_port, True)
+
+            # create l2 load-balance group
+            lag_gid, lag_msg = add_l2_loadbal_group(self.controller, 1, [l2_o_gid], True)
+
+            # create l2 interface (filtered) group for inport
+            l2_i_gid, l2_i_msg = add_one_l2_interface_group( self.controller, in_port, vlan_in_port_untagged,
+                                                             is_tagged=False, send_barrier=True)
+
+            # create l2 flood group with mix of l2i and l2-loadbal
+            floodmsg = add_l2_flood_group_with_gids( self.controller, [lag_gid, l2_i_gid] , vlan_in_port_untagged, id=0 )
+            Groups.put( l2_o_gid )
+            Groups.put( lag_gid )
+            Groups.put( l2_i_gid )
+            Groups.put( floodmsg.group_id )
+
+            # table 10 flows for untagged input
+            add_one_vlan_table_flow( self.controller, in_port, vlan_id=vlan_in_port_untagged, flag=VLAN_TABLE_FLAG_ONLY_BOTH,
+                                     send_barrier=True)
+
+            # add bridging flows for flooding groups
+            add_bridge_flow( self.controller, dst_mac=None, vlanid=vlan_in_port_untagged, group_id=floodmsg.group_id )
+            # unknown dstmac packet will hit flood group
+            un_mac_dst_str = '00:11:22:ff:ff:ff'
+
+            # check one direction -> packet enters through filtered port (inport) and exits through
+            # unfiltered port (outport) via the flood and lag groups
+            pkt = str( simple_tcp_packet( pktlen=80, eth_dst=un_mac_dst_str ) )
+            exp_pkt = str( simple_tcp_packet( pktlen=84, dl_vlan_enable=True, vlan_vid=vlan_in_port_untagged, eth_dst=un_mac_dst_str ) )
+            self.dataplane.send( in_port, pkt )
+            verify_packet( self, exp_pkt, out_port )
+            verify_no_other_packets( self )
+
+            # check other direction -> packet enters through unfiltered port (outport) and exits through
+            # filtered port (inport) via the flood group
+            add_one_vlan_table_flow( self.controller, out_port, vlan_id=vlan_in_port_untagged, flag=VLAN_TABLE_FLAG_ONLY_BOTH,
+                                     send_barrier=True)
+            pkt = str( simple_tcp_packet( pktlen=80, eth_dst=un_mac_dst_str ) )
+            exp_pkt = pkt # ingress packet gets internal vlan 31 which gets popped at l2 interface group before egress
+            self.dataplane.send( out_port, pkt )
+            verify_packet( self, exp_pkt, in_port )
+            verify_no_other_packets( self )
+
+        finally:
+            print("done")
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
+
+@disabled
+class LagMix( base_tests.SimpleDataPlane ):
+    """
+    Checks the combination of L2 filtered and unfiltered interface groups
+    in a L2 load balancing (LAG) group - this should not work according to spec
+    """
+    def runTest( self ):
+        Groups = Queue.LifoQueue( )
+        try:
+            if len( config[ "port_map" ] ) < 2:
+                logging.info( "Port count less than 2, can't run this case" )
+                return
+
+            ports = sorted(config[ "port_map" ].keys( ))
+            in_port = ports[0]
+            out_port = ports[1]
+            vlan_in_port_untagged = 31
+
+            # add l2 unfiltered interface group
+            l2_o_gid, l2_o_msg = add_one_l2_unfiltered_group( self.controller, out_port, True)
+
+            # create  l2 interface (filtered) group
+            l2_i_gid, l2_i_msg = add_one_l2_interface_group( self.controller, in_port, vlan_in_port_untagged,
+                                                             is_tagged=False, send_barrier=True)
+
+            # create l2 load-balance group with a mix of filtered and unfiltered groups
+            """
+            XXX the following does not work - the group does not get created but curiously we don't get an openflow
+            error message. The ofdpa logs show
+            ofdbGroupBucketValidate: Referenced Group Id 0x1f000c not of type L2 Unfiltered Interface.
+            We do get an openflow error message for the flood group that follows because it cannot
+            find the lag group it points to (because it did not get created).
+            """
+            lag_gid, lag_msg = add_l2_loadbal_group(self.controller, 1, [l2_o_gid, l2_i_gid], True)
+
+            # create l2 flood group to point to lag group
+            floodmsg = add_l2_flood_group_with_gids( self.controller, [lag_gid] , vlan_in_port_untagged, id=0 )
+            Groups.put( floodmsg.group_id, l2_i_gid, lag_gid )
+            Groups.put( l2_o_gid )
+
+        finally:
+            #print("done")
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
+
+
+@disabled
+class LagXconn( base_tests.SimpleDataPlane ):
+    """
+    Checks the L2 load balancing (LAG) with vlan crossconnects.
+    Note that for this to work, basic VlanCrossConnect test above (without LAG) should work first
+    Note: this doesn't work on XGS or QMX yet with premium 1.1.7
+    """
+    def runTest( self ):
+        Groups = Queue.LifoQueue( )
+        try:
+            if len( config[ "port_map" ] ) < 2:
+                logging.info( "Port count less than 2, can't run this case" )
+                return
+
+            ports = sorted(config[ "port_map" ].keys( ))
+            in_port = ports[0]
+            out_port = ports[1]
+
+            # add l2 interface unfiltered group
+            l2_o_gid, l2_o_msg = add_one_l2_unfiltered_group( self.controller, out_port, True)
+
+            # create l2 load-balance group
+            lag_gid, lag_msg = add_l2_loadbal_group(self.controller, 1, [l2_o_gid], True)
+            Groups.put(l2_o_gid, lag_gid)
+
+            # a subscriber [id, ip in hex, inner_vlan, outer_vlan, ip in dot form]
+            sub_info = [10, 0xc0a80001, 12, 11, "192.168.0.1"]
+
+            input_src_mac = [ 0x00, 0x00, 0x00, 0xcc, 0xcc, sub_info[0] ]
+            input_src_mac_str = ':'.join( [ '%02X' % x for x in input_src_mac ] )
+            input_dst_mac = [ 0x00, 0x00, 0x00, 0x22, 0x22, 0x00 ]
+            input_dst_mac_str = ':'.join( [ '%02X' % x for x in input_dst_mac ] )
+            output_dst_mac = [ 0x00, 0x00, 0x00, 0x33, 0x33, 0x00 ]
+            output_dst_mac_str = ':'.join( [ '%02X' % x for x in output_dst_mac ] )
+
+            dip = sub_info[1]
+            ports = config[ "port_map" ].keys( )
+            inner_vlan = sub_info[2]
+            outer_vlan = sub_info[3]
+            index = inner_vlan
+            port = ports[0]
+            out_port = ports[1]
+
+            # add vlan flow table
+            add_one_vlan_table_flow( self.controller, port, inner_vlan, outer_vlan, vrf=0, flag=VLAN_TABLE_FLAG_ONLY_STACKED )
+            add_one_vlan_1_table_flow_pw( self.controller, port, index, new_outer_vlan_id=outer_vlan, outer_vlan_id=outer_vlan,
+                                          inner_vlan_id=inner_vlan, cross_connect=True, send_barrier=True)
+            """
+            XXX The following flow in table 13 is rejected by the switch (qmx) probably due to the reference to lag group
+            ofdpa logs say: 08-22 00:43:10.344338 [ofstatemanager] Error from Forwarding while inserting flow: Unknown error
+            """
+            add_mpls_l2_port_flow(ctrl=self.controller, of_port=port, mpls_l2_port=port, tunnel_index=index, ref_gid=lag_gid,
+                                  qos_index=0, goto=ACL_FLOW_TABLE)
+            do_barrier( self.controller )
+
+            ip_src = sub_info[4]
+            ip_dst = '192.168.0.{}'.format(sub_info[0])
+            parsed_pkt = qinq_tcp_packet( pktlen=120, vlan_vid=inner_vlan, dl_vlan_outer=outer_vlan,
+                                          eth_dst=input_dst_mac_str, eth_src=input_src_mac_str, ip_ttl=64, ip_src=ip_src, ip_dst=ip_dst )
+            parsed_pkt = simple_tcp_packet_two_vlan( pktlen=120, out_dl_vlan_enable=True, in_dl_vlan_enable=True,
+                                                     in_vlan_vid=inner_vlan, out_vlan_vid=outer_vlan,
+                                                     eth_dst=input_dst_mac_str, eth_src=input_src_mac_str,
+                                                     ip_ttl=64, ip_src=ip_src, ip_dst=ip_dst )
+
+            pkt = str( parsed_pkt )
+            self.dataplane.send( port, pkt )
+            verify_packet( self, pkt, out_port )
+            verify_no_other_packets( self )
+
+        finally:
+            #print("done")
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
+
+@disabled
+class LagRouting( base_tests.SimpleDataPlane ):
+    """
+    Checks the L2 load balancing (LAG) with routing flows.
+    Specifically route -> L3Unicast -> Lag -> L2 Unfiltered interface
+    """
+    def runTest( self ):
+        Groups = Queue.LifoQueue( )
+        try:
+            if len( config[ "port_map" ] ) < 2:
+                logging.info( "Port count less than 2, can't run this case" )
+                return
+
+            ports = sorted(config[ "port_map" ].keys( ))
+            in_port = ports[0]
+            out_port = ports[1]
+
+            # add l2 interface unfiltered group
+            l2_o_gid, l2_o_msg = add_one_l2_unfiltered_group( self.controller, out_port, True)
+
+            # create l2 load-balance group
+            lag_gid, lag_msg = add_l2_loadbal_group(self.controller, 1, [l2_o_gid], True)
+            Groups.put(l2_o_gid, lag_gid)
+
+            intf_src_mac = [ 0x00, 0x00, 0x00, 0xcc, 0xcc, 0xcc ]
+            dst_mac = [ 0x00, 0x00, 0x00, 0x22, 0x22, 0x00 ]
+            dip = 0xc0a80001
+            vlan_id = 31
+
+            # create L3 Unicast group to point to Lag group
+            l3_msg = add_l3_unicast_group( self.controller, out_port, vlanid=vlan_id, id=vlan_id,
+                                           src_mac=intf_src_mac, dst_mac=dst_mac, gid=lag_gid )
+            # add vlan flow table
+            add_one_vlan_table_flow( self.controller, in_port, 1, vlan_id, flag=VLAN_TABLE_FLAG_ONLY_TAG )
+            # add termination flow
+            if config["switch_type"] == "qmx":
+                add_termination_flow( self.controller, 0, 0x0800, intf_src_mac, vlan_id )
+            else:
+                add_termination_flow( self.controller, in_port, 0x0800, intf_src_mac, vlan_id )
+            # add unicast routing flow
+            add_unicast_routing_flow( self.controller, 0x0800, dip, 0xffffffff, l3_msg.group_id )
+
+            Groups.put( l3_msg.group_id )
+            do_barrier( self.controller )
+
+            switch_mac = ':'.join( [ '%02X' % x for x in intf_src_mac ] )
+            mac_src = '00:00:00:22:22:%02X' % (in_port)
+            ip_src = '192.168.0.2'
+            ip_dst = '192.168.0.1'
+            parsed_pkt = simple_tcp_packet( pktlen=100, dl_vlan_enable=True,
+                                            vlan_vid=vlan_id, eth_dst=switch_mac, eth_src=mac_src, ip_ttl=64,
+                                            ip_src=ip_src, ip_dst=ip_dst )
+            pkt = str( parsed_pkt )
+            self.dataplane.send( in_port, pkt )
+            # build expected packet
+            mac_dst = '00:00:00:22:22:00'
+            exp_pkt = simple_tcp_packet( pktlen=100, dl_vlan_enable=True,
+                                         vlan_vid=vlan_id, eth_dst=mac_dst, eth_src=switch_mac, ip_ttl=63,
+                                         ip_src=ip_src, ip_dst=ip_dst )
+            pkt = str( exp_pkt )
+            verify_packet( self, pkt, out_port )
+            verify_no_other_packets( self )
+
+        finally:
+            #print("done")
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )