VOL-1267 Support for VEIP UNI Type

Supports both PPTP and VEIP UNI types.  Tested with ALPHA all-in-one onu/rg.
Testing likely requires configuration on the ONU/RG device to create the veip "wan" port.
I have screenshots of the steps needed.

Existing PPTP onu still work as-is.

Change-Id: Icbb05dd3da4dd0d3ccb63b5a8a9bed7afb57d29e
diff --git a/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py b/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
index fa15830..4254bf9 100644
--- a/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
+++ b/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
@@ -49,24 +49,13 @@
 OP = EntityOperations
 RC = ReasonCodes
 
-
 _ = third_party
 log = structlog.get_logger()
 
-
-BRDCM_DEFAULT_VLAN = 4091
-ADMIN_STATE_LOCK = 1
-ADMIN_STATE_UNLOCK = 0
-RESERVED_VLAN_ID = 4095
 _STARTUP_RETRY_WAIT = 20
 _MAXIMUM_PORT = 128          # UNI ports
 
 
-
-_ = third_party
-
-
-
 class BrcmOpenomciOnuHandler(object):
 
     def __init__(self, adapter, device_id):
@@ -757,39 +746,59 @@
             # the first time we received a successful (no timeout) OMCI Rx response.
             try:
 
-                ani_g = config.ani_g_entities
-                uni_g = config.uni_g_entities
-                pptp = config.pptp_entities
+                # sort the lists so we get consistent port ordering.
+                ani_list = sorted(config.ani_g_entities) if config.ani_g_entities else []
+                uni_list = sorted(config.uni_g_entities) if config.uni_g_entities else []
+                pptp_list = sorted(config.pptp_entities) if config.pptp_entities else []
+                veip_list = sorted(config.veip_entities) if config.veip_entities else []
 
-                # TODO: there are onu out there that have uni ports available but no pptp.  These all-in-one devices
-                # likely need virtual ethernet interface point ME #329 configured rather than pptp.
-                if ani_g is None or uni_g is None or pptp is None:
+                if ani_list is None or uni_list is None:
                     device.reason = 'onu-missing-required-elements'
+                    self.log.warn("no-ani-or-unis")
                     self.adapter_agent.update_device(device)
                     raise Exception("onu-missing-required-elements")
 
-                # Currently logging the ani and uni for information purposes.   Actually act on the pptp as its ME ID
-                # is the most correct one to use in later tasks.
-                for key, value in ani_g.iteritems():
-                    self.log.debug("discovered-ani", key=key, value=value)
+                # Currently logging the ani, pptp, veip, and uni for information purposes.
+                # Actually act on the veip/pptp as its ME is the most correct one to use in later tasks.
+                for entity_id in ani_list:
+                    ani_value = config.ani_g_entities[entity_id]
+                    self.log.debug("discovered-ani", entity_id=entity_id, value=ani_value)
+                    # TODO: currently only one OLT PON port/ANI, so this works out.  With NGPON there will be 2..?
+                    self._total_tcont_count = ani_value.get('total-tcont-count')
+                    self.log.debug("set-total-tcont-count", tcont_count=self._total_tcont_count)
 
-                for key, value in uni_g.iteritems():
-                    self.log.debug("discovered-uni", key=key, value=value)
+                for entity_id in pptp_list:
+                    pptp_value = config.pptp_entities[entity_id]
+                    self.log.debug("discovered-pptp", entity_id=entity_id, value=pptp_value)
 
-                for key, value in pptp.iteritems():
-                    self.log.debug("discovered-pptp-uni", key=key, value=value)
-                    entity_id = key
-                    self._add_uni_port(entity_id)
+                for entity_id in veip_list:
+                    veip_value = config.veip_entities[entity_id]
+                    self.log.debug("discovered-veip", key=entity_id, value=veip_value)
 
