OpenOLT - hooks for customizing OpenOLT adapter

Change-Id: I88e0ab6bb5f2c14637f1d1462511f56f3d38c732
diff --git a/voltha/adapters/acme/__init__.py b/voltha/adapters/acme/__init__.py
new file mode 100644
index 0000000..b0fb0b2
--- /dev/null
+++ b/voltha/adapters/acme/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
diff --git a/voltha/adapters/acme/acme.py b/voltha/adapters/acme/acme.py
new file mode 100644
index 0000000..3682f23
--- /dev/null
+++ b/voltha/adapters/acme/acme.py
@@ -0,0 +1,77 @@
+#
+# Copyright 2018 the original author or authors.
+#
+# 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.
+#
+
+"""
+Acme - A dummy olt adapter that customizes Openolt adapter.
+"""
+import structlog
+from copy import deepcopy
+
+from voltha.protos.device_pb2 import DeviceType
+from voltha.protos.adapter_pb2 import AdapterConfig
+from voltha.protos.adapter_pb2 import Adapter
+from voltha.protos.common_pb2 import LogLevel
+from voltha.adapters.openolt.openolt import OpenoltAdapter, OpenOltDefaults
+from voltha.adapters.openolt.openolt_device import OpenoltDevice
+from voltha.adapters.acme.acme_platform import AcmePlatform
+
+log = structlog.get_logger()
+
+class AcmeAdapter(OpenoltAdapter):
+    name = 'acme'
+
+    supported_device_types = [
+        DeviceType(
+            id=name,
+            adapter=name,
+            accepts_bulk_flow_update=True,
+            accepts_direct_logical_flows_update=True
+        )
+    ]
+
+    def __init__(self, adapter_agent, config):
+        super(AcmeAdapter, self).__init__(adapter_agent, config)
+
+        # overwrite the descriptor
+        self.descriptor = Adapter(
+            id=self.name,
+            vendor='Acme Inc.',
+            version='0.1',
+            config=AdapterConfig(log_level=LogLevel.INFO)
+        )
+
+    def adopt_device(self, device):
+        log.info('adopt-device', device=device)
+
+        support_classes = deepcopy(OpenOltDefaults)
+
+        # Customize platform
+        support_classes['platform'] = AcmePlatform
+
+        kwargs = {
+            'support_classes': support_classes,
+            'adapter_agent': self.adapter_agent,
+            'device': device,
+            'device_num': self.num_devices + 1
+        }
+        try:
+            self.devices[device.id] = OpenoltDevice(**kwargs)
+        except Exception as e:
+            log.error('Failed to adopt OpenOLT device', error=e)
+            del self.devices[device.id]
+            raise
+        else:
+            self.num_devices += 1
diff --git a/voltha/adapters/acme/acme_platform.py b/voltha/adapters/acme/acme_platform.py
new file mode 100644
index 0000000..e3ebf71
--- /dev/null
+++ b/voltha/adapters/acme/acme_platform.py
@@ -0,0 +1,107 @@
+#
+# Copyright 2018 the original author or authors.
+#
+# 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 voltha.protos.device_pb2 import Port
+import voltha.protos.device_pb2 as dev_pb2
+
+
+MAX_ONUS_PER_PON = 2
+
+class AcmePlatform(object):
+
+    def __init__(self, log, device_info):
+        self.log = log
+        self.device_info = device_info
+
+    def mk_alloc_id(self, intf_id, onu_id, idx=0):
+        # FIXME - driver should do prefixing 1 << 10 as it is Maple specific
+        # return 1<<10 | onu_id<<6 | idx
+        return 1023 + intf_id * MAX_ONUS_PER_PON + onu_id  # FIXME
+
+
+    def mk_gemport_id(self, intf_id, onu_id, idx=0):
+        return 1024 + (((MAX_ONUS_PER_PON * intf_id + onu_id - 1) * 7) + idx)
+
+    def onu_id_from_gemport_id(self, gemport_id):
+        return (((gemport_id - 1024) // 7) % MAX_ONUS_PER_PON) + 1
+
+    def mk_uni_port_num(self, intf_id, onu_id):
+        return intf_id << 11 | onu_id << 4
+
+    def mk_flow_id(self, intf_id, onu_id, idx):
+        return intf_id << 9 | onu_id << 4 | idx
+
+
+    def onu_id_from_port_num(self, port_num):
+        return (port_num >> 4) & 0x7F
+
+
+    def intf_id_from_uni_port_num(self, port_num):
+        return (port_num >> 11) & 0xF
+
+
+    def intf_id_from_pon_port_no(self, port_no):
+        return port_no & 0xF
+
+
+    def intf_id_to_port_no(self, intf_id, intf_type):
+        if intf_type is Port.ETHERNET_NNI:
+            return (0x1 << 16) | intf_id
+        elif intf_type is Port.PON_OLT:
+            return 0x2 << 28 | intf_id
+        else:
+            raise Exception('Invalid port type')
+
+
+    def intf_id_from_nni_port_num(self, port_num):
+        return port_num & 0xFFFF
+
+
+    def intf_id_to_port_type_name(self, intf_id):
+        if (2 << 28 ^ intf_id) < 16:
+            return Port.PON_OLT
+        elif intf_id & (0x1 << 16) == (0x1 << 16):
+            return Port.ETHERNET_NNI
+        else:
+            return None
+
+    def port_type_name_by_port_index(self, port_index):
+        try:
+            return dev_pb2._PORT_PORTTYPE.values_by_number[port_index].name
+        except Exception as err:
+            raise Exception(err)
+
+    def extract_access_from_flow(self, in_port, out_port):
+        if self.is_upstream(out_port):
+            return (self.intf_id_from_uni_port_num(in_port),
+                    self.onu_id_from_port_num(in_port))
+        else:
+            return (self.intf_id_from_uni_port_num(out_port),
+                    self.onu_id_from_port_num(out_port))
+
+    def is_upstream(self, out_port):
+
+        if out_port in [0xfffd, 0xfffffffd]:
+            # To Controller
+            return True
+        if (out_port & (0x1 << 16)) == (0x1 << 16):
+            # NNI interface
+            return True
+
+        return False
+
+    def max_onus_per_pon(self):
+        return MAX_ONUS_PER_PON
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 87796ca..4e8b2dd 100644
--- a/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
+++ b/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
@@ -42,8 +42,6 @@
 from voltha.adapters.brcm_openomci_onu.uni_port import *
 from voltha.adapters.brcm_openomci_onu.onu_traffic_descriptor import *
 
-import voltha.adapters.openolt.openolt_platform as platform
-
 OP = EntityOperations
 RC = ReasonCodes
 
@@ -806,8 +804,10 @@
             self.log.error('openolt_adapter_agent-could-not-be-retrieved')
 
         # TODO: This knowledge is locked away in openolt.  and it assumes one onu equals one uni...
-        uni_no_start = platform.mk_uni_port_num(self._onu_indication.intf_id,
-                                                self._onu_indication.onu_id)
+        parent_device = self.adapter_agent.get_device(device.parent_id)
+        parent_adapter = parent_adapter_agent.adapter.devices[parent_device.id]
+        uni_no_start = parent_adapter.platform.mk_uni_port_num(
+            self._onu_indication.intf_id, self._onu_indication.onu_id)
 
         # TODO: Some or parts of this likely need to move to UniPort. especially the format stuff
         working_port = self._next_port_number
diff --git a/voltha/adapters/openolt/openolt.py b/voltha/adapters/openolt/openolt.py
index 239cc2a..7ccec65 100644
--- a/voltha/adapters/openolt/openolt.py
+++ b/voltha/adapters/openolt/openolt.py
@@ -29,11 +29,26 @@
 from voltha.protos.common_pb2 import OperationResp
 from voltha.protos.device_pb2 import DeviceType, DeviceTypes
 from voltha.registry import registry
+from voltha.adapters.openolt.openolt_flow_mgr import OpenOltFlowMgr
+from voltha.adapters.openolt.openolt_alarms import OpenOltAlarmMgr
+from voltha.adapters.openolt.openolt_statistics import OpenOltStatisticsMgr
+from voltha.adapters.openolt.openolt_bw import OpenOltBW
+from voltha.adapters.openolt.openolt_platform import OpenOltPlatform
 
 _ = third_party
 log = structlog.get_logger()
 
 
+OpenOltDefaults = {
+    'support_classes': {
+        'platform': OpenOltPlatform,
+        'flow_mgr': OpenOltFlowMgr,
+        'alarm_mgr': OpenOltAlarmMgr,
+        'stats_mgr': OpenOltStatisticsMgr,
+        'bw_mgr': OpenOltBW
+    }
+}
+
 @implementer(IAdapterInterface)
 class OpenoltAdapter(object):
     name = 'openolt'
@@ -88,7 +103,9 @@
 
     def adopt_device(self, device):
         log.info('adopt-device', device=device)
+
         kwargs = {
+            'support_classes': OpenOltDefaults['support_classes'],
             'adapter_agent': self.adapter_agent,
             'device': device,
             'device_num': self.num_devices + 1
diff --git a/voltha/adapters/openolt/openolt_alarms.py b/voltha/adapters/openolt/openolt_alarms.py
index efb9c1f..764a013 100644
--- a/voltha/adapters/openolt/openolt_alarms.py
+++ b/voltha/adapters/openolt/openolt_alarms.py
@@ -15,8 +15,6 @@
 #
 
 import arrow
-import voltha.adapters.openolt.openolt_platform as platform
-# from voltha.protos.device_pb2 import Port
 from voltha.extensions.alarms.adapter_alarms import AdapterAlarms
 from voltha.extensions.alarms.simulator.simulate_alarms import AdapterAlarmSimulator
 from voltha.extensions.alarms.olt.olt_los_alarm import OltLosAlarm
@@ -37,7 +35,8 @@
 
 
 class OpenOltAlarmMgr(object):
-    def __init__(self, log, adapter_agent, device_id, logical_device_id):
+    def __init__(self, log, adapter_agent, device_id, logical_device_id,
+                 platform):
         """
         20180711 -  Addition of adapter_agent and device_id
             to facilitate alarm processing and kafka posting
@@ -49,6 +48,7 @@
         self.adapter_agent = adapter_agent
         self.device_id = device_id
         self.logical_device_id = logical_device_id
+        self.platform = platform
         """
         The following is added to reduce the continual posting of OLT LOS alarming
         to Kafka.   Set enable_alarm_suppress = true to enable  otherwise the
@@ -112,13 +112,13 @@
             self.log.debug('los indication received', los_ind=los_ind,
                            int_id=los_ind.intf_id, status=los_ind.status)
             try:
-                port_type_name = platform.intf_id_to_port_type_name(los_ind.intf_id)
+                port_type_name = self.platform.intf_id_to_port_type_name(los_ind.intf_id)
                 if los_ind.status == 1 or los_ind.status == "on":
                     # Zero out the suppression counter on OLT_LOS raise
                     self.alarm_suppress['olt_los_clear'] = 0
                     OltLosAlarm(self.alarms, intf_id=los_ind.intf_id, port_type_name=port_type_name).raise_alarm()
                 else:
-                    """ 
+                    """
                         Check if there has been more that one los clear following a previous los
                     """
                     if self.alarm_suppress['olt_los_clear'] == 0 and self.enable_alarm_suppress:
@@ -455,7 +455,7 @@
             onu_device = None
             onu_device = self.adapter_agent.get_child_device(
                 self.device_id,
-                parent_port_no=platform.intf_id_to_port_no(
+                parent_port_no=self.platform.intf_id_to_port_no(
                     port_intf_id, device_pb2.Port.PON_OLT),
                 onu_id=onu_id)
             onu_device_id = onu_device.id
diff --git a/voltha/adapters/openolt/openolt_device.py b/voltha/adapters/openolt/openolt_device.py
index 0255901..e5bf92f 100644
--- a/voltha/adapters/openolt/openolt_device.py
+++ b/voltha/adapters/openolt/openolt_device.py
@@ -36,11 +36,6 @@
 from voltha.protos.bbf_fiber_tcont_body_pb2 import TcontsConfigData
 from voltha.protos.bbf_fiber_gemport_body_pb2 import GemportsConfigData
 
-from voltha.adapters.openolt.openolt_statistics import OpenOltStatisticsMgr
-import voltha.adapters.openolt.openolt_platform as platform
-from voltha.adapters.openolt.openolt_flow_mgr import OpenOltFlowMgr
-from voltha.adapters.openolt.openolt_alarms import OpenOltAlarmMgr
-from voltha.adapters.openolt.openolt_bw import OpenOltBW
 from voltha.extensions.alarms.onu.onu_discovery_alarm import OnuDiscoveryAlarm
 
 
@@ -90,6 +85,13 @@
         self.adapter_agent = kwargs['adapter_agent']
         self.device_num = kwargs['device_num']
         device = kwargs['device']
+
+        self.platform_class = kwargs['support_classes']['platform']
+        self.flow_mgr_class = kwargs['support_classes']['flow_mgr']
+        self.alarm_mgr_class = kwargs['support_classes']['alarm_mgr']
+        self.stats_mgr_class = kwargs['support_classes']['stats_mgr']
+        self.bw_mgr_class = kwargs['support_classes']['bw_mgr']
+
         is_reconciliation = kwargs.get('reconciliation', False)
         self.device_id = device.id
         self.host_and_port = device.host_and_port
@@ -151,12 +153,6 @@
         self.channel = grpc.insecure_channel(self.host_and_port)
         self.channel_ready_future = grpc.channel_ready_future(self.channel)
 
-        self.alarm_mgr = OpenOltAlarmMgr(self.log, self.adapter_agent,
-                                         self.device_id,
-                                         self.logical_device_id)
-        self.stats_mgr = OpenOltStatisticsMgr(self, self.log)
-        self.bw_mgr = OpenOltBW(self.log, self.proxy)
-
         self.log.info('openolt-device-created', device_id=self.device_id)
 
     def post_init(self, event):
@@ -188,16 +184,23 @@
         device_info = self.stub.GetDeviceInfo(openolt_pb2.Empty())
         self.log.info('Device connected', device_info=device_info)
 
+        self.platform = self.platform_class(self.log, device_info)
+
+        self.flow_mgr = self.flow_mgr_class(self.log, self.stub,
+                                            self.device_id,
+                                            self.logical_device_id,
+                                            self.platform)
+        self.alarm_mgr = self.alarm_mgr_class(self.log, self.adapter_agent,
+                                              self.device_id,
+                                              self.logical_device_id,
+                                              self.platform)
+        self.stats_mgr = self.stats_mgr_class(self, self.log, self.platform)
+        self.bw_mgr = self.bw_mgr_class(self.log, self.proxy)
+
         device.vendor = device_info.vendor
         device.model = device_info.model
         device.hardware_version = device_info.hardware_version
         device.firmware_version = device_info.firmware_version
-
-        self.flow_mgr = OpenOltFlowMgr(self.log, self.stub, self.device_id,
-                                       self.logical_device_id)
-
-        # TODO: check for uptime and reboot if too long (VOL-1192)
-
         device.connect_status = ConnectStatus.REACHABLE
         self.adapter_agent.update_device(device)
 
@@ -221,7 +224,7 @@
         # Children ports
         child_devices = self.adapter_agent.get_child_devices(self.device_id)
         for onu_device in child_devices:
-            uni_no = platform.mk_uni_port_num(
+            uni_no = self.platform.mk_uni_port_num(
                 onu_device.proxy_address.channel_id,
                 onu_device.proxy_address.onu_id)
             uni_name = self.port_name(uni_no, Port.ETHERNET_UNI,
@@ -395,7 +398,7 @@
             try:
                 self.add_onu_device(
                     intf_id,
-                    platform.intf_id_to_port_no(intf_id, Port.PON_OLT),
+                    self.platform.intf_id_to_port_no(intf_id, Port.PON_OLT),
                     onu_id, serial_number)
                 self.activate_onu(intf_id, onu_id, serial_number,
                                   serial_number_str)
@@ -455,7 +458,7 @@
         else:
             onu_device = self.adapter_agent.get_child_device(
                     self.device_id,
-                    parent_port_no=platform.intf_id_to_port_no(
+                    parent_port_no=self.platform.intf_id_to_port_no(
                             onu_indication.intf_id, Port.PON_OLT),
                     onu_id=onu_indication.onu_id)
 
@@ -464,10 +467,10 @@
                            onu_id=onu_indication.onu_id)
             return
 
-        if platform.intf_id_from_pon_port_no(onu_device.parent_port_no) \
+        if self.platform.intf_id_from_pon_port_no(onu_device.parent_port_no) \
                 != onu_indication.intf_id:
             self.log.warn('ONU-is-on-a-different-intf-id-now',
-                          previous_intf_id=platform.intf_id_from_pon_port_no(
+                          previous_intf_id=self.platform.intf_id_from_pon_port_no(
                               onu_device.parent_port_no),
                           current_intf_id=onu_indication.intf_id)
             # FIXME - handle intf_id mismatch (ONU move?)
@@ -479,7 +482,7 @@
                           expected_onu_id=onu_device.proxy_address.onu_id,
                           received_onu_id=onu_indication.onu_id)
 
-        uni_no = platform.mk_uni_port_num(onu_indication.intf_id,
+        uni_no = self.platform.mk_uni_port_num(onu_indication.intf_id,
                                           onu_indication.onu_id)
         uni_name = self.port_name(uni_no, Port.ETHERNET_UNI,
                                   serial_number=onu_device.serial_number)
@@ -552,12 +555,12 @@
 
             # tcont creation (onu)
             tcont = TcontsConfigData()
-            tcont.alloc_id = platform.mk_alloc_id(
+            tcont.alloc_id = self.platform.mk_alloc_id(
                 onu_indication.intf_id, onu_indication.onu_id)
 
             # gem port creation
             gem_port = GemportsConfigData()
-            gem_port.gemport_id = platform.mk_gemport_id(
+            gem_port.gemport_id = self.platform.mk_gemport_id(
                 onu_indication.intf_id,
                 onu_indication.onu_id)
 
@@ -620,7 +623,7 @@
 
         onu_device = self.adapter_agent.get_child_device(
             self.device_id, onu_id=omci_indication.onu_id,
-            parent_port_no=platform.intf_id_to_port_no(
+            parent_port_no=self.platform.intf_id_to_port_no(
                 omci_indication.intf_id, Port.PON_OLT), )
 
         self.adapter_agent.receive_proxied_message(onu_device.proxy_address,
@@ -635,11 +638,11 @@
                        flow_id=pkt_indication.flow_id)
 
         if pkt_indication.intf_type == "pon":
-            onu_id = platform.onu_id_from_gemport_id(pkt_indication.gemport_id)
-            logical_port_num = platform.mk_uni_port_num(pkt_indication.intf_id,
+            onu_id = self.platform.onu_id_from_gemport_id(pkt_indication.gemport_id)
+            logical_port_num = self.platform.mk_uni_port_num(pkt_indication.intf_id,
                                                         onu_id)
         elif pkt_indication.intf_type == "nni":
-            logical_port_num = platform.intf_id_to_port_no(
+            logical_port_num = self.platform.intf_id_to_port_no(
                 pkt_indication.intf_id,
                 Port.ETHERNET_NNI)
 
@@ -682,13 +685,13 @@
 
             self.log.debug(
                 'sending-packet-to-ONU', egress_port=egress_port,
-                intf_id=platform.intf_id_from_uni_port_num(egress_port),
-                onu_id=platform.onu_id_from_port_num(egress_port),
+                intf_id=self.platform.intf_id_from_uni_port_num(egress_port),
+                onu_id=self.platform.onu_id_from_port_num(egress_port),
                 packet=str(payload).encode("HEX"))
 
             onu_pkt = openolt_pb2.OnuPacket(
-                intf_id=platform.intf_id_from_uni_port_num(egress_port),
-                onu_id=platform.onu_id_from_port_num(egress_port),
+                intf_id=self.platform.intf_id_from_uni_port_num(egress_port),
+                onu_id=self.platform.onu_id_from_port_num(egress_port),
                 pkt=send_pkt)
 
             self.stub.OnuPacketOut(onu_pkt)
@@ -700,7 +703,7 @@
             send_pkt = binascii.unhexlify(str(pkt).encode("HEX"))
 
             uplink_pkt = openolt_pb2.UplinkPacket(
-                intf_id=platform.intf_id_from_nni_port_num(egress_port),
+                intf_id=self.platform.intf_id_from_nni_port_num(egress_port),
                 pkt=send_pkt)
 
             self.stub.UplinkPacketOut(uplink_pkt)
@@ -713,7 +716,7 @@
     def send_proxied_message(self, proxy_address, msg):
         onu_device = self.adapter_agent.get_child_device(
             self.device_id, onu_id=proxy_address.onu_id,
-            parent_port_no=platform.intf_id_to_port_no(
+            parent_port_no=self.platform.intf_id_to_port_no(
                 proxy_address.channel_id, Port.PON_OLT)
         )
         if onu_device.connect_status != ConnectStatus.REACHABLE:
@@ -801,7 +804,7 @@
         return '00:00' + mac
 
     def add_port(self, intf_id, port_type, oper_status):
-        port_no = platform.intf_id_to_port_no(intf_id, port_type)
+        port_no = self.platform.intf_id_to_port_no(intf_id, port_type)
 
         label = self.port_name(port_no, port_type, intf_id)
 
@@ -843,12 +846,12 @@
         pon_onu_ids = [onu_device.proxy_address.onu_id
                        for onu_device in onu_devices
                        if onu_device.proxy_address.channel_id == intf_id]
-        for i in range(1, platform.MAX_ONUS_PER_PON):
+        for i in range(1, self.platform.max_onus_per_pon()):
             if i not in pon_onu_ids:
                 return i
 
         self.log.error('All available onu_ids taken on this pon',
-                       intf_id=intf_id, ids_taken=platform.MAX_ONUS_PER_PON)
+                       intf_id=intf_id, ids_taken=self.platform.max_onus_per_pon())
         return None
 
     def update_flow_table(self, flows):
diff --git a/voltha/adapters/openolt/openolt_flow_mgr.py b/voltha/adapters/openolt/openolt_flow_mgr.py
index 4a57c5c..9e21510 100644
--- a/voltha/adapters/openolt/openolt_flow_mgr.py
+++ b/voltha/adapters/openolt/openolt_flow_mgr.py
@@ -22,7 +22,6 @@
     OFPXMT_OFB_VLAN_VID
 from voltha.protos.device_pb2 import Port
 import voltha.core.flow_decomposer as fd
-import openolt_platform as platform
 from voltha.adapters.openolt.protos import openolt_pb2
 from voltha.registry import registry
 
@@ -72,11 +71,13 @@
 
 class OpenOltFlowMgr(object):
 
-    def __init__(self, log, stub, device_id, logical_device_id):
+    def __init__(self, log, stub, device_id, logical_device_id,
+                 platform):
         self.log = log
         self.stub = stub
         self.device_id = device_id
         self.logical_device_id = logical_device_id
+        self.platform = platform
         self.logical_flows_proxy = registry('core').get_proxy(
             '/logical_devices/{}/flows'.format(self.logical_device_id))
         self.flows_proxy = registry('core').get_proxy(
@@ -188,7 +189,7 @@
                     classifier_info[METADATA] = field.vlan_vid & 0xfff
 
 
-        (intf_id, onu_id) = platform.extract_access_from_flow(
+        (intf_id, onu_id) = self.platform.extract_access_from_flow(
             classifier_info[IN_PORT], action_info[OUTPUT])
 
 
@@ -317,9 +318,9 @@
     def add_hsia_flow(self, intf_id, onu_id, classifier, action,
                       direction, hsia_id, logical_flow):
 
-        gemport_id = platform.mk_gemport_id(intf_id, onu_id)
-        alloc_id = platform.mk_alloc_id(intf_id, onu_id)
-        flow_id = platform.mk_flow_id(intf_id, onu_id, hsia_id)
+        gemport_id = self.platform.mk_gemport_id(intf_id, onu_id)
+        alloc_id = self.platform.mk_alloc_id(intf_id, onu_id)
+        flow_id = self.platform.mk_flow_id(intf_id, onu_id, hsia_id)
 
         flow = openolt_pb2.Flow(
                 onu_id=onu_id, flow_id=flow_id, flow_type=direction,
@@ -341,9 +342,9 @@
         classifier[PACKET_TAG_TYPE] = SINGLE_TAG
         classifier.pop(VLAN_VID, None)
 
-        gemport_id = platform.mk_gemport_id(intf_id, onu_id)
-        alloc_id = platform.mk_alloc_id(intf_id, onu_id)
-        flow_id = platform.mk_flow_id(intf_id, onu_id, DHCP_FLOW_INDEX)
+        gemport_id = self.platform.mk_gemport_id(intf_id, onu_id)
+        alloc_id = self.platform.mk_alloc_id(intf_id, onu_id)
+        flow_id = self.platform.mk_flow_id(intf_id, onu_id, DHCP_FLOW_INDEX)
 
         upstream_flow = openolt_pb2.Flow(
             onu_id=onu_id, flow_id=flow_id, flow_type=UPSTREAM,
@@ -362,15 +363,15 @@
         for oxm_field in downstream_logical_flow.match.oxm_fields:
             if oxm_field.ofb_field.type == OFPXMT_OFB_IN_PORT:
                 oxm_field.ofb_field.port = \
-                    platform.intf_id_to_port_no(0, Port.ETHERNET_NNI)
+                    self.platform.intf_id_to_port_no(0, Port.ETHERNET_NNI)
 
         classifier[UDP_SRC] = 67
         classifier[UDP_DST] = 68
         classifier[PACKET_TAG_TYPE] = DOUBLE_TAG
         action.pop(PUSH_VLAN, None)
 
-        flow_id = platform.mk_flow_id(intf_id, onu_id,
-                                      DHCP_DOWNLINK_FLOW_INDEX)
+        flow_id = self.platform.mk_flow_id(intf_id, onu_id,
+                                           DHCP_DOWNLINK_FLOW_INDEX)
 
         downstream_flow = openolt_pb2.Flow(
             onu_id=onu_id, flow_id=flow_id, flow_type=DOWNSTREAM,
@@ -395,9 +396,9 @@
 
         # Add Upstream EAPOL Flow.
 
-        gemport_id = platform.mk_gemport_id(intf_id, onu_id)
-        alloc_id = platform.mk_alloc_id(intf_id, onu_id)
-        uplink_flow_id = platform.mk_flow_id(intf_id, onu_id, eapol_id)
+        gemport_id = self.platform.mk_gemport_id(intf_id, onu_id)
+        alloc_id = self.platform.mk_alloc_id(intf_id, onu_id)
+        uplink_flow_id = self.platform.mk_flow_id(intf_id, onu_id, eapol_id)
 
         upstream_flow = openolt_pb2.Flow(
             onu_id=onu_id, flow_id=uplink_flow_id, flow_type=UPSTREAM,
@@ -428,8 +429,8 @@
             downlink_action[PUSH_VLAN] = True
             downlink_action[VLAN_VID] = vlan_id
 
-            downlink_flow_id = platform.mk_flow_id(intf_id, onu_id,
-                                               DOWNSTREAM_FLOW_FOR_PACKET_OUT)
+            downlink_flow_id = self.platform.mk_flow_id(
+                intf_id, onu_id, DOWNSTREAM_FLOW_FOR_PACKET_OUT)
 
             downstream_flow = openolt_pb2.Flow(
                 onu_id=onu_id, flow_id=downlink_flow_id, flow_type=DOWNSTREAM,
@@ -450,7 +451,7 @@
 
             downstream_logical_flow.instructions.extend(
                 fd.mk_instructions_from_actions([fd.output(
-                    platform.mk_uni_port_num(intf_id, onu_id))]))
+                    self.platform.mk_uni_port_num(intf_id, onu_id))]))
 
             self.add_flow_to_device(downstream_flow, downstream_logical_flow)
 
@@ -560,9 +561,9 @@
                     if field.eth_type == EAP_ETH_TYPE:
                         eap_flow = True
                 if field.type == fd.IN_PORT:
-                    eap_intf_id = platform.intf_id_from_uni_port_num(
+                    eap_intf_id = self.platform.intf_id_from_uni_port_num(
                         field.port)
-                    eap_onu_id = platform.onu_id_from_port_num(field.port)
+                    eap_onu_id = self.platform.onu_id_from_port_num(field.port)
 
             if eap_flow:
                 self.log.debug('eap flow detected', onu_id=onu_id,
@@ -581,7 +582,7 @@
             in_port = fd.get_in_port(flow)
             out_port = fd.get_out_port(flow)
             if in_port == port and \
-                platform.intf_id_to_port_type_name(out_port) \
+                self.platform.intf_id_to_port_type_name(out_port) \
                     == Port.ETHERNET_NNI:
                 fields = fd.get_ofb_fields(flow)
                 self.log.debug('subscriber flow found', fields=fields)
diff --git a/voltha/adapters/openolt/openolt_platform.py b/voltha/adapters/openolt/openolt_platform.py
index a9dc3fd..0a8a2fd 100644
--- a/voltha/adapters/openolt/openolt_platform.py
+++ b/voltha/adapters/openolt/openolt_platform.py
@@ -106,80 +106,88 @@
 # MAX_ONUS_PER_PON = 112
 MAX_ONUS_PER_PON = 32
 
-def mk_alloc_id(intf_id, onu_id, idx=0):
-    # FIXME - driver should do prefixing 1 << 10 as it is Maple specific
-    # return 1<<10 | onu_id<<6 | idx
-    return 1023 + intf_id * MAX_ONUS_PER_PON + onu_id  # FIXME
+class OpenOltPlatform(object):
+
+    def __init__(self, log, device_info):
+        self.log = log
+        self.device_info = device_info
+
+    def mk_alloc_id(self, intf_id, onu_id, idx=0):
+        # FIXME - driver should do prefixing 1 << 10 as it is Maple specific
+        # return 1<<10 | onu_id<<6 | idx
+        return 1023 + intf_id * MAX_ONUS_PER_PON + onu_id  # FIXME
 
 
-def mk_gemport_id(intf_id, onu_id, idx=0):
-    return 1024 + (((MAX_ONUS_PER_PON * intf_id + onu_id - 1) * 7) + idx)
+    def mk_gemport_id(self, intf_id, onu_id, idx=0):
+        return 1024 + (((MAX_ONUS_PER_PON * intf_id + onu_id - 1) * 7) + idx)
 
-def onu_id_from_gemport_id(gemport_id):
-    return (((gemport_id - 1024) // 7) % MAX_ONUS_PER_PON) + 1
+    def onu_id_from_gemport_id(self, gemport_id):
+        return (((gemport_id - 1024) // 7) % MAX_ONUS_PER_PON) + 1
 
-def mk_uni_port_num(intf_id, onu_id):
-    return intf_id << 11 | onu_id << 4
+    def mk_uni_port_num(self, intf_id, onu_id):
+        return intf_id << 11 | onu_id << 4
 
-def mk_flow_id(intf_id, onu_id, idx):
-    return intf_id << 9 | onu_id << 4 | idx
+    def mk_flow_id(self, intf_id, onu_id, idx):
+        return intf_id << 9 | onu_id << 4 | idx
 
 
-def onu_id_from_port_num(port_num):
-    return (port_num >> 4) & 0x7F
+    def onu_id_from_port_num(self, port_num):
+        return (port_num >> 4) & 0x7F
 
 
-def intf_id_from_uni_port_num(port_num):
-    return (port_num >> 11) & 0xF
+    def intf_id_from_uni_port_num(self, port_num):
+        return (port_num >> 11) & 0xF
 
 
-def intf_id_from_pon_port_no(port_no):
-    return port_no & 0xF
+    def intf_id_from_pon_port_no(self, port_no):
+        return port_no & 0xF
 
 
-def intf_id_to_port_no(intf_id, intf_type):
-    if intf_type is Port.ETHERNET_NNI:
-        return (0x1 << 16) | intf_id
-    elif intf_type is Port.PON_OLT:
-        return 0x2 << 28 | intf_id
-    else:
-        raise Exception('Invalid port type')
+    def intf_id_to_port_no(self, intf_id, intf_type):
+        if intf_type is Port.ETHERNET_NNI:
+            return (0x1 << 16) | intf_id
+        elif intf_type is Port.PON_OLT:
+            return 0x2 << 28 | intf_id
+        else:
+            raise Exception('Invalid port type')
 
 
-def intf_id_from_nni_port_num(port_num):
-    return port_num & 0xFFFF
+    def intf_id_from_nni_port_num(self, port_num):
+        return port_num & 0xFFFF
 
 
-def intf_id_to_port_type_name(intf_id):
-    if (2 << 28 ^ intf_id) < 16:
-        return Port.PON_OLT
-    elif intf_id & (0x1 << 16) == (0x1 << 16):
-        return Port.ETHERNET_NNI
-    else:
-        return None
+    def intf_id_to_port_type_name(self, intf_id):
+        if (2 << 28 ^ intf_id) < 16:
+            return Port.PON_OLT
+        elif intf_id & (0x1 << 16) == (0x1 << 16):
+            return Port.ETHERNET_NNI
+        else:
+            return None
 
-def port_type_name_by_port_index(port_index):
-    try:
-        return dev_pb2._PORT_PORTTYPE.values_by_number[port_index].name
-    except Exception as err:
-        raise Exception(err)
+    def port_type_name_by_port_index(self, port_index):
+        try:
+            return dev_pb2._PORT_PORTTYPE.values_by_number[port_index].name
+        except Exception as err:
+            raise Exception(err)
 
-def extract_access_from_flow(in_port, out_port):
-    if is_upstream(out_port):
-        return (intf_id_from_uni_port_num(in_port), onu_id_from_port_num(
-            in_port))
-    else:
-        return (intf_id_from_uni_port_num(out_port), onu_id_from_port_num(
-            out_port))
+    def extract_access_from_flow(self, in_port, out_port):
+        if self.is_upstream(out_port):
+            return (self.intf_id_from_uni_port_num(in_port),
+                    self.onu_id_from_port_num(in_port))
+        else:
+            return (self.intf_id_from_uni_port_num(out_port),
+                    self.onu_id_from_port_num(out_port))
 
-def is_upstream(out_port):
+    def is_upstream(self, out_port):
 
-    if out_port in [0xfffd, 0xfffffffd]:
-        # To Controller
-        return True
-    if (out_port & (0x1 << 16)) == (0x1 << 16):
-        # NNI interface
-        return True
+        if out_port in [0xfffd, 0xfffffffd]:
+            # To Controller
+            return True
+        if (out_port & (0x1 << 16)) == (0x1 << 16):
+            # NNI interface
+            return True
 
-    return False
+        return False
 
+    def max_onus_per_pon(self):
+        return MAX_ONUS_PER_PON
diff --git a/voltha/adapters/openolt/openolt_statistics.py b/voltha/adapters/openolt/openolt_statistics.py
index 118b025..bfdb61c 100644
--- a/voltha/adapters/openolt/openolt_statistics.py
+++ b/voltha/adapters/openolt/openolt_statistics.py
@@ -15,7 +15,6 @@
 #
 # from voltha.protos.events_pb2 import KpiEvent, MetricValuePairs
 # from voltha.protos.events_pb2 import KpiEventType
-# import voltha.adapters.openolt.openolt_platform as platform
 
 # from voltha.adapters.openolt.nni_port import NniPort
 # from voltha.adapters.openolt.pon_port import PonPort
@@ -24,11 +23,10 @@
 from twisted.internet import reactor, defer
 from voltha.extensions.kpi.olt.olt_pm_metrics import OltPmMetrics
 from voltha.protos.device_pb2 import PmConfig, PmConfigs, PmGroupConfig, Port
-import voltha.adapters.openolt.openolt_platform as platform
 
 
 class OpenOltStatisticsMgr(object):
-    def __init__(self, openolt_device, log, **kargs):
+    def __init__(self, openolt_device, log, platform, **kargs):
 
         """
         kargs are used to pass debugging flags at this time.
@@ -38,6 +36,7 @@
         """
         self.device = openolt_device
         self.log = log
+        self.platform = platform
         # Northbound and Southbound ports
         # added to initialize the pm_metrics
         self.northbound_ports = self.init_ports(type="nni")
@@ -66,7 +65,7 @@
                 """
                     override the default naming structures in the OltPmMetrics class.
                     This is being done until the protos can be modified in the BAL driver
-                    
+
                 """
                 self.pm_metrics.nni_pm_names = (self.get_openolt_port_pm_names())['nni_pm_names']
                 self.pm_metrics.nni_metrics_config = {m: PmConfig(name=m, type=t, enabled=True)
@@ -135,8 +134,8 @@
         try:
             intf_id = port_stats.intf_id
 
-            if platform.intf_id_to_port_no(0, Port.ETHERNET_NNI) < intf_id < \
-                    platform.intf_id_to_port_no(4, Port.ETHERNET_NNI) :
+            if self.platform.intf_id_to_port_no(0, Port.ETHERNET_NNI) < intf_id < \
+                    self.platform.intf_id_to_port_no(4, Port.ETHERNET_NNI) :
                 """
                 for this release we are only interested in the first NNI for
                 Northbound.
@@ -166,15 +165,15 @@
                 """
                    Based upon the intf_id map to an nni port or a pon port
                     the intf_id is the key to the north or south bound collections
-                    
+
                     Based upon the intf_id the port object (nni_port or pon_port) will
                     have its data attr. updated by the current dataset collected.
-                    
+
                     For prefixing the rule is currently to use the port number and not the intf_id
-                    
+
                 """
                 #FIXME : Just use first NNI for now
-                if intf_id == platform.intf_id_to_port_no(0,
+                if intf_id == self.platform.intf_id_to_port_no(0,
                                                           Port.ETHERNET_NNI):
                     #NNI port (just the first one)
                     self.update_port_object_kpi_data(
@@ -218,9 +217,9 @@
     """
     The following 4 methods customer naming, the generation of the port objects, building of those
     objects and populating new data.   The pm metrics operate on the value that are contained in the Port objects.
-    This class updates those port objects with the current data from the grpc indication and 
+    This class updates those port objects with the current data from the grpc indication and
     post the data on a fixed interval.
-    
+
     """
     def get_openolt_port_pm_names(self):
         """
@@ -386,14 +385,14 @@
         :return:
         """
         try:
-            """ 
-             This builds a port object which is added to the 
+            """
+             This builds a port object which is added to the
              appropriate northbound or southbound values
             """
             if type == "nni":
                 kwargs = {
                     'port_no': port_num,
-                    'intf_id': platform.intf_id_to_port_no(port_num,
+                    'intf_id': self.platform.intf_id_to_port_no(port_num,
                                                            Port.ETHERNET_NNI),
                     "device_id": self.device.device_id
                 }
@@ -405,9 +404,9 @@
                 #  intf_id and pon_id are currently equal.
                 kwargs = {
                     'port_no': port_num,
-                    'intf_id':  platform.intf_id_to_port_no(port_num,
+                    'intf_id':  self.platform.intf_id_to_port_no(port_num,
                                                            Port.PON_OLT),
-                    'pon-id':  platform.intf_id_to_port_no(port_num,
+                    'pon-id':  self.platform.intf_id_to_port_no(port_num,
                                                            Port.PON_OLT),
                     "device_id": self.device.device_id
                 }
@@ -487,12 +486,12 @@
         Statistics  taken from nni_port
         self.intf_id = 0  #handled by getter
         self.port_no = 0  #handled by getter
-        self.port_id = 0  #handled by getter  
+        self.port_id = 0  #handled by getter
 
         Note:  In the current implementation of the kpis coming from the BAL the stats are the
         samne model for NNI and PON.
 
-        TODO:   Integrate additional kpis for the PON and other southbound port objecgts.      
+        TODO:   Integrate additional kpis for the PON and other southbound port objecgts.
 
         """
 
@@ -604,4 +603,4 @@
         return "NniPort-{}: Admin: {}, Oper: {}, parent: {}".format(self._port_no,
                                                                     self._admin_state,
                                                                     self._oper_status,
-                                                                    self._parent)
\ No newline at end of file
+                                                                    self._parent)