Add VLAN splinters unit test.
authorBen Pfaff <blp@nicira.com>
Tue, 25 Jun 2013 21:00:58 +0000 (14:00 -0700)
committerBen Pfaff <blp@nicira.com>
Tue, 25 Jun 2013 21:00:58 +0000 (14:00 -0700)
CC: Ethan Jackson <ethan@nicira.com>
Signed-off-by: Ben Pfaff <blp@nicira.com>
lib/dummy.c
lib/dummy.h
lib/vlandev.c
lib/vlandev.h
tests/automake.mk
tests/ofproto-macros.at
tests/testsuite.at
tests/vlan-splinters.at [new file with mode: 0644]

index 1354de6..0918037 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2011, 2012 Nicira, Inc.
+ * Copyright (c) 2010, 2011, 2012, 2013 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -33,5 +33,6 @@ dummy_enable(bool override)
     netdev_dummy_register(override);
     dpif_dummy_register(override);
     timeval_dummy_register();
+    vlandev_dummy_enable();
 }
 
index 2204170..b20519a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2011, 2012 Nicira, Inc.
+ * Copyright (c) 2010, 2011, 2012, 2013 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,5 +26,6 @@ void dummy_enable(bool override);
 void dpif_dummy_register(bool override);
 void netdev_dummy_register(bool override);
 void timeval_dummy_register(void);
+void vlandev_dummy_enable(void);
 
 #endif /* dummy.h */
index 2440def..0ccb6ca 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 Nicira, Inc.
+ * Copyright (c) 2011, 2013 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "vlandev.h"
 
 #include <errno.h>
+#include <net/if.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
 
+#include "dummy.h"
 #include "hash.h"
 #include "shash.h"
 #include "vlog.h"
 
 VLOG_DEFINE_THIS_MODULE(vlandev);
 
+/* A vlandev implementation. */
+struct vlandev_class {
+    int (*vd_refresh)(void);
+    int (*vd_add)(const char *real_dev, int vid);
+    int (*vd_del)(const char *vlan_dev);
+};
+
+#ifdef LINUX_DATAPATH
+static const struct vlandev_class vlandev_linux_class;
+#endif
+static const struct vlandev_class vlandev_stub_class;
+static const struct vlandev_class vlandev_dummy_class;
+
+/* The in-use vlandev implementation. */
+static const struct vlandev_class *vd_class;
+
+/* Maps from a VLAN device name (e.g. "eth0.10") to struct vlan_dev. */
+static struct shash vlan_devs = SHASH_INITIALIZER(&vlan_devs);
+
+/* Maps from a VLAN real device name (e.g. "eth0") to struct vlan_real_dev. */
+static struct shash vlan_real_devs = SHASH_INITIALIZER(&vlan_real_devs);
+
+static int vlandev_add__(const char *vlan_dev, const char *real_dev, int vid);
+static int vlandev_del__(const char *vlan_dev);
+static void vlandev_clear__(void);
+
+static const struct vlandev_class *
+vlandev_get_class(void)
+{
+    if (!vd_class) {
+#ifdef LINUX_DATAPATH
+        vd_class = &vlandev_linux_class;
+#else
+        vd_class = &vlandev_stub_class;
+#endif
+    }
+    return vd_class;
+}
+
+/* On Linux, the default implementation of VLAN devices creates and destroys
+ * Linux VLAN devices.  On other OSess, the default implementation is a
+ * nonfunctional stub.  In either case, this function replaces this default
+ * implementation by a "dummy" implementation that simply reports back whatever
+ * the client sets up with vlandev_add() and vlandev_del().
+ *
+ * Don't call this function directly; use dummy_enable() from dummy.h. */
+void
+vlandev_dummy_enable(void)
+{
+    if (vd_class != &vlandev_dummy_class) {
+        vd_class = &vlandev_dummy_class;
+        vlandev_clear__();
+    }
+}
+
+/* Creates a new VLAN device for VLAN 'vid' on top of real Ethernet device
+ * 'real_dev'.  Returns 0 if successful, otherwise a positive errno value.  On
+ * OSes other than Linux, in the absence of dummies (see
+ * vlandev_dummy_enable()), this always fails.
+ *
+ * The name of the new VLAN device is not easily predictable, because Linux
+ * provides multiple naming schemes, does not allow the client to specify a
+ * name, and does not directly report the new VLAN device's name.  Use
+ * vlandev_refresh() then vlandev_get_name() to find out the new VLAN device's
+ * name,. */
+int
+vlandev_add(const char *real_dev, int vid)
+{
+    return vlandev_get_class()->vd_add(real_dev, vid);
+}
+
+/* Deletes the VLAN device named 'vlan_dev'.  Returns 0 if successful,
+ * otherwise a positive errno value.  On OSes other than Linux, in the absence
+ * of dummies (see vlandev_dummy_enable()), this always fails. */
+int
+vlandev_del(const char *vlan_dev)
+{
+    return vlandev_get_class()->vd_del(vlan_dev);
+}
+
+/* Refreshes the cache of real device to VLAN device mappings reported by
+ * vlandev_get_real_devs() and vlandev_get_name().  Without calling this
+ * function, changes made by vlandev_add() and vlandev_del() may not be
+ * reflected by vlandev_get_real_devs() and vlandev_get_name() output. */
+int
+vlandev_refresh(void)
+{
+    const struct vlandev_class *class = vlandev_get_class();
+    return class->vd_refresh ? class->vd_refresh() : 0;
+}
+
+/* Returns a shash mapping from the name of real Ethernet devices used as the
+ * basis of VLAN devices to struct vlan_real_devs.  The caller must not modify
+ * or free anything in the returned shash.
+ *
+ * Changes made by vlandev_add() and vlandev_del() may not be reflected in this
+ * function's output without an intervening call to vlandev_refresh(). */
+struct shash *
+vlandev_get_real_devs(void)
+{
+    return &vlan_real_devs;
+}
+
+/* Returns the name of the VLAN device for VLAN 'vid' on top of
+ * 'real_dev_name', or NULL if there is no such VLAN device.
+ *
+ * Changes made by vlandev_add() and vlandev_del() may not be reflected in this
+ * function's output without an intervening call to vlandev_refresh(). */
+const char *
+vlandev_get_name(const char *real_dev_name, int vid)
+{
+    const struct vlan_real_dev *real_dev;
+
+    real_dev = shash_find_data(&vlan_real_devs, real_dev_name);
+    if (real_dev) {
+        const struct vlan_dev *vlan_dev;
+
+        HMAP_FOR_EACH_WITH_HASH (vlan_dev, hmap_node, hash_int(vid, 0),
+                                 &real_dev->vlan_devs) {
+            if (vlan_dev->vid == vid) {
+                return vlan_dev->name;
+            }
+        }
+    }
+
+    return NULL;
+}
+\f
+/* The Linux vlandev implementation. */
+
 #ifdef LINUX_DATAPATH
 #include "rtnetlink-link.h"
 #include <linux/if_vlan.h>