-                    # TODO: only one uni/pptp for now. flow bug in openolt
-                    break
+                for entity_id in uni_list:
+                    uni_value = config.uni_g_entities[entity_id]
+                    self.log.debug("discovered-uni", entity_id=entity_id, value=uni_value)
 
-                self._total_tcont_count = ani_g.get('total-tcont-count')
+                    # TODO: can only support one UNI per ONU at this time. break out as soon as we have a good UNI
+                    if entity_id in pptp_list:
+                        self._add_uni_port(entity_id, uni_type=UniType.PPTP)
+                        break
+                    elif entity_id in veip_list:
+                        self._add_uni_port(entity_id, uni_type=UniType.VEIP)
+                        break
+                    else:
+                        self.log.warn("unable-to-find-uni-in-pptp-or-veip", key=entity_id, value=uni_value)
+
                 self._qos_flexibility = config.qos_configuration_flexibility or 0
                 self._omcc_version = config.omcc_version or OMCCVersion.Unknown
-                self.log.debug("set-total-tcont-count", tcont_count=self._total_tcont_count)
 
-                self._dev_info_loaded = True
+                if self._unis:
+                    self._dev_info_loaded = True
+                else:
+                    device.reason = 'no-usable-unis'
+                    self.adapter_agent.update_device(device)
+                    self.log.warn("no-usable-unis")
+                    raise Exception("no-usable-unis")
 
             except Exception as e:
                 self.log.exception('device-info-load', e=e)
@@ -829,7 +838,7 @@
             self.log.info('device-info-not-loaded-skipping-mib-download')
 
 
-    def _add_uni_port(self, entity_id):
+    def _add_uni_port(self, entity_id, uni_type=UniType.PPTP):
         self.log.debug('function-entry')
 
         device = self.adapter_agent.get_device(self.device_id)
@@ -837,7 +846,7 @@
 
         parent_adapter_agent = registry('adapter_loader').get_agent(parent_device.adapter)
         if parent_adapter_agent is None:
-            self.log.error('openolt_adapter_agent-could-not-be-retrieved')
+            self.log.error('parent-adapter-could-not-be-retrieved')
 
         # TODO: This knowledge is locked away in openolt.  and it assumes one onu equals one uni...
         parent_device = self.adapter_agent.get_device(device.parent_id)
@@ -852,13 +861,14 @@
 
         mac_bridge_port_num = working_port + 1
 
-        self.log.debug('live-port-number-ready', uni_no=uni_no, uni_name=uni_name)
+        self.log.debug('uni-port-inputs', uni_no=uni_no, uni_name=uni_name, uni_type=uni_type,
+                       entity_id=entity_id, mac_bridge_port_num=mac_bridge_port_num)
 
-        uni_port = UniPort.create(self, uni_name, uni_no, uni_name, device.vlan, device.vlan)
+        uni_port = UniPort.create(self, uni_name, uni_no, uni_name, uni_type)
         uni_port.entity_id = entity_id
         uni_port.enabled = True
         uni_port.mac_bridge_port_num = mac_bridge_port_num
-        uni_port.add_logical_port(uni_port.port_number, subscriber_vlan=device.vlan)
+        uni_port.add_logical_port(uni_port.port_number)
 
         self.log.debug("created-uni-port", uni=uni_port)
 
diff --git a/voltha/adapters/brcm_openomci_onu/omci/brcm_mib_download_task.py b/voltha/adapters/brcm_openomci_onu/omci/brcm_mib_download_task.py
index b1394b6..ebc7492 100644
--- a/voltha/adapters/brcm_openomci_onu/omci/brcm_mib_download_task.py
+++ b/voltha/adapters/brcm_openomci_onu/omci/brcm_mib_download_task.py
@@ -20,6 +20,7 @@
 from voltha.extensions.omci.omci_me import *
 from voltha.extensions.omci.tasks.task import Task
 from voltha.extensions.omci.omci_defs import *
+from voltha.adapters.brcm_openomci_onu.uni_port import *
 
 OP = EntityOperations
 RC = ReasonCodes
