[CORD-381] Implement L3McastToL2 tests

- Be careful with 224.1.1.1

Change-Id: I3f233315520be19ce062363898a4bb829531e3b1
diff --git a/README.md b/README.md
index c90c160..74ef3b6 100755
--- a/README.md
+++ b/README.md
@@ -90,30 +90,40 @@
 
 The following tests are implemented and these are their results.
 
-Test Results     | i12_1.7 | 2.0 GA | 3.0 EA0
--------          | ------- | ------ | -------
-/0Ucast          | X       | ok     | ok
-/24UnicastTagged | ok      | ok     | ok
-/32UnicastTagged | ok      | ok     | ok
-/24ECMPL3        | ok      | ok     | ok
-/32ECMPL3        | ok      | ok     | ok
-/24ECMPVPN       | ok      | ok     | ok
-/32ECMPVPN       | ok      | ok     | ok
-/32VPN           | ok      | ok     | ok
-/24VPN           | ok      | ok     | ok
-EcmpGroupMod     | X       | X      | ok
-PacketInArp      | ok      | ok     | ok
-MTU1500          | ok      | ok     | ok
-MplsTermination  | ok      | ok     | ok
-MplsFwd          | X       | ok     | ok
-L2FloodQinQ      | ok      | ok     | ok
-L2UnicastTagged  | ok      | ok     | ok
-L3McastToL3      | ok      | X      | ok
-L3McastToL2      | ok      | X      | X
-FloodGroupMod    | X       | X      | ok
-PacketInUDP      | ok      | ok     | ok
-Unfiltered       | X       | ok     | X
-Untagged         | n/a     | n/a    | ok
+Test Results       | i12_1.7 | 2.0 GA | 3.0 EA0
+-------            | ------- | ------ | -------
+/0Ucast            | X       | ok     | ok
+/24UnicastTagged   | ok      | ok     | ok
+/32UnicastTagged   | ok      | ok     | ok
+/24ECMPL3          | ok      | ok     | ok
+/32ECMPL3          | ok      | ok     | ok
+/24ECMPVPN         | ok      | ok     | ok
+/32ECMPVPN         | ok      | ok     | ok
+/32VPN             | ok      | ok     | ok
+/24VPN             | ok      | ok     | ok
+EcmpGroupMod       | X       | X      | ok
+PacketInArp        | ok      | ok     | ok
+MTU1500            | ok      | ok     | ok
+MplsTermination    | ok      | ok     | ok
+MplsFwd            | X       | ok     | ok
+L2FloodQinQ        | ok      | ok     | ok
+L2UnicastTagged    | ok      | ok     | ok
+L3McastToL3        | ok      | X      | ok
+L3McastToL2_1*     | ?       | ?      | ok
+L3McastToL2_2**    | ?       | ?      | ok
+L3McastToL2_3***   | ?       | ?      | ok
+L3McastToL2_4****  | ok      | ?      | ok
+L3McastToL2_5***** | ?       | ?      | ok
+FloodGroupMod      | X       | X      | ok
+PacketInUDP        | ok      | ok     | ok
+Unfiltered         | X       | ok     | X
+Untagged           | ok      | n/a    | ok
+
+*       Untag -> Untag (4094 as internal vlan)
+**      Untag -> Tag
+***     Tag   -> Untag
+****    Tag   -> Tag
+*****   Tag   -> Tag (Translated)
 
 n/a means test is not available for that version of the pipeline.
 
diff --git a/accton/accton_util.py b/accton/accton_util.py
index 46e04c5..cf1634c 100755
--- a/accton/accton_util.py
+++ b/accton/accton_util.py
@@ -626,6 +626,36 @@
     logging.info("Add allow all vlan on port %d " %(in_port))
     ctrl.message_send(request)
 
+def add_one_vlan_table_flow_translation(ctrl, of_port, vlan_id=1, new_vlan_id=-1, vrf=0, flag=VLAN_TABLE_FLAG_ONLY_BOTH, send_barrier=False):
+    # Install a flow for VLAN translation
+    # in VLAN table.
+    # table 10: vlan
+    # goto to table 20
+    if (flag == VLAN_TABLE_FLAG_ONLY_TAG) or (flag == VLAN_TABLE_FLAG_ONLY_BOTH) or (flag == 4):
+        match = ofp.match()
+        match.oxm_list.append(ofp.oxm.in_port(of_port))
+        match.oxm_list.append(ofp.oxm.vlan_vid_masked(0x1000+vlan_id,0x1fff))
+
+        actions=[]
+        if vrf!=0:
+            actions.append(ofp.action.set_field(ofp.oxm.exp2ByteValue(exp_type=1, value=vrf)))
+        if new_vlan_id != -1:
+            actions.append(ofp.action.set_field(ofp.oxm.vlan_vid(0x1000+new_vlan_id)))
+
+        request = ofp.message.flow_add(
+            table_id=10,
+            cookie=42,
+            match=match,
+            instructions=[
+                ofp.instruction.apply_actions(
+                     actions=actions
+                ),
+                ofp.instruction.goto_table(20)
+            ],
+            priority=0)
+        logging.info("Add vlan %d tagged packets on port %d and go to table 20" %( vlan_id, of_port))
+        ctrl.message_send(request)
+
 def add_one_vlan_table_flow(ctrl, of_port, vlan_id=1, vrf=0, flag=VLAN_TABLE_FLAG_ONLY_BOTH, send_barrier=False):
     # table 10: vlan
     # goto to table 20
