[ovs-dev] [mirror 12/13] tests: Rewrite code for comparing sets of ODP actions.

Justin Pettit jpettit at nicira.com
Wed Nov 16 07:04:32 UTC 2011


Looks good.  The do_normalize_actions() function is inconsistent about what memory it cleans up before exiting.  Since it's short-lived, it doesn't really matter, of course.  I also think the function could use some explanation about what it's doing--it's pretty hard to see by looking at the code alone.  The Perl version supplied a bit of a description, but I think even that could have used a couple more sentences.

--Justin


On Oct 26, 2011, at 10:09 AM, Ben Pfaff wrote:

> The compare-odp-actions.pl utility isn't fully general, even for its
> intended purpose of allowing sets of ODP actions to be compared
> ignoring unimportant differences in ordering of output actions and
> VLAN set actions.  I decided that the proper way to do it was to have
> a utility that can actually parse the actions, instead of just
> doing textual transformations on them.  So, this commit replaces
> compare-odp-actions.pl by "ovs-dpctl normalize-actions", which is
> sufficiently general for the intended purpose.
> 
> The new ovs-dpctl functionality can be easily extended to handle
> differences in fields other than VLAN, but only VLAN is needed so
> far.
> 
> This will be needed in an upcoming commit that in some cases
> introduces redundant "set vlan" actions into the ODP actions, which
> compare-odp-actions.pl doesn't tolerate.
> ---
> tests/automake.mk            |    1 -
> tests/compare-odp-actions.pl |   66 --------------
> tests/ofproto-dpif.at        |    6 +-
> utilities/ovs-dpctl.c        |  206 ++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 210 insertions(+), 69 deletions(-)
> delete mode 100644 tests/compare-odp-actions.pl
> 
> diff --git a/tests/automake.mk b/tests/automake.mk
> index f11e291..09de521 100644
> --- a/tests/automake.mk
> +++ b/tests/automake.mk
> @@ -58,7 +58,6 @@ TESTSUITE_AT = \
> 	tests/vlog.at
> TESTSUITE = $(srcdir)/tests/testsuite
> DISTCLEANFILES += tests/atconfig tests/atlocal
> -EXTRA_DIST += tests/compare-odp-actions.pl
> 
> AUTOTEST_PATH = utilities:vswitchd:ovsdb:tests
> 
> diff --git a/tests/compare-odp-actions.pl b/tests/compare-odp-actions.pl
> deleted file mode 100644
> index b18a593..0000000
> --- a/tests/compare-odp-actions.pl
> +++ /dev/null
> @@ -1,66 +0,0 @@
> -# -*- perl -*-
> -
> -use strict;
> -use warnings;
> -
> -if (@ARGV < 2) {
> -    print <<EOF;
> -$0: to check ODP sets of actions for equivalence
> -usage: $0 ACTIONS1 ACTIONS2 [NAME=NUMBER]...
> -where ACTIONS1 and ACTIONS2 are sets of ODP actions as output by, e.g.
> -      "ovs-dpctl dump-flows" and each NAME=NUMBER pair identifies an ODP
> -      port's name-to-number mapping.
> -
> -Exits with status 0 if ACTIONS1 and ACTIONS2 are equivalent, with
> -status 1 if they differ.
> -EOF
> -    exit 1;
> -}
> -
> -# Construct mappings between port numbers and names.
> -our (%name_to_number);
> -our (%number_to_name);
> -for (@ARGV[2..$#ARGV]) {
> -    my ($name, $number) = /([^=]+)=([0-9]+)/
> -      or die "$_: bad syntax (use --help for help)\n";
> -    $number_to_name{$number} = $name;
> -    $name_to_number{$name} = $number;
> -}
> -
> -my $n1 = normalize_odp_actions($ARGV[0]);
> -my $n2 = normalize_odp_actions($ARGV[1]);
> -print "Normalized action set 1: $n1\n";
> -print "Normalized action set 2: $n2\n";
> -exit($n1 ne $n2);
> -
> -sub normalize_odp_actions {
> -    my ($actions) = @_;
> -
> -    # Transliterate all commas inside parentheses into semicolons.
> -    undef while $actions =~ s/(\([^),]*),([^)]*\))/$1;$2/g;
> -
> -    # Split on commas.
> -    my (@actions) = split(',', $actions);
> -
> -    # Map port numbers into port names.
> -    foreach my $s (@actions) {
> -        $s = $number_to_name{$s} if exists($number_to_name{$s});
> -    }
> -
> -    # Sort sequential groups of port names into alphabetical order.
> -    for (my $i = 0; $i <= $#actions; ) {
> -        my $j = $i + 1;
> -        if (exists($name_to_number{$actions[$i]})) {
> -            for (; $j <= $#actions; $j++) {
> -                last if !exists($name_to_number{$actions[$j]});
> -            }
> -        }
> -        @actions[$i..($j - 1)] = sort(@actions[$i..($j - 1)]);
> -        $i = $j;
> -    }
> -
> -    # Now compose a string again and transliterate semicolons back to commas.
> -    $actions = join(',', @actions);
> -    $actions =~ tr/;/,/;
> -    return $actions;
> -}
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index ec5c238..312ec1f 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -220,8 +220,10 @@ do
>   AT_CHECK([ovs-appctl ofproto/trace br0 "$flow"], [0], [stdout])
>   actual=`tail -1 stdout | sed 's/Datapath actions: //'`
> 
> -  AT_CHECK([echo "in_port=$in_port vlan=$vlan"
> -            $PERL $srcdir/compare-odp-actions.pl "$expected" "$actual" br0=$br0 p1=$p1 p2=$p2 p3=$p3 p4=$p4 p5=$p5 p6=$p6 p7=$p7 p8=$p8], [0], [ignore])
> +  echo "in_port=$in_port vlan=$vlan"
> +  AT_CHECK([ovs-dpctl normalize-actions "$flow" "$expected" br0=$br0 p1=$p1 p2=$p2 p3=$p3 p4=$p4 p5=$p5 p6=$p6 p7=$p7 p8=$p8], [0], [stdout])
> +  mv stdout expout
> +  AT_CHECK([ovs-dpctl normalize-actions "$flow" "$actual" br0=$br0 p1=$p1 p2=$p2 p3=$p3 p4=$p4 p5=$p5 p6=$p6 p7=$p7 p8=$p8], [0], [expout])
> done
> 
> OVS_VSWITCHD_STOP
> diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
> index 69a66b6..30e06b4 100644
> --- a/utilities/ovs-dpctl.c
> +++ b/utilities/ovs-dpctl.c
> @@ -16,6 +16,7 @@
> 
> #include <config.h>
> #include <arpa/inet.h>
> +#include <assert.h>
> #include <errno.h>
> #include <getopt.h>
> #include <inttypes.h>
> @@ -35,9 +36,12 @@
> #include "dirs.h"
> #include "dpif.h"
> #include "dynamic-string.h"
> +#include "flow.h"
> #include "netdev.h"
> +#include "netlink.h"
> #include "odp-util.h"
> #include "ofpbuf.h"
> +#include "packets.h"
> #include "shash.h"
> #include "sset.h"
> #include "timeval.h"
> @@ -49,6 +53,12 @@ VLOG_DEFINE_THIS_MODULE(dpctl);
> /* -s, --statistics: Print port statistics? */
> static bool print_statistics;
> 
> +/* -m, --more: Output verbosity.
> + *
> + * So far only undocumented commands honor this option, so we don't document
> + * the option itself. */
> +static int verbosity;
> +
> static const struct command all_commands[];
> 
> static void usage(void) NO_RETURN;
> @@ -73,6 +83,7 @@ parse_options(int argc, char *argv[])
>     };
>     static struct option long_options[] = {
>         {"statistics", no_argument, NULL, 's'},
> +        {"more", no_argument, NULL, 'm'},
>         {"timeout", required_argument, NULL, 't'},
>         {"help", no_argument, NULL, 'h'},
>         {"version", no_argument, NULL, 'V'},
> @@ -95,6 +106,10 @@ parse_options(int argc, char *argv[])
>             print_statistics = true;
>             break;
> 
> +        case 'm':
> +            verbosity++;
> +            break;
> +
>         case 't':
>             timeout = strtoul(optarg, NULL, 10);
>             if (timeout <= 0) {
> @@ -718,6 +733,196 @@ do_parse_actions(int argc, char *argv[])
>     }
> }
> 
> +struct actions_for_flow {
> +    struct hmap_node hmap_node;
> +    struct flow flow;
> +    struct ofpbuf actions;
> +};
> +
> +static struct actions_for_flow *
> +get_actions_for_flow(struct hmap *actions_per_flow, const struct flow *flow)
> +{
> +    uint32_t hash = flow_hash(flow, 0);
> +    struct actions_for_flow *af;
> +
> +    HMAP_FOR_EACH_WITH_HASH (af, hmap_node, hash, actions_per_flow) {
> +        if (flow_equal(&af->flow, flow)) {
> +            return af;
> +        }
> +    }
> +
> +    af = xmalloc(sizeof *af);
> +    af->flow = *flow;
> +    ofpbuf_init(&af->actions, 0);
> +    hmap_insert(actions_per_flow, &af->hmap_node, hash);
> +    return af;
> +}
> +
> +static int
> +compare_actions_for_flow(const void *a_, const void *b_)
> +{
> +    struct actions_for_flow *const *a = a_;
> +    struct actions_for_flow *const *b = b_;
> +
> +    return flow_compare_3way(&(*a)->flow, &(*b)->flow);
> +}
> +
> +static int
> +compare_output_actions(const void *a_, const void *b_)
> +{
> +    const struct nlattr *a = a_;
> +    const struct nlattr *b = b_;
> +    uint32_t a_port = nl_attr_get_u32(a);
> +    uint32_t b_port = nl_attr_get_u32(b);
> +
> +    return a_port < b_port ? -1 : a_port > b_port;
> +}
> +
> +static void
> +sort_output_actions__(struct nlattr *first, struct nlattr *end)
> +{
> +    size_t bytes = (uint8_t *) end - (uint8_t *) first;
> +    size_t n = bytes / NL_A_U32_SIZE;
> +
> +    assert(bytes % NL_A_U32_SIZE == 0);
> +    qsort(first, n, NL_A_U32_SIZE, compare_output_actions);
> +}
> +
> +static void
> +sort_output_actions(struct nlattr *actions, size_t length)
> +{
> +    struct nlattr *first_output = NULL;
> +    struct nlattr *a;
> +    int left;
> +
> +    NL_ATTR_FOR_EACH (a, left, actions, length) {
> +        if (nl_attr_type(a) == OVS_ACTION_ATTR_OUTPUT) {
> +            if (!first_output) {
> +                first_output = a;
> +            }
> +        } else {
> +            if (first_output) {
> +                sort_output_actions__(first_output, a);
> +                first_output = NULL;
> +            }
> +        }
> +    }
> +    if (first_output) {
> +        uint8_t *end = (uint8_t *) actions + length;
> +        sort_output_actions__(first_output, (struct nlattr *) end);
> +    }
> +}
> +
> +static void
> +do_normalize_actions(int argc, char *argv[])
> +{
> +    struct shash port_names;
> +    struct ofpbuf keybuf;
> +    struct flow flow;
> +    struct ofpbuf odp_actions;
> +    struct hmap actions_per_flow;
> +    struct actions_for_flow **afs;
> +    struct actions_for_flow *af;
> +    struct nlattr *a;
> +    size_t n_afs;
> +    struct ds s;
> +    int left;
> +    int i;
> +
> +    ds_init(&s);
> +
> +    shash_init(&port_names);
> +    for (i = 3; i < argc; i++) {
> +        char name[16];
> +        int number;
> +        int n = -1;
> +
> +        if (sscanf(argv[i], "%15[^=]=%d%n", name, &number, &n) > 0 && n > 0) {
> +            shash_add(&port_names, name, (void *) number);
> +        } else {
> +            ovs_fatal(0, "%s: expected NAME=NUMBER", argv[i]);
> +        }
> +    }
> +
> +    /* Parse flow key. */
> +    ofpbuf_init(&keybuf, 0);
> +    run(odp_flow_key_from_string(argv[1], &port_names, &keybuf),
> +        "odp_flow_key_from_string");
> +
> +    ds_clear(&s);
> +    odp_flow_key_format(keybuf.data, keybuf.size, &s);
> +    printf("input flow: %s\n", ds_cstr(&s));
> +
> +    run(odp_flow_key_to_flow(keybuf.data, keybuf.size, &flow),
> +        "odp_flow_key_to_flow");
> +    ofpbuf_uninit(&keybuf);
> +
> +    /* Parse actions. */
> +    ofpbuf_init(&odp_actions, 0);
> +    run(odp_actions_from_string(argv[2], &port_names, &odp_actions),
> +        "odp_actions_from_string");
> +
> +    if (verbosity) {
> +        ds_clear(&s);
> +        format_odp_actions(&s, odp_actions.data, odp_actions.size);
> +        printf("input actions: %s\n", ds_cstr(&s));
> +    }
> +
> +    hmap_init(&actions_per_flow);
> +    NL_ATTR_FOR_EACH (a, left, odp_actions.data, odp_actions.size) {
> +        if (nl_attr_type(a) == OVS_ACTION_ATTR_POP
> +            && nl_attr_get_u16(a) == OVS_KEY_ATTR_8021Q) {
> +            flow.vlan_tci = htons(0);
> +            continue;
> +        }
> +
> +        if (nl_attr_type(a) == OVS_ACTION_ATTR_PUSH) {
> +            const struct nlattr *nested = nl_attr_get(a);
> +
> +            if (nl_attr_type(nested) == OVS_KEY_ATTR_8021Q) {
> +                const struct ovs_key_8021q *q_key;
> +
> +                q_key = nl_attr_get_unspec(nested, sizeof(*q_key));
> +                flow.vlan_tci = q_key->q_tci | htons(VLAN_CFI);
> +                continue;
> +            }
> +        }
> +
> +        af = get_actions_for_flow(&actions_per_flow, &flow);
> +        nl_msg_put_unspec(&af->actions, nl_attr_type(a),
> +                          nl_attr_get(a), nl_attr_get_size(a));
> +    }
> +
> +    n_afs = hmap_count(&actions_per_flow);
> +    afs = xmalloc(n_afs * sizeof *afs);
> +    i = 0;
> +    HMAP_FOR_EACH (af, hmap_node, &actions_per_flow) {
> +        afs[i++] = af;
> +    }
> +    assert(i == n_afs);
> +
> +    qsort(afs, n_afs, sizeof *afs, compare_actions_for_flow);
> +
> +    for (i = 0; i < n_afs; i++) {
> +        const struct actions_for_flow *af = afs[i];
> +
> +        sort_output_actions(af->actions.data, af->actions.size);
> +
> +        if (af->flow.vlan_tci != htons(0)) {
> +            printf("vlan(vid=%"PRIu16",pcp=%d): ",
> +                   vlan_tci_to_vid(af->flow.vlan_tci),
> +                   vlan_tci_to_pcp(af->flow.vlan_tci));
> +        } else {
> +            printf("no vlan: ");
> +        }
> +
> +        ds_clear(&s);
> +        format_odp_actions(&s, af->actions.data, af->actions.size);
> +        puts(ds_cstr(&s));
> +    }
> +    ds_destroy(&s);
> +}
> +
> static const struct command all_commands[] = {
>     { "add-dp", 1, INT_MAX, do_add_dp },
>     { "del-dp", 1, 1, do_del_dp },
> @@ -732,6 +937,7 @@ static const struct command all_commands[] = {
> 
>     /* Undocumented commands for testing. */
>     { "parse-actions", 1, INT_MAX, do_parse_actions },
> +    { "normalize-actions", 2, INT_MAX, do_normalize_actions },
> 
>     { NULL, 0, 0, NULL },
> };
> -- 
> 1.7.2.5
> 




More information about the dev mailing list