VOL-1277 Create BBSim adapter

Change-Id: I0162bfcc2727e6017a79913add9e0ed58b277868
diff --git a/voltha/adapters/bbsimolt/__init__.py b/voltha/adapters/bbsimolt/__init__.py
new file mode 100644
index 0000000..b0fb0b2
--- /dev/null
+++ b/voltha/adapters/bbsimolt/__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/bbsimolt/bbsimolt.py b/voltha/adapters/bbsimolt/bbsimolt.py
new file mode 100644
index 0000000..be07996
--- /dev/null
+++ b/voltha/adapters/bbsimolt/bbsimolt.py
@@ -0,0 +1,81 @@
+#
+# 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.
+#
+
+"""
+BBSim adapter based on a acme 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_resource_manager import OpenOltResourceMgr
+from voltha.adapters.openolt.openolt_flow_mgr import OpenOltFlowMgr
+from voltha.adapters.openolt.openolt_statistics import OpenOltStatisticsMgr
+from voltha.adapters.openolt.openolt_alarms import OpenOltAlarmMgr
+from voltha.adapters.openolt.openolt_bw import OpenOltBW
+from voltha.adapters.bbsimolt.bbsimolt_platform import BBSimOltPlatform
+from voltha.adapters.bbsimolt.bbsimolt_device import BBSimOltDevice
+
+log = structlog.get_logger()
+
+class BBSimOltAdapter(OpenoltAdapter):
+    name = 'bbsimolt'
+
+    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(BBSimOltAdapter, self).__init__(adapter_agent, config)
+
+        # overwrite the descriptor
+        self.descriptor = Adapter(
+            id=self.name,
+            vendor='CORD',
+            version='0.1',
+            config=AdapterConfig(log_level=LogLevel.INFO)
+        )
+
+    def adopt_device(self, device):
+        log.info('adopt-device', device=device)
+
+        support_classes = deepcopy(OpenOltDefaults)['support_classes']
+
+        # Customize platform
+        support_classes['platform'] = BBSimOltPlatform
+        kwargs = {
+            'support_classes': support_classes,
+            'adapter_agent': self.adapter_agent,
+            'device': device,
+            'device_num': self.num_devices + 1
+        }
+        try:
+            self.devices[device.id] = BBSimOltDevice(**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/bbsimolt/bbsimolt_device.py b/voltha/adapters/bbsimolt/bbsimolt_device.py
new file mode 100644
index 0000000..bcea426
--- /dev/null
+++ b/voltha/adapters/bbsimolt/bbsimolt_device.py
@@ -0,0 +1,309 @@
+#
+# 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 twisted.internet import reactor
+
+from scapy.layers.l2 import Ether
+from voltha.adapters.openolt.openolt import OpenoltDevice
+from voltha.protos.device_pb2 import Port
+from voltha.adapters.openolt.protos import openolt_pb2
+from voltha.protos.common_pb2 import ConnectStatus, OperStatus, AdminState
+from voltha.protos.bbf_fiber_tcont_body_pb2 import TcontsConfigData
+from voltha.protos.bbf_fiber_gemport_body_pb2 import GemportsConfigData
+from voltha.protos.bbf_fiber_base_pb2 import VEnetConfig
+from voltha.extensions.alarms.onu.onu_discovery_alarm import OnuDiscoveryAlarm
+
+from voltha.registry import registry
+
+class BBSimOltDevice(OpenoltDevice):
+    def __init__(self, **kwargs):
+        super(BBSimOltDevice, self).__init__(**kwargs)
+
+    def onu_discovery_indication(self, onu_disc_indication):
+        intf_id = onu_disc_indication.intf_id
+        serial_number = onu_disc_indication.serial_number
+
+        serial_number_str = self.stringify_serial_number(serial_number)
+
+        self.log.debug("onu discovery indication", intf_id=intf_id,
+                       serial_number=serial_number_str)
+
+        # Post ONU Discover alarm  20180809_0805
+        try:
+            OnuDiscoveryAlarm(self.alarm_mgr.alarms, pon_id=intf_id,
+                              serial_number=serial_number_str).raise_alarm()
+        except Exception as disc_alarm_error:
+            self.log.exception("onu-discovery-alarm-error",
+                               errmsg=disc_alarm_error.message)
+            # continue for now.
+
+        onu_device = self.adapter_agent.get_child_device(
+            self.device_id,
+            serial_number=serial_number_str)
+
+        if onu_device is None:
+            onu_id = self.new_onu_id(intf_id)
+            try:
+                self.add_onu_device(
+                    intf_id,
+                    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)
+            except Exception as e:
+                self.log.exception('onu-activation-failed', e=e)
+
+        else:
+            if onu_device.connect_status != ConnectStatus.REACHABLE:
+                    onu_device.connect_status = ConnectStatus.REACHABLE
+                    self.adapter_agent.update_device(onu_device)
+
+            onu_id = onu_device.proxy_address.onu_id
+            if onu_device.oper_status == OperStatus.DISCOVERED \
+               or onu_device.oper_status == OperStatus.ACTIVATING:
+                self.log.debug("ignore onu discovery indication, \
+                               the onu has been discovered and should be \
+                               activating shorlty", intf_id=intf_id,
+                               onu_id=onu_id, state=onu_device.oper_status)
+            elif onu_device.oper_status == OperStatus.ACTIVE:
+                self.log.warn("onu discovery indication whereas onu is \
+                              supposed to be active",
+                              intf_id=intf_id, onu_id=onu_id,
+                              state=onu_device.oper_status)
+            elif onu_device.oper_status == OperStatus.UNKNOWN:
+                self.log.info("onu in unknown state, recovering from olt \
+                              reboot probably, activate onu", intf_id=intf_id,
+                              onu_id=onu_id, serial_number=serial_number_str)
+
+                onu_device.oper_status = OperStatus.DISCOVERED
+                self.adapter_agent.update_device(onu_device)
+                try:
+                    self.activate_onu(intf_id, onu_id, serial_number,
+                                      serial_number_str)
+                except Exception as e:
+                    self.log.error('onu-activation-error',
+                                   serial_number=serial_number_str, error=e)
+            else:
+                self.log.warn('unexpected state', onu_id=onu_id,
+                              onu_device_oper_state=onu_device.oper_status)
+
+    def packet_indication(self, pkt_indication):
+
+        self.log.debug("packet indication", intf_id=pkt_indication.intf_id,
+                       gemport_id=pkt_indication.gemport_id,
+                       flow_id=pkt_indication.flow_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)
+
+        pkt = Ether(pkt_indication.pkt)
+        kw = dict(logical_device_id=self.logical_device_id,
+                  logical_port_no=logical_port_num)
+        self.adapter_agent.send_packet_in(packet=str(pkt), **kw)
+
+    def onu_indication(self, onu_indication):
+        self.log.debug("onu indication", intf_id=onu_indication.intf_id,
+                       onu_id=onu_indication.onu_id,
+                       serial_number=onu_indication.serial_number,
+                       oper_state=onu_indication.oper_state,
+                       admin_state=onu_indication.admin_state)
+        try:
+            serial_number_str = self.stringify_serial_number(
+                onu_indication.serial_number)
+        except Exception as e:
+            serial_number_str = None
+
+        if serial_number_str is not None:
+            onu_device = self.adapter_agent.get_child_device(
+                    self.device_id,
+                    serial_number=serial_number_str)
+        else:
+            onu_device = self.adapter_agent.get_child_device(
+                    self.device_id,
+                    parent_port_no=self.platform.intf_id_to_port_no(
+                            onu_indication.intf_id, Port.PON_OLT),
+                    onu_id=onu_indication.onu_id)
+
+        if onu_device is None:
+            self.log.error('onu not found', intf_id=onu_indication.intf_id,
+                           onu_id=onu_indication.onu_id)
+            return
+
+        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=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?)
+
+        if onu_device.proxy_address.onu_id != onu_indication.onu_id:
+            # FIXME - handle onu id mismatch
+            self.log.warn('ONU-id-mismatch, can happen if both voltha and '
+                          'the olt rebooted',
+                          expected_onu_id=onu_device.proxy_address.onu_id,
+                          received_onu_id=onu_indication.onu_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)
+
+        self.log.debug('port-number-ready', uni_no=uni_no, uni_name=uni_name)
+
+        # Admin state
+        if onu_indication.admin_state == 'down':
+            if onu_indication.oper_state != 'down':
+                self.log.error('ONU-admin-state-down-and-oper-status-not-down',
+                               oper_state=onu_indication.oper_state)
+                # Forcing the oper state change code to execute
+                onu_indication.oper_state = 'down'
+
+            # Port and logical port update is taken care of by oper state block
+
+        elif onu_indication.admin_state == 'up':
+            pass
+
+        else:
+            self.log.warn('Invalid-or-not-implemented-admin-state',
+                          received_admin_state=onu_indication.admin_state)
+
+        self.log.debug('admin-state-dealt-with')
+
+        onu_adapter_agent = \
+            registry('adapter_loader').get_agent(onu_device.adapter)
+        if onu_adapter_agent is None:
+            self.log.error('onu_adapter_agent-could-not-be-retrieved',
+                           onu_device=onu_device)
+            return
+
+        # Operating state
+        if onu_indication.oper_state == 'down':
+
+            if onu_device.connect_status != ConnectStatus.UNREACHABLE:
+                onu_device.connect_status = ConnectStatus.UNREACHABLE
+                self.adapter_agent.update_device(onu_device)
+
+            # Move to discovered state
+            self.log.debug('onu-oper-state-is-down')
+
+            if onu_device.oper_status != OperStatus.DISCOVERED:
+                onu_device.oper_status = OperStatus.DISCOVERED
+                self.adapter_agent.update_device(onu_device)
+            # Set port oper state to Discovered
+            self.onu_ports_down(onu_device, uni_no, uni_name,
+                                OperStatus.DISCOVERED)
+
+            if onu_device.adapter == 'brcm_openomci_onu':
+                self.log.debug('using-brcm_openomci_onu')
+                onu_adapter_agent.update_interface(onu_device,
+                                                   {'oper_state': 'down'})
+
+        elif onu_indication.oper_state == 'up':
+
+            if onu_device.connect_status != ConnectStatus.REACHABLE:
+                onu_device.connect_status = ConnectStatus.REACHABLE
+                self.adapter_agent.update_device(onu_device)
+
+            if onu_device.oper_status != OperStatus.DISCOVERED:
+                self.log.debug("ignore onu indication",
+                               intf_id=onu_indication.intf_id,
+                               onu_id=onu_indication.onu_id,
+                               state=onu_device.oper_status,
+                               msg_oper_state=onu_indication.oper_state)
+                return
+
+            # Device was in Discovered state, setting it to active
+
+            # Prepare onu configuration
+            # If we are using the old/current broadcom adapter otherwise
+            # use the openomci adapter
+            if onu_device.adapter == 'broadcom_onu':
+                self.log.debug('using-broadcom_onu')
+
+                # onu initialization, base configuration (bridge setup ...)
+                def onu_initialization():
+
+                    onu_adapter_agent.adapter.devices_handlers[onu_device.id] \
+                                     .message_exchange()
+                    self.log.debug('broadcom-message-exchange-started')
+
+                # tcont creation (onu)
+                tcont = TcontsConfigData()
+                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 = self.platform.mk_gemport_id(
+                    onu_indication.intf_id,
+                    onu_indication.onu_id)
+
+                # ports creation/update
+                def port_config():
+
+                    # "v_enet" creation (olt)
+
+                    # add_port update port when it exists
+                    self.adapter_agent.add_port(
+                        self.device_id,
+                        Port(
+                            port_no=uni_no,
+                            label=uni_name,
+                            type=Port.ETHERNET_UNI,
+                            admin_state=AdminState.ENABLED,
+                            oper_status=OperStatus.ACTIVE))
+
+                    # v_enet creation (onu)
+
+                    venet = VEnetConfig(name=uni_name)
+                    venet.interface.name = uni_name
+                    onu_adapter_agent.create_interface(onu_device, venet)
+
+                # ONU device status update in the datastore
+                def onu_update_oper_status():
+                    onu_device.oper_status = OperStatus.ACTIVE
+                    onu_device.connect_status = ConnectStatus.REACHABLE
+                    self.adapter_agent.update_device(onu_device)
+
+                # FIXME : the asynchronicity has to be taken care of properly
+                onu_initialization()
+                reactor.callLater(10, onu_adapter_agent.create_tcont,
+                                  device=onu_device, tcont_data=tcont,
+                                  traffic_descriptor_data=None)
+                reactor.callLater(11, onu_adapter_agent.create_gemport,
+                                  onu_device, gem_port)
+                reactor.callLater(12, port_config)
+                reactor.callLater(12, onu_update_oper_status)
+
+            else:
+                self.log.error('unsupported-openolt-onu-adapter')
+
+        else:
+            self.log.warn('Not-implemented-or-invalid-value-of-oper-state',
+                          oper_state=onu_indication.oper_state)
+
+    def activate_onu(self, intf_id, onu_id, serial_number,
+                     serial_number_str):
+        pir = self.bw_mgr.pir(serial_number_str)
+        self.log.debug("activating-onu", intf_id=intf_id, onu_id=onu_id,
+                       serial_number_str=serial_number_str,
+                       serial_number=serial_number, pir=pir)
+        onu = openolt_pb2.Onu(intf_id=intf_id, onu_id=onu_id,
+                              serial_number=serial_number, pir=pir)
+        self.stub.ActivateOnu(onu)
+        self.log.info('onu-activated', serial_number=serial_number_str)
\ No newline at end of file
diff --git a/voltha/adapters/bbsimolt/bbsimolt_platform.py b/voltha/adapters/bbsimolt/bbsimolt_platform.py
new file mode 100644
index 0000000..5da100a
--- /dev/null
+++ b/voltha/adapters/bbsimolt/bbsimolt_platform.py
@@ -0,0 +1,106 @@
+#
+# 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 = 64
+
+class BBSimOltPlatform(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