VOL-1582: Support for handling Transparent Flows in Flow decomposer and OpenOLT adapter

Change-Id: I7d539a9b6f7f3ac1b68851efc1e43d4f14ea2ce6
diff --git a/voltha/adapters/openolt/openolt_data_model.py b/voltha/adapters/openolt/openolt_data_model.py
index 3191555..9bc8d3d 100644
--- a/voltha/adapters/openolt/openolt_data_model.py
+++ b/voltha/adapters/openolt/openolt_data_model.py
@@ -428,6 +428,8 @@
                          logical_port.ofp_port.port_no)
         return ofp_port_name
 
+    def get_logical_port(self, logical_device_id, port_id):
+        return self.adapter_agent.get_logical_port(logical_device_id, port_id)
 
     # #######################################################################
     # Methods used by Alarm and Statistics Manager (TODO - re-visit)
diff --git a/voltha/adapters/openolt/openolt_flow_mgr.py b/voltha/adapters/openolt/openolt_flow_mgr.py
index d0bf2f7..0b002ba 100644
--- a/voltha/adapters/openolt/openolt_flow_mgr.py
+++ b/voltha/adapters/openolt/openolt_flow_mgr.py
@@ -33,6 +33,7 @@
 
 # Flow categories
 HSIA_FLOW = "HSIA_FLOW"
+HSIA_TRANSPARENT = "HSIA_TRANSPARENT-{}"
 
 EAP_ETH_TYPE = 0x888e
 LLDP_ETH_TYPE = 0x88cc
@@ -166,7 +167,9 @@
                 self.log.debug('field-type-ipv4-src',
                                ipv4_dst=classifier_info[IPV4_SRC])
             elif field.type == fd.METADATA:
-                pass
+                classifier_info[METADATA] = field.table_metadata
+                self.log.debug('field-type-metadata',
+                               metadata=classifier_info[METADATA])
             else:
                 raise NotImplementedError('field.type={}'.format(
                     field.type))
@@ -297,6 +300,24 @@
         self.divide_and_add_flow(intf_id, onu_id, uni_id, port_no,
                                  classifier_info, action_info, flow, tp_id, us_meter_id, ds_meter_id)
 
+    def _is_uni_port(self, port_no):
+        try:
+            port = self.data_model.get_logical_port(self.logical_device_id,
+                                                       'uni-{}'.format(port_no))
+            if port is not None:
+                return (not port.root_port), port.device_id
+            else:
+                return False, None
+        except Exception as e:
+            self.log.debug("port-not-found", e=e)
+            return False, None
+
+    def _is_upstream_flow(self, port_no):
+        return self._is_uni_port(port_no)[0]
+
+    def _is_downstream_flow(self, port_no):
+        return not self._is_upstream_flow(port_no)
+
     def _clear_flow_id_from_rm(self, flow, flow_id, flow_direction):
         try:
             pon_intf, onu_id, uni_id, eth_type \
@@ -407,6 +428,18 @@
         self.log.debug(" tp-path-in-delete", tp_path=tp_path)
         return self.tech_profile[intf_id].delete_tech_profile_instance(tp_path)
 
+    def is_no_l2_modification_flow(self, classifier, action):
+        no_l2_classifier_set = {IN_PORT, METADATA, VLAN_VID}
+        no_l2_action_set = {OUTPUT}
+        incoming_classifier_set = set(classifier.keys())
+        incoming_action_set = set(action.keys())
+
+        if no_l2_classifier_set.issubset(incoming_classifier_set) and \
+            no_l2_action_set.issubset(incoming_action_set) and \
+                len(incoming_action_set) == 1:
+            return True
+        return False
+
     def divide_and_add_flow(self, intf_id, onu_id, uni_id, port_no, classifier,
                             action, flow, tp_id, us_meter_id, ds_meter_id):
 
@@ -527,6 +560,32 @@
                 self._install_flow_on_all_gemports(self.add_downstream_data_flow,
                                                    kwargs, ds_gem_port_attr_list
                                                    )
+
+        elif self.is_no_l2_modification_flow(classifier, action) and \
+                self._is_upstream_flow(classifier[IN_PORT]):
+            kwargs['is_l2_mod_flow'] = False
+            if VLAN_PCP in classifier:
+                kwargs['gemport_id'] = self._get_gem_port_for_pcp(
+                    classifier[VLAN_PCP], us_gem_port_attr_list
+                )
+                self.add_upstream_data_flow(**kwargs)
+            else:
+                self._install_flow_on_all_gemports(self.add_upstream_data_flow,
+                                                   kwargs, us_gem_port_attr_list
+                                                   )
+        elif self.is_no_l2_modification_flow(classifier, action) and \
+                self._is_downstream_flow(classifier[IN_PORT]):
+            kwargs['is_l2_mod_flow'] = False
+            if VLAN_PCP in classifier:
+                kwargs['gemport_id'] = self._get_gem_port_for_pcp(
+                    classifier[VLAN_PCP], ds_gem_port_attr_list
+                )
+                self.add_downstream_data_flow(**kwargs)
+            else:
+                self._install_flow_on_all_gemports(self.add_downstream_data_flow,
+                                                   kwargs, ds_gem_port_attr_list
+                                                   )
+
         else:
             self.log.debug('Invalid-flow-type-to-handle',
                            classifier=classifier,
@@ -871,20 +930,26 @@
         return alloc_id, gem_port_ids
 
     def add_upstream_data_flow(self, intf_id, onu_id, uni_id, port_no, classifier,
-                               action, logical_flow, alloc_id, gemport_id):
+                               action, logical_flow, alloc_id, gemport_id, is_l2_mod_flow=True):
 
-        classifier[PACKET_TAG_TYPE] = SINGLE_TAG
+        if is_l2_mod_flow:
+            classifier[PACKET_TAG_TYPE] = SINGLE_TAG
+        else:
+            classifier[PACKET_TAG_TYPE] = DOUBLE_TAG
 
         self.add_hsia_flow(intf_id, onu_id, uni_id, port_no, classifier,
                            action, UPSTREAM,
                            logical_flow, alloc_id, gemport_id)
 
     def add_downstream_data_flow(self, intf_id, onu_id, uni_id, port_no, classifier,
-                                 action, logical_flow, alloc_id, gemport_id):
-        classifier[PACKET_TAG_TYPE] = DOUBLE_TAG
-        # Needed ???? It should be already there
-        action[POP_VLAN] = True
-        action[VLAN_VID] = classifier[VLAN_VID]
+                                 action, logical_flow, alloc_id, gemport_id, is_l2_mod_flow=True):
+        if is_l2_mod_flow:
+            classifier[PACKET_TAG_TYPE] = DOUBLE_TAG
+            classifier[POP_VLAN] = True
+            action[VLAN_VID] = classifier[VLAN_VID]
+
+        else:
+            classifier[PACKET_TAG_TYPE] = DOUBLE_TAG
 
         self.add_hsia_flow(intf_id, onu_id, uni_id, port_no, classifier,
                            action, DOWNSTREAM,
@@ -908,8 +973,13 @@
             # takes priority over flow_cookie to find any available HSIA_FLOW
             # id for the ONU.
 
+            flow_category = HSIA_FLOW
+
+            if self.is_no_l2_modification_flow(classifier, action):
+                flow_category = HSIA_TRANSPARENT.format(classifier[VLAN_VID])
+
             flow_id = self.resource_mgr.get_flow_id(intf_id, onu_id, uni_id,
-                                                    flow_category=HSIA_FLOW,
+                                                    flow_category=flow_category,
                                                     flow_pcp=classifier[VLAN_PCP])
             if flow_id is None:
                 self.log.error("hsia-flow-unavailable")
@@ -927,7 +997,7 @@
             if self.add_flow_to_device(flow, logical_flow):
                 flow_info = self._get_flow_info_as_json_blob(flow,
                                                              flow_store_cookie,
-                                                             HSIA_FLOW)
+                                                             flow_category)
                 self.update_flow_info_to_kv_store(flow.access_intf_id,
                                                   flow.onu_id, flow.uni_id,
                                                   flow.flow_id, flow_info)
diff --git a/voltha/core/flow_decomposer.py b/voltha/core/flow_decomposer.py
index 29c5e34..8027bee 100644
--- a/voltha/core/flow_decomposer.py
+++ b/voltha/core/flow_decomposer.py
@@ -290,7 +290,6 @@
             actions.extend(instruction.actions.actions)
     return actions
 
-
 def get_ofb_fields(flow):
     assert isinstance(flow, ofp.ofp_flow_stats)
     assert flow.match.type == ofp.OFPMT_OXM
@@ -373,6 +372,24 @@
 def has_next_table(flow):
     return get_goto_table_id(flow) is not None
 
+def has_vlan_mod_action(flow):
+    # set containing possible vlan mod actions
+    vlan_mod_actions_set = set([ofp.OFPAT_PUSH_VLAN, ofp.OFPAT_POP_VLAN, ofp.OFPAT_SET_FIELD])
+    vlan_actions = []
+    for instruction in flow.instructions:
+        if instruction.type == ofp.OFPIT_APPLY_ACTIONS or instruction.type == ofp.OFPIT_WRITE_ACTIONS:
+            for action in instruction.actions.actions:
+                vlan_actions.append(action.type)
+    # actions in the current flow
+    curr_vlan_action_set = set(vlan_actions)
+
+    # See if we have one more vlan mod actions in the current actions present in the flow
+    # Return True if we have at least one vlan mod action, else False.
+    if len(curr_vlan_action_set.intersection(vlan_mod_actions_set)):
+        return True
+    else:
+        return False
+
 def get_group(flow):
     for action in get_actions(flow):
         if action.type == GROUP:
@@ -756,6 +773,31 @@
                         metadata=metadata_from_write_metadata
                     ))
 
+                elif flow.table_id == 0 and out_port_no is not None \
+                     and not has_vlan_mod_action(flow):
+                    # Transparent upstream flow
+                    log.debug('decomposing-transparent-olt-flow-in-upstream', match=flow.match)
+                    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, )
+                        ],
+                        actions=[
+                            action for action in get_actions(flow)
+                            if action.type != OUTPUT
+                        ] + [
+                            output(egress_hop.egress_port.port_no)
+                        ],
+                        meter_id=meter_id,
+                        metadata=metadata_from_write_metadata
+                    ))
+
                 else:
                     # unknown upstream flow
                     log.error('unknown-upstream-flow', flow=flow,
@@ -805,6 +847,45 @@
                         metadata=metadata_from_write_metadata
                     ))
 
+                elif flow.table_id == 0 and out_port_no is not None \
+                     and not has_vlan_mod_action(flow):
+                    # Transparent downstream flow
+                    log.debug('decomposing-transparent-olt-flow-in-downstream', match=flow.match)
+                    # For downstream flows without output port action we need to
+                    # recalculate route with the output extracted from the metadata
+                    # to determine the PON port to send to the correct ONU/UNI
+                    egress_port_number = get_egress_port_number_from_metadata(flow)
+
+                    if egress_port_number is not None:
+                        route = self.get_route(in_port_no, egress_port_number)
+                        if route is None:
+                            log.error('no-route-downstream', in_port_no=in_port_no,
+                                      egress_port_number=egress_port_number, comment='deleting flow')
+                            self.flow_delete(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(
+                        priority=flow.priority,
+                        cookie=flow.cookie,
+                        match_fields=[
+                            in_port(ingress_hop.ingress_port.port_no)
+                        ] + [
+                            field for field in get_ofb_fields(flow)
+                            if field.type not in (IN_PORT,)
+                        ],
+                        actions=[
+                            action for action in get_actions(flow)
+                        ] + [
+                            output(ingress_hop.egress_port.port_no)
+                        ],
+                        meter_id=meter_id,
+                        metadata=metadata_from_write_metadata
+                    ))
+
                 elif flow.table_id == 1 and out_port_no is not None:
                     # ONU flow in downstream direction (unicast traffic)
                     log.debug('decomposing-onu-flow-in-downstream', match=flow.match)