@@ -84,8 +85,6 @@
 
         # TODO: only using a single UNI/ethernet port
         self._uni_port = self._handler.uni_ports[0]
-        self._uni_port_num = self._uni_port.mac_bridge_port_num
-        self._ethernet_uni_entity_id = self._uni_port.entity_id
 
         # Port numbers
         self._pon_port_num = 3  # TODO why 3.  maybe this is the ani port number.  look at anis list
@@ -344,16 +343,26 @@
             #            - Nothing
             #  References:
             #            - MAC Bridge Service Profile (the bridge)
-            #            - PPTP Ethernet UNI
+            #            - PPTP Ethernet or VEIP UNI
 
             # TODO: do this for all uni/ports...
             # TODO: magic. make a static variable for tp_type
+
+            # default to PPTP
+            tp_type = None
+            if self._uni_port.type is UniType.VEIP:
+                tp_type = 11
+            elif self._uni_port.type is UniType.PPTP:
+                tp_type = 1
+            else:
+                tp_type = 1
+
             msg = MacBridgePortConfigurationDataFrame(
-                self._ethernet_uni_entity_id,            # Entity ID - This is read-only/set-by-create !!!
+                self._uni_port.entity_id,            # Entity ID - This is read-only/set-by-create !!!
                 bridge_id_pointer=self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
-                port_num=self._uni_port_num,          # Port ID
-                tp_type=1,                            # PPTP Ethernet UNI
-                tp_pointer=self._ethernet_uni_entity_id     # Ethernet UNI ID
+                port_num=self._uni_port.mac_bridge_port_num,   # Port ID
+                tp_type=tp_type,                               # PPTP Ethernet or VEIP UNI
+                tp_pointer=self._uni_port.entity_id            # Ethernet UNI ID
             )
             frame = msg.create()
             self.log.debug('openomci-msg', msg=msg)
@@ -476,14 +485,24 @@
             #  EntityID relates to the VLAN TCIS
             #  References:
             #            - VLAN TCIS from previously created VLAN Tagging filter data
-            #            - PPTP Ethernet UNI
+            #            - PPTP Ethernet or VEIP UNI
             #
 
             # TODO: do this for all uni/ports...
             # TODO: magic.  static variable for assoc_type
