From: Ben Pfaff Date: Tue, 25 Jun 2013 21:00:58 +0000 (-0700) Subject: Add VLAN splinters unit test. X-Git-Tag: sliver-openvswitch-1.10.90-3~6^2~53 X-Git-Url: http://git.onelab.eu/?p=sliver-openvswitch.git;a=commitdiff_plain;h=1c3e353dbd10241ced4a04a5da655364704388ba Add VLAN splinters unit test. CC: Ethan Jackson Signed-off-by: Ben Pfaff --- diff --git a/lib/dummy.c b/lib/dummy.c index 1354de626..091803739 100644 --- a/lib/dummy.c +++ b/lib/dummy.c @@ -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(); } diff --git a/lib/dummy.h b/lib/dummy.h index 220417078..b20519adf 100644 --- a/lib/dummy.h +++ b/lib/dummy.h @@ -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 */ diff --git a/lib/vlandev.c b/lib/vlandev.c index 2440def5a..0ccb6ca51 100644 --- a/lib/vlandev.c +++ b/lib/vlandev.c @@ -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. @@ -19,15 +19,147 @@ #include "vlandev.h" #include +#include #include #include +#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; +} + +/* The Linux vlandev implementation. */ + #ifdef LINUX_DATAPATH #include "rtnetlink-link.h" #include @@ -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 + +/* 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 +}; + +/* 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 +}; + +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 diff --git a/lib/vlandev.h b/lib/vlandev.h index ab74ecdd5..e25ffcbd4 100644 --- a/lib/vlandev.h +++ b/lib/vlandev.h @@ -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 */ diff --git a/tests/automake.mk b/tests/automake.mk index 1af41ccb7..6e07a3d08 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -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 \ diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at index e8ac9d0e5..d034105da 100644 --- a/tests/ofproto-macros.at +++ b/tests/ofproto-macros.at @@ -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 diff --git a/tests/testsuite.at b/tests/testsuite.at index da5259386..fbc701b35 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -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 index 000000000..3cc6187b0 --- /dev/null +++ b/tests/vlan-splinters.at @@ -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