PONSIM: PON simulator with real dataplane handling

This was needed because neither CPQD nor OVS can handle
both zero-tagged packets and 802.1ad (QinQ).

- extensive unittest proves ponsim functional correctness
  (for the common use-cases needed in the PON scenario)
- integrated with frameio and coupled with a rather
  simple gRPC NBI, ponsim can be operated from Voltha
  just like a real PON system
- posim_olt/_onu adapters added to Voltha to work on
  ponsim
- CLI can be used to preprovision and activate a PONSIM
  instance (e.g., preprovision_olt -t ponsim_olt -H localhost:50060)
- Some of olt-oftest:olt-complex testcases can be run on
  the ponsim device (in vagrant/Ubuntu environment),
  but there are some remaining issues to work out:
  - barrier calls in OF do not guaranty that the flow
    is already installed on the device. This is a generic
    issue, not just for ponsim.
  - the whole test framework is inconsistent about zero-
    tagged vs. untagged frames at the ONUs, while ponsim
    is rather pedantica and does exactly what was defined
    in the flows.

Change-Id: I0dd564c932416ae1566935492134cb5b08113bdc
diff --git a/voltha/core/adapter_agent.py b/voltha/core/adapter_agent.py
index 0c5448f..3bf8857 100644
--- a/voltha/core/adapter_agent.py
+++ b/voltha/core/adapter_agent.py
@@ -31,6 +31,7 @@
 from voltha.protos.voltha_pb2 import DeviceGroup, LogicalDevice, \
     LogicalPort, AdminState
 from voltha.registry import registry
+from voltha.core.flow_decomposer import OUTPUT
 
 
 @implementer(IAdapterAgent)
@@ -64,11 +65,11 @@
         try:
             adapter = self.adapter_cls(self, config)
             yield adapter.start()
+            self.adapter = adapter
+            self.adapter_node_proxy = self._update_adapter_node()
+            self._update_device_types()
         except Exception, e:
             self.log.exception(e)
-        self.adapter = adapter
-        self.adapter_node_proxy = self._update_adapter_node()
-        self._update_device_types()
         self.log.info('started')
         returnValue(self)
 
@@ -199,6 +200,10 @@
                 return i
             i += 1
 
+    def get_logical_device(self, logical_device_id):
+        return self.root_proxy.get('/logical_devices/{}'.format(
+            logical_device_id))
+
     def create_logical_device(self, logical_device):
         assert isinstance(logical_device, LogicalDevice)
 
@@ -210,8 +215,24 @@
         self._make_up_to_date('/logical_devices',
                               logical_device.id, logical_device)
 
+        self.event_bus.subscribe(
+            topic='packet-out:{}'.format(logical_device.id),
+            callback=lambda _, p: self.receive_packet_out(logical_device.id, p)
+        )
+
         return logical_device
 
+    def receive_packet_out(self, logical_device_id, ofp_packet_out):
+
+        def get_port_out(opo):
+            for action in opo.actions:
+                if action.type == OUTPUT:
+                    return action.output.port
+
+        out_port = get_port_out(ofp_packet_out)
+        frame = ofp_packet_out.data
+        self.adapter.receive_packet_out(logical_device_id, out_port, frame)
+
     def add_logical_port(self, logical_device_id, port):
         assert isinstance(port, LogicalPort)
         self._make_up_to_date(
diff --git a/voltha/core/logical_device_agent.py b/voltha/core/logical_device_agent.py
index f11247e..7dbf0b5 100644
--- a/voltha/core/logical_device_agent.py
+++ b/voltha/core/logical_device_agent.py
@@ -17,23 +17,23 @@
 """
 Model that captures the current state of a logical device
 """
-import threading
 from collections import OrderedDict
 
 import structlog
 
 from common.event_bus import EventBusClient
+from common.frameio.frameio import hexify
 from voltha.core.config.config_proxy import CallbackType
 from voltha.core.device_graph import DeviceGraph
 from voltha.core.flow_decomposer import FlowDecomposer, \
     flow_stats_entry_from_flow_mod_message, group_entry_from_group_mod, \
-    mk_flow_stat, in_port, vlan_vid, vlan_pcp, pop_vlan, output, set_field
+    mk_flow_stat, in_port, vlan_vid, vlan_pcp, pop_vlan, output, set_field, \
+    push_vlan
 from voltha.protos import third_party
 from voltha.protos import openflow_13_pb2 as ofp
 from voltha.protos.device_pb2 import Port
 from voltha.protos.logical_device_pb2 import LogicalPort
 from voltha.protos.openflow_13_pb2 import Flows, FlowGroups
-from voltha.registry import registry
 
 _ = third_party
 
@@ -450,16 +450,9 @@
     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PACKET_OUT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
     def packet_out(self, ofp_packet_out):
-        self.log.debug('packet-out', packet=ofp_packet_out)
-        print threading.current_thread().name
-        print 'PACKET_OUT:', ofp_packet_out
-        # TODO for debug purposes, lets turn this around and send it back
-        if 0:
-            self.packet_in(ofp.ofp_packet_in(
-                buffer_id=ofp_packet_out.buffer_id,
-                reason=ofp.OFPR_NO_MATCH,
-                data=ofp_packet_out.data
-            ))
+        self.log.info('packet-out', packet=ofp_packet_out)
+        topic = 'packet-out:{}'.format(self.logical_device_id)
+        self.event_bus.publish(topic, ofp_packet_out)
 
     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PACKET_IN ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -485,8 +478,8 @@
         self.packet_in(packet_in)
 
     def packet_in(self, ofp_packet_in):
-        # TODO
-        print 'PACKET_IN:', ofp_packet_in
+        self.log.info('packet-in', logical_device_id=self.logical_device_id,
+                      pkt=ofp_packet_in, data=hexify(ofp_packet_in.data))
         self.local_handler.send_packet_in(
             self.logical_device_id, ofp_packet_in)
 
@@ -630,7 +623,30 @@
                         set_field(vlan_vid(ofp.OFPVID_PRESENT | device.vlan)),
                         output(upstream_ports[0].port_no)
                     ]
-                )
+                ),
+                mk_flow_stat(
+                    priority=500,
+                    match_fields=[
+                        in_port(downstream_ports[0].port_no),
+                        vlan_vid(0)
+                    ],
+                    actions=[
+                        push_vlan(0x8100),
+                        set_field(vlan_vid(ofp.OFPVID_PRESENT | device.vlan)),
+                        output(upstream_ports[0].port_no)
+                    ]
+                ),
+                mk_flow_stat(
+                    priority=500,
+                    match_fields=[
+                        in_port(upstream_ports[0].port_no),
+                        vlan_vid(ofp.OFPVID_PRESENT | device.vlan)
+                    ],
+                    actions=[
+                        set_field(vlan_vid(ofp.OFPVID_PRESENT | 0)),
+                        output(downstream_ports[0].port_no)
+                    ]
+                ),
             ])
             groups = OrderedDict()
             return flows, groups