Initial set of Fabric switch test cases

Change-Id: I86fd2b67d3b773aa496f5ef61f1e1fdf51fd9925
diff --git a/Fabric/Utilities/platforms/bpp.py b/Fabric/Utilities/platforms/bpp.py
new file mode 100644
index 0000000..ba4e99a
--- /dev/null
+++ b/Fabric/Utilities/platforms/bpp.py
@@ -0,0 +1,89 @@
+
+# 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.
+
+
+"""
+Platform configuration file
+platform == bpp
+"""
+
+###############################################################################
+#
+# This platform assumes BPP VPI specifications on the command line. 
+#
+###############################################################################
+
+import sys
+import os
+import argparse
+import subprocess
+import dppv
+
+# The port specification is passed via the "--platform-args" option to OFTest. 
+# Note that we must guard against abbreviations supported by argparse
+if not "--platform-args" in " ".join(sys.argv):
+    raise Exception("--platform-args must be specified")
+
+ap = argparse.ArgumentParser("bpp")
+ap.add_argument("--platform-args")
+(ops, rest) = ap.parse_known_args()
+
+if not "@" in ops.platform_args:
+    # Assume it is just a device name. Get the ports from the track database. 
+    if "," in ops.platform_args:
+        (type_, d) = ops.platform_args.split(",")
+    else:
+        (type_, d) = ("udp", ops.platform_args)
+
+    trackScript = "/usr/bin/track"
+    if not os.path.exists(trackScript):
+        raise Exception("Cannot find the track script (looked at %s" % trackScript)
+
+    ports = eval("[" + subprocess.check_output([trackScript, "getports", d]) + "]")
+    ops.platform_args = type_ + "," + ",".join( "%s@%s:%s" % (p, d, p) for p in ports)
+
+    print "new platform_args: ", ops.platform_args
+    exit;
+#
+###############################################################################
+
+vpi_port_map = {}
+ports = ops.platform_args.split(",")
+bpptype = "udp"
+if ports[0] == "udp" or ports[0] == "tcp":
+    bpptype = ports.pop(0)
+
+for ps in ports:
+    (p, vpi) = ps.split("@")
+    vpi_port_map[int(p)] = "bpp|%s|%s" % (bpptype, vpi)
+
+print vpi_port_map; 
+
+def platform_config_update(config):
+    """
+    Update configuration for the remote platform
+
+    @param config The configuration dictionary to use/update
+    This routine defines the port map used for this configuration
+    """
+
+    global vpi_port_map
+    config["port_map"] = vpi_port_map.copy()
+    config["caps_table_idx"] = 0
+    #
+    # The class for DataPlanePorts must be specified here:
+    #
+    config['dataplane'] = { 'portclass': dppv.DataPlanePortVPI }
+    config['allow_user'] = True
diff --git a/Fabric/Utilities/platforms/dppv.py b/Fabric/Utilities/platforms/dppv.py
new file mode 100644
index 0000000..da7de65
--- /dev/null
+++ b/Fabric/Utilities/platforms/dppv.py
@@ -0,0 +1,95 @@
+
+# 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.
+
+
+###############################################################################
+#
+# DataPlanePort implementation for VPI platforms. 
+#
+# The VPI-based platforms available here (bpp and vpi) depend upon
+# this module for the implementation of the OFTest DataPlanePort interface. 
+#
+###############################################################################
+import sys
+import os
+import logging
+import time
+
+from oftest import config
+
+# Requires the libvpi and pyvpi packages
+from vpi import vpi
+
+class DataPlanePortVPI:
+    """
+    Class defining a port monitoring VPI object.
+
+    """
+    
+    #
+    # OFTest creates and destroys DataPlanePorts for each test. 
+    # We just cache them here. 
+    cachedVPIs = {}
+    
+    def vpiInit(self, interface_name, port_number, pcap_dir="."):
+        self.vpiSpec = interface_name; 
+        if self.vpiSpec in self.cachedVPIs:
+            self.vpi = self.cachedVPIs[self.vpiSpec]
+        else:
+            self.vpi = vpi.Vpi(self.vpiSpec)
+            pcapspec = "pcapdump|%s/oft-port%.2d.pcap|mpls|PORT%d" % (pcap_dir, port_number, port_number)
+            self.vpi.AddSendRecvListener(pcapspec); 
+            self.cachedVPIs[self.vpiSpec] = self.vpi
+
+        return self.vpi
+
+    
+    def __init__(self, interface_name, port_number):
+        """
+        Set up a port monitor object
+        @param interface_name The name of the physical interface like eth1
+        @param port_number The port number associated with this port
+        """
+        self.interface_name = interface_name
+        self.port_number = port_number
+        logname = "VPI:" + interface_name
+        self.logger = logging.getLogger(logname)
+        
+        path = "."
+        if config['log_file']:
+            path = config['log_file']
+
+        if self.vpiInit(interface_name, port_number, 
+                        os.path.dirname(os.path.abspath(path))) == None:
+            raise Exception("Could not create VPI interface %s" % interface_name)
+
+        self.logger.info("VPI: %s:%d\n" % (interface_name, port_number))
+
+    def fileno(self):
+        return self.vpi.DescriptorGet()
+
+    def recv(self):
+        pkt = self.vpi.Recv(False) 
+        return (pkt, time.time())
+
+    def send(self, packet):
+        """
+        Send a packet to the dataplane port
+        @param packet The packet data to send to the port
+        @retval The number of bytes sent
+        """
+        _len = len(packet); 
+        self.vpi.Send(packet)
+        return _len; 
diff --git a/Fabric/Utilities/platforms/eth.py b/Fabric/Utilities/platforms/eth.py
new file mode 100644
index 0000000..056edce
--- /dev/null
+++ b/Fabric/Utilities/platforms/eth.py
@@ -0,0 +1,44 @@
+
+# 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.
+
+
+"""
+Eth platform
+
+This platform uses the --interface command line option to choose the ethernet interfaces.
+"""
+
+def platform_config_update(config):
+    """
+    Update configuration for the local platform
+
+    @param config The configuration dictionary to use/update
+    """
+
+    port_map = {}
+
+    for (ofport, interface) in config["interfaces"]:
+        port_map[ofport] = interface
+
+    # Default to a veth configuration compatible with the reference switch
+    if not port_map:
+        port_map = {
+            1: 'veth1',
+            2: 'veth3',
+            3: 'veth5',
+            4: 'veth7',
+        }
+
+    config['port_map'] = port_map
diff --git a/Fabric/Utilities/platforms/local.py b/Fabric/Utilities/platforms/local.py
new file mode 100644
index 0000000..7caa1e5
--- /dev/null
+++ b/Fabric/Utilities/platforms/local.py
@@ -0,0 +1,38 @@
+
+# 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.
+
+
+"""
+Local platform
+
+This platform uses veth pairs to send packets to and from a userspace
+switch. The switch should be connected to veth0, veth2, veth4, and veth6.
+"""
+
+def platform_config_update(config):
+    """
+    Update configuration for the local platform
+
+    @param config The configuration dictionary to use/update
+    """
+    base_of_port = 1
+    base_if_index = 1
+    port_count = 4
+
+    port_map = {}
+    # Use every other veth interface (veth1, veth3, ...)
+    for idx in range(port_count):
+        port_map[base_of_port + idx] = "veth%d" % (base_if_index + 2 * idx)
+    config['port_map'] = port_map
diff --git a/Fabric/Utilities/platforms/ovs-dummy.py b/Fabric/Utilities/platforms/ovs-dummy.py
new file mode 100644
index 0000000..1d81091
--- /dev/null
+++ b/Fabric/Utilities/platforms/ovs-dummy.py
@@ -0,0 +1,155 @@
+
+# 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.
+
+
+"""
+Dummy platform
+
+This platform uses Open vSwitch dummy interfaces.
+"""
+
+import logging
+import os
+import select
+import socket
+import struct
+import sys
+import time
+from threading import Thread
+from threading import Lock
+
+RCV_TIMEOUT = 10000
+RUN_DIR = os.environ.get("OVS_RUNDIR", "/var/run/openvswitch")
+
+class DataPlanePortOVSDummy:
+    """
+    Class defining a port monitoring object that uses Unix domain
+    sockets for ports, intended for connecting to Open vSwitch "dummy"
+    netdevs.
+    """
+
+    def __init__(self, interface_name, port_number, max_pkts=1024):
+        """
+        Set up a port monitor object
+        @param interface_name The name of the physical interface like eth1
+        @param port_number The port number associated with this port
+        @param max_pkts Maximum number of pkts to keep in queue
+        """
+        self.interface_name = interface_name
+        self.max_pkts = max_pkts
+        self.port_number = port_number
+        self.txq = []
+        self.rxbuf = ""
+        logname = "dp-" + interface_name
+        self.logger = logging.getLogger(logname)
+        try:
+            self.socket = DataPlanePortOVSDummy.interface_open(interface_name)
+        except:
+            self.logger.info("Could not open socket")
+            raise
+        self.logger.info("Opened port monitor (class %s)", type(self).__name__)
+
+    @staticmethod
+    def interface_open(interface_name):
+        """
+        Open a Unix domain socket interface.
+        @param interface_name port name as a string such as 'eth1'
+        @retval s socket
+        """
+        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        s.settimeout(RCV_TIMEOUT)
+        s.setblocking(0)
+        s.connect("%s/%s" % (RUN_DIR, interface_name))
+        return s
+
+    def __del__(self):
+        if self.socket:
+            self.socket.close()
+
+    def fileno(self):
+        """
+        Return an integer file descriptor that can be passed to select(2).
+        """
+        return self.socket.fileno()
+
+    def recv(self):
+        while True:
+            rout, wout, eout = select.select([self.socket], [], [], 0)
+            if not rout:
+                return
+
+            if len(self.rxbuf) < 2:
+                n = 2 - len(self.rxbuf)
+            else:
+                frame_len = struct.unpack('>h', self.rxbuf[:2])[0]
+                n = (2 + frame_len) - len(self.rxbuf)
+
+            data = self.socket.recv(n)
+            self.rxbuf += data
+            if len(data) == n and len(self.rxbuf) > 2:
+                rcvtime = time.time()
+                packet = self.rxbuf[2:]
+                self.logger.debug("Pkt len " + str(len(packet)) +
+                         " in at " + str(rcvtime) + " on port " +
+                         str(self.port_number))
+                self.rxbuf = ""
+                return (packet, rcvtime)
+
+    def send(self, packet):
+        if len(self.txq) < self.max_pkts:
+            self.txq.append(struct.pack('>h', len(packet)) + packet)
+            retval = len(packet)
+        else:
+            retval = 0
+        self.__run_tx()
+        return retval
+
+    def __run_tx(self):
+        while self.txq:
+            rout, wout, eout = select.select([], [self.socket], [], 0)
+            if not wout:
+                return
+
+            retval = self.socket.send(self.txq[0])
+            if retval > 0:
+                self.txq[0] = self.txq[0][retval:]
+                if len(self.txq[0]) == 0:
+                    self.txq = self.txq[1:]
+        
+    def down(self):
+        pass
+
+    def up(self):
+        pass
+
+# Update this dictionary to suit your environment.
+dummy_port_map = {
+    1 : "p1",
+    2 : "p2",
+    3 : "p3",
+    4 : "p4"
+}
+
+def platform_config_update(config):
+    """
+    Update configuration for the dummy platform
+
+    @param config The configuration dictionary to use/update
+    """
+    global dummy_port_map
+    config["port_map"] = dummy_port_map.copy()
+    config["caps_table_idx"] = 0
+    config["dataplane"] = {"portclass": DataPlanePortOVSDummy}
+    config["allow_user"] = True
diff --git a/Fabric/Utilities/platforms/remote.py b/Fabric/Utilities/platforms/remote.py
new file mode 100644
index 0000000..95d1e25
--- /dev/null
+++ b/Fabric/Utilities/platforms/remote.py
@@ -0,0 +1,39 @@
+
+# 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.
+
+
+"""
+Remote platform
+
+This platform uses physical ethernet interfaces.
+"""
+
+# Update this dictionary to suit your environment.
+remote_port_map = {
+    23 : "eth2",
+    24 : "eth3",
+    25 : "eth4",
+    26 : "eth5"
+}
+
+def platform_config_update(config):
+    """
+    Update configuration for the remote platform
+
+    @param config The configuration dictionary to use/update
+    """
+    global remote_port_map
+    config["port_map"] = remote_port_map.copy()
+    config["caps_table_idx"] = 0
diff --git a/Fabric/Utilities/platforms/veth8.py b/Fabric/Utilities/platforms/veth8.py
new file mode 100644
index 0000000..665e250
--- /dev/null
+++ b/Fabric/Utilities/platforms/veth8.py
@@ -0,0 +1,39 @@
+
+# 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.
+
+
+"""
+veth8 platform
+
+This platform uses 8 veth pairs. The switch should connect to veth0, veth2, ..., veth14.
+"""
+
+def platform_config_update(config):
+    """
+    Update configuration for the local platform
+
+    @param config The configuration dictionary to use/update
+    """
+
+    config['port_map'] = {
+        1: 'veth1',
+        2: 'veth3',
+        3: 'veth5',
+        4: 'veth7',
+        5: 'veth9',
+        6: 'veth11',
+        7: 'veth13',
+        8: 'veth15',
+    }
diff --git a/Fabric/Utilities/platforms/vpip.py b/Fabric/Utilities/platforms/vpip.py
new file mode 100644
index 0000000..5efea77
--- /dev/null
+++ b/Fabric/Utilities/platforms/vpip.py
@@ -0,0 +1,65 @@
+
+# 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.
+
+
+"""
+Platform configuration file
+platform == vpi
+"""
+
+###############################################################################
+#
+# This platform module allows VPI port specifications on the command line. 
+#
+###############################################################################
+
+import sys
+import os
+import argparse
+import dppv
+
+# The port specification is passed via the "--platform-args" option to OFTest. 
+# Note that we must guard against abbreviations supported by argparse
+if not "--platform-args" in " ".join(sys.argv):
+    raise Exception("--platform-args must be specified")
+
+ap = argparse.ArgumentParser("vpi")
+ap.add_argument("--platform-args")
+(ops, rest) = ap.parse_known_args()
+
+vpi_port_map = {}
+ports = ops.platform_args.split(",")
+for ps in ports:
+    (p, vpi) = ps.split("@")
+    vpi_port_map[int(p)] = vpi
+
+print vpi_port_map; 
+
+def platform_config_update(config):
+    """
+    Update configuration for the remote platform
+
+    @param config The configuration dictionary to use/update
+    This routine defines the port map used for this configuration
+    """
+
+    global vpi_port_map
+    config["port_map"] = vpi_port_map.copy()
+    config["caps_table_idx"] = 0
+    #
+    # The class for DataPlanePorts must be specified here:
+    #
+    config['dataplane'] = { 'portclass': dppv.DataPlanePortVPI }
+    config['allow_user'] = True