+
+            # default to PPTP
+            association_type = None
+            if self._uni_port.type is UniType.VEIP:
+                association_type = 10
+            elif self._uni_port.type is UniType.PPTP:
+                association_type = 2
+            else:
+                association_type = 2
+
             attributes = dict(
-                association_type=2,                                 # Assoc Type, PPTP Ethernet UNI
-                associated_me_pointer=self._ethernet_uni_entity_id,  # Assoc ME, PPTP Entity Id
+                association_type=association_type,                  # Assoc Type, PPTP/VEIP Ethernet UNI
+                associated_me_pointer=self._uni_port.entity_id      # Assoc ME, PPTP/VEIP Entity Id
 
                 # Specifies the TPIDs in use and that operations in the downstream direction are
                 # inverse to the operations in the upstream direction
@@ -565,12 +584,21 @@
         #            - Nothing
         try:
             state = 1 if force_lock or not uni_port.enabled else 0
-            msg = PptpEthernetUniFrame(uni_port.entity_id,
-                                       attributes=dict(administrative_state=state))
-            frame = msg.set()
-            self.log.debug('openomci-msg', msg=msg)
-            results = yield omci_cc.send(frame)
-            self.check_status_and_state(results, 'set-pptp-ethernet-uni-lock-restore')
+            msg = None
+            if (uni_port.type is UniType.PPTP):
+                msg = PptpEthernetUniFrame(uni_port.entity_id,
+                                           attributes=dict(administrative_state=state))
+            elif (uni_port.type is UniType.VEIP):
+                msg = VeipUniFrame(uni_port.entity_id,
+                                   attributes=dict(administrative_state=state))
+            else:
+                self.log.warn('unknown-uni-type', uni_port=uni_port)
+
+            if msg:
+               frame = msg.set()
+               self.log.debug('openomci-msg', msg=msg)
+               results = yield omci_cc.send(frame)
+               self.check_status_and_state(results, 'set-pptp-ethernet-uni-lock-restore')
 
         except TimeoutError as e:
             self.log.warn('rx-timeout', e=e)
diff --git a/voltha/adapters/brcm_openomci_onu/omci/brcm_uni_lock_task.py b/voltha/adapters/brcm_openomci_onu/omci/brcm_uni_lock_task.py
index b998b0e..4eae51b 100644
--- a/voltha/adapters/brcm_openomci_onu/omci/brcm_uni_lock_task.py
+++ b/voltha/adapters/brcm_openomci_onu/omci/brcm_uni_lock_task.py
@@ -18,7 +18,7 @@
 from twisted.internet.defer import inlineCallbacks, failure, returnValue
 from voltha.extensions.omci.omci_defs import ReasonCodes, EntityOperations
 from voltha.extensions.omci.omci_me import OntGFrame
-from voltha.extensions.omci.omci_me import PptpEthernetUniFrame
+from voltha.extensions.omci.omci_me import PptpEthernetUniFrame, VeipUniFrame
 
 RC = ReasonCodes
 OP = EntityOperations
@@ -72,6 +72,7 @@
         super(BrcmUniLockTask, self).start()
         self._local_deferred = reactor.callLater(0, self.perform_lock)
 
+
     @inlineCallbacks
     def perform_lock(self):
         """
@@ -98,24 +99,20 @@
             else:
                 self.log.warn('cannot-set-lock-ontg', lock=self._lock)
 
-            pptp = self._config.pptp_entities
+            pptp_list = sorted(self._config.pptp_entities) if self._config.pptp_entities else []
+            veip_list = sorted(self._config.veip_entities) if self._config.veip_entities else []
 
-            for key, value in pptp.iteritems():
-                msg = PptpEthernetUniFrame(key,
+            for entity_id in pptp_list:
+                pptp_value = self._config.pptp_entities[entity_id]
+                msg = PptpEthernetUniFrame(entity_id,
                                            attributes=dict(administrative_state=state))
-                frame = msg.set()
-                self.log.debug('openomci-msg', msg=msg)
-                results = yield self._device.omci_cc.send(frame)
-                self.strobe_watchdog()
+                self._send_uni_lock_msg(entity_id, pptp_value, msg)
 
-                status = results.fields['omci_message'].fields['success_code']
-                self.log.info('response-status', status=status)
-
-                # Success?
-                if status in (RC.Success.value, RC.InstanceExists):
-                    self.log.debug('set-lock-uni', uni=key, value=value, lock=self._lock)
-                else:
-                    self.log.warn('cannot-set-lock-uni', uni=key, value=value, lock=self._lock)
+            for entity_id in veip_list:
+                veip_value = self._config.veip_entities[entity_id]
+                msg = VeipUniFrame(entity_id,
+                                           attributes=dict(administrative_state=state))
+                self._send_uni_lock_msg(entity_id, veip_value, msg)
 
             self.deferred.callback(self)
 
@@ -123,3 +120,21 @@
             self.log.exception('setting-uni-lock-state', e=e)
             self.deferred.errback(failure.Failure(e))
 
+
+    @inlineCallbacks
+    def _send_uni_lock_msg(self, entity_id, value, me_message):
+        frame = me_message.set()
+        self.log.debug('openomci-msg', msg=me_message)
+        results = yield self._device.omci_cc.send(frame)
+        self.strobe_watchdog()
+
+        status = results.fields['omci_message'].fields['success_code']
+        self.log.info('response-status', status=status)
+
+        # Success?
+        if status in (RC.Success.value, RC.InstanceExists):
+            self.log.debug('set-lock-uni', uni=entity_id, value=value, lock=self._lock)
+        else:
+            self.log.warn('cannot-set-lock-uni', uni=entity_id, value=value, lock=self._lock)
+
+        returnValue(None)
diff --git a/voltha/adapters/brcm_openomci_onu/uni_port.py b/voltha/adapters/brcm_openomci_onu/uni_port.py
index cede99d..a887531 100644
--- a/voltha/adapters/brcm_openomci_onu/uni_port.py
+++ b/voltha/adapters/brcm_openomci_onu/uni_port.py
@@ -14,6 +14,7 @@
 # limitations under the License.
 
 import structlog
+from enum import Enum
 from voltha.protos.common_pb2 import OperStatus, AdminState
 from voltha.protos.device_pb2 import Port
 from voltha.protos.openflow_13_pb2 import OFPPF_10GB_FD
@@ -22,90 +23,73 @@
 from voltha.protos.openflow_13_pb2 import OFPPS_LIVE, OFPPF_FIBER, OFPPS_LINK_DOWN
 from voltha.protos.openflow_13_pb2 import ofp_port
 
+class UniType(Enum):
+    """
+    UNI Types Defined in G.988
+    """
+    PPTP = 'PhysicalPathTerminationPointEthernet'
+    VEIP = 'VirtualEthernetInterfacePoint'
+    # TODO: Add others as they become supported
+
 
 class UniPort(object):
     """Wraps southbound-port(s) support for ONU"""
-    DEFAULT_UNTAGGED_VLAN = 4091
 
-    def __init__(self, handler, name, port_no, ofp_port_no, subscriber_vlan=None,
-                 untagged_vlan=None):
+    def __init__(self, handler, name, port_no, ofp_port_no,
+                 type=UniType.PPTP):
         self.log = structlog.get_logger(device_id=handler.device_id,
                                         port_no=port_no)
-        self.log.debug('function-entry')
         self._enabled = False
         self._handler = handler
         self._name = name
         self._port = None
         self._port_number = port_no
-        self._ofp_port_no = ofp_port_no         # Set at by creator (vENET create)
-        self._logical_port_number = None        # Set at time of logical port creation
-        self._subscriber_vlan = subscriber_vlan
-        self._untagged_vlan = untagged_vlan
-        self._entity_id = None                  # TODO: Use port number from UNI-G entity ID
+        self._ofp_port_no = ofp_port_no
+        self._logical_port_number = None
+        self._entity_id = None
         self._mac_bridge_port_num = 0
+        self._type = type
 
         self._admin_state = AdminState.ENABLED
         self._oper_status = OperStatus.ACTIVE
-        # TODO Add state, stats, alarm reference, ...
-        pass
 
     def __str__(self):
         return "UniPort: {}:{}".format(self.name, self.port_number)
 
     @staticmethod
-    def create(handler, name, port_no, ofp_port_no, subscriber_vlan, untagged_vlan):
-        log = structlog.get_logger(device_id=handler.device_id, name=name)
-        log.debug('function-entry')
-        port = UniPort(handler, name, port_no, ofp_port_no, subscriber_vlan, untagged_vlan)
-
+    def create(handler, name, port_no, ofp_port_no, type):
+        port = UniPort(handler, name, port_no, ofp_port_no, type)
         return port
 
     def _start(self):
-        self.log.debug('function-entry')
         self._cancel_deferred()
-
         self._admin_state = AdminState.ENABLED
         self._oper_status = OperStatus.ACTIVE
-
         self._update_adapter_agent()
-        # TODO: start h/w sync
-        # TODO: Enable the actual physical port?
-        pass
 
     def _stop(self):
-        self.log.debug('function-entry')
         self._cancel_deferred()
-
         self._admin_state = AdminState.DISABLED
         self._oper_status = OperStatus.UNKNOWN
-
         self._update_adapter_agent()
-        # TODO: Disable/power-down the actual physical port?
-        pass
 
     def delete(self):
-        self.log.debug('function-entry')
         self.enabled = False
         self._handler = None
-        # TODO: anything else
 
     def _cancel_deferred(self):
-        self.log.debug('function-entry')
         pass
 
     @property
     def name(self):
-        self.log.debug('function-entry')
         return self._name
 
     @property
     def enabled(self):
-        self.log.debug('function-entry')
         return self._enabled
 
     @enabled.setter
     def enabled(self, value):
-        self.log.debug('function-entry')
         if self._enabled != value:
             self._enabled = value
 
@@ -120,12 +104,10 @@
         Port number used when creating MacBridgePortConfigurationDataFrame port number
         :return: (int) port number
         """
-        self.log.debug('function-entry')
         return self._mac_bridge_port_num
 
     @mac_bridge_port_num.setter
     def mac_bridge_port_num(self, value):
-        self.log.debug('function-entry')
         self._mac_bridge_port_num = value
 
     @property
@@ -134,7 +116,6 @@
         Physical device port number
         :return: (int) port number
         """
-        self.log.debug('function-entry')
         return self._port_number
 
     @property
@@ -142,38 +123,33 @@
         """
         OMCI UNI_G entity ID for port
         """
-        self.log.debug('function-entry')
         return self._entity_id
 
     @entity_id.setter
     def entity_id(self, value):
-        self.log.debug('function-entry')
         assert self._entity_id is None, 'Cannot reset the Entity ID'
         self._entity_id = value
 
     @property
-    def subscriber_vlan(self):
-        """
-        Subscriber vlan assigned to this UNI
-        :return: (int) subscriber vlan
-        """
-        self.log.debug('function-entry')
-        return self._subscriber_vlan
-
-    @property
     def logical_port_number(self):
         """
         Logical device port number (used as OpenFlow port for UNI)
         :return: (int) port number
         """
-        self.log.debug('function-entry')
         return self._logical_port_number
 
+    @property
+    def type(self):
+        """
+        UNI Type used in OMCI messaging
+        :return: (UniType) One of the enumerated types
+        """
+        return self._type
+
     def _update_adapter_agent(self):
         """
         Update the port status and state in the core
         """
-        self.log.debug('function-entry')
         self.log.debug('update-adapter-agent', admin_state=self._admin_state,
                        oper_status=self._oper_status)
 
@@ -194,8 +170,6 @@
         Get the VOLTHA PORT object for this port
         :return: VOLTHA Port object
         """
-        self.log.debug('function-entry')
-
         self._port = Port(port_no=self.port_number,
                           label=self.port_id_name(),
                           type=Port.ETHERNET_UNI,
@@ -206,7 +180,7 @@
     def port_id_name(self):
         return 'uni-{}'.format(self._logical_port_number)
 
-    def add_logical_port(self, openflow_port_no, subscriber_vlan=None,
+    def add_logical_port(self, openflow_port_no,
                          capabilities=OFPPF_10GB_FD | OFPPF_FIBER,
                          speed=OFPPF_10GB_FD):
 
@@ -226,18 +200,12 @@
             self._logical_port_number = None
 
         port_no = openflow_port_no or self._ofp_port_no
-        vlan = subscriber_vlan or self._subscriber_vlan
 
         if self._logical_port_number is None and port_no is not None:
             self._logical_port_number = port_no
-            self._subscriber_vlan = vlan
 
             device = self._handler.adapter_agent.get_device(self._handler.device_id)
 
-            if vlan is not None and device.vlan != vlan:
-                device.vlan = vlan
-                self._handler.adapter_agent.update_device(device)
-
             # leave the ports down until omci mib download has finished.  otherwise flows push before time
             openflow_port = ofp_port(
                 port_no=port_no,
@@ -263,4 +231,3 @@
                                                              device_port_no=self._port_number))
 
             self.log.debug('logical-port', openflow_port=openflow_port)
-            # TODO: Should we use the UNI object 'name' as the id for OpenFlow?