@@ -35,8 +167,6 @@ VLOG_DEFINE_THIS_MODULE(vlandev);
 #include "netdev-linux.h"
 
 static struct nln_notifier *vlan_cache_notifier;
-static struct shash vlan_devs = SHASH_INITIALIZER(&vlan_devs);
-static struct shash vlan_real_devs = SHASH_INITIALIZER(&vlan_real_devs);
 static bool cache_valid;
 
 static void
@@ -46,11 +176,10 @@ vlan_cache_cb(const struct rtnetlink_link_change *change OVS_UNUSED,
     cache_valid = false;
 }
 
-int
-vlandev_refresh(void)
+static int
+vlandev_linux_refresh(void)
 {
     const char *fn = "/proc/net/vlan/config";
-    struct shash_node *node;
     char line[128];
     FILE *stream;
 
@@ -66,17 +195,7 @@ vlandev_refresh(void)
         return 0;
     }
 
-    /* Free old cache.
-     *
-     * The 'name' members point to strings owned by the "shash"es so we do not
-     * free them ourselves. */
-    shash_clear_free_data(&vlan_devs);
-    SHASH_FOR_EACH (node, &vlan_real_devs) {
-        struct vlan_real_dev *vrd = node->data;
-
-        hmap_destroy(&vrd->vlan_devs);
-    }
-    shash_clear_free_data(&vlan_real_devs);
+    vlandev_clear__();
 
     /* Repopulate cache. */
     stream = fopen(fn, "r");
@@ -101,26 +220,9 @@ vlandev_refresh(void)
         char vlan_dev[16], real_dev[16];
         int vid;
 
