[ovs-dev] [mirror 12/13] tests: Rewrite code for comparing sets of ODP actions.
Ben Pfaff
blp at nicira.com
Thu Nov 17 18:23:47 UTC 2011
I added this comment:
/* usage: "ovs-dpctl normalize-actions FLOW ACTIONS" where FLOW and ACTIONS
* have the syntax used by "ovs-dpctl dump-flows".
*
* This command prints ACTIONS in a format that shows what happens for each
* VLAN, independent of the order of the ACTIONS. For example, there is more
* than one way to output a packet on VLANs 9 and 11, but this command will
* print the same output for any form.
*
* The idea here generalizes beyond VLANs (e.g. to setting other fields) but
* so far the implementation only covers VLANs. */
On Tue, Nov 15, 2011 at 11:04:32PM -0800, Justin Pettit wrote:
> 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