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/ponsim/realio.py b/ponsim/realio.py
new file mode 100644
index 0000000..bca3382
--- /dev/null
+++ b/ponsim/realio.py
@@ -0,0 +1,68 @@
+#
+# 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.
+#
+import structlog
+from scapy.layers.l2 import Ether
+from scapy.packet import Packet
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+from common.frameio.frameio import FrameIOManager, hexify
+
+log = structlog.get_logger()
+
+
+class RealIo(object):
+
+    def __init__(self, iface_map):
+        self.port_to_iface_name = iface_map
+        self.iface_name_to_port = dict((n, p) for p, n in iface_map.items())
+        self.frame_io = FrameIOManager()
+        self.ponsim = None
+        self.io_ports = dict()
+
+    @inlineCallbacks
+    def start(self):
+        log.debug('starting')
+        yield self.frame_io.start()
+        for port, iface_name in self.port_to_iface_name.items():
+            io_port = self.frame_io.add_interface(iface_name, self.ingress)
+            self.io_ports[port] = io_port
+        log.info('started')
+        returnValue(self)
+
+    def stop(self):
+        log.debug('stopping')
+        for port in self.io_ports.values():
+            self.frame_io.del_interface(port.iface_name)
+        self.frame_io.stop()
+        log.info('stopped')
+
+    def register_ponsim(self, ponsim):
+        self.ponsim = ponsim
+
+    def ingress(self, io_port, frame):
+        port = self.iface_name_to_port.get(io_port.iface_name)
+        log.debug('ingress', port=port, iface_name=io_port.iface_name,
+                  frame=hexify(frame))
+        decoded_frame = Ether(frame)
+        if self.ponsim is not None:
+            self.ponsim.ingress(port, decoded_frame)
+
+    def egress(self, port, frame):
+        if isinstance(frame, Packet):
+            frame = str(frame)
+        io_port = self.io_ports[port]
+        log.debug('sending', port=port, frame=hexify(frame))
+        io_port.send(frame)