VOL-419: Flows not passed to Device Adapter if more than one PON Port
Changed flow to use requested priority per Nokia comment

Change-Id: I1ba954d56dbb65e4773b35a9df4a424458df4263
diff --git a/tests/utests/voltha/core/test_flow_decomposer.py b/tests/utests/voltha/core/test_flow_decomposer.py
index 6d6b5d5..5da4bc7 100644
--- a/tests/utests/voltha/core/test_flow_decomposer.py
+++ b/tests/utests/voltha/core/test_flow_decomposer.py
@@ -66,6 +66,11 @@
         4: LogicalPort(id='4', device_id='onu4', device_port_no=2),
     }
 
+    def get_wildcard_input_ports(self, exclude_port=None):
+        logical_ports =  self._logical_ports.iterkeys()
+        return [port_no for port_no in logical_ports
+                if port_no != exclude_port]
+
     _routes = {
 
         # DOWNSTREAM ROUTES
@@ -193,22 +198,6 @@
     }
 
     _default_rules = {
-        'olt': (
-            OrderedDict((f.id, f) for f in [
-                mk_flow_stat(
-                    match_fields=[
-                        in_port(2),
-                        vlan_vid(ofp.OFPVID_PRESENT | 4000),
-                        vlan_pcp(0)
-                    ],
-                    actions=[
-                        pop_vlan(),
-                        output(1)
-                    ]
-                )
-            ]),
-            OrderedDict()
-        ),
         'onu1': (
             OrderedDict((f.id, f) for f in [
                 mk_flow_stat(
@@ -315,10 +304,11 @@
                 output(1)
             ]
         ))
-        self.assertFlowsEqual(olt_flows.values()[1], mk_flow_stat(
+        self.assertFlowsEqual(olt_flows.values()[0], mk_flow_stat(
             priority=1000,
             match_fields=[
                 in_port(1),
+                vlan_vid(ofp.OFPVID_PRESENT | 1),
                 eth_type(0x888e)
             ],
             actions=[
@@ -327,6 +317,19 @@
                 output(2)
             ]
         ))
+        self.assertFlowsEqual(olt_flows.values()[1], mk_flow_stat(
+            priority=1000,
+            match_fields=[
+                in_port(2),
+                vlan_vid(ofp.OFPVID_PRESENT | 4000),
+                vlan_pcp(0),
+                metadata(1)
+            ],
+            actions=[
+                pop_vlan(),
+                output(1)
+            ]
+        ))
 
     def test_dhcp_reroute_rule_decomposition(self):
         flow = mk_flow_stat(
@@ -359,10 +362,11 @@
                 output(1)
             ]
         ))
-        self.assertFlowsEqual(olt_flows.values()[1], mk_flow_stat(
+        self.assertFlowsEqual(olt_flows.values()[0], mk_flow_stat(
             priority=1000,
             match_fields=[
                 in_port(1),
+                vlan_vid(ofp.OFPVID_PRESENT | 1),
                 eth_type(0x0800),
                 ipv4_dst(0xffffffff),
                 ip_proto(17),
@@ -375,6 +379,19 @@
                 output(2)
             ]
         ))
+        self.assertFlowsEqual(olt_flows.values()[1], mk_flow_stat(
+            priority=1000,
+            match_fields=[
+                in_port(2),
+                vlan_vid(ofp.OFPVID_PRESENT | 4000),
+                vlan_pcp(0),
+                metadata(1)
+            ],
+            actions=[
+                pop_vlan(),
+                output(1)
+            ]
+        ))
 
     def test_igmp_reroute_rule_decomposition(self):
         flow = mk_flow_stat(
@@ -404,10 +421,11 @@
                 output(1)
             ]
         ))
-        self.assertFlowsEqual(olt_flows.values()[1], mk_flow_stat(
+        self.assertFlowsEqual(olt_flows.values()[0], mk_flow_stat(
             priority=1000,
             match_fields=[
                 in_port(1),
+                vlan_vid(ofp.OFPVID_PRESENT | 1),
                 eth_type(0x0800),
                 ip_proto(2)
             ],
@@ -417,6 +435,19 @@
                 output(2)
             ]
         ))
+        self.assertFlowsEqual(olt_flows.values()[1], mk_flow_stat(
+            priority=1000,
+            match_fields=[
+                in_port(2),
+                vlan_vid(ofp.OFPVID_PRESENT | 4000),
+                vlan_pcp(0),
+                metadata(1)
+            ],
+            actions=[
+                pop_vlan(),
+                output(1)
+            ]
+        ))
 
     def test_wildcarded_igmp_reroute_rule_decomposition(self):
         flow = mk_flow_stat(
@@ -433,25 +464,23 @@
         olt_flows, olt_groups = device_rules['olt']
         self.assertEqual(len(onu1_flows), 1)
         self.assertEqual(len(onu1_groups), 0)
-        self.assertEqual(len(olt_flows), 2)
+        self.assertEqual(len(olt_flows), 8)
         self.assertEqual(len(olt_groups), 0)
         self.assertFlowsEqual(onu1_flows.values()[0], mk_flow_stat(
             match_fields=[
-                in_port(2),
-                vlan_vid(ofp.OFPVID_PRESENT | 0),
+                in_port(2), vlan_vid(ofp.OFPVID_PRESENT | 0)
             ],
             actions=[
                 set_field(vlan_vid(ofp.OFPVID_PRESENT | 101)),
                 output(1)
             ]
         ))
-        self.assertFlowsEqual(olt_flows.values()[1], mk_flow_stat(
+        self.assertFlowsEqual(olt_flows.values()[0], mk_flow_stat(
             priority=2000,
             cookie=140,
             match_fields=[
-                in_port(1),
-                eth_type(0x0800),
-                ip_proto(2)
+                in_port(1), vlan_vid(ofp.OFPVID_PRESENT | 0),
+                eth_type(0x0800), ip_proto(2)
             ],
             actions=[
                 push_vlan(0x8100),
@@ -459,6 +488,85 @@
                 output(2)
             ]
         ))
+        self.assertFlowsEqual(olt_flows.values()[1], mk_flow_stat(
+            priority=2000,
+            match_fields=[
+                in_port(2), vlan_vid(ofp.OFPVID_PRESENT | 4000),
+                vlan_pcp(0), metadata(0)
+            ],
+            actions=[
+                pop_vlan(), output(1)
+            ]
+        ))
+        self.assertFlowsEqual(olt_flows.values()[2], mk_flow_stat(
+            priority=2000,
+            cookie=140,
+            match_fields=[
+                in_port(1), vlan_vid(ofp.OFPVID_PRESENT | 1),
+                eth_type(0x0800), ip_proto(2)
+            ],
+            actions=[
+                push_vlan(0x8100),
+                set_field(vlan_vid(ofp.OFPVID_PRESENT | 4000)),
+                output(2)
+            ]
+        ))
+        self.assertFlowsEqual(olt_flows.values()[3], mk_flow_stat(
+            priority=2000,
+            match_fields=[
+                in_port(2), vlan_vid(ofp.OFPVID_PRESENT | 4000),
+                vlan_pcp(0), metadata(1)
+            ],
+            actions=[
+                pop_vlan(), output(1)
+            ]
+        ))
+        self.assertFlowsEqual(olt_flows.values()[4], mk_flow_stat(
+            priority=2000,
+            cookie=140,
+            match_fields=[
+                in_port(1), vlan_vid(ofp.OFPVID_PRESENT | 3),
+                eth_type(0x0800), ip_proto(2)
+            ],
+            actions=[
+                push_vlan(0x8100),
+                set_field(vlan_vid(ofp.OFPVID_PRESENT | 4000)),
+                output(2)
+            ]
+        ))
+        self.assertFlowsEqual(olt_flows.values()[5], mk_flow_stat(
+            priority=2000,
+            match_fields=[
+                in_port(2), vlan_vid(ofp.OFPVID_PRESENT | 4000),
+                vlan_pcp(0), metadata(3)
+            ],
+            actions=[
+                pop_vlan(), output(1)
+            ]
+        ))
+        self.assertFlowsEqual(olt_flows.values()[6], mk_flow_stat(
+            priority=2000,
+            cookie=140,
+            match_fields=[
+                in_port(1), vlan_vid(ofp.OFPVID_PRESENT | 4),
+                eth_type(0x0800), ip_proto(2)
+            ],
+            actions=[
+                push_vlan(0x8100),
+                set_field(vlan_vid(ofp.OFPVID_PRESENT | 4000)),
+                output(2)
+            ]
+        ))
+        self.assertFlowsEqual(olt_flows.values()[7], mk_flow_stat(
+            priority=2000,
+            match_fields=[
+                in_port(2), vlan_vid(ofp.OFPVID_PRESENT | 4000),
+                vlan_pcp(0), metadata(4)
+            ],
+            actions=[
+                pop_vlan(), output(1)
+            ]
+        ))
 
     def test_unicast_upstream_rule_decomposition(self):
         flow1 = mk_flow_stat(
@@ -492,7 +600,7 @@
         olt_flows, olt_groups = device_rules['olt']
         self.assertEqual(len(onu1_flows), 2)
         self.assertEqual(len(onu1_groups), 0)
-        self.assertEqual(len(olt_flows), 2)
+        self.assertEqual(len(olt_flows), 1)
         self.assertEqual(len(olt_groups), 0)
         self.assertFlowsEqual(onu1_flows.values()[1], mk_flow_stat(
             priority=500,
@@ -506,7 +614,7 @@
                 output(1)
             ]
         ))
-        self.assertFlowsEqual(olt_flows.values()[1], mk_flow_stat(
+        self.assertFlowsEqual(olt_flows.values()[0], mk_flow_stat(
             priority=500,
             match_fields=[
                 in_port(1),
@@ -551,9 +659,9 @@
         olt_flows, olt_groups = device_rules['olt']
         self.assertEqual(len(onu1_flows), 2)
         self.assertEqual(len(onu1_groups), 0)
-        self.assertEqual(len(olt_flows), 2)
+        self.assertEqual(len(olt_flows), 1)
         self.assertEqual(len(olt_groups), 0)
-        self.assertFlowsEqual(olt_flows.values()[1], mk_flow_stat(
+        self.assertFlowsEqual(olt_flows.values()[0], mk_flow_stat(
             priority=500,
             match_fields=[
                 in_port(2),
@@ -606,9 +714,9 @@
         olt_flows, olt_groups = device_rules['olt']
         self.assertEqual(len(onu1_flows), 2)
         self.assertEqual(len(onu1_groups), 0)
-        self.assertEqual(len(olt_flows), 2)
+        self.assertEqual(len(olt_flows), 1)
         self.assertEqual(len(olt_groups), 0)
-        self.assertFlowsEqual(olt_flows.values()[1], mk_flow_stat(
+        self.assertFlowsEqual(olt_flows.values()[0], mk_flow_stat(
             priority=500,
             match_fields=[
                 in_port(2),
diff --git a/tests/utests/voltha/core/test_logical_device_agent.py b/tests/utests/voltha/core/test_logical_device_agent.py
index 42ced6f..aa374c6 100644
--- a/tests/utests/voltha/core/test_logical_device_agent.py
+++ b/tests/utests/voltha/core/test_logical_device_agent.py
@@ -374,8 +374,8 @@
 
     def test_default_rules(self):
         rules = self.lda.get_all_default_rules()
-        # we assume one default flow and no default group for each of 3 devs
-        self.assertEqual(len(rules['olt'][0]), 1)
+        # no default olt downstream and no default group for each of 3 devs
+        self.assertEqual(len(rules['olt'][0]), 0)
         self.assertEqual(len(rules['olt'][1]), 0)
         self.assertEqual(len(rules['onu1'][0]), 3)
         self.assertEqual(len(rules['onu1'][1]), 0)
@@ -442,16 +442,23 @@
         self.assertEqual(len(self.device_groups['onu1'].items), 0)
         self.assertEqual(len(self.device_groups['onu2'].items), 0)
 
-        # the only non-default flow (check without the id field)
+        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(1), vlan_vid(4096 + 1), eth_type(0x888e)],
+            actions=[
+                push_vlan(0x8100),
+                set_field(vlan_vid(4096 + 4000)),
+                output(0)
+            ]
+        ))
         self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
-                priority=1000,
-                match_fields=[in_port(1), eth_type(0x888e)],
-                actions=[
-                    push_vlan(0x8100),
-                    set_field(vlan_vid(4096 + 4000)),
-                    output(0)
-                ]
-            ))
+            priority=1000,
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(1)],
+            actions=[
+                pop_vlan(),
+                output(1)
+            ]
+        ))
 
     def test_wildcarded_igmp_rule(self):
         self.lda.update_flow_table(mk_simple_flow_mod(
@@ -461,23 +468,47 @@
         ))
 
         self.lda._flow_table_updated(self.flows)
-        self.assertEqual(len(self.device_flows['olt'].items), 2)
+        self.assertEqual(len(self.device_flows['olt'].items), 4)
         self.assertEqual(len(self.device_flows['onu1'].items), 3)
         self.assertEqual(len(self.device_flows['onu2'].items), 3)
         self.assertEqual(len(self.device_groups['olt'].items), 0)
         self.assertEqual(len(self.device_groups['onu1'].items), 0)
         self.assertEqual(len(self.device_groups['onu2'].items), 0)
 
-        # the only non-default flow
-        self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
+        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
             priority=1000,
-            match_fields=[in_port(1), eth_type(0x800), ip_proto(2)],
+            match_fields=[in_port(1), vlan_vid(4096 + 1), eth_type(0x800), ip_proto(2)],
             actions=[
                 push_vlan(0x8100),
                 set_field(vlan_vid(4096 + 4000)),
                 output(0)
             ]
         ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(1)],
+            actions=[
+                pop_vlan(),
+                output(1)
+            ]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[2], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(1), vlan_vid(4096 + 2), eth_type(0x800), ip_proto(2)],
+            actions=[
+                push_vlan(0x8100),
+                set_field(vlan_vid(4096 + 4000)),
+                output(0)
+            ]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[3], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(2)],
+            actions=[
+                pop_vlan(),
+                output(1)
+            ]
+        ))
 
     def test_multicast_group_with_one_subscriber(self):
         self.lda.update_group_table(mk_multicast_group_mod(
@@ -500,14 +531,14 @@
             actions=[group(2)]
         ))
         self.lda._flow_table_updated(self.flows)
-        self.assertEqual(len(self.device_flows['olt'].items), 2)
+        self.assertEqual(len(self.device_flows['olt'].items), 1)
         self.assertEqual(len(self.device_flows['onu1'].items), 4)
         self.assertEqual(len(self.device_flows['onu2'].items), 3)
         self.assertEqual(len(self.device_groups['olt'].items), 0)
         self.assertEqual(len(self.device_groups['onu1'].items), 0)
         self.assertEqual(len(self.device_groups['onu2'].items), 0)
 
-        self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
+        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
             priority=1000,
             match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
                 ipv4_dst(0xe60a0a0a)],
@@ -549,14 +580,14 @@
             actions=[group(2)]
         ))
         self.lda._flow_table_updated(self.flows)
-        self.assertEqual(len(self.device_flows['olt'].items), 2)
+        self.assertEqual(len(self.device_flows['olt'].items), 1)
         self.assertEqual(len(self.device_flows['onu1'].items), 4)
         self.assertEqual(len(self.device_flows['onu2'].items), 4)
         self.assertEqual(len(self.device_groups['olt'].items), 0)
         self.assertEqual(len(self.device_groups['onu1'].items), 0)
         self.assertEqual(len(self.device_groups['onu2'].items), 0)
 
-        self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
+        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
             priority=1000,
             match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
                 ipv4_dst(0xe60a0a0a)],
@@ -596,17 +627,18 @@
             actions=[group(2)]
         ))
         self.lda._flow_table_updated(self.flows)
-        self.assertEqual(len(self.device_flows['olt'].items), 2)
+        self.assertEqual(len(self.device_flows['olt'].items), 1)
         self.assertEqual(len(self.device_flows['onu1'].items), 3)
         self.assertEqual(len(self.device_flows['onu2'].items), 3)
+        self.assertEqual(len(self.device_flows['onu2'].items), 3)
         self.assertEqual(len(self.device_groups['olt'].items), 0)
         self.assertEqual(len(self.device_groups['onu1'].items), 0)
         self.assertEqual(len(self.device_groups['onu2'].items), 0)
 
-        self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
+        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
             priority=1000,
-            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
-                ipv4_dst(0xe60a0a0a)],
+            match_fields=[in_port(0),  eth_type(0x800), vlan_vid(4096 + 140),
+                          ipv4_dst(0xe60a0a0a)],
             actions=[
                 pop_vlan(),
                 output(1)
@@ -724,7 +756,7 @@
         self.lda._flow_table_updated(self.flows)
 
         # now check device level flows
-        self.assertEqual(len(self.device_flows['olt'].items), 12)
+        self.assertEqual(len(self.device_flows['olt'].items), 16)
         self.assertEqual(len(self.device_flows['onu1'].items), 5)
         self.assertEqual(len(self.device_flows['onu2'].items), 5)
         self.assertEqual(len(self.device_groups['olt'].items), 0)
@@ -734,65 +766,93 @@
         # Flows installed on the OLT
         self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
             priority=2000,
-            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0)],
-            actions=[pop_vlan(), output(1)]
-        ))
-        self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
-            priority=2000,
-            match_fields=[in_port(1), eth_type(0x888e)],
+            match_fields=[in_port(1), vlan_vid(4096 + 1), eth_type(0x888e)],
             actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
                      output(0)]
         ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
+            priority=2000,
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(1)],
+            actions=[pop_vlan(), output(1)]
+        ))
         self.assertFlowsEqual(self.device_flows['olt'].items[2], mk_flow_stat(
             priority=1000,
-            match_fields=[in_port(1), eth_type(0x800), ip_proto(2)],
+            match_fields=[in_port(1), vlan_vid(4096 + 1), eth_type(0x800), ip_proto(2)],
             actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
                      output(0)]
         ))
         self.assertFlowsEqual(self.device_flows['olt'].items[3], mk_flow_stat(
             priority=1000,
-            match_fields=[in_port(1), eth_type(0x800), ip_proto(17),
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(1)],
+            actions=[pop_vlan(), output(1)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[4], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(1), vlan_vid(4096 + 1), eth_type(0x800), ip_proto(17),
                           udp_src(68), udp_dst(67)],
             actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
                      output(0)]
         ))
-        self.assertFlowsEqual(self.device_flows['olt'].items[4], mk_flow_stat(
-            priority=1000,
-            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140), ipv4_dst(0xe4010101)],
-            actions=[pop_vlan(), output(1)]
-        ))
         self.assertFlowsEqual(self.device_flows['olt'].items[5], mk_flow_stat(
-            priority=1000,
-            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140), ipv4_dst(0xe4010102)],
-            actions=[pop_vlan(), output(1)]
+            priority=2000,
+            match_fields=[in_port(1), vlan_vid(4096 + 2), eth_type(0x888e)],
+            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
+                     output(0)]
         ))
         self.assertFlowsEqual(self.device_flows['olt'].items[6], mk_flow_stat(
-            priority=1000,
-            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140), ipv4_dst(0xe4010103)],
+            priority=2000,
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(2)],
             actions=[pop_vlan(), output(1)]
         ))
         self.assertFlowsEqual(self.device_flows['olt'].items[7], mk_flow_stat(
             priority=1000,
-            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140), ipv4_dst(0xe4010104)],
-            actions=[pop_vlan(), output(1)]
+            match_fields=[in_port(1), vlan_vid(4096 + 2), eth_type(0x800), ip_proto(2)],
+            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
+                     output(0)]
         ))
         self.assertFlowsEqual(self.device_flows['olt'].items[8], mk_flow_stat(
-            priority=500,
-            match_fields=[in_port(0), vlan_vid(4096 + 1000), metadata(101)],
+            priority=1000,
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(2)],
             actions=[pop_vlan(), output(1)]
         ))
         self.assertFlowsEqual(self.device_flows['olt'].items[9], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(1), vlan_vid(4096 + 2), eth_type(0x800), ip_proto(17),
+                          udp_src(68), udp_dst(67)],
+            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
+                     output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[10], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
+                          ipv4_dst(0xe4010101)],
+            actions=[pop_vlan(), output(1)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[11], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
+                          ipv4_dst(0xe4010102)],
+            actions=[pop_vlan(), output(1)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[12], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
+                          ipv4_dst(0xe4010103)],
+            actions=[pop_vlan(), output(1)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[13], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
+                          ipv4_dst(0xe4010104)],
+            actions=[pop_vlan(), output(1)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[14], mk_flow_stat(
             priority=500,
             match_fields=[in_port(1), vlan_vid(4096 + 101)],
             actions=[
                 push_vlan(0x8100), set_field(vlan_vid(4096 + 1000)), output(0)]
         ))
-        self.assertFlowsEqual(self.device_flows['olt'].items[10], mk_flow_stat(
-            priority=500,
-            match_fields=[in_port(0), vlan_vid(4096 + 1000), metadata(102)],
-            actions=[pop_vlan(), output(1)]
-        ))
-        self.assertFlowsEqual(self.device_flows['olt'].items[11], mk_flow_stat(
+        self.assertFlowsEqual(self.device_flows['olt'].items[15], mk_flow_stat(
             priority=500,
             match_fields=[in_port(1), vlan_vid(4096 + 102)],
             actions=[
diff --git a/tests/utests/voltha/core/test_multipon_lda.py b/tests/utests/voltha/core/test_multipon_lda.py
new file mode 100644
index 0000000..0561dec
--- /dev/null
+++ b/tests/utests/voltha/core/test_multipon_lda.py
@@ -0,0 +1,652 @@
+
+from unittest import main
+
+from mock import Mock
+
+from tests.utests.voltha.core.flow_helpers import FlowHelpers
+from voltha.core import logical_device_agent
+from voltha.core.flow_decomposer import *
+from voltha.core.logical_device_agent import LogicalDeviceAgent
+from voltha.protos.device_pb2 import Device, Port
+from voltha.protos.logical_device_pb2 import LogicalDevice, LogicalPort
+from voltha.protos.openflow_13_pb2 import Flows, FlowGroups
+
+
+class test_multipon_logical_device_agent(FlowHelpers):
+
+    def setup_mock_registry(self):
+        registry = Mock()
+        logical_device_agent.registry = registry
+
+    def setUp(self):
+        self.setup_mock_registry()
+
+        self.flows = Flows(items=[])
+        self.groups = FlowGroups(items=[])
+        self.ld_ports = [
+            LogicalPort(
+                id='0',
+                device_id='olt',
+                device_port_no=0,
+                root_port=True,
+                ofp_port=ofp.ofp_port(port_no=0)
+            ),
+            LogicalPort(
+                id='101',
+                device_id='onu1',
+                device_port_no=0,
+                ofp_port=ofp.ofp_port(port_no=101)
+            ),
+            LogicalPort(
+                id='201',
+                device_id='onu2',
+                device_port_no=0,
+                ofp_port=ofp.ofp_port(port_no=201)
+            )
+        ]
+
+        self.devices = {
+            'olt': Device(
+                id='olt', root=True, parent_id='id'),
+            'onu1': Device(
+                id='onu1', parent_id='olt', parent_port_no=1, vlan=101),
+            'onu2': Device(
+                id='onu2', parent_id='olt', parent_port_no=2, vlan=201),
+        }
+
+        self.ports = {
+            'olt': [
+                Port(port_no=0, type=Port.ETHERNET_NNI, device_id='olt'),
+                Port(port_no=1, type=Port.PON_OLT, device_id='olt',
+                     peers=[Port.PeerPort(device_id='onu1', port_no=1)]),
+                Port(port_no=2, type=Port.PON_OLT, device_id='olt',
+                     peers=[Port.PeerPort(device_id='onu2', port_no=1)])
+            ],
+            'onu1': [
+                Port(port_no=0, type=Port.ETHERNET_UNI, device_id='onu1'),
+                Port(port_no=1, type=Port.PON_ONU, device_id='onu1',
+                     peers=[Port.PeerPort(device_id='olt', port_no=1)])
+            ],
+            'onu2': [
+                Port(port_no=0, type=Port.ETHERNET_UNI, device_id='onu2'),
+                Port(port_no=1, type=Port.PON_ONU, device_id='onu2',
+                     peers=[Port.PeerPort(device_id='olt', port_no=2)])
+            ],
+        }
+
+        self.device_flows = {
+            'olt': Flows(),
+            'onu1': Flows(),
+            'onu2': Flows()
+        }
+
+        self.device_groups = {
+            'olt': FlowGroups(),
+            'onu1': FlowGroups(),
+            'onu2': FlowGroups()
+        }
+
+        self.ld = LogicalDevice(id='id', root_device_id='olt')
+
+        self.root_proxy = Mock()
+        def get_devices(path):
+            if path == '':
+                return self.devices.values()
+            if path.endswith('/ports'):
+                return self.ports[path[:-len('/ports')]]
+            elif path.find('/') == -1:
+                return self.devices[path]
+            else:
+                raise Exception(
+                    'Nothing to yield for path /devices/{}'.format(path))
+        def update_devices(path, data):
+            if path.endswith('/flows'):
+                self.device_flows[path[:-len('/flows')]] = data
+            elif path.endswith('/flow_groups'):
+                self.device_groups[path[:-len('/flow_groups')]] = data
+            else:
+                raise NotImplementedError(
+                    'not handling path /devices/{}'.format(path))
+
+        self.root_proxy.get = lambda p: \
+            get_devices(p[len('/devices/'):]) if p.startswith('/devices') \
+                else None
+        self.root_proxy.update = lambda p, d: \
+            update_devices(p[len('/devices/'):], d) \
+                if p.startswith('/devices') \
+                else None
+        self.ld_proxy = Mock()
+        self.ld_proxy.get = lambda p: \
+            self.ld_ports if p == '/ports' else (
+                self.ld if p == '/' else None
+            )
+
+        self.flows_proxy = Mock()
+        self.flows_proxy.get = lambda _: self.flows  # always '/' path
+        def update_flows(_, flows):  # always '/' path
+            self.flows = flows
+        self.flows_proxy.update = update_flows
+
+        self.groups_proxy = Mock()
+        self.groups_proxy.get = lambda _: self.groups  # always '/' path
+        def update_groups(_, groups):  # always '/' path
+            self.groups = groups
+        self.groups_proxy.update = update_groups
+
+        self.core = Mock()
+        self.core.get_proxy = lambda path: \
+            self.root_proxy if path == '/' else (
+                self.ld_proxy if path.endswith('id') else (
+                    self.flows_proxy if path.endswith('flows') else
+                    self.groups_proxy
+                )
+            )
+
+        self.lda = LogicalDeviceAgent(self.core, self.ld)
+
+    def test_init(self):
+        pass  # really just tests the setUp method
+
+    # ~~~~~~~~~~~~~~~~~~~~ DEFAULT RULES AND ROUTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    def test_default_rules(self):
+        rules = self.lda.get_all_default_rules()
+        # no default olt downstream and no default group for each of 3 devs
+        self.assertEqual(len(rules['olt'][0]), 0)
+        self.assertEqual(len(rules['olt'][1]), 0)
+        self.assertEqual(len(rules['onu1'][0]), 3)
+        self.assertEqual(len(rules['onu1'][1]), 0)
+        self.assertEqual(len(rules['onu2'][0]), 3)
+        self.assertEqual(len(rules['onu2'][1]), 0)
+
+    def test_routes(self):
+        self.lda.get_all_default_rules()  # this will prepare the _routes
+        routes = self.lda._routes
+        self.assertEqual(len(routes), 4)
+        self.assertEqual(set(routes.keys()),
+                         set([(0, 101), (0, 201), (101, 0), (201, 0)]))
+
+        # verify all routes
+        route = routes[(0, 101)]
+        self.assertEqual(len(route), 2)
+        self.assertEqual(route[0].device, self.devices['olt'])
+        self.assertEqual(route[0].ingress_port, self.ports['olt'][0])
+        self.assertEqual(route[0].egress_port, self.ports['olt'][1])
+        self.assertEqual(route[1].device, self.devices['onu1'])
+        self.assertEqual(route[1].ingress_port, self.ports['onu1'][1])
+        self.assertEqual(route[1].egress_port, self.ports['onu1'][0])
+
+        route = routes[(0, 201)]
+        self.assertEqual(len(route), 2)
+        self.assertEqual(route[0].device, self.devices['olt'])
+        self.assertEqual(route[0].ingress_port, self.ports['olt'][0])
+        self.assertEqual(route[0].egress_port, self.ports['olt'][2])
+        self.assertEqual(route[1].device, self.devices['onu2'])
+        self.assertEqual(route[1].ingress_port, self.ports['onu2'][1])
+        self.assertEqual(route[1].egress_port, self.ports['onu2'][0])
+
+        route = routes[(101, 0)]
+        self.assertEqual(len(route), 2)
+        self.assertEqual(route[0].device, self.devices['onu1'])
+        self.assertEqual(route[0].ingress_port, self.ports['onu1'][0])
+        self.assertEqual(route[0].egress_port, self.ports['onu1'][1])
+        self.assertEqual(route[1].device, self.devices['olt'])
+        self.assertEqual(route[1].ingress_port, self.ports['olt'][1])
+        self.assertEqual(route[1].egress_port, self.ports['olt'][0])
+
+        route = routes[(201, 0)]
+        self.assertEqual(len(route), 2)
+        self.assertEqual(route[0].device, self.devices['onu2'])
+        self.assertEqual(route[0].ingress_port, self.ports['onu2'][0])
+        self.assertEqual(route[0].egress_port, self.ports['onu2'][1])
+        self.assertEqual(route[1].device, self.devices['olt'])
+        self.assertEqual(route[1].ingress_port, self.ports['olt'][2])
+        self.assertEqual(route[1].egress_port, self.ports['olt'][0])
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~~~ FLOW DECOMP TESTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    def test_eapol_flow_decomp_case(self):
+        self.lda.update_flow_table(mk_simple_flow_mod(
+            priority=1000,
+            match_fields=[in_port(201), eth_type(0x888e)],
+            actions=[output(ofp.OFPP_CONTROLLER)]
+        ))
+        self.lda._flow_table_updated(self.flows)
+        self.assertEqual(len(self.device_flows['olt'].items), 2)
+        self.assertEqual(len(self.device_flows['onu1'].items), 3)
+        self.assertEqual(len(self.device_flows['onu2'].items), 3)
+        self.assertEqual(len(self.device_groups['olt'].items), 0)
+        self.assertEqual(len(self.device_groups['onu1'].items), 0)
+        self.assertEqual(len(self.device_groups['onu2'].items), 0)
+
+        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(2), vlan_vid(4096 + 201), eth_type(0x888e)],
+            actions=[
+                push_vlan(0x8100),
+                set_field(vlan_vid(4096 + 4000)),
+                output(0)
+            ]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(201)],
+            actions=[
+                pop_vlan(),
+                output(2)
+            ]
+        ))
+
+    def test_wildcarded_igmp_rule(self):
+        self.lda.update_flow_table(mk_simple_flow_mod(
+            priority=1000,
+            match_fields=[eth_type(0x800), ip_proto(2)],
+            actions=[output(ofp.OFPP_CONTROLLER)]
+        ))
+
+        self.lda._flow_table_updated(self.flows)
+        self.assertEqual(len(self.device_flows['olt'].items), 4)
+        self.assertEqual(len(self.device_flows['onu1'].items), 3)
+        self.assertEqual(len(self.device_flows['onu2'].items), 3)
+        self.assertEqual(len(self.device_groups['olt'].items), 0)
+        self.assertEqual(len(self.device_groups['onu1'].items), 0)
+        self.assertEqual(len(self.device_groups['onu2'].items), 0)
+
+        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(2), vlan_vid(4096 + 101), eth_type(0x800), ip_proto(2)],
+            actions=[
+                push_vlan(0x8100),
+                set_field(vlan_vid(4096 + 4000)),
+                output(0)
+            ]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(101)],
+            actions=[
+                pop_vlan(),
+                output(2)
+            ]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[2], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(2), vlan_vid(4096 + 201), eth_type(0x800), ip_proto(2)],
+            actions=[
+                push_vlan(0x8100),
+                set_field(vlan_vid(4096 + 4000)),
+                output(0)
+            ]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[3], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(201)],
+            actions=[
+                pop_vlan(),
+                output(2)
+            ]
+        ))
+
+    def test_multicast_group_with_one_subscriber(self):
+        self.lda.update_group_table(mk_multicast_group_mod(
+            group_id=2,
+            buckets=[
+                ofp.ofp_bucket(actions=[
+                    pop_vlan(),
+                    output(201)
+                ]),
+            ]
+        ))
+        self.lda.update_flow_table(mk_simple_flow_mod(
+            priority=1000,
+            match_fields=[
+                in_port(0),
+                eth_type(0x800),
+                vlan_vid(4096 + 140),
+                ipv4_dst(0xe60a0a0a)
+            ],
+            actions=[group(2)]
+        ))
+        self.lda._flow_table_updated(self.flows)
+        self.assertEqual(len(self.device_flows['olt'].items), 1)
+        self.assertEqual(len(self.device_flows['onu1'].items), 3)
+        self.assertEqual(len(self.device_flows['onu2'].items), 4)
+        self.assertEqual(len(self.device_groups['olt'].items), 0)
+        self.assertEqual(len(self.device_groups['onu1'].items), 0)
+        self.assertEqual(len(self.device_groups['onu2'].items), 0)
+
+        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
+                          ipv4_dst(0xe60a0a0a)],
+            actions=[
+                pop_vlan(),
+                output(2)
+            ]
+        ))
+        self.assertFlowsEqual(self.device_flows['onu2'].items[3], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(1), eth_type(0x800), ipv4_dst(0xe60a0a0a)],
+            actions=[
+                output(0)
+            ]
+        ))
+
+    def test_multicast_group_with_no_subscribers(self):
+        self.lda.update_group_table(mk_multicast_group_mod(
+            group_id=2,
+            buckets=[]  # No subscribers
+        ))
+        self.lda.update_flow_table(mk_simple_flow_mod(
+            priority=1000,
+            match_fields=[
+                in_port(0),
+                eth_type(0x800),
+                vlan_vid(4096 + 140),
+                ipv4_dst(0xe60a0a0a)
+            ],
+            actions=[group(2)]
+        ))
+        self.lda._flow_table_updated(self.flows)
+        self.assertEqual(len(self.device_flows['olt'].items), 1)
+        self.assertEqual(len(self.device_flows['onu1'].items), 3)
+        self.assertEqual(len(self.device_flows['onu2'].items), 3)
+        self.assertEqual(len(self.device_flows['onu2'].items), 3)
+        self.assertEqual(len(self.device_groups['olt'].items), 0)
+        self.assertEqual(len(self.device_groups['onu1'].items), 0)
+        self.assertEqual(len(self.device_groups['onu2'].items), 0)
+
+        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
+                          ipv4_dst(0xe60a0a0a)],
+            actions=[
+                pop_vlan(),
+                output(2)
+            ]
+        ))
+    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ COMPLEX TESTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    def test_complex_flow_table_decomposition(self):
+
+        # Various controller-bound rules
+        for _in_port in (101, 201):
+            self.lda.update_flow_table(mk_simple_flow_mod(
+                priority=2000,
+                match_fields=[in_port(_in_port), eth_type(0x888e)],
+                actions=[
+                    push_vlan(0x8100),
+                    set_field(vlan_vid(4096 + 4000)),
+                    output(ofp.OFPP_CONTROLLER)
+                ]
+            ))
+            self.lda.update_flow_table(mk_simple_flow_mod(
+                priority=1000,
+                match_fields=[in_port(_in_port), eth_type(0x800), ip_proto(2)],
+                actions=[output(ofp.OFPP_CONTROLLER)]
+            ))
+            self.lda.update_flow_table(mk_simple_flow_mod(
+                priority=1000,
+                match_fields=[in_port(_in_port), eth_type(0x800), ip_proto(17),
+                              udp_src(68), udp_dst(67)],
+                actions=[output(ofp.OFPP_CONTROLLER)]
+            ))
+
+        # Multicast channels
+        mcast_setup = (
+            (1, 0xe4010101, ()),
+            (2, 0xe4010102, (101,)),
+            (3, 0xe4010103, (201,)),
+            (4, 0xe4010104, (101, 201)),
+        )
+        for group_id, mcast_addr, ports in mcast_setup:
+            self.lda.update_group_table(mk_multicast_group_mod(
+                group_id=group_id,
+                buckets=[
+                    ofp.ofp_bucket(actions=[
+                        pop_vlan(),
+                        output(port)
+                    ]) for port in ports
+                ]))
+            self.lda.update_flow_table(mk_simple_flow_mod(
+                priority=1000,
+                match_fields=[
+                    in_port(0),
+                    eth_type(0x800),
+                    vlan_vid(4096 + 140),
+                    ipv4_dst(mcast_addr)
+                ],
+                actions=[
+                    group(group_id)
+                ]
+            ))
+
+        # Unicast channels for each subscriber
+        for port, c_vid in ((101, 101), (201, 201)):
+
+            # Downstream flow 1 for nni to pon
+            self.lda.update_flow_table(mk_simple_flow_mod(
+                priority=500,
+                match_fields=[
+                    in_port(0),
+                    vlan_vid(4096 + 1000),
+                    metadata(c_vid)
+                ],
+                actions=[pop_vlan()],
+                next_table_id=1
+            ))
+
+            # Downstream flow 2
+            self.lda.update_flow_table(mk_simple_flow_mod(
+                priority=500,
+                match_fields=[in_port(0), vlan_vid(4096 + c_vid)],
+                actions=[set_field(vlan_vid(4096 + 0)), output(port)]
+            ))
+
+            # upstream flow 1 for the 0-tagged case
+            self.lda.update_flow_table(mk_simple_flow_mod(
+                priority=500,
+                match_fields=[in_port(port), vlan_vid(4096 + 0)],
+                actions=[set_field(vlan_vid(4096 + c_vid))],
+                next_table_id=1
+            ))
+            # ... and for the untagged case
+            self.lda.update_flow_table(mk_simple_flow_mod(
+                priority=500,
+                match_fields=[in_port(port), vlan_vid(0)],
+                actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + c_vid))],
+                next_table_id=1
+            ))
+
+            # Upstream flow 2 for s-tag
+            self.lda.update_flow_table(mk_simple_flow_mod(
+                priority=500,
+                match_fields=[in_port(port), vlan_vid(4096 + c_vid)],
+                actions=[
+                    push_vlan(0x8100),
+                    set_field(vlan_vid(4096 + 1000)),
+                    output(0)
+                ]
+            ))
+
+        self.assertEqual(len(self.flows.items), 20)
+        self.assertEqual(len(self.groups.items), 4)
+
+        # trigger flow table decomposition
+        self.lda._flow_table_updated(self.flows)
+
+        # now check device level flows
+        self.assertEqual(len(self.device_flows['olt'].items), 18)
+        self.assertEqual(len(self.device_flows['onu1'].items), 5)
+        self.assertEqual(len(self.device_flows['onu2'].items), 5)
+        self.assertEqual(len(self.device_groups['olt'].items), 0)
+        self.assertEqual(len(self.device_groups['onu1'].items), 0)
+        self.assertEqual(len(self.device_groups['onu2'].items), 0)
+
+        # Flows installed on the OLT
+        self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
+            priority=2000,
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(101)],
+            actions=[pop_vlan(), output(1)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
+            priority=2000,
+            match_fields=[in_port(1), vlan_vid(4096 + 101), eth_type(0x888e)],
+            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
+                     output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[2], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(1), vlan_vid(4096 + 101), eth_type(0x800), ip_proto(2)],
+            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
+                     output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[3], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(101)],
+            actions=[pop_vlan(), output(1)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[4], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(1), vlan_vid(4096 + 101), eth_type(0x800), ip_proto(17),
+                          udp_src(68), udp_dst(67)],
+            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
+                     output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[5], mk_flow_stat(
+            priority=2000,
+            match_fields=[in_port(2), vlan_vid(4096 + 201), eth_type(0x888e)],
+            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
+                     output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[6], mk_flow_stat(
+            priority=2000,
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(201)],
+            actions=[pop_vlan(), output(2)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[7], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(2), vlan_vid(4096 + 201), eth_type(0x800), ip_proto(2)],
+            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
+                     output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[8], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(201)],
+            actions=[pop_vlan(), output(2)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[9], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(2), vlan_vid(4096 + 201), eth_type(0x800), ip_proto(17),
+                          udp_src(68), udp_dst(67)],
+            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
+                     output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[10], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
+                          ipv4_dst(0xe4010101)],
+            actions=[pop_vlan(), output(2)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[11], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
+                          ipv4_dst(0xe4010102)],
+            actions=[pop_vlan(), output(2)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[12], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
+                          ipv4_dst(0xe4010103)],
+            actions=[pop_vlan(), output(2)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[13], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
+                          ipv4_dst(0xe4010104)],
+            actions=[pop_vlan(), output(2)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[14], mk_flow_stat(
+            priority=500,
+            match_fields=[in_port(0), vlan_vid(4096 + 1000), metadata(101)],
+            actions=[pop_vlan(), output(1)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[15], mk_flow_stat(
+            priority=500,
+            match_fields=[in_port(1), vlan_vid(4096 + 101)],
+            actions=[
+                push_vlan(0x8100), set_field(vlan_vid(4096 + 1000)), output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[16], mk_flow_stat(
+            priority=500,
+            match_fields=[in_port(0), vlan_vid(4096 + 1000), metadata(201)],
+            actions=[pop_vlan(), output(2)]
+        ))
+        self.assertFlowsEqual(self.device_flows['olt'].items[17], mk_flow_stat(
+            priority=500,
+            match_fields=[in_port(2), vlan_vid(4096 + 201)],
+            actions=[
+                push_vlan(0x8100), set_field(vlan_vid(4096 + 1000)), output(0)]
+        ))
+
+        # Flows installed on the ONU1
+        self.assertFlowsEqual(self.device_flows['onu1'].items[0], mk_flow_stat(
+            priority=500,
+            match_fields=[in_port(0), vlan_vid(4096 + 0)],
+            actions=[
+                set_field(vlan_vid(4096 + 101)), output(1)]
+        ))
+        self.assertFlowsEqual(self.device_flows['onu1'].items[3], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(1), eth_type(0x800), ipv4_dst(0xe4010102)],
+            actions=[output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['onu1'].items[4], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(1), eth_type(0x800), ipv4_dst(0xe4010104)],
+            actions=[output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['onu1'].items[2], mk_flow_stat(
+            priority=500,
+            match_fields=[in_port(1), vlan_vid(4096 + 101)],
+            actions=[set_field(vlan_vid(4096 + 0)), output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['onu1'].items[1], mk_flow_stat(
+            priority=500,
+            match_fields=[in_port(0), vlan_vid(0)],
+            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 101)),
+                     output(1)]
+        ))
+
+        # Flows installed on the ONU2
+        self.assertFlowsEqual(self.device_flows['onu2'].items[0], mk_flow_stat(
+            priority=500,
+            match_fields=[in_port(0), vlan_vid(4096 + 0)],
+            actions=[
+                set_field(vlan_vid(4096 + 201)), output(1)]
+        ))
+        self.assertFlowsEqual(self.device_flows['onu2'].items[3], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(1), eth_type(0x800), ipv4_dst(0xe4010103)],
+            actions=[output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['onu2'].items[4], mk_flow_stat(
+            priority=1000,
+            match_fields=[in_port(1), eth_type(0x800), ipv4_dst(0xe4010104)],
+            actions=[output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['onu2'].items[2], mk_flow_stat(
+            priority=500,
+            match_fields=[in_port(1), vlan_vid(4096 + 201)],
+            actions=[set_field(vlan_vid(4096 + 0)), output(0)]
+        ))
+        self.assertFlowsEqual(self.device_flows['onu2'].items[1], mk_flow_stat(
+            priority=500,
+            match_fields=[in_port(0), vlan_vid(0)],
+            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 201)),
+                     output(1)]
+        ))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/voltha/core/flow_decomposer.py b/voltha/core/flow_decomposer.py
index cfc4940..d123743 100644
--- a/voltha/core/flow_decomposer.py
+++ b/voltha/core/flow_decomposer.py
@@ -315,6 +315,11 @@
             return instruction.goto_table.table_id
     return None
 
+def get_metadata(flow):
+    for field in get_ofb_fields(flow):
+        if field.type == METADATA:
+            return field.table_metadata
+    return None
 
 # test and extract next table and group information
 
@@ -533,21 +538,43 @@
             # TODO make the 4000 configurable
             fl_lst, _ = device_rules.setdefault(
                 egress_hop.device.id, ([], []))
-            fl_lst.append(mk_flow_stat(
-                priority=flow.priority,
-                cookie=flow.cookie,
-                match_fields=[
-                    in_port(egress_hop.ingress_port.port_no)
-                ] + [
-                    field for field in get_ofb_fields(flow)
-                    if field.type not in (IN_PORT, VLAN_VID)
-                ],
-                actions=[
-                    push_vlan(0x8100),
-                    set_field(vlan_vid(ofp.OFPVID_PRESENT | 4000)),
-                    output(egress_hop.egress_port.port_no)]
-            ))
 
+            # in_port_no is None for wildcard input case, do not include
+            # upstream port for 4000 flow in input
+            if in_port_no is None:
+                in_ports = self.get_wildcard_input_ports(exclude_port=
+                                                         egress_hop.egress_port.port_no)
+            else:
+                in_ports = [in_port_no]
+
+            for input_port in in_ports:
+                fl_lst.append(mk_flow_stat(        # Upstream flow
+                    priority=flow.priority,
+                    cookie=flow.cookie,
+                    match_fields=[
+                        in_port(egress_hop.ingress_port.port_no),
+                        vlan_vid(ofp.OFPVID_PRESENT | input_port)
+                    ] + [
+                        field for field in get_ofb_fields(flow)
+                        if field.type not in (IN_PORT, VLAN_VID)
+                    ],
+                    actions=[
+                        push_vlan(0x8100),
+                        set_field(vlan_vid(ofp.OFPVID_PRESENT | 4000)),
+                        output(egress_hop.egress_port.port_no)]
+                ))
+                fl_lst.append(mk_flow_stat(            # Downstream flow
+                    priority=flow.priority,
+                    match_fields=[
+                        in_port(egress_hop.egress_port.port_no),
+                        vlan_vid(ofp.OFPVID_PRESENT | 4000),
+                        vlan_pcp(0),
+                        metadata(input_port)
+                    ],
+                    actions=[
+                        pop_vlan(),
+                        output(egress_hop.ingress_port.port_no)]
+                ))
         else:
             # NOT A CONTROLLER-BOUND FLOW
             if is_upstream():
@@ -601,6 +628,20 @@
             else:  # downstream
                 if has_next_table(flow):
                     assert out_port_no is None
+
+                    # For downstream flows with dual-tags, recalculate route with
+                    # inner-tag as logical output port. Otherwise PON-0 is always
+                    # selected.
+                    inner_tag = get_metadata(flow)
+                    if inner_tag is not None:
+                        route = self.get_route(in_port_no, inner_tag)
+                        if route is None:
+                            log.error('no-route-double-tag', in_port_no=in_port_no,
+                                      out_port_no=inner_tag, comment='ignoring flow')
+                            return device_rules
+                        assert len(route) == 2
+                        ingress_hop, egress_hop = route
+
                     fl_lst, _ = device_rules.setdefault(
                         ingress_hop.device.id, ([], []))
                     fl_lst.append(mk_flow_stat(
@@ -688,7 +729,7 @@
 
                         assert len(route2) == 2
                         ingress_hop2, egress_hop = route2
-                        assert ingress_hop == ingress_hop2
+                        assert ingress_hop.ingress_port == ingress_hop2.ingress_port
 
                         fl_lst, _ = device_rules.setdefault(
                             egress_hop.device.id, ([], []))
@@ -719,4 +760,5 @@
     def get_route(self, ingress_port_no, egress_port_no):
         raise NotImplementedError('derived class must provide')
 
-
+    def get_wildcard_input_ports(self, exclude_port=None):
+        raise NotImplementedError('derived class must provide')
diff --git a/voltha/core/logical_device_agent.py b/voltha/core/logical_device_agent.py
index 1d8fe4b..c6bd3e8 100644
--- a/voltha/core/logical_device_agent.py
+++ b/voltha/core/logical_device_agent.py
@@ -614,41 +614,14 @@
                 self.root_proxy, logical_ports)
             self._default_rules = self._generate_default_rules(graph)
             root_ports = [p for p in logical_ports if p.root_port]
-            assert len(root_ports) == 1
+            assert len(root_ports) == 1, 'Only one root port supported at this time'
             self._nni_logical_port_no = root_ports[0].ofp_port.port_no
 
 
     def _generate_default_rules(self, graph):
 
         def root_device_default_rules(device):
-            ports = self.root_proxy.get('/devices/{}/ports'.format(device.id))
-            upstream_ports = [
-                port for port in ports if port.type == Port.ETHERNET_NNI
-            ]
-            assert len(upstream_ports) == 1
-            downstream_ports = [
-                port for port in ports if port.type == Port.PON_OLT \
-                                            or port.type == Port.VENET_OLT
-            ]
-            _is_any_venet_port = any(_port.type == Port.VENET_OLT for _port in
-                                  downstream_ports)
-            if _is_any_venet_port != True:
-                assert len(downstream_ports) == 1, \
-                    'Initially, we only handle one PON port'
-            flows = OrderedDict((f.id, f) for f in [
-                mk_flow_stat(
-                    priority=2000,
-                    match_fields=[
-                        in_port(upstream_ports[0].port_no),
-                        vlan_vid(ofp.OFPVID_PRESENT | 4000),
-                        vlan_pcp(0)
-                    ],
-                    actions=[
-                        pop_vlan(),
-                        output(downstream_ports[0].port_no)
-                    ]
-                )
-            ])
+            flows = OrderedDict()
             groups = OrderedDict()
             return flows, groups
 
@@ -755,3 +728,8 @@
     def get_all_default_rules(self):
         self._assure_cached_tables_up_to_date()
         return self._default_rules
+
+    def get_wildcard_input_ports(self, exclude_port=None):
+        logical_ports = self.self_proxy.get('/ports')
+        return [port.ofp_port.port_no for port in logical_ports
+                if port.ofp_port.port_no != exclude_port]