Inject per-ONU metadata field for unicast flows

This is a CLI change to mimic a useful ONOS behavior when generating
logical flows for the PON. Specifically, ONOS injects a metadata
field in each flow rule for unicast downstream traffic, namely into
the first of the two flow rules handling the outer tag. The metadata
value is the vlan id of the inner tag. Without this metadata there
is no easily accessible information as to what inner tag that flow
is meant for.

This metadata value can be considered as a "hint" by the OLT adapters
to tie a downstream flow rule to a specific PON link/channel.

This is not an elegant solution, in that it slightly misuses the
metadata field. The more proper long-term solution would be to either
model the PON channels explicitly as flow ports, or use phys-port/port
pairs (the former representing the PON port itself, and the other
representing the logical channel/link on the PON.

It is recommended to switch to the cleaner solution at a later time.

Change-Id: I2a461014d697d01010101010101052609d742d04
diff --git a/cli/main.py b/cli/main.py
index 8f7c064..a4f47b9 100755
--- a/cli/main.py
+++ b/cli/main.py
@@ -356,17 +356,22 @@
         stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
         ports = stub.ListLogicalDevicePorts(
             voltha_pb2.ID(id=logical_device_id)).items
-        nni = uni = vlan = None
+        nni = None
+        unis = []
         for port in ports:
-            if nni is None and port.root_port:
+            if port.root_port:
+                assert nni is None, "There shall be only one root port"
                 nni = port.ofp_port.port_no
-            if uni is None and not port.root_port:
+            else:
                 uni = port.ofp_port.port_no
                 uni_device = self.get_device(port.device_id)
                 vlan = uni_device.vlan
-            if nni is not None and uni is not None:
-                return nni, uni, vlan
-        raise Exception('No valid port pair found (no ONUs yet?)')
+                unis.append((uni, vlan))
+
+        assert nni is not None, "No NNI port found"
+        assert unis, "Not a single UNI?"
+
+        return nni, unis
 
     def do_install_eapol_flow(self, line):
         """
@@ -377,24 +382,25 @@
         logical_device_id = line or self.default_logical_device_id
 
         # gather NNI and UNI port IDs
-        nni_port_no, uni_port_no, _ = self.get_logical_ports(logical_device_id)
+        nni_port_no, unis = self.get_logical_ports(logical_device_id)
 
         # construct and push flow rule
-        update = FlowTableUpdate(
-            id=logical_device_id,
-            flow_mod=mk_simple_flow_mod(
-                priority=2000,
-                match_fields=[in_port(uni_port_no), eth_type(0x888e)],
-                actions=[
-                    # push_vlan(0x8100),
-                    # set_field(vlan_vid(4096 + 4000)),
-                    output(ofp.OFPP_CONTROLLER)
-                ]
-            )
-        )
         stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
-        res = stub.UpdateLogicalDeviceFlowTable(update)
-        self.poutput('success ({})'.format(res))
+        for uni_port_no, _ in unis:
+            update = FlowTableUpdate(
+                id=logical_device_id,
+                flow_mod=mk_simple_flow_mod(
+                    priority=2000,
+                    match_fields=[in_port(uni_port_no), eth_type(0x888e)],
+                    actions=[
+                        # push_vlan(0x8100),
+                        # set_field(vlan_vid(4096 + 4000)),
+                        output(ofp.OFPP_CONTROLLER)
+                    ]
+                )
+            )
+            res = stub.UpdateLogicalDeviceFlowTable(update)
+            self.poutput('success for uni {} ({})'.format(uni_port_no, res))
 
     complete_install_eapol_flow = VolthaCli.complete_logical_device
 
@@ -407,40 +413,48 @@
         logical_device_id = line or self.default_logical_device_id
 
         # gather NNI and UNI port IDs
-        nni_port_no, uni_port_no, _ = self.get_logical_ports(logical_device_id)
+        nni_port_no, unis = self.get_logical_ports(logical_device_id)
 
         # construct and push flow rules
         stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
 
-        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
-            id=logical_device_id,
-            flow_mod=mk_simple_flow_mod(
-                priority=2000,
-                match_fields=[in_port(uni_port_no), eth_type(0x888e)],
-                actions=[
-                    # push_vlan(0x8100),
-                    # set_field(vlan_vid(4096 + 4000)),
-                    output(ofp.OFPP_CONTROLLER)
-                ]
-            )
-        ))
-        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
-            id=logical_device_id,
-            flow_mod=mk_simple_flow_mod(
-                priority=1000,
-                match_fields=[eth_type(0x800), ip_proto(2)],
-                actions=[output(ofp.OFPP_CONTROLLER)]
-            )
-        ))
-        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
-            id=logical_device_id,
-            flow_mod=mk_simple_flow_mod(
-                priority=1000,
-                match_fields=[eth_type(0x800), ip_proto(17), udp_dst(67)],
-                actions=[output(ofp.OFPP_CONTROLLER)]
-            )
-        ))
-
+        for uni_port_no, _ in unis:
+            stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+                id=logical_device_id,
+                flow_mod=mk_simple_flow_mod(
+                    priority=2000,
+                    match_fields=[
+                        in_port(uni_port_no),
+                        eth_type(0x888e)
+                    ],
+                    actions=[output(ofp.OFPP_CONTROLLER)]
+                )
+            ))
+            stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+                id=logical_device_id,
+                flow_mod=mk_simple_flow_mod(
+                    priority=1000,
+                    match_fields=[
+                        in_port(uni_port_no),
+                        eth_type(0x800),
+                        ip_proto(2)
+                    ],
+                    actions=[output(ofp.OFPP_CONTROLLER)]
+                )
+            ))
+            stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+                id=logical_device_id,
+                flow_mod=mk_simple_flow_mod(
+                    priority=1000,
+                    match_fields=[
+                        in_port(uni_port_no),
+                        eth_type(0x800),
+                        ip_proto(17),
+                        udp_dst(67)
+                    ],
+                    actions=[output(ofp.OFPP_CONTROLLER)]
+                )
+            ))
         self.poutput('success')
 
     complete_install_all_controller_bound_flows = \
@@ -454,108 +468,114 @@
         logical_device_id = line or self.default_logical_device_id
 
         # gather NNI and UNI port IDs
-        nni_port_no, uni_port_no, c_vid = \
-            self.get_logical_ports(logical_device_id)
+        nni_port_no, unis = self.get_logical_ports(logical_device_id)
 
         # construct and push flow rules
         stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
 
-        # Controller-bound flows
-        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
-            id=logical_device_id,
-            flow_mod=mk_simple_flow_mod(
-                priority=2000,
-                match_fields=[in_port(uni_port_no), eth_type(0x888e)],
-                actions=[
-                    # push_vlan(0x8100),
-                    # set_field(vlan_vid(4096 + 4000)),
-                    output(ofp.OFPP_CONTROLLER)
-                ]
-            )
-        ))
-        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
-            id=logical_device_id,
-            flow_mod=mk_simple_flow_mod(
-                priority=1000,
-                match_fields=[eth_type(0x800), ip_proto(2)],
-                actions=[output(ofp.OFPP_CONTROLLER)]
-            )
-        ))
-        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
-            id=logical_device_id,
-            flow_mod=mk_simple_flow_mod(
-                priority=1000,
-                match_fields=[eth_type(0x800), ip_proto(17), udp_dst(67)],
-                actions=[output(ofp.OFPP_CONTROLLER)]
-            )
-        ))
+        for uni_port_no, c_vid in unis:
 
-        # Unicast flows:
-        # Downstream flow 1
-        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
-            id=logical_device_id,
-            flow_mod=mk_simple_flow_mod(
-                priority=500,
-                match_fields=[
-                    in_port(nni_port_no),
-                    vlan_vid(4096 + 1000),
-                    metadata(40)  # here to mimic an ONOS artifact
-                ],
-                actions=[pop_vlan()],
-                next_table_id=1
-            )
-        ))
-        # Downstream flow 2
-        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
-            id=logical_device_id,
-            flow_mod=mk_simple_flow_mod(
-                priority=500,
-                match_fields=[in_port(nni_port_no), vlan_vid(4096 + c_vid)],
-                actions=[set_field(vlan_vid(4096 + 0)), output(uni_port_no)]
-            )
-        ))
-        # Upstream flow 1 for 0-tagged case
-        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
-            id=logical_device_id,
-            flow_mod=mk_simple_flow_mod(
-                priority=500,
-                match_fields=[in_port(uni_port_no), vlan_vid(4096 + 0)],
-                actions=[set_field(vlan_vid(4096 + c_vid))],
-                next_table_id=1
-            )
-        ))
-        # Upstream flow 1 for untagged case
-        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
-            id=logical_device_id,
-            flow_mod=mk_simple_flow_mod(
-                priority=500,
-                match_fields=[in_port(uni_port_no), vlan_vid(0)],
-                actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + c_vid))],
-                next_table_id=1
-            )
-        ))
-        # Upstream flow 2 for s-tag
-        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
-            id=logical_device_id,
-            flow_mod=mk_simple_flow_mod(
-                priority=500,
-                match_fields=[in_port(uni_port_no), vlan_vid(4096 + c_vid)],
-                actions=[
-                    push_vlan(0x8100),
-                    set_field(vlan_vid(4096 + 1000)),
-                    output(nni_port_no)
-                ]
-            )
-        ))
+            # Controller-bound flows
+            stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+                id=logical_device_id,
+                flow_mod=mk_simple_flow_mod(
+                    priority=2000,
+                    match_fields=[in_port(uni_port_no), eth_type(0x888e)],
+                    actions=[
+                        # push_vlan(0x8100),
+                        # set_field(vlan_vid(4096 + 4000)),
+                        output(ofp.OFPP_CONTROLLER)
+                    ]
+                )
+            ))
+            stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+                id=logical_device_id,
+                flow_mod=mk_simple_flow_mod(
+                    priority=1000,
+                    match_fields=[eth_type(0x800), ip_proto(2)],
+                    actions=[output(ofp.OFPP_CONTROLLER)]
+                )
+            ))
+            stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+                id=logical_device_id,
+                flow_mod=mk_simple_flow_mod(
+                    priority=1000,
+                    match_fields=[eth_type(0x800), ip_proto(17), udp_dst(67)],
+                    actions=[output(ofp.OFPP_CONTROLLER)]
+                )
+            ))
+
+            # Unicast flows:
+            # Downstream flow 1
+            stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+                id=logical_device_id,
+                flow_mod=mk_simple_flow_mod(
+                    priority=500,
+                    match_fields=[
+                        in_port(nni_port_no),
+                        vlan_vid(4096 + 1000),
+                        metadata(c_vid)  # here to mimic an ONOS artifact
+                    ],
+                    actions=[pop_vlan()],
+                    next_table_id=1
+                )
+            ))
+            # Downstream flow 2
+            stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+                id=logical_device_id,
+                flow_mod=mk_simple_flow_mod(
+                    priority=500,
+                    table_id=1,
+                    match_fields=[in_port(nni_port_no), vlan_vid(4096 + c_vid)],
+                    actions=[set_field(vlan_vid(4096 + 0)), output(uni_port_no)]
+                )
+            ))
+            # Upstream flow 1 for 0-tagged case
+            stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+                id=logical_device_id,
+                flow_mod=mk_simple_flow_mod(
+                    priority=500,
+                    match_fields=[in_port(uni_port_no), vlan_vid(4096 + 0)],
+                    actions=[set_field(vlan_vid(4096 + c_vid))],
+                    next_table_id=1
+                )
+            ))
+            # Upstream flow 1 for untagged case
+            stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+                id=logical_device_id,
+                flow_mod=mk_simple_flow_mod(
+                    priority=500,
+                    match_fields=[in_port(uni_port_no), vlan_vid(0)],
+                    actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + c_vid))],
+                    next_table_id=1
+                )
+            ))
+            # Upstream flow 2 for s-tag
+            stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+                id=logical_device_id,
+                flow_mod=mk_simple_flow_mod(
+                    priority=500,
+                    table_id=1,
+                    match_fields=[in_port(uni_port_no), vlan_vid(4096 + c_vid)],
+                    actions=[
+                        push_vlan(0x8100),
+                        set_field(vlan_vid(4096 + 1000)),
+                        output(nni_port_no)
+                    ]
+                )
+            ))
 
         # Push a few multicast flows
-        # 1st with one bucket for our uni
+        # 1st with one bucket for our uni 0
         stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
             id=logical_device_id,
             group_mod=mk_multicast_group_mod(
                 group_id=1,
                 buckets=[
-                    ofp.ofp_bucket(actions=[pop_vlan(), output(uni_port_no)])
+                    ofp.ofp_bucket(actions=[
+                        pop_vlan(),
+                        output(unis[0][0])
+                    ])
                 ]
             )
         ))
@@ -572,13 +592,15 @@
                 actions=[group(1)]
             )
         ))
-        # 2st with one bucket for our uni
+
+        # 2nd with one bucket for uni 0 and 1
         stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
             id=logical_device_id,
             group_mod=mk_multicast_group_mod(
                 group_id=2,
                 buckets=[
-                    ofp.ofp_bucket(actions=[pop_vlan(), output(uni_port_no)])
+                    ofp.ofp_bucket(actions=[pop_vlan(), output(unis[0][0])]),
+                    ofp.ofp_bucket(actions=[pop_vlan(), output(unis[1][0])])
                 ]
             )
         ))
@@ -595,6 +617,7 @@
                 actions=[group(2)]
             )
         ))
+
         # 3rd with empty bucket
         stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
             id=logical_device_id,
@@ -629,22 +652,26 @@
         logical_device_id = line or self.default_logical_device_id
 
         # gather NNI and UNI port IDs
-        nni_port_no, uni_port_no, c_vid = \
-            self.get_logical_ports(logical_device_id)
+        nni_port_no, unis = self.get_logical_ports(logical_device_id)
 
         # construct and push flow rules
         stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
 
         # Controller-bound flows
-        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
-            id=logical_device_id,
-            flow_mod=mk_simple_flow_mod(
-                priority=1000,
-                match_fields=[eth_type(0x800), ip_proto(17), udp_dst(67)],
-                actions=[output(ofp.OFPP_CONTROLLER)]
-            )
-        ))
-
+        for uni_port_no, _ in unis:
+            stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+                id=logical_device_id,
+                flow_mod=mk_simple_flow_mod(
+                    priority=1000,
+                    match_fields=[
+                        in_port(uni_port_no),
+                        eth_type(0x800),
+                        ip_proto(17),
+                        udp_dst(67)
+                    ],
+                    actions=[output(ofp.OFPP_CONTROLLER)]
+                )
+            ))
 
         self.poutput('success')
 
diff --git a/tests/utests/voltha/core/test_logical_device_agent.py b/tests/utests/voltha/core/test_logical_device_agent.py
index dbb42c6..45c78d0 100644
--- a/tests/utests/voltha/core/test_logical_device_agent.py
+++ b/tests/utests/voltha/core/test_logical_device_agent.py
@@ -625,17 +625,17 @@
                     output(ofp.OFPP_CONTROLLER)
                 ]
             ))
-        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.update_flow_table(mk_simple_flow_mod(
-            priority=1000,
-            match_fields=[eth_type(0x800), ip_proto(17),
-                          udp_src(68), udp_dst(67)],
-            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(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 = (
@@ -667,38 +667,42 @@
             ))
 
         # Unicast channels for each subscriber
-        # Downstream flow 1 for both
-        self.lda.update_flow_table(mk_simple_flow_mod(
-            priority=500,
-            match_fields=[
-                in_port(0),
-                vlan_vid(4096 + 1000),
-                metadata(128)
-            ],
-            actions=[pop_vlan()],
-            next_table_id=1
-        ))
-        # Downstream flow 2 and upsrteam flow 1 for each ONU
         for port, c_vid in ((1, 101), (2, 102)):
+
+            # Downstream flow 1 for both
+            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)]
             ))
-            # for the 0-tagged case
+
+            # 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
             ))
-            # for the untagged case
+            # ... 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,
@@ -710,14 +714,14 @@
                 ]
             ))
 
-        self.assertEqual(len(self.flows.items), 17)
+        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), 8)
+        self.assertEqual(len(self.device_flows['olt'].items), 9)
         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)
@@ -756,7 +760,7 @@
         ))
         self.assertFlowsEqual(self.device_flows['olt'].items[5], mk_flow_stat(
             priority=500,
-            match_fields=[in_port(0), vlan_vid(4096 + 1000), metadata(128)],
+            match_fields=[in_port(0), vlan_vid(4096 + 1000), metadata(101)],
             actions=[pop_vlan(), output(1)]
         ))
         self.assertFlowsEqual(self.device_flows['olt'].items[6], mk_flow_stat(
@@ -767,6 +771,11 @@
         ))
         self.assertFlowsEqual(self.device_flows['olt'].items[7], 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[8], mk_flow_stat(
+            priority=500,
             match_fields=[in_port(1), vlan_vid(4096 + 102)],
             actions=[
                 push_vlan(0x8100), set_field(vlan_vid(4096 + 1000)), output(0)]
diff --git a/voltha/adapters/tibit_olt/tibit_olt.py b/voltha/adapters/tibit_olt/tibit_olt.py
index a10d70d..0713674 100644
--- a/voltha/adapters/tibit_olt/tibit_olt.py
+++ b/voltha/adapters/tibit_olt/tibit_olt.py
@@ -730,7 +730,7 @@
                 else:
                     raise Exception('Port should be 1 or 2 by our convention')
 
-            except Exception, e:
+            except Exception as e:
                 log.exception('failed-to-install-flow', e=e, flow=flow)
 
     def update_flows_incrementally(self, device, flow_changes, group_changes):