VOL-1398: Adtran-ONU - Initial containerization commit

Change-Id: I7afcc1ad65b9ef80da994b0b0ddf74860911bb46
diff --git a/adapters/adtran_onu/flow/flow_entry.py b/adapters/adtran_onu/flow/flow_entry.py
new file mode 100644
index 0000000..d3ec17d
--- /dev/null
+++ b/adapters/adtran_onu/flow/flow_entry.py
@@ -0,0 +1,272 @@
+# Copyright 2017-present Adtran, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from enum import IntEnum
+from pyvoltha.common.openflow.utils import *
+from pyvoltha.protos.openflow_13_pb2 import OFPP_MAX
+
+log = structlog.get_logger()
+
+# IP Protocol numbers
+_supported_ip_protocols = [
+    1,          # ICMP
+    2,          # IGMP
+    6,          # TCP
+    17,         # UDP
+]
+
+
+class FlowEntry(object):
+    """
+    Provide a class that wraps the flow rule and also provides state/status for a FlowEntry.
+
+    When a new flow is sent, it is first decoded to check for any potential errors. If None are
+    found, the entry is created and it is analyzed to see if it can be combined to with any other flows
+    to create or modify an existing EVC.
+
+    Note: Since only E-LINE is supported, modification of an existing EVC is not performed.
+    """
+    class FlowDirection(IntEnum):
+        UPSTREAM = 0          # UNI port to ANI Port
+        DOWNSTREAM = 1        # ANI port to UNI Port
+        ANI = 2               # ANI port to ANI Port
+        UNI = 3               # UNI port to UNI Port
+        OTHER = 4             # Unable to determine
+
+    _flow_dir_map = {
+        (FlowDirection.UNI, FlowDirection.ANI): FlowDirection.UPSTREAM,
+        (FlowDirection.ANI, FlowDirection.UNI): FlowDirection.DOWNSTREAM
+    }
+
+    upstream_flow_types = {FlowDirection.UPSTREAM}
+    downstream_flow_types = {FlowDirection.DOWNSTREAM}
+
+    # Well known EtherTypes
+    class EtherType(IntEnum):
+        EAPOL = 0x888E
+        IPv4 = 0x0800
+        IPv6 = 0x86DD
+        ARP = 0x0806
+        LLDP = 0x88CC
+
+    # Well known IP Protocols
+    class IpProtocol(IntEnum):
+        IGMP = 2
+        UDP = 17
+
+    def __init__(self, flow, handler):
+        self._handler = handler
+        self.flow_id = flow.id
+        self._flow_direction = FlowEntry.FlowDirection.OTHER
+        self._is_multicast = False
+        self.tech_profile_id = None
+
+        # Selection properties
+        self.in_port = None
+        self.vlan_vid = None
+        self.vlan_pcp = None
+        self.etype = None
+        self.proto = None
+        self.ipv4_dst = None
+        self.udp_dst = None         # UDP Port #
+        self.udp_src = None         # UDP Port #
+        self.inner_vid = None
+
+        # Actions
+        self.out_port = None
+        self.pop_vlan = False
+        self.push_vlan_tpid = None
+        self.set_vlan_vid = None
+        self._name = self.create_flow_name()
+
+    def __str__(self):
+        return 'flow_entry: {}, in: {}, out: {}, vid: {}, inner:{}, eth: {}, IP: {}'.format(
+            self.name, self.in_port, self.out_port, self.vlan_vid, self.inner_vid,
+            self.etype, self.proto)
+
+    def __repr__(self):
+        return str(self)
+
+    @property
+    def name(self):
+        return self._name    # TODO: Is a name really needed in production?
+
+    def create_flow_name(self):
+        return 'flow-{}-{}'.format(self.device_id, self.flow_id)
+
+    @property
+    def handler(self):
+        return self._handler
+
+    @property
+    def device_id(self):
+        return self.handler.device_id
+
+    @property
+    def flow_direction(self):
+        return self._flow_direction
+
+    @property
+    def is_multicast_flow(self):
+        return self._is_multicast
+
+    @staticmethod
+    def create(flow, handler):
+        """
+        Create the appropriate FlowEntry wrapper for the flow.  This method returns a two
+        results.
+
+        The first result is the flow entry that was created. This could be a match to an
+        existing flow since it is a bulk update.  None is returned only if no match to
+        an existing entry is found and decode failed (unsupported field)
+
+        :param flow:   (Flow) Flow entry passed to VOLTHA adapter
+        :param handler: (DeviceHandler) handler for the device
+        :return: (FlowEntry) Created flow entry, None on decode failure
+        """
+        # Exit early if it already exists
+        try:
+            flow_entry = FlowEntry(flow, handler)
+
+            if not flow_entry.decode(flow):
+                return None
+
+            # TODO: Do we want to do the OMCI here ?
+
+            return flow_entry
+
+        except Exception as e:
+            log.exception('flow-entry-processing', e=e)
+            return None
+
+    def decode(self, flow):
+        """
+        Examine flow rules and extract appropriate settings
+        """
+        log.debug('start-decode')
+        status = self._decode_traffic_selector(flow) and self._decode_traffic_treatment(flow)
+
+        if status:
+            ani_ports = [pon.port_number for pon in self._handler.pon_ports]
+            uni_ports = [uni.port_number for uni in self._handler.uni_ports]
+
+            # Determine direction of the flow
+            def port_type(port_number):
+                if port_number in ani_ports:
+                    return FlowEntry.FlowDirection.ANI
+
+                elif port_number in uni_ports:
+                    return FlowEntry.FlowDirection.UNI
+
+                return FlowEntry.FlowDirection.OTHER
+
+            self._flow_direction = FlowEntry._flow_dir_map.get((port_type(self.in_port),
+                                                                port_type(self.out_port)),
+                                                               FlowEntry.FlowDirection.OTHER)
+        return status
+
+    def _decode_traffic_selector(self, flow):
+        """
+        Extract traffic selection settings
+        """
+        self.in_port = get_in_port(flow)
+
+        if self.in_port > OFPP_MAX:
+            log.warn('logical-input-ports-not-supported')
+            return False
+
+        for field in get_ofb_fields(flow):
+            if field.type == IN_PORT:
+                assert self.in_port == field.port, 'Multiple Input Ports found in flow rule'
+
+            elif field.type == VLAN_VID:
+                self.vlan_vid = field.vlan_vid & 0xfff
+                log.debug('*** field.type == VLAN_VID', value=field.vlan_vid, vlan_id=self.vlan_vid)
+                self._is_multicast = False  # TODO: self.vlan_id in self._handler.multicast_vlans
+
+            elif field.type == VLAN_PCP:
+                log.debug('*** field.type == VLAN_PCP', value=field.vlan_pcp)
+                self.vlan_pcp = field.vlan_pcp
+
+            elif field.type == ETH_TYPE:
+                log.debug('*** field.type == ETH_TYPE', value=field.eth_type)
+                self.etype = field.eth_type
+
+            elif field.type == IP_PROTO:
+                log.debug('*** field.type == IP_PROTO', value=field.ip_proto)
+                self.proto = field.ip_proto
+
+                if self.proto not in _supported_ip_protocols:
+                    log.error('Unsupported IP Protocol', ip_proto=self.proto)
+                    return False
+
+            elif field.type == IPV4_DST:
+                log.debug('*** field.type == IPV4_DST', value=field.ipv4_dst)
+                self.ipv4_dst = field.ipv4_dst
+
+            elif field.type == UDP_DST:
+                log.debug('*** field.type == UDP_DST', value=field.udp_dst)
+                self.udp_dst = field.udp_dst
+
+            elif field.type == UDP_SRC:
+                log.debug('*** field.type == UDP_SRC', value=field.udp_src)
+                self.udp_src = field.udp_src
+
+            elif field.type == METADATA:
+                log.debug('*** field.type == METADATA', value=field.table_metadata)
+                self.inner_vid = field.table_metadata
+                log.debug('*** field.type == METADATA', value=field.table_metadata,
+                          inner_vid=self.inner_vid)
+            else:
+                log.warn('unsupported-selection-field', type=field.type)
+                self._status_message = 'Unsupported field.type={}'.format(field.type)
+                return False
+
+        return True
+
+    def _decode_traffic_treatment(self, flow):
+        self.out_port = get_out_port(flow)
+
+        if self.out_port > OFPP_MAX:
+            log.warn('logical-output-ports-not-supported')
+            return False
+
+        for act in get_actions(flow):
+            if act.type == OUTPUT:
+                assert self.out_port == act.output.port, 'Multiple Output Ports found in flow rule'
+                pass           # Handled earlier
+
+            elif act.type == POP_VLAN:
+                log.debug('*** action.type == POP_VLAN')
+                self.pop_vlan = True
+
+            elif act.type == PUSH_VLAN:
+                log.debug('*** action.type == PUSH_VLAN', value=act.push)
+                tpid = act.push.ethertype
+                self.push_tpid = tpid
+                assert tpid == 0x8100, 'Only TPID 0x8100 is currently supported'
+
+            elif act.type == SET_FIELD:
+                log.debug('*** action.type == SET_FIELD', value=act.set_field.field)
+                assert (act.set_field.field.oxm_class == ofp.OFPXMC_OPENFLOW_BASIC)
+                field = act.set_field.field.ofb_field
+                if field.type == VLAN_VID:
+                    self.set_vlan_vid = field.vlan_vid & 0xfff
+
+            else:
+                log.warn('unsupported-action', action=act)
+                self._status_message = 'Unsupported action.type={}'.format(act.type)
+                return False
+
+        return True