-        if (sscanf(line, "%15[^ |] | %d | %15s", vlan_dev, &vid, real_dev) == 3
-            && vid >= 0 && vid <= 4095
-            && !shash_find(&vlan_devs, vlan_dev)) {
-            struct vlan_real_dev *vrd;
-            struct vlan_dev *vd;
-
-            vrd = shash_find_data(&vlan_real_devs, real_dev);
-            if (!vrd) {
-                vrd = xmalloc(sizeof *vrd);
-                vrd->name = xstrdup(real_dev);
-                hmap_init(&vrd->vlan_devs);
-                shash_add_nocopy(&vlan_real_devs, vrd->name, vrd);
-            }
-
-            vd = xmalloc(sizeof *vd);
-            hmap_insert(&vrd->vlan_devs, &vd->hmap_node, hash_int(vid, 0));
-            vd->name = xstrdup(vlan_dev);
-            vd->vid = vid;
-            vd->real_dev = vrd;
-            shash_add_nocopy(&vlan_devs, vd->name, vd);
+        if (sscanf(line, "%15[^ |] | %d | %15s",
+                   vlan_dev, &vid, real_dev) == 3) {
+            vlandev_add__(vlan_dev, real_dev, vid);
         }
     }
     fclose(stream);
@@ -129,32 +231,6 @@ vlandev_refresh(void)
     return 0;
 }
 
-struct shash *
-vlandev_get_real_devs(void)
-{
-    return &vlan_real_devs;
-}
-
-const char *
-vlandev_get_name(const char *real_dev_name, int vid)
-{
-    const struct vlan_real_dev *real_dev;
-
-    real_dev = shash_find_data(&vlan_real_devs, real_dev_name);
-    if (real_dev) {
-        const struct vlan_dev *vlan_dev;
-
-        HMAP_FOR_EACH_WITH_HASH (vlan_dev, hmap_node, hash_int(vid, 0),
-                                 &real_dev->vlan_devs) {
-            if (vlan_dev->vid == vid) {
-                return vlan_dev->name;
-            }
-        }
-    }
-
-    return NULL;
-}
-
 static int
 do_vlan_ioctl(const char *netdev_name, struct vlan_ioctl_args *via,
               int cmd, const char *cmd_name)
@@ -179,8 +255,8 @@ do_vlan_ioctl(const char *netdev_name, struct vlan_ioctl_args *via,
     return error;
 }
 
-int
-vlandev_add(const char *real_dev, int vid)
+static int
+vlandev_linux_add(const char *real_dev, int vid)
 {
     struct vlan_ioctl_args via;
     int error;
@@ -195,8 +271,8 @@ vlandev_add(const char *real_dev, int vid)
     return error;
 }
 
-int
-vlandev_del(const char *vlan_dev)
+static int
+vlandev_linux_del(const char *vlan_dev)
 {
     struct vlan_ioctl_args via;
     int error;
@@ -208,40 +284,134 @@ vlandev_del(const char *vlan_dev)
     }
     return error;
 }
-#else  /* !LINUX_DATAPATH */
-/* Stubs. */
 
-int
-vlandev_refresh(void)
+static const struct vlandev_class vlandev_linux_class = {
+    vlandev_linux_refresh,
+    vlandev_linux_add,
+    vlandev_linux_del
+};
+#endif
+\f
+/* Stub implementation. */
+
+static int
+vlandev_stub_add(const char *real_dev OVS_UNUSED, int vid OVS_UNUSED)
 {
-    return 0;
+    VLOG_ERR("not supported on non-Linux platform");
+    return EOPNOTSUPP;
 }
 
-struct shash *
-vlandev_get_real_devs(void)
+static int
+vlandev_stub_del(const char *vlan_dev OVS_UNUSED)
 {
-    static struct shash vlan_real_devs = SHASH_INITIALIZER(&vlan_real_devs);
+    VLOG_ERR("not supported on non-Linux platform");
+    return EOPNOTSUPP;
+}
 
-    return &vlan_real_devs;
+static const struct vlandev_class vlandev_stub_class = {
+    NULL,                       /* vd_refresh */
+    vlandev_stub_add,
+    vlandev_stub_del
+};
+\f
+/* Dummy implementation. */
+
+static int
+vlandev_dummy_add(const char *real_dev, int vid)
+{
+    char name[IFNAMSIZ];
+
+    if (snprintf(name, sizeof name, "%s.%d", real_dev, vid) >= sizeof name) {
+        return ENAMETOOLONG;
+    }
+    return vlandev_add__(name, real_dev, vid);
 }
 
-const char *
-vlandev_get_name(const char *real_dev_name OVS_UNUSED, int vid OVS_UNUSED)
+static int
+vlandev_dummy_del(const char *vlan_dev)
 {
-    return NULL;
+    return vlandev_del__(vlan_dev);
 }
 