diff --git a/ofdpa/flows.py b/ofdpa/flows.py
index dc5d2e9..675b284 100755
--- a/ofdpa/flows.py
+++ b/ofdpa/flows.py
@@ -943,6 +943,9 @@
     different ports. 4094-port is used as egress vlan_id.
     """
     def runTest( self ):
+        """
+        port1 (vlan 300)-> All Ports (vlan 300)
+        """
         Groups = Queue.LifoQueue( )
         try:
         # We can forward on the in_port but egress_vlan has to be different from ingress_vlan
@@ -1001,6 +1004,354 @@
 
                 verify_no_other_packets( self )
 
+            parsed_pkt = simple_udp_packet( pktlen=100, dl_vlan_enable=True, vlan_vid=vlan_id,
+                    eth_dst=dst_mac_str, eth_src=port1_mac_str, ip_ttl=64, ip_src=src_ip_str,
+                    ip_dst=dst_ip_str )
+            pkt = str( parsed_pkt )
+            self.dataplane.send( port1, pkt )
+            for port in config[ "port_map" ].keys( ):
+                if port == port2 or port == port1:
+                    verify_no_packet( self, pkt, port )
+                    continue
+                verify_packet( self, pkt, port )
+            verify_no_other_packets( self )
+        finally:
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
+
+class L3McastToL2UntagToUntag( base_tests.SimpleDataPlane ):
+    """
+    Mcast routing, in this test case the traffic is untagged.
+    4094 is used as internal vlan_id. The packet goes out
+    untagged.
+    """
+    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" )
+                assert (False)
+                return
+            ports      = config[ "port_map" ].keys( )
+            dst_ip_str = "224.0.0.1"
+            (
+                port_to_in_vlan,
+                port_to_out_vlan,
+                port_to_src_mac_str,
+                port_to_dst_mac_str,
+                port_to_src_ip_str,
+                Groups) = fill_mcast_pipeline_L3toL2(
+                self.controller,
+                logging,
+                ports,
+                is_ingress_tagged   = False,
+                is_egress_tagged    = False,
+                is_vlan_translated  = False,
+                is_max_vlan         = True
+                )
+
+            for in_port in ports:
+
+                parsed_pkt = simple_udp_packet(
+                    pktlen  = 96,
+                    eth_dst = port_to_dst_mac_str[in_port],
+                    eth_src = port_to_src_mac_str[in_port],
+                    ip_ttl  = 64,
+                    ip_src  = port_to_src_ip_str[in_port],
+                    ip_dst  = dst_ip_str
+                    )
+                pkt = str( parsed_pkt )
+                self.dataplane.send( in_port, pkt )
+
+                for out_port in ports:
+
+                    parsed_pkt = simple_udp_packet(
+                        pktlen  = 96,
+                        eth_dst = port_to_dst_mac_str[in_port],
+                        eth_src = port_to_src_mac_str[in_port],
+                        ip_ttl  = 64,
+                        ip_src  = port_to_src_ip_str[in_port],
+                        ip_dst  = dst_ip_str
+                        )
+                    pkt = str( parsed_pkt )
+                    if out_port == in_port:
+                        verify_no_packet( self, pkt, in_port )
+                        continue
+                    verify_packet( self, pkt, out_port )
+                    verify_no_other_packets( self )
+        finally:
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
+
+class L3McastToL2UntagToTag( base_tests.SimpleDataPlane ):
+    """
+    Mcast routing, in this test case the traffic is untagged.
+    300 is used as vlan_id. The packet goes out
+    tagged.
+    """
+    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" )
+                assert (False)
+                return
+            ports      = config[ "port_map" ].keys( )
+            dst_ip_str = "224.0.0.1"
+            (
+                port_to_in_vlan,
+                port_to_out_vlan,
+                port_to_src_mac_str,
+                port_to_dst_mac_str,
+                port_to_src_ip_str,
+                Groups) = fill_mcast_pipeline_L3toL2(
+                self.controller,
+                logging,
+                ports,
+                is_ingress_tagged   = False,
+                is_egress_tagged    = True,
+                is_vlan_translated  = False,
+                is_max_vlan         = False
+                )
+
+            for in_port in ports:
+
+                parsed_pkt = simple_udp_packet(
+                    pktlen  = 96,
+                    eth_dst = port_to_dst_mac_str[in_port],
+                    eth_src = port_to_src_mac_str[in_port],
+                    ip_ttl  = 64,
+                    ip_src  = port_to_src_ip_str[in_port],
+                    ip_dst  = dst_ip_str
+                    )
+                pkt = str( parsed_pkt )
+                self.dataplane.send( in_port, pkt )
+
+                for out_port in ports:
+
+                    parsed_pkt = simple_udp_packet(
+                        pktlen          = 100,
+                        dl_vlan_enable  = True,
+                        vlan_vid        = port_to_out_vlan[in_port],
+                        eth_dst         = port_to_dst_mac_str[in_port],
+                        eth_src         = port_to_src_mac_str[in_port],
+                        ip_ttl          = 64,
+                        ip_src          = port_to_src_ip_str[in_port],
+                        ip_dst          = dst_ip_str
+                        )
+                    pkt = str( parsed_pkt )
+                    if out_port == in_port:
+                        verify_no_packet( self, pkt, in_port )
+                        continue
+                    verify_packet( self, pkt, out_port )
+                    verify_no_other_packets( self )
+        finally:
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
+
+
+class L3McastToL2TagToUntag( base_tests.SimpleDataPlane ):
+    """
+    Mcast routing, in this test case the traffic is tagged.
+    300 is used as vlan_id. The packet goes out
+    untagged.
+    """
+    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" )
+                assert (False)
+                return
+            ports      = config[ "port_map" ].keys( )
+            dst_ip_str = "224.0.0.1"
+            (
+                port_to_in_vlan,
+                port_to_out_vlan,
+                port_to_src_mac_str,
+                port_to_dst_mac_str,
+                port_to_src_ip_str,
+                Groups) = fill_mcast_pipeline_L3toL2(
+                self.controller,
+                logging,
+                ports,
+                is_ingress_tagged   = True,
+                is_egress_tagged    = False,
+                is_vlan_translated  = False,
+                is_max_vlan         = False
+                )
+
+            for in_port in ports:
+
+                parsed_pkt = simple_udp_packet(
+                    pktlen         = 100,
+                    dl_vlan_enable = True,
+                    vlan_vid       = port_to_in_vlan[in_port],
+                    eth_dst        = port_to_dst_mac_str[in_port],
+                    eth_src        = port_to_src_mac_str[in_port],
+                    ip_ttl         = 64,
+                    ip_src         = port_to_src_ip_str[in_port],
+                    ip_dst         = dst_ip_str
+                    )
+                pkt = str( parsed_pkt )
+                self.dataplane.send( in_port, pkt )
+
+                for out_port in ports:
+
+                    parsed_pkt = simple_udp_packet(
+                        pktlen          = 96,
+                        eth_dst         = port_to_dst_mac_str[in_port],
+                        eth_src         = port_to_src_mac_str[in_port],
+                        ip_ttl          = 64,
+                        ip_src          = port_to_src_ip_str[in_port],
+                        ip_dst          = dst_ip_str
+                        )
+                    pkt = str( parsed_pkt )
+                    if out_port == in_port:
+                        verify_no_packet( self, pkt, in_port )
+                        continue
+                    verify_packet( self, pkt, out_port )
+                    verify_no_other_packets( self )
+        finally:
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
+
+class L3McastToL2TagToTag( base_tests.SimpleDataPlane ):
+    """
+    Mcast routing, in this test case the traffic is tagged.
+    300 is used as vlan_id. The packet goes out tagged.
+    """
+    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" )
+                assert (False)
+                return
+            ports      = config[ "port_map" ].keys( )
+            dst_ip_str = "224.0.0.1"
+            (
+                port_to_in_vlan,
+                port_to_out_vlan,
+                port_to_src_mac_str,
+                port_to_dst_mac_str,
+                port_to_src_ip_str,
+                Groups) = fill_mcast_pipeline_L3toL2(
+                self.controller,
+                logging,
+                ports,
+                is_ingress_tagged   = True,
+                is_egress_tagged    = True,
+                is_vlan_translated  = False,
+                is_max_vlan         = False
+                )
+
+            for in_port in ports:
+
+                parsed_pkt = simple_udp_packet(
+                    pktlen         = 100,
+                    dl_vlan_enable = True,
+                    vlan_vid       = port_to_in_vlan[in_port],
+                    eth_dst        = port_to_dst_mac_str[in_port],
+                    eth_src        = port_to_src_mac_str[in_port],
+                    ip_ttl         = 64,
+                    ip_src         = port_to_src_ip_str[in_port],
+                    ip_dst         = dst_ip_str
+                    )
+                pkt = str( parsed_pkt )
+                self.dataplane.send( in_port, pkt )
+
+                for out_port in ports:
+
+                    parsed_pkt = simple_udp_packet(
+                        pktlen         = 100,
+                        dl_vlan_enable = True,
+                        vlan_vid       = port_to_in_vlan[in_port],
+                        eth_dst        = port_to_dst_mac_str[in_port],
+                        eth_src        = port_to_src_mac_str[in_port],
+                        ip_ttl         = 64,
+                        ip_src         = port_to_src_ip_str[in_port],
+                        ip_dst         = dst_ip_str
+                        )
+                    pkt = str( parsed_pkt )
+                    if out_port == in_port:
+                        verify_no_packet( self, pkt, in_port )
+                        continue
+                    verify_packet( self, pkt, out_port )
+                    verify_no_other_packets( self )
+        finally:
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
+
+class L3McastToL2TagToTagTranslated( base_tests.SimpleDataPlane ):
+    """
+    Mcast routing, in this test case the traffic is tagged.
+    port+1 is used as ingress vlan_id. The packet goes out
+    tagged. 4094-port is used as egress vlan_id
+    """
+    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" )
+                assert (False)
+                return
+            ports      = config[ "port_map" ].keys( )
+            dst_ip_str = "224.0.0.1"
+            (
+                port_to_in_vlan,
+                port_to_out_vlan,
+                port_to_src_mac_str,
+                port_to_dst_mac_str,
+                port_to_src_ip_str,
+                Groups) = fill_mcast_pipeline_L3toL2(
+                self.controller,
+                logging,
+                ports,
+                is_ingress_tagged   = True,
+                is_egress_tagged    = True,
+                is_vlan_translated  = True,
+                is_max_vlan         = False
+                )
+
+            for in_port in ports:
+
+                parsed_pkt = simple_udp_packet(
+                    pktlen         = 100,
+                    dl_vlan_enable = True,
+                    vlan_vid       = port_to_in_vlan[in_port],
+                    eth_dst        = port_to_dst_mac_str[in_port],
+                    eth_src        = port_to_src_mac_str[in_port],
+                    ip_ttl         = 64,
+                    ip_src         = port_to_src_ip_str[in_port],
+                    ip_dst         = dst_ip_str
+                    )
+                pkt = str( parsed_pkt )
+                self.dataplane.send( in_port, pkt )
+
+                for out_port in ports:
+
+                    parsed_pkt = simple_udp_packet(
+                        pktlen         = 100,
+                        dl_vlan_enable = True,
+                        vlan_vid       = port_to_out_vlan[in_port],
+                        eth_dst        = port_to_dst_mac_str[in_port],
+                        eth_src        = port_to_src_mac_str[in_port],
+                        ip_ttl         = 64,
+                        ip_src         = port_to_src_ip_str[in_port],
+                        ip_dst         = dst_ip_str
+                        )
+                    pkt = str( parsed_pkt )
+                    if out_port == in_port:
+                        verify_no_packet( self, pkt, in_port )
+                        continue
+                    verify_packet( self, pkt, out_port )
+                    verify_no_other_packets( self )
         finally:
             delete_all_flows( self.controller )
             delete_groups( self.controller, Groups )
diff --git a/ofdpa/utils.py b/ofdpa/utils.py
index 5bbce7f..7fd78a0 100644
--- a/ofdpa/utils.py
+++ b/ofdpa/utils.py
@@ -3,6 +3,126 @@
 from oftest.testutils import *
 from accton_util import *
 
+def fill_mcast_pipeline_L3toL2(
+    controller,
+    logging,
+    ports,
+    is_ingress_tagged,
+    is_egress_tagged,
+    is_vlan_translated,
+    is_max_vlan
+    ):
+    """
+    This method, according to the scenario, fills properly
+    the pipeline. The method generates using ports data the
+    necessary information to fill the multicast pipeline and
+    fills properly the pipeline which consists in this scenario:
+
+    i) to create l2 interface groups;
+    ii) to create l3 multicast groups;
+    iii) to add multicast flows;
+    iv) to add termination; flows;
+    v) to add vlan flows
+
+    Scenarios:
+    1) ingress untagged, egress untagged
+    2) ingress untagged, egress tagged
+    3) ingress tagged, egress untagged
+    4) ingress tagged, egress tagged, no translation
+    5) ingress tagged, egress tagged, translation
+    """
+
+    MAX_INTERNAL_VLAN           = 4094
+    # Used for no translation
+    FIXED_VLAN                  = 300
+    Groups                      = Queue.LifoQueue( )
+    L2_Groups                   = []
+    port_to_in_vlan             = {}
+    port_to_out_vlan            = {}
+    port_to_src_mac             = {}
+    port_to_src_mac_str         = {}
+    port_to_dst_mac             = {}
+    port_to_dst_mac_str         = {}
+    port_to_src_ip              = {}
+    port_to_src_ip_str          = {}
+    src_ip_0                    = 0xc0a80100
+    src_ip_0_str                = "192.168.1.%s"
+    dst_ip                      = 0xe0000001
+    switch_mac                  = [ 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00 ]
+
+    for port in ports:
+        in_vlan_id  = port + 1
+        out_vlan_id = MAX_INTERNAL_VLAN - port
+        if is_max_vlan and not is_vlan_translated:
+            in_vlan_id  = MAX_INTERNAL_VLAN
+            out_vlan_id = MAX_INTERNAL_VLAN
+        elif not is_max_vlan and not is_vlan_translated:
+            in_vlan_id  = FIXED_VLAN
+            out_vlan_id = FIXED_VLAN
+        src_mac                     = [ 0x00, 0x11, 0x11, 0x11, 0x11, port ]
+        src_mac_str                 = ':'.join( [ '%02X' % x for x in src_mac ] )
+        dst_mac                     = [ 0x01, 0x00, 0x5e, 0x01, 0x01, port ]
+        dst_mac_str                 = ':'.join( [ '%02X' % x for x in dst_mac ] )
+        src_ip                      = src_ip_0 + port
+        src_ip_str                  = src_ip_0_str % port
+        port_to_in_vlan[port]       = in_vlan_id
+        port_to_out_vlan[port]      = out_vlan_id
+        port_to_src_mac[port]       = src_mac
+        port_to_src_mac_str[port]   = src_mac_str
+        port_to_dst_mac[port]       = dst_mac
+        port_to_dst_mac_str[port]   = dst_mac_str
+        port_to_src_ip[port]        = src_ip
+        port_to_src_ip_str[port]    = src_ip_str
+
+    for in_port in ports:
+
+        L2_Groups = []
+        # add vlan flows table
+        add_one_vlan_table_flow( controller, in_port, port_to_in_vlan[in_port], flag=VLAN_TABLE_FLAG_ONLY_TAG )
+        if not is_ingress_tagged:
+            add_one_vlan_table_flow( controller, in_port, port_to_in_vlan[in_port], flag=VLAN_TABLE_FLAG_ONLY_UNTAG )
+        elif is_vlan_translated:
+            add_one_vlan_table_flow_translation( controller, in_port, port_to_in_vlan[in_port], port_to_out_vlan[in_port], flag=VLAN_TABLE_FLAG_ONLY_TAG)
+        # add termination flow
+        if not is_vlan_translated:
+            add_termination_flow( controller, in_port, 0x0800, switch_mac, port_to_in_vlan[in_port] )
+        else:
+            add_termination_flow( controller, in_port, 0x0800, switch_mac, port_to_out_vlan[in_port] )
+
+        for out_port in ports:
+            if out_port == in_port:
+                continue
+            # add l2 interface group, vlan_id equals for each port and must coincide with mcast_group vlan_id
+            if not is_vlan_translated:
+                l2gid, msg = add_one_l2_interface_group( controller, out_port, vlan_id=port_to_in_vlan[in_port],
+                is_tagged=is_egress_tagged, send_barrier=True )
+            else:
+                l2gid, msg = add_one_l2_interface_group( controller, out_port, vlan_id=port_to_out_vlan[in_port],
+                is_tagged=is_egress_tagged, send_barrier=True )
+            Groups._put( l2gid )
+            L2_Groups.append( l2gid )
+
+        # add l3 mcast group
+        if not is_vlan_translated:
+            mcat_group_msg = add_l3_mcast_group( controller, port_to_in_vlan[in_port], in_port, L2_Groups )
+        else:
+            mcat_group_msg = add_l3_mcast_group( controller, port_to_out_vlan[in_port], in_port, L2_Groups )
+        Groups._put( mcat_group_msg.group_id )
+        # add mcast routing flow
+        if not is_vlan_translated:
+            add_mcast4_routing_flow( controller, port_to_in_vlan[in_port], port_to_src_ip[in_port], 0, dst_ip, mcat_group_msg.group_id )
+        else:
+            add_mcast4_routing_flow( controller, port_to_out_vlan[in_port], port_to_src_ip[in_port], 0, dst_ip, mcat_group_msg.group_id )
+
+    return (
+        port_to_in_vlan,
+        port_to_out_vlan,
+        port_to_src_mac_str,
+        port_to_dst_mac_str,
+        port_to_src_ip_str,
+        Groups
+        )
+
 def fill_mcast_pipeline_L3toL3(
     controller,
     logging,