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)