-int
-vlandev_add(const char *real_dev OVS_UNUSED, int vid OVS_UNUSED)
+static const struct vlandev_class vlandev_dummy_class = {
+    NULL,                       /* vd_refresh */
+    vlandev_dummy_add,
+    vlandev_dummy_del
+};
+\f
+static int
+vlandev_add__(const char *vlan_dev, const char *real_dev, int vid)
 {
-    VLOG_ERR("not supported on non-Linux platform");
-    return EOPNOTSUPP;
+    uint32_t vid_hash = hash_int(vid, 0);
+    struct vlan_real_dev *vrd;
+    struct vlan_dev *vd;
+
+    if (vid < 0 || vid > 4095) {
+        return EINVAL;
+    } else if (shash_find(&vlan_devs, vlan_dev)) {
+        return EEXIST;
+    }
+
+    vrd = shash_find_data(&vlan_real_devs, real_dev);
+    if (!vrd) {
+        vrd = xmalloc(sizeof *vrd);
+        vrd->name = xstrdup(real_dev);
+        hmap_init(&vrd->vlan_devs);
+        shash_add_nocopy(&vlan_real_devs, vrd->name, vrd);
+    } else {
+        HMAP_FOR_EACH_WITH_HASH (vd, hmap_node, vid_hash, &vrd->vlan_devs) {
+            if (vd->vid == vid) {
+                return EEXIST;
+            }
+        }
+    }
+
+    vd = xmalloc(sizeof *vd);
+    hmap_insert(&vrd->vlan_devs, &vd->hmap_node, vid_hash);
+    vd->name = xstrdup(vlan_dev);
+    vd->vid = vid;
+    vd->real_dev = vrd;
+    shash_add_nocopy(&vlan_devs, vd->name, vd);
+
+    return 0;
 }
 
-int
-vlandev_del(const char *vlan_dev OVS_UNUSED)
+static int
+vlandev_del__(const char *vlan_dev)
 {
-    VLOG_ERR("not supported on non-Linux platform");
-    return EOPNOTSUPP;
+    struct shash_node *vd_node = shash_find(&vlan_devs, vlan_dev);
+    if (!vd_node) {
+        struct vlan_dev *vd = vd_node->data;
+        struct vlan_real_dev *vrd = vd->real_dev;
+
+        hmap_remove(&vrd->vlan_devs, &vd->hmap_node);
+        if (hmap_is_empty(&vrd->vlan_devs)) {
+            shash_find_and_delete_assert(&vlan_real_devs, vrd->name);
+            free(vrd);
+        }
+
+        shash_delete(&vlan_devs, vd_node);
+        free(vd);
+
+        return 0;
+    } else {
+        return ENOENT;
+    }
+}
+
+/* Clear 'vlan_devs' and 'vlan_real_devs' in preparation for repopulating. */
+static void
+vlandev_clear__(void)
+{
+    /* We do not free the 'name' members of struct vlan_dev and struct
+     * vlan_real_dev, because the "shash"es own them.. */
+    struct shash_node *node;
+
+    shash_clear_free_data(&vlan_devs);
+    SHASH_FOR_EACH (node, &vlan_real_devs) {
+        struct vlan_real_dev *vrd = node->data;
+
+        hmap_destroy(&vrd->vlan_devs);
+    }
+    shash_clear_free_data(&vlan_real_devs);
 }
-#endif
index ab74ecd..e25ffcb 100644 (file)
@@ -40,13 +40,11 @@ struct vlan_real_dev {
     struct hmap vlan_devs;      /* All child VLAN devices, hashed by VID. */
 };
 
-int vlandev_refresh(void);
+int vlandev_add(const char *real_dev, int vid);
+int vlandev_del(const char *vlan_dev);
 
+int vlandev_refresh(void);
 struct shash *vlandev_get_real_devs(void);
-
 const char *vlandev_get_name(const char *real_dev_name, int vid);
 
-int vlandev_add(const char *real_dev, int vid);
-int vlandev_del(const char *vlan_dev);
-
 #endif /* vlandev.h */
index 1af41cc..6e07a3d 100644 (file)
@@ -37,6 +37,7 @@ TESTSUITE_AT = \
        tests/reconnect.at \
        tests/ovs-vswitchd.at \
        tests/ofproto-dpif.at \
+       tests/vlan-splinters.at \
        tests/ofproto-macros.at \
        tests/ofproto.at \
        tests/ovsdb.at \
index e8ac9d0..d034105 100644 (file)
@@ -38,7 +38,7 @@ m4_define([STRIP_DURATION], [[sed 's/\bduration=[0-9.]*s/duration=?s/']])
 m4_define([STRIP_USED], [[sed 's/used:[0-9]\.[0-9]*/used:0.0/']])
 m4_define([TESTABLE_LOG], [-vPATTERN:ANY:'%c|%p|%m'])
 
-# OVS_VSWITCHD_START([vsctl-args], [vsctl-output])
+# OVS_VSWITCHD_START([vsctl-args], [vsctl-output], [=override])
 #
 # Creates a database and starts ovsdb-server, starts ovs-vswitchd
 # connected to that database, calls ovs-vsctl to create a bridge named
@@ -46,6 +46,11 @@ m4_define([TESTABLE_LOG], [-vPATTERN:ANY:'%c|%p|%m'])
 # commands to ovs-vsctl.  If 'vsctl-args' causes ovs-vsctl to provide
 # output (e.g. because it includes "create" commands) then 'vsctl-output'
 # specifies the expected output after filtering through uuidfilt.pl.
+#
+# If a test needs to use "system" devices (as dummies), then specify
+# =override (literally) as the third argument.  Otherwise, system devices
+# won't work at all (which makes sense because tests should not access a
+# system's real Ethernet devices).
 m4_define([OVS_VSWITCHD_START],
   [OVS_RUNDIR=`pwd`; export OVS_RUNDIR
    OVS_LOGDIR=`pwd`; export OVS_LOGDIR
@@ -68,7 +73,7 @@ m4_define([OVS_VSWITCHD_START],
    AT_CHECK([ovs-vsctl --no-wait init])
 
    dnl Start ovs-vswitchd.
-   AT_CHECK([ovs-vswitchd --detach --no-chdir --pidfile --enable-dummy --disable-system --log-file -vvconn -vofproto_dpif], [0], [], [stderr])
+   AT_CHECK([ovs-vswitchd --detach --no-chdir --pidfile --enable-dummy$3 --disable-system --log-file -vvconn -vofproto_dpif], [0], [], [stderr])
    AT_CAPTURE_FILE([ovs-vswitchd.log])
    AT_CHECK([[sed < stderr '
 /vlog|INFO|opened log file/d
index da52593..fbc701b 100644 (file)
@@ -98,6 +98,7 @@ m4_include([tests/reconnect.at])
 m4_include([tests/ovs-vswitchd.at])
 m4_include([tests/ofproto.at])
 m4_include([tests/ofproto-dpif.at])
+m4_include([tests/vlan-splinters.at])
 m4_include([tests/ovsdb.at])
 m4_include([tests/ovs-vsctl.at])
 m4_include([tests/ovs-monitor-ipsec.at])
diff --git a/tests/vlan-splinters.at b/tests/vlan-splinters.at
new file mode 100644 (file)
index 0000000..3cc6187
--- /dev/null
@@ -0,0 +1,46 @@
+AT_BANNER([VLAN splinters])
+
+AT_SETUP([VLAN splinters])
+OVS_VSWITCHD_START([], [], [=override])
+ADD_OF_PORTS([br0], 1, 2, 3, 4)
+AT_CHECK([ovs-vsctl \
+  -- set Bridge br0 fail-mode=standalone flood_vlans=0,9,11,15 \
+  -- set port br0 tag=0 \
+  -- set port p1 trunks=0,9,11,15 \
+  -- set interface p1 other-config:enable-vlan-splinters=true \
+  -- set port p2 tag=9 \
+  -- set port p3 tag=11 \
+  -- set port p4 tag=15])
+
+ovs-appctl dpif/show | sed -n '
+s/\./_/g
+s/^[[  ]]*\([[^        ]][[^   ]]*\) [[0-9]]*\/\([[0-9]]*\).*/\1=\2/p
+' > port-numbers
+cat port-numbers
+. ./port-numbers
+
+for args in '9 p2' '11 p3' '15 p4'; do
+    set $args
+    vlan=$1
+    eval access_port=\$$2
+    eval splinter_port=\$p1_$vlan
+
+    # Check that when a packet is received on $splinter_port, it is
+    # treated as if it had been received on p1 in the correct VLAN.
+    AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "in_port($splinter_port)"],
+             [0], [stdout])
+    AT_CHECK_UNQUOTED([sed -n '/^Flow/p; /^Datapath/p' stdout], [0], [dnl
+Flow: metadata=0,in_port=$p1,dl_vlan=$vlan,dl_vlan_pcp=0,dl_src=00:00:00:00:00:00,dl_dst=00:00:00:00:00:00,dl_type=0x05ff
+Datapath actions: $access_port
+])
+
+    # Check that when an OpenFlow action sends a packet to p1 on
+    # splintered VLAN $vlan, it is actually output to $splinter_port.
+    AT_CHECK([ovs-appctl ofproto/trace ovs-dummy "in_port($access_port)"],
+             [0], [stdout])
+    AT_CHECK_UNQUOTED([tail -1 stdout], [0], [Datapath actions: $splinter_port
+])
+done
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP