Maple OLT and Broadcom ONU adapter skeletons

Change-Id: I09729d17a3be221a64f3261a039dd9809a1ad65b
diff --git a/voltha/adapters/maple/__init__.py b/voltha/adapters/broadcom_onu/__init__.py
similarity index 100%
copy from voltha/adapters/maple/__init__.py
copy to voltha/adapters/broadcom_onu/__init__.py
diff --git a/voltha/adapters/broadcom_onu/broadcom_onu.py b/voltha/adapters/broadcom_onu/broadcom_onu.py
new file mode 100644
index 0000000..4ddeeb5
--- /dev/null
+++ b/voltha/adapters/broadcom_onu/broadcom_onu.py
@@ -0,0 +1,217 @@
+#
+# Copyright 2016 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.
+#
+
+"""
+Mock device adapter for testing.
+"""
+from uuid import uuid4
+
+import structlog
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, DeferredQueue
+from zope.interface import implementer
+
+from common.utils.asleep import asleep
+from voltha.adapters.interface import IAdapterInterface
+from voltha.core.logical_device_agent import mac_str_to_tuple
+from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
+from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Device, Port
+from voltha.protos.health_pb2 import HealthStatus
+from voltha.protos.common_pb2 import LogLevel, OperStatus, ConnectStatus, \
+    AdminState
+from voltha.protos.logical_device_pb2 import LogicalDevice, LogicalPort
+from voltha.protos.openflow_13_pb2 import ofp_desc, ofp_port, OFPPF_1GB_FD, \
+    OFPPF_FIBER, OFPPS_LIVE, ofp_switch_features, OFPC_PORT_STATS, \
+    OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
+
+log = structlog.get_logger()
+
+
+@implementer(IAdapterInterface)
+class BroadcomOnuAdapter(object):
+
+    name = 'broadcom_onu'
+
+    supported_device_types = [
+        DeviceType(
+            id='broadcom_onu',
+            adapter=name,
+            accepts_bulk_flow_update=True
+        )
+    ]
+
+    def __init__(self, adapter_agent, config):
+        self.adapter_agent = adapter_agent
+        self.config = config
+        self.descriptor = Adapter(
+            id=self.name,
+            vendor='Voltha project',
+            version='0.1',
+            config=AdapterConfig(log_level=LogLevel.INFO)
+        )
+        self.incoming_messages = DeferredQueue()
+
+    def start(self):
+        log.debug('starting')
+        log.info('started')
+
+    def stop(self):
+        log.debug('stopping')
+        log.info('stopped')
+
+    def adapter_descriptor(self):
+        return self.descriptor
+
+    def device_types(self):
+        return DeviceTypes(items=self.supported_device_types)
+
+    def health(self):
+        return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
+
+    def change_master_state(self, master):
+        raise NotImplementedError()
+
+    def adopt_device(self, device):
+        # We kick of a simulated activation scenario
+        reactor.callLater(0.2, self._simulate_device_activation, device)
+        return device
+
+    def abandon_device(self, device):
+        raise NotImplementedError()
+
+    def deactivate_device(self, device):
+        raise NotImplementedError()
+
+    @inlineCallbacks
+    def _simulate_device_activation(self, device):
+
+        # first we verify that we got parent reference and proxy info
+        assert device.parent_id
+        assert device.proxy_address.device_id
+        assert device.proxy_address.channel_id
+
+        # we pretend that we were able to contact the device and obtain
+        # additional information about it
+        device.vendor = 'Broadcom'
+        device.model = 'to be filled'
+        device.hardware_version = 'to be filled'
+        device.firmware_version = 'to be filled'
+        device.software_version = 'to be filled'
+        device.serial_number = uuid4().hex
+        device.connect_status = ConnectStatus.REACHABLE
+        self.adapter_agent.update_device(device)
+
+        # then shortly after we create some ports for the device
+        yield asleep(0.05)
+        uni_port = Port(
+            port_no=0,
+            label='UNI facing Ethernet port',
+            type=Port.ETHERNET_UNI,
+            admin_state=AdminState.ENABLED,
+            oper_status=OperStatus.ACTIVE
+        )
+        self.adapter_agent.add_port(device.id, uni_port)
+        self.adapter_agent.add_port(device.id, Port(
+            port_no=1,
+            label='PON port',
+            type=Port.PON_ONU,
+            admin_state=AdminState.ENABLED,
+            oper_status=OperStatus.ACTIVE,
+            peers=[
+                Port.PeerPort(
+                    device_id=device.parent_id,
+                    port_no=device.parent_port_no
+                )
+            ]
+        ))
+
+        # TODO adding vports to the logical device shall be done by agent?
+        # then we create the logical device port that corresponds to the UNI
+        # port of the device
+        yield asleep(0.05)
+
+        # obtain logical device id
+        parent_device = self.adapter_agent.get_device(device.parent_id)
+        logical_device_id = parent_device.parent_id
+        assert logical_device_id
+
+        # we are going to use the proxy_address.channel_id as unique number
+        # and name for the virtual ports, as this is guaranteed to be unique
+        # in the context of the OLT port, so it is also unique in the context
+        # of the logical device
+        port_no = device.proxy_address.channel_id
+        cap = OFPPF_1GB_FD | OFPPF_FIBER
+        self.adapter_agent.add_logical_port(logical_device_id, LogicalPort(
+            id=str(port_no),
+            ofp_port=ofp_port(
+                port_no=port_no,
+                hw_addr=mac_str_to_tuple('00:00:00:00:00:%02x' % port_no),
+                name='uni-{}'.format(port_no),
+                config=0,
+                state=OFPPS_LIVE,
+                curr=cap,
+                advertised=cap,
+                peer=cap,
+                curr_speed=OFPPF_1GB_FD,
+                max_speed=OFPPF_1GB_FD
+            ),
+            device_id=device.id,
+            device_port_no=uni_port.port_no
+        ))
+
+        # simulate a proxied message sending and receving a reply
+        reply = yield self._simulate_message_exchange(device)
+
+        # and finally update to "ACTIVE"
+        device = self.adapter_agent.get_device(device.id)
+        device.oper_status = OperStatus.ACTIVE
+        self.adapter_agent.update_device(device)
+
+    def update_flows_bulk(self, device, flows, groups):
+        log.debug('bulk-flow-update', device_id=device.id,
+                  flows=flows, groups=groups)
+
+    def update_flows_incrementally(self, device, flow_changes, group_changes):
+        raise NotImplementedError()
+
+    def send_proxied_message(self, proxy_address, msg):
+        raise NotImplementedError()
+
+    def receive_proxied_message(self, proxy_address, msg):
+        # just place incoming message to a list
+        self.incoming_messages.put((proxy_address, msg))
+
+    @inlineCallbacks
+    def _simulate_message_exchange(self, device):
+
+        # register for receiving async messages
+        self.adapter_agent.register_for_proxied_messages(device.proxy_address)
+
+        # reset incoming message queue
+        while self.incoming_messages.pending:
+            _ = yield self.incoming_messages.get()
+
+        # construct message
+        msg = 'test message'
+
+        # send message
+        self.adapter_agent.send_proxied_message(device.proxy_address, msg)
+
+        # wait till we detect incoming message
+        yield self.incoming_messages.get()
+
+        # by returning we allow the device to be shown as active, which
+        # indirectly verified that message passing works
diff --git a/voltha/adapters/maple/README.md b/voltha/adapters/maple/README.md
deleted file mode 100644
index e69de29..0000000
--- a/voltha/adapters/maple/README.md
+++ /dev/null
diff --git a/voltha/adapters/maple/maple.py b/voltha/adapters/maple/maple.py
deleted file mode 100644
index e69de29..0000000
--- a/voltha/adapters/maple/maple.py
+++ /dev/null
diff --git a/voltha/adapters/maple_olt/README.md b/voltha/adapters/maple_olt/README.md
new file mode 100644
index 0000000..c8b8599
--- /dev/null
+++ b/voltha/adapters/maple_olt/README.md
@@ -0,0 +1,134 @@
+# Developer notes:
+
+Before auto-discovery is implemented, you can follow the steps below to activate a Maple PON.
+These steps assume:
+
+* Voltha starts with fresh state (was just launched in single-instance mode)
+* Maple OLT and Broadcom ONU(s) are powered on and properly connected with splitters.
+* There is network reach from Voltha's host environment to the Broadcom Maple OLT via
+  a specific interface of the host OS. We symbolically refer to this Linux
+  interface as \<interface\>.
+* All commands are to be executed from the root dir of the voltha project after
+  env.sh was sourced, and at least 'make protos' was executed on the latest code.
+
+
+## Step 1: Launch Voltha with the proper interface value.
+
+```
+./voltha/main.py -I <interface>  # example: ./voltha/main.py -I eth2
+```
+
+## Step 2: Launch Chameleon (in a different terminal)
+
+```
+./chamaleon/main.py
+```
+
+## Step 3: Verify Maple and Broadcom adapters loaded
+
+In a third terminal, issue the following REST requests:
+
+```
+curl -s http://localhost:8881/api/v1/local/adapters | jq
+```
+
+This should list (among other entries) two entries for Broadcom devices:
+one for the Maple OLT and one for the Broadcom ONU.
+
+The following request should show the device types supported:
+
+```
+curl -s http://localhost:8881/api/v1/local/device_types | jq
+```
+
+This should include two entries for Broadcom devices, one for the OLT
+and one for the ONU.
+
+## Step 4: Pre-provision a Maple OLT
+
+Issue the following command to pre-provision the Maple OLT:
+
+```
+curl -s -X POST -d '{"type": "maple_olt", "mac_address": "00:00:00:00:00:01"}' \
+    http://localhost:8881/api/v1/local/devices | jq '.' | tee olt.json
+```
+
+This shall return with a complete Device JSON object, including a 12-character
+id of the new device and a preprovisioned state as admin state (it also saved the
+json blob in a olt.json file):
+
+```
+{
+  "vendor": "",
+  "software_version": "",
+  "parent_port_no": 0,
+  "connect_status": "UNKNOWN",
+  "root": false,
+  "adapter": "tibit_olt",
+  "vlan": 0,
+  "hardware_version": "",
+  "ports": [],
+  "parent_id": "",
+  "oper_status": "UNKNOWN",
+  "admin_state": "PREPROVISIONED",
+  "mac_address": "00:00:00:00:00:01",
+  "serial_number": "",
+  "model": "",
+  "type": "maple_olt",
+  "id": "2db8e16804ec",
+  "firmware_version": ""
+}
+```
+
+For simplicity, store the device id as shell variable:
+
+```
+OLT_ID=$(jq .id olt.json | sed 's/"//g')
+```
+
+## Step 5: Activate the OLT
+
+To activate the OLT, issue the following using the OLT_ID memorized above:
+
+```
+curl -s -X POST http://localhost:8881/api/v1/local/devices/$OLT_ID/activate
+```
+
+After this, if you retrieve the state of the OLT device, it should be enabled
+and in the 'ACTIVATING' operational status:
+
+```
+curl http://localhost:8881/api/v1/local/devices/$OLT_ID | jq '.oper_status,.admin_state'
+"ACTIVATING"
+"ENABLED"
+```
+When the device is ACTIVE, the logical devices and logical ports should be created.  To check
+the logical devices and logical ports, use the following commands.
+
+```
+curl -s http://localhost:8881/api/v1/local/logical_devices | jq '.'
+# Note: Need to pull out logical device id.
+curl -s http://localhost:8881/api/v1/local/logical_devices/47d2bb42a2c6/ports | jq '.'
+```
+
+
+
+
+
+# OLD stuff
+
+[This will be moved to some other place soon.]
+
+To get the EOAM stack to work with the ONOS olt-test, the following
+command was used in the shell to launch the olt-test.
+
+NOTE: This command should soon be eliminated as the adapter should
+be started by VOLTHA. By running the commands as listed below, then
+the olt-test can take advantage of the virtual environment.
+
+```
+$ cd <LOCATION_OF_VOLTHA>
+$ sudo -s
+# . ./env.sh
+(venv-linux) # PYTHONPATH=$HOME/dev/voltha/voltha/adapters/tibit ./oftest/oft --test-dir=olt-oftest/ -i 1@enp1s0f0 -i 2@enp1s0f1 --port 6633 -V 1.3 -t "olt_port=1;onu_port=2;in_out_port=1;device_type='tibit'" olt-complex.TestScenario1SingleOnu
+```
diff --git a/voltha/adapters/maple/__init__.py b/voltha/adapters/maple_olt/__init__.py
similarity index 100%
rename from voltha/adapters/maple/__init__.py
rename to voltha/adapters/maple_olt/__init__.py
diff --git a/voltha/adapters/maple_olt/maple_olt.py b/voltha/adapters/maple_olt/maple_olt.py
new file mode 100644
index 0000000..f250ae9
--- /dev/null
+++ b/voltha/adapters/maple_olt/maple_olt.py
@@ -0,0 +1,222 @@
+#
+# Copyright 2016 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 uuid import uuid4
+
+import structlog
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks
+from zope.interface import implementer
+
+from common.utils.asleep import asleep
+from voltha.adapters.interface import IAdapterInterface
+from voltha.core.logical_device_agent import mac_str_to_tuple
+from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
+from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Device, Port
+from voltha.protos.health_pb2 import HealthStatus
+from voltha.protos.common_pb2 import LogLevel, OperStatus, ConnectStatus, \
+    AdminState
+from voltha.protos.logical_device_pb2 import LogicalDevice, LogicalPort
+from voltha.protos.openflow_13_pb2 import ofp_desc, ofp_port, OFPPF_1GB_FD, \
+    OFPPF_FIBER, OFPPS_LIVE, ofp_switch_features, OFPC_PORT_STATS, \
+    OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
+
+log = structlog.get_logger()
+
+
+@implementer(IAdapterInterface)
+class MapleOltAdapter(object):
+
+    name = 'maple_olt'
+
+    supported_device_types = [
+        DeviceType(
+            id='maple_olt',
+            adapter=name,
+            accepts_bulk_flow_update=True
+        )
+    ]
+
+    def __init__(self, adapter_agent, config):
+        self.adapter_agent = adapter_agent
+        self.config = config
+        self.descriptor = Adapter(
+            id=self.name,
+            vendor='Voltha project',
+            version='0.1',
+            config=AdapterConfig(log_level=LogLevel.INFO)
+        )
+
+    def start(self):
+        log.debug('starting')
+        log.info('started')
+
+    def stop(self):
+        log.debug('stopping')
+        log.info('stopped')
+
+    def adapter_descriptor(self):
+        return self.descriptor
+
+    def device_types(self):
+        return DeviceTypes(items=self.supported_device_types)
+
+    def health(self):
+        return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
+
+    def change_master_state(self, master):
+        raise NotImplementedError()
+
+    def adopt_device(self, device):
+        log.info('adopt-device', device=device)
+        # We kick of a simulated activation scenario
+        reactor.callLater(0.2, self._activate_device, device)
+        return device
+
+    def abandon_device(self, device):
+        raise NotImplementedError(0
+                                  )
+    def deactivate_device(self, device):
+        raise NotImplementedError()
+
+    def _activate_device(self, device):
+
+        # first we pretend that we were able to contact the device and obtain
+        # additional information about it
+        device.root = True
+        device.vendor = 'Broadcom'
+        device.model = 'Maple XYZ'
+        device.hardware_version = 'Fill this'
+        device.firmware_version = 'Fill this'
+        device.software_version = 'Fill this'
+        device.serial_number = 'Fill this'
+        device.connect_status = ConnectStatus.REACHABLE
+        self.adapter_agent.update_device(device)
+
+        # register ports
+        nni_port = Port(
+            port_no=0,
+            label='NNI facing Ethernet port',
+            type=Port.ETHERNET_NNI,
+            admin_state=AdminState.ENABLED,
+            oper_status=OperStatus.ACTIVE
+        )
+        self.adapter_agent.add_port(device.id, nni_port)
+        self.adapter_agent.add_port(device.id, Port(
+            port_no=1,
+            label='PON port',
+            type=Port.PON_OLT,
+            admin_state=AdminState.ENABLED,
+            oper_status=OperStatus.ACTIVE
+        ))
+
+        # register logical device (as we are a root device)
+        logical_device_id = uuid4().hex[:12]
+        ld = LogicalDevice(
+            id=logical_device_id,
+            datapath_id=int('0x' + logical_device_id[:8], 16), # from id
+            desc=ofp_desc(
+                mfr_desc='cord porject',
+                hw_desc='n/a',
+                sw_desc='logical device for Maple-based PON',
+                serial_num=uuid4().hex,
+                dp_desc='n/a'
+            ),
+            switch_features=ofp_switch_features(
+                n_buffers=256,  # TODO fake for now
+                n_tables=2,  # TODO ditto
+                capabilities=(  # TODO and ditto
+                    OFPC_FLOW_STATS
+                    | OFPC_TABLE_STATS
+                    | OFPC_PORT_STATS
+                    | OFPC_GROUP_STATS
+                )
+            ),
+            root_device_id=device.id
+        )
+        self.adapter_agent.create_logical_device(ld)
+        cap = OFPPF_1GB_FD | OFPPF_FIBER
+        self.adapter_agent.add_logical_port(ld.id, LogicalPort(
+            id='nni',
+            ofp_port=ofp_port(
+                port_no=0,
+                hw_addr=mac_str_to_tuple('00:00:00:00:00:%02x' % 129),
+                name='nni',
+                config=0,
+                state=OFPPS_LIVE,
+                curr=cap,
+                advertised=cap,
+                peer=cap,
+                curr_speed=OFPPF_1GB_FD,
+                max_speed=OFPPF_1GB_FD
+            ),
+            device_id=device.id,
+            device_port_no=nni_port.port_no,
+            root_port=True
+        ))
+
+        # and finally update to active
+        device = self.adapter_agent.get_device(device.id)
+        device.parent_id = ld.id
+        device.oper_status = OperStatus.ACTIVE
+        self.adapter_agent.update_device(device)
+
+        reactor.callLater(0.1, self._simulate_detection_of_onus, device)
+
+    def _simulate_detection_of_onus(self, device):
+        for i in xrange(1, 5):
+            log.info('activate-olt-for-onu-{}'.format(i))
+            vlan_id = self._olt_side_onu_activation(i)
+            self.adapter_agent.child_device_detected(
+                parent_device_id=device.id,
+                parent_port_no=1,
+                child_device_type='broadcom_onu',
+                proxy_address=Device.ProxyAddress(
+                    device_id=device.id,
+                    channel_id=vlan_id
+                ),
+                vlan=vlan_id
+            )
+
+    def _olt_side_onu_activation(self, seq):
+        """
+        This is where if this was a real OLT, the OLT-side activation for
+        the new ONU should be performed. By the time we return, the OLT shall
+        be able to provide tunneled (proxy) communication to the given ONU,
+        using the returned information.
+        """
+        vlan_id = seq + 100
+        return vlan_id
+
+    def update_flows_bulk(self, device, flows, groups):
+        log.debug('bulk-flow-update', device_id=device.id,
+                  flows=flows, groups=groups)
+
+    def update_flows_incrementally(self, device, flow_changes, group_changes):
+        raise NotImplementedError()
+
+    def send_proxied_message(self, proxy_address, msg):
+        log.info('send-proxied-message', proxy_address=proxy_address, msg=msg)
+        # we mimic a response by sending the same message back in a short time
+        reactor.callLater(
+            0.2,
+            self.adapter_agent.receive_proxied_message,
+            proxy_address,
+            msg
+        )
+
+    def receive_proxied_message(self, proxy_address, msg):
+        raise NotImplementedError()