diff mbox series

[ovs-dev,v3] dpctl: add add/mod/del-flows command to dpctl

Message ID 160257809545.175360.13166705201555359545.stgit@ebuild
State Accepted
Headers show
Series [ovs-dev,v3] dpctl: add add/mod/del-flows command to dpctl | expand

Commit Message

Eelco Chaudron Oct. 13, 2020, 8:36 a.m. UTC
When you would like to add, modify, or delete a lot of flows in the
datapath, for example when you want to measure performance, adding
one flow at the time won't scale. This as it takes a decent amount
of time to set up the datapath connection.

This new command is in-line with the same command available in
ovs-ofctl which allows the same thing, with the only difference that
we do not verify all lines before we start execution. This allows for
a continuous add/delete stream. For example with a command like this:

python3 -c 'while True:
  for i in range(0, 1000):
    print("add in_port(0),eth(),eth_type(0x800),ipv4(src=100.1.{}.{}) 1".format(int(i / 256), i % 256))
  for i in range(0, 1000):
    print("delete in_port(0),eth(),eth_type(0x800),ipv4(src=100.1.{}.{})".format(int(i / 256), i % 256))' \
|  sudo utilities/ovs-dpctl add-flows -


Signed-off-by: Eelco Chaudron <echaudro@redhat.com>
---
v3: - Fixed NEWS section
    - Added/update mod-flows/del-flows command to support FILE input
    - Added some selftests
v2: - Added change to NEWS
    - Updated man page to be more clear

 NEWS                  |    3 +
 lib/dpctl.c           |  195 ++++++++++++++++++++++++++++++++++++++++++++-----
 lib/dpctl.man         |   16 ++++
 tests/dpctl.at        |   50 +++++++++++++
 utilities/ovs-dpctl.c |   10 ++-
 5 files changed, 251 insertions(+), 23 deletions(-)

Comments

Paolo Valerio Dec. 22, 2020, 3:16 p.m. UTC | #1
Eelco Chaudron <echaudro@redhat.com> writes:

> When you would like to add, modify, or delete a lot of flows in the
> datapath, for example when you want to measure performance, adding
> one flow at the time won't scale. This as it takes a decent amount
> of time to set up the datapath connection.
>
> This new command is in-line with the same command available in
> ovs-ofctl which allows the same thing, with the only difference that
> we do not verify all lines before we start execution. This allows for
> a continuous add/delete stream. For example with a command like this:
>
> python3 -c 'while True:
>   for i in range(0, 1000):
>     print("add in_port(0),eth(),eth_type(0x800),ipv4(src=100.1.{}.{}) 1".format(int(i / 256), i % 256))
>   for i in range(0, 1000):
>     print("delete in_port(0),eth(),eth_type(0x800),ipv4(src=100.1.{}.{})".format(int(i / 256), i % 256))' \
> |  sudo utilities/ovs-dpctl add-flows -
>
>
> Signed-off-by: Eelco Chaudron <echaudro@redhat.com>
> ---
> v3: - Fixed NEWS section
>     - Added/update mod-flows/del-flows command to support FILE input
>     - Added some selftests
> v2: - Added change to NEWS
>     - Updated man page to be more clear
>
>  NEWS                  |    3 +
>  lib/dpctl.c           |  195 ++++++++++++++++++++++++++++++++++++++++++++-----
>  lib/dpctl.man         |   16 ++++
>  tests/dpctl.at        |   50 +++++++++++++
>  utilities/ovs-dpctl.c |   10 ++-
>  5 files changed, 251 insertions(+), 23 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index 4619e73bf..fed222765 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -7,6 +7,9 @@ Post-v2.14.0
>       * Removed support for vhost-user dequeue zero-copy.
>     - The environment variable OVS_UNBOUND_CONF, if set, is now used
>       as the DNS resolver's (unbound) configuration file.
> +  - ovs-dpctl and 'ovs-appctl dpctl/':
> +    * New commands where added, which allow adding, deleting, or modifying
> +      flows based on information read from a file.
>

Tested it successfully.
NEWS has conflicts, besides that, it would be nice to add the commands
({add,mod,del}-flows) explicitly in the description for consistency with
the other dpctl entries.

Otherwise,

Acked-by: Paolo Valerio <pvalerio@redhat.com>

>  
>  v2.14.0 - 17 Aug 2020
> diff --git a/lib/dpctl.c b/lib/dpctl.c
> index 2f859a753..6fdcfd26f 100644
> --- a/lib/dpctl.c
> +++ b/lib/dpctl.c
> @@ -52,6 +52,12 @@
>  #include "openvswitch/ofp-flow.h"
>  #include "openvswitch/ofp-port.h"
>  
> +enum {
> +    DPCTL_FLOWS_ADD = 0,
> +    DPCTL_FLOWS_DEL,
> +    DPCTL_FLOWS_MOD
> +};
> +
>  typedef int dpctl_command_handler(int argc, const char *argv[],
>                                    struct dpctl_params *);
>  struct dpctl_command {
> @@ -1102,28 +1108,22 @@ out_free:
>  }
>  
>  static int
> -dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
> -               struct dpctl_params *dpctl_p)
> +dpctl_put_flow_dpif(struct dpif *dpif, const char *key_s,
> +                    const char *actions_s,
> +                    enum dpif_flow_put_flags flags,
> +                    struct dpctl_params *dpctl_p)
>  {
> -    const char *key_s = argv[argc - 2];
> -    const char *actions_s = argv[argc - 1];
>      struct dpif_flow_stats stats;
>      struct dpif_port dpif_port;
>      struct dpif_port_dump port_dump;
>      struct ofpbuf actions;
>      struct ofpbuf key;
>      struct ofpbuf mask;
> -    struct dpif *dpif;
>      ovs_u128 ufid;
>      bool ufid_present;
>      struct simap port_names;
>      int n, error;
>  
> -    error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
> -    if (error) {
> -        return error;
> -    }
> -
>      ufid_present = false;
>      n = odp_ufid_from_string(key_s, &ufid);
>      if (n < 0) {
> @@ -1195,6 +1195,24 @@ out_freeactions:
>  out_freekeymask:
>      ofpbuf_uninit(&mask);
>      ofpbuf_uninit(&key);
> +    return error;
> +}
> +
> +static int
> +dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
> +               struct dpctl_params *dpctl_p)
> +{
> +    struct dpif *dpif;
> +    int error;
> +
> +    error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
> +    if (error) {
> +        return error;
> +    }
> +
> +    error = dpctl_put_flow_dpif(dpif, argv[argc - 2], argv[argc - 1], flags,
> +                                dpctl_p);
> +
>      dpif_close(dpif);
>      return error;
>  }
> @@ -1268,26 +1286,21 @@ out:
>  }
>  
>  static int
> -dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
> +dpctl_del_flow_dpif(struct dpif *dpif, const char *key_s,
> +                    struct dpctl_params *dpctl_p)
>  {
> -    const char *key_s = argv[argc - 1];
>      struct dpif_flow_stats stats;
>      struct dpif_port dpif_port;
>      struct dpif_port_dump port_dump;
>      struct ofpbuf key;
>      struct ofpbuf mask; /* To be ignored. */
> -    struct dpif *dpif;
> +
>      ovs_u128 ufid;
>      bool ufid_generated;
>      bool ufid_present;
>      struct simap port_names;
>      int n, error;
>  
> -    error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
> -    if (error) {
> -        return error;
> -    }
> -
>      ufid_present = false;
>      n = odp_ufid_from_string(key_s, &ufid);
>      if (n < 0) {
> @@ -1353,16 +1366,156 @@ out:
>      ofpbuf_uninit(&mask);
>      ofpbuf_uninit(&key);
>      simap_destroy(&port_names);
> +    return error;
> +}
> +
> +static int
> +dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
> +{
> +    const char *key_s = argv[argc - 1];
> +    struct dpif *dpif;
> +    int error;
> +
> +    error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
> +    if (error) {
> +        return error;
> +    }
> +
> +    error = dpctl_del_flow_dpif(dpif, key_s, dpctl_p);
> +
>      dpif_close(dpif);
>      return error;
>  }
>  
> +static int
> +dpctl_parse_flow_line(int command, struct ds *s, char **flow, char **action)
> +{
> +    const char *line = ds_cstr(s);
> +    size_t len;
> +
> +    /* First figure out the command, or fallback to FLOWS_ADD. */
> +    line += strspn(line, " \t\r\n");
> +    len = strcspn(line, ", \t\r\n");
> +
> +    if (!strncmp(line, "add", len)) {
> +         command = DPCTL_FLOWS_ADD;
> +    } else if (!strncmp(line, "delete", len)) {
> +        command = DPCTL_FLOWS_DEL;
> +    } else if (!strncmp(line, "modify", len)) {
> +        command = DPCTL_FLOWS_MOD;
> +    } else {
> +        len = 0;
> +    }
> +    line += len;
> +
> +    /* Isolate flow and action (for add/modify). */
> +    line += strspn(line, " \t\r\n");
> +    len = strcspn(line, " \t\r\n");
> +
> +    if (len == 0) {
> +        *flow = NULL;
> +        *action = NULL;
> +        return command;
> +    }
> +
> +    *flow = strndup(line, len);
> +
> +    line += len;
> +    line += strspn(line, " \t\r\n");
> +    if (strlen(line)) {
> +        *action = xstrdup(line);
> +    } else {
> +        *action = NULL;
> +    }
> +
> +    return command;
> +}
> +
> +static int
> +dpctl_process_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
> +{
> +    const char *file_name = argv[argc - 1];
> +    int line_number = 0;
> +    struct dpif *dpif;
> +    struct ds line;
> +    FILE *stream;
> +    int error;
> +    int def_cmd = DPCTL_FLOWS_ADD;
> +
> +    if (strstr(argv[0], "mod-flows")) {
> +        def_cmd = DPCTL_FLOWS_MOD;
> +    } else if (strstr(argv[0], "del-flows")) {
> +        def_cmd = DPCTL_FLOWS_DEL;
> +    }
> +
> +    error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
> +    if (error) {
> +        return error;
> +    }
> +
> +    stream = !strcmp(file_name, "-") ? stdin : fopen(file_name, "r");
> +    if (!stream) {
> +        error = errno;
> +        dpctl_error(dpctl_p, error, "Opening file \"%s\" failed", file_name);
> +        goto out_close_dpif;
> +    }
> +
> +    ds_init(&line);
> +    while (!ds_get_preprocessed_line(&line, stream, &line_number)) {
> +        /* We do not process all the lines first and then execute the actions
> +         * as we would like to take commands as a continuous stream of
> +         * commands from stdin.
> +         */
> +        char *flow = NULL;
> +        char *action = NULL;
> +        int cmd = dpctl_parse_flow_line(def_cmd, &line, &flow, &action);
> +
> +        if ((!flow && !action)
> +            || ((cmd == DPCTL_FLOWS_ADD || cmd == DPCTL_FLOWS_MOD) && !action)
> +            || (cmd == DPCTL_FLOWS_DEL && action)) {
> +            dpctl_error(dpctl_p, 0,
> +                        "Failed parsing line number %u, skipped!",
> +                        line_number);
> +        } else {
> +            switch (cmd) {
> +            case DPCTL_FLOWS_ADD:
> +                dpctl_put_flow_dpif(dpif, flow, action,
> +                                    DPIF_FP_CREATE, dpctl_p);
> +                break;
> +            case DPCTL_FLOWS_MOD:
> +                dpctl_put_flow_dpif(dpif, flow, action,
> +                                    DPIF_FP_MODIFY, dpctl_p);
> +                break;
> +            case DPCTL_FLOWS_DEL:
> +                dpctl_del_flow_dpif(dpif, flow, dpctl_p);
> +                break;
> +            }
> +        }
> +
> +        free(flow);
> +        free(action);
> +    }
> +
> +    ds_destroy(&line);
> +    if (stream != stdin) {
> +        fclose(stream);
> +    }
> +out_close_dpif:
> +    dpif_close(dpif);
> +    return 0;
> +}
> +
>  static int
>  dpctl_del_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
>  {
>      struct dpif *dpif;
> +    int error;
>  
> -    int error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
> +    if ((!dp_arg_exists(argc, argv) && argc == 2) || argc > 2) {
> +        return dpctl_process_flows(argc, argv, dpctl_p);
> +    }
> +
> +    error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
>      if (error) {
>          return error;
>      }
> @@ -2528,7 +2681,9 @@ static const struct dpctl_command all_commands[] = {
>      { "mod-flow", "[dp] flow actions", 2, 3, dpctl_mod_flow, DP_RW },
>      { "get-flow", "[dp] ufid", 1, 2, dpctl_get_flow, DP_RO },
>      { "del-flow", "[dp] flow", 1, 2, dpctl_del_flow, DP_RW },
> -    { "del-flows", "[dp]", 0, 1, dpctl_del_flows, DP_RW },
> +    { "add-flows", "[dp] file", 1, 2, dpctl_process_flows, DP_RW },
> +    { "mod-flows", "[dp] file", 1, 2, dpctl_process_flows, DP_RW },
> +    { "del-flows", "[dp] [file]", 0, 2, dpctl_del_flows, DP_RW },
>      { "dump-conntrack", "[dp] [zone=N]", 0, 2, dpctl_dump_conntrack, DP_RO },
>      { "flush-conntrack", "[dp] [zone=N] [ct-tuple]", 0, 3,
>        dpctl_flush_conntrack, DP_RW },
> diff --git a/lib/dpctl.man b/lib/dpctl.man
> index 727d1f7be..50f7379a8 100644
> --- a/lib/dpctl.man
> +++ b/lib/dpctl.man
> @@ -194,6 +194,22 @@ ovs-dpctl add-flow myDP \\
>  .
>  .RE
>  .TP
> +\*(DX\fBadd\-flows\fR [\fIdp\fR] \fIfile\fR
> +.TQ
> +\*(DX\fBmod\-flows\fR [\fIdp\fR] \fIfile\fR
> +.TQ
> +\*(DX\fBdel\-flows\fR [\fIdp\fR] \fIfile\fR
> +Reads flow entries from \fIfile\fR (or \fBstdin\fR if \fIfile\fR is
> +\fB\-\fR) and adds, modifies, or deletes each entry to the datapath.
> +.
> +Each flow specification (e.g., each line in \fIfile\fR) may start with
> +\fBadd\fR, \fBmodify\fR, or \fBdelete\fR keyword to specify whether a
> +flow is to be added, modified, or deleted. A flow specification without
> +one of these keywords is treated based on the used command.  All flow
> +modifications are executed as individual transactions in the order
> +specified.
> +.
> +.TP
>  .DO "[\fB\-s\fR | \fB\-\-statistics\fR]" "\*(DX\fBdel\-flow\fR" "[\fIdp\fR] \fIflow\fR"
>  Deletes the flow from \fIdp\fR's flow table that matches \fIflow\fR.
>  If \fB\-s\fR or \fB\-\-statistics\fR is specified, then
> diff --git a/tests/dpctl.at b/tests/dpctl.at
> index deec54959..7454a51ec 100644
> --- a/tests/dpctl.at
> +++ b/tests/dpctl.at
> @@ -85,3 +85,53 @@ OVS_VSWITCHD_STOP(["/dummy@br0: port_del failed/d
>  /dummy@br0: failed to add vif1.0 as port/d
>  /Dropped 1 log messages in last/d"])
>  AT_CLEANUP
> +
> +AT_SETUP([dpctl - add/mod/del-flows])
> +OVS_VSWITCHD_START
> +AT_CHECK([ovs-appctl dpctl/add-dp dummy@br0])
> +AT_DATA([flows.txt], [dnl
> +in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234) 2
> +])
> +AT_CHECK([ovs-appctl dpctl/add-flows dummy@br0 flows.txt], [0], [dnl
> +])
> +AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
> +flow-dump from the main thread:
> +recirc_id(0),in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234), packets:0, bytes:0, used:never, actions:2
> +])
> +AT_DATA([flows.txt], [dnl
> +in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234) 3
> +])
> +AT_CHECK([ovs-appctl dpctl/mod-flows dummy@br0 flows.txt], [0], [dnl
> +])
> +AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
> +flow-dump from the main thread:
> +recirc_id(0),in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234), packets:0, bytes:0, used:never, actions:3
> +])
> +AT_DATA([flows.txt], [dnl
> +in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234)
> +])
> +AT_CHECK([ovs-appctl dpctl/del-flows dummy@br0 flows.txt], [0], [dnl
> +])
> +AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
> +])
> +AT_DATA([flows.txt], [dnl
> +add in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234) 2
> +add in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:03),eth_type(0x1234) 2
> +add in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:04),eth_type(0x1234) 2
> +modify in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234) 1
> +delete in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:03),eth_type(0x1234)
> +])
> +AT_CHECK([ovs-appctl dpctl/add-flows dummy@br0 flows.txt], [0], [dnl
> +])
> +AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
> +flow-dump from the main thread:
> +recirc_id(0),in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234), packets:0, bytes:0, used:never, actions:1
> +recirc_id(0),in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:04),eth_type(0x1234), packets:0, bytes:0, used:never, actions:2
> +])
> +AT_CHECK([ovs-appctl dpctl/del-flows dummy@br0], [0], [dnl
> +])
> +AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
> +])
> +AT_CHECK([ovs-appctl dpctl/del-dp dummy@br0])
> +OVS_VSWITCHD_STOP
> +AT_CLEANUP
> diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
> index 7d99607f4..f616995c3 100644
> --- a/utilities/ovs-dpctl.c
> +++ b/utilities/ovs-dpctl.c
> @@ -191,10 +191,13 @@ usage(void *userdata OVS_UNUSED)
>             "  show DP...               show basic info on each DP\n"
>             "  dump-flows [DP]          display flows in DP\n"
>             "  add-flow [DP] FLOW ACTIONS add FLOW with ACTIONS to DP\n"
> +           "  add-flows [DP] FILE        add flows from FILE\n"
>             "  mod-flow [DP] FLOW ACTIONS change FLOW actions to ACTIONS in DP\n"
> +           "  mod-flows [DP] FILE        change flows from FILE\n"
>             "  get-flow [DP] ufid:UFID    fetch flow corresponding to UFID\n"
>             "  del-flow [DP] FLOW         delete FLOW from DP\n"
> -           "  del-flows [DP]             delete all flows from DP\n"
> +           "  del-flows [DP] [FILE]      " \
> +               "delete all or specified flows from DP\n"
>             "  dump-conntrack [DP] [zone=ZONE]  " \
>                 "display conntrack entries for ZONE\n"
>             "  flush-conntrack [DP] [zone=ZONE] [ct-tuple]" \
> @@ -205,8 +208,9 @@ usage(void *userdata OVS_UNUSED)
>             "Each IFACE on add-dp, add-if, and set-if may be followed by\n"
>             "comma-separated options.  See ovs-dpctl(8) for syntax, or the\n"
>             "Interface table in ovs-vswitchd.conf.db(5) for an options list.\n"
> -           "For COMMAND dump-flows, add-flow, mod-flow, del-flow and\n"
> -           "del-flows, DP is optional if there is only one datapath.\n",
> +           "For COMMAND dump-flows, add-flow, add-flows, mod-flow,\n"
> +           "mod-flows, del-flow and del-flows, DP is optional if there is\n"
> +           "only one datapath.\n",
>             program_name, program_name);
>      vlog_usage();
>      printf("\nOptions for show and mod-flow:\n"
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Eelco Chaudron Dec. 22, 2020, 3:48 p.m. UTC | #2
On 22 Dec 2020, at 16:16, Paolo Valerio wrote:

> Eelco Chaudron <echaudro@redhat.com> writes:
>
>> When you would like to add, modify, or delete a lot of flows in the
>> datapath, for example when you want to measure performance, adding
>> one flow at the time won't scale. This as it takes a decent amount
>> of time to set up the datapath connection.
>>
>> This new command is in-line with the same command available in
>> ovs-ofctl which allows the same thing, with the only difference that
>> we do not verify all lines before we start execution. This allows for
>> a continuous add/delete stream. For example with a command like this:
>>
>> python3 -c 'while True:
>>   for i in range(0, 1000):
>>     print("add in_port(0),eth(),eth_type(0x800),ipv4(src=100.1.{}.{}) 
>> 1".format(int(i / 256), i % 256))
>>   for i in range(0, 1000):
>>     print("delete 
>> in_port(0),eth(),eth_type(0x800),ipv4(src=100.1.{}.{})".format(int(i 
>> / 256), i % 256))' \
>> |  sudo utilities/ovs-dpctl add-flows -
>>
>>
>> Signed-off-by: Eelco Chaudron <echaudro@redhat.com>
>> ---
>> v3: - Fixed NEWS section
>>     - Added/update mod-flows/del-flows command to support FILE input
>>     - Added some selftests
>> v2: - Added change to NEWS
>>     - Updated man page to be more clear
>>
>>  NEWS                  |    3 +
>>  lib/dpctl.c           |  195 
>> ++++++++++++++++++++++++++++++++++++++++++++-----
>>  lib/dpctl.man         |   16 ++++
>>  tests/dpctl.at        |   50 +++++++++++++
>>  utilities/ovs-dpctl.c |   10 ++-
>>  5 files changed, 251 insertions(+), 23 deletions(-)
>>
>> diff --git a/NEWS b/NEWS
>> index 4619e73bf..fed222765 100644
>> --- a/NEWS
>> +++ b/NEWS
>> @@ -7,6 +7,9 @@ Post-v2.14.0
>>       * Removed support for vhost-user dequeue zero-copy.
>>     - The environment variable OVS_UNBOUND_CONF, if set, is now used
>>       as the DNS resolver's (unbound) configuration file.
>> +  - ovs-dpctl and 'ovs-appctl dpctl/':
>> +    * New commands where added, which allow adding, deleting, or 
>> modifying
>> +      flows based on information read from a file.
>>
>
> Tested it successfully.

Thanks Paolo!

> NEWS has conflicts, besides that, it would be nice to add the commands
> ({add,mod,del}-flows) explicitly in the description for consistency 
> with
> the other dpctl entries.

Guess you mean in the NEWS file itself, like:

+  - ‘ovs-dpctl {add,mod,del}-flows’ and 'ovs-appctl 
dpctl/{add,mod,del}-flows':
+    * New commands were added, which allow adding, deleting, or 
modifying
+      flows based on information read from a file.

Ilya let me know if you need a new revision if this is all that needs 
changing?

> Otherwise,
>
> Acked-by: Paolo Valerio <pvalerio@redhat.com>
>
>>
>>  v2.14.0 - 17 Aug 2020
>> diff --git a/lib/dpctl.c b/lib/dpctl.c
>> index 2f859a753..6fdcfd26f 100644
>> --- a/lib/dpctl.c
>> +++ b/lib/dpctl.c
>> @@ -52,6 +52,12 @@
>>  #include "openvswitch/ofp-flow.h"
>>  #include "openvswitch/ofp-port.h"
>>
>> +enum {
>> +    DPCTL_FLOWS_ADD = 0,
>> +    DPCTL_FLOWS_DEL,
>> +    DPCTL_FLOWS_MOD
>> +};
>> +
>>  typedef int dpctl_command_handler(int argc, const char *argv[],
>>                                    struct dpctl_params *);
>>  struct dpctl_command {
>> @@ -1102,28 +1108,22 @@ out_free:
>>  }
>>
>>  static int
>> -dpctl_put_flow(int argc, const char *argv[], enum 
>> dpif_flow_put_flags flags,
>> -               struct dpctl_params *dpctl_p)
>> +dpctl_put_flow_dpif(struct dpif *dpif, const char *key_s,
>> +                    const char *actions_s,
>> +                    enum dpif_flow_put_flags flags,
>> +                    struct dpctl_params *dpctl_p)
>>  {
>> -    const char *key_s = argv[argc - 2];
>> -    const char *actions_s = argv[argc - 1];
>>      struct dpif_flow_stats stats;
>>      struct dpif_port dpif_port;
>>      struct dpif_port_dump port_dump;
>>      struct ofpbuf actions;
>>      struct ofpbuf key;
>>      struct ofpbuf mask;
>> -    struct dpif *dpif;
>>      ovs_u128 ufid;
>>      bool ufid_present;
>>      struct simap port_names;
>>      int n, error;
>>
>> -    error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
>> -    if (error) {
>> -        return error;
>> -    }
>> -
>>      ufid_present = false;
>>      n = odp_ufid_from_string(key_s, &ufid);
>>      if (n < 0) {
>> @@ -1195,6 +1195,24 @@ out_freeactions:
>>  out_freekeymask:
>>      ofpbuf_uninit(&mask);
>>      ofpbuf_uninit(&key);
>> +    return error;
>> +}
>> +
>> +static int
>> +dpctl_put_flow(int argc, const char *argv[], enum 
>> dpif_flow_put_flags flags,
>> +               struct dpctl_params *dpctl_p)
>> +{
>> +    struct dpif *dpif;
>> +    int error;
>> +
>> +    error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
>> +    if (error) {
>> +        return error;
>> +    }
>> +
>> +    error = dpctl_put_flow_dpif(dpif, argv[argc - 2], argv[argc - 
>> 1], flags,
>> +                                dpctl_p);
>> +
>>      dpif_close(dpif);
>>      return error;
>>  }
>> @@ -1268,26 +1286,21 @@ out:
>>  }
>>
>>  static int
>> -dpctl_del_flow(int argc, const char *argv[], struct dpctl_params 
>> *dpctl_p)
>> +dpctl_del_flow_dpif(struct dpif *dpif, const char *key_s,
>> +                    struct dpctl_params *dpctl_p)
>>  {
>> -    const char *key_s = argv[argc - 1];
>>      struct dpif_flow_stats stats;
>>      struct dpif_port dpif_port;
>>      struct dpif_port_dump port_dump;
>>      struct ofpbuf key;
>>      struct ofpbuf mask; /* To be ignored. */
>> -    struct dpif *dpif;
>> +
>>      ovs_u128 ufid;
>>      bool ufid_generated;
>>      bool ufid_present;
>>      struct simap port_names;
>>      int n, error;
>>
>> -    error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
>> -    if (error) {
>> -        return error;
>> -    }
>> -
>>      ufid_present = false;
>>      n = odp_ufid_from_string(key_s, &ufid);
>>      if (n < 0) {
>> @@ -1353,16 +1366,156 @@ out:
>>      ofpbuf_uninit(&mask);
>>      ofpbuf_uninit(&key);
>>      simap_destroy(&port_names);
>> +    return error;
>> +}
>> +
>> +static int
>> +dpctl_del_flow(int argc, const char *argv[], struct dpctl_params 
>> *dpctl_p)
>> +{
>> +    const char *key_s = argv[argc - 1];
>> +    struct dpif *dpif;
>> +    int error;
>> +
>> +    error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
>> +    if (error) {
>> +        return error;
>> +    }
>> +
>> +    error = dpctl_del_flow_dpif(dpif, key_s, dpctl_p);
>> +
>>      dpif_close(dpif);
>>      return error;
>>  }
>>
>> +static int
>> +dpctl_parse_flow_line(int command, struct ds *s, char **flow, char 
>> **action)
>> +{
>> +    const char *line = ds_cstr(s);
>> +    size_t len;
>> +
>> +    /* First figure out the command, or fallback to FLOWS_ADD. */
>> +    line += strspn(line, " \t\r\n");
>> +    len = strcspn(line, ", \t\r\n");
>> +
>> +    if (!strncmp(line, "add", len)) {
>> +         command = DPCTL_FLOWS_ADD;
>> +    } else if (!strncmp(line, "delete", len)) {
>> +        command = DPCTL_FLOWS_DEL;
>> +    } else if (!strncmp(line, "modify", len)) {
>> +        command = DPCTL_FLOWS_MOD;
>> +    } else {
>> +        len = 0;
>> +    }
>> +    line += len;
>> +
>> +    /* Isolate flow and action (for add/modify). */
>> +    line += strspn(line, " \t\r\n");
>> +    len = strcspn(line, " \t\r\n");
>> +
>> +    if (len == 0) {
>> +        *flow = NULL;
>> +        *action = NULL;
>> +        return command;
>> +    }
>> +
>> +    *flow = strndup(line, len);
>> +
>> +    line += len;
>> +    line += strspn(line, " \t\r\n");
>> +    if (strlen(line)) {
>> +        *action = xstrdup(line);
>> +    } else {
>> +        *action = NULL;
>> +    }
>> +
>> +    return command;
>> +}
>> +
>> +static int
>> +dpctl_process_flows(int argc, const char *argv[], struct 
>> dpctl_params *dpctl_p)
>> +{
>> +    const char *file_name = argv[argc - 1];
>> +    int line_number = 0;
>> +    struct dpif *dpif;
>> +    struct ds line;
>> +    FILE *stream;
>> +    int error;
>> +    int def_cmd = DPCTL_FLOWS_ADD;
>> +
>> +    if (strstr(argv[0], "mod-flows")) {
>> +        def_cmd = DPCTL_FLOWS_MOD;
>> +    } else if (strstr(argv[0], "del-flows")) {
>> +        def_cmd = DPCTL_FLOWS_DEL;
>> +    }
>> +
>> +    error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
>> +    if (error) {
>> +        return error;
>> +    }
>> +
>> +    stream = !strcmp(file_name, "-") ? stdin : fopen(file_name, 
>> "r");
>> +    if (!stream) {
>> +        error = errno;
>> +        dpctl_error(dpctl_p, error, "Opening file \"%s\" failed", 
>> file_name);
>> +        goto out_close_dpif;
>> +    }
>> +
>> +    ds_init(&line);
>> +    while (!ds_get_preprocessed_line(&line, stream, &line_number)) {
>> +        /* We do not process all the lines first and then execute 
>> the actions
>> +         * as we would like to take commands as a continuous stream 
>> of
>> +         * commands from stdin.
>> +         */
>> +        char *flow = NULL;
>> +        char *action = NULL;
>> +        int cmd = dpctl_parse_flow_line(def_cmd, &line, &flow, 
>> &action);
>> +
>> +        if ((!flow && !action)
>> +            || ((cmd == DPCTL_FLOWS_ADD || cmd == DPCTL_FLOWS_MOD) 
>> && !action)
>> +            || (cmd == DPCTL_FLOWS_DEL && action)) {
>> +            dpctl_error(dpctl_p, 0,
>> +                        "Failed parsing line number %u, skipped!",
>> +                        line_number);
>> +        } else {
>> +            switch (cmd) {
>> +            case DPCTL_FLOWS_ADD:
>> +                dpctl_put_flow_dpif(dpif, flow, action,
>> +                                    DPIF_FP_CREATE, dpctl_p);
>> +                break;
>> +            case DPCTL_FLOWS_MOD:
>> +                dpctl_put_flow_dpif(dpif, flow, action,
>> +                                    DPIF_FP_MODIFY, dpctl_p);
>> +                break;
>> +            case DPCTL_FLOWS_DEL:
>> +                dpctl_del_flow_dpif(dpif, flow, dpctl_p);
>> +                break;
>> +            }
>> +        }
>> +
>> +        free(flow);
>> +        free(action);
>> +    }
>> +
>> +    ds_destroy(&line);
>> +    if (stream != stdin) {
>> +        fclose(stream);
>> +    }
>> +out_close_dpif:
>> +    dpif_close(dpif);
>> +    return 0;
>> +}
>> +
>>  static int
>>  dpctl_del_flows(int argc, const char *argv[], struct dpctl_params 
>> *dpctl_p)
>>  {
>>      struct dpif *dpif;
>> +    int error;
>>
>> -    int error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
>> +    if ((!dp_arg_exists(argc, argv) && argc == 2) || argc > 2) {
>> +        return dpctl_process_flows(argc, argv, dpctl_p);
>> +    }
>> +
>> +    error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
>>      if (error) {
>>          return error;
>>      }
>> @@ -2528,7 +2681,9 @@ static const struct dpctl_command 
>> all_commands[] = {
>>      { "mod-flow", "[dp] flow actions", 2, 3, dpctl_mod_flow, DP_RW 
>> },
>>      { "get-flow", "[dp] ufid", 1, 2, dpctl_get_flow, DP_RO },
>>      { "del-flow", "[dp] flow", 1, 2, dpctl_del_flow, DP_RW },
>> -    { "del-flows", "[dp]", 0, 1, dpctl_del_flows, DP_RW },
>> +    { "add-flows", "[dp] file", 1, 2, dpctl_process_flows, DP_RW },
>> +    { "mod-flows", "[dp] file", 1, 2, dpctl_process_flows, DP_RW },
>> +    { "del-flows", "[dp] [file]", 0, 2, dpctl_del_flows, DP_RW },
>>      { "dump-conntrack", "[dp] [zone=N]", 0, 2, dpctl_dump_conntrack, 
>> DP_RO },
>>      { "flush-conntrack", "[dp] [zone=N] [ct-tuple]", 0, 3,
>>        dpctl_flush_conntrack, DP_RW },
>> diff --git a/lib/dpctl.man b/lib/dpctl.man
>> index 727d1f7be..50f7379a8 100644
>> --- a/lib/dpctl.man
>> +++ b/lib/dpctl.man
>> @@ -194,6 +194,22 @@ ovs-dpctl add-flow myDP \\
>>  .
>>  .RE
>>  .TP
>> +\*(DX\fBadd\-flows\fR [\fIdp\fR] \fIfile\fR
>> +.TQ
>> +\*(DX\fBmod\-flows\fR [\fIdp\fR] \fIfile\fR
>> +.TQ
>> +\*(DX\fBdel\-flows\fR [\fIdp\fR] \fIfile\fR
>> +Reads flow entries from \fIfile\fR (or \fBstdin\fR if \fIfile\fR is
>> +\fB\-\fR) and adds, modifies, or deletes each entry to the datapath.
>> +.
>> +Each flow specification (e.g., each line in \fIfile\fR) may start 
>> with
>> +\fBadd\fR, \fBmodify\fR, or \fBdelete\fR keyword to specify whether 
>> a
>> +flow is to be added, modified, or deleted. A flow specification 
>> without
>> +one of these keywords is treated based on the used command.  All 
>> flow
>> +modifications are executed as individual transactions in the order
>> +specified.
>> +.
>> +.TP
>>  .DO "[\fB\-s\fR | \fB\-\-statistics\fR]" "\*(DX\fBdel\-flow\fR" 
>> "[\fIdp\fR] \fIflow\fR"
>>  Deletes the flow from \fIdp\fR's flow table that matches \fIflow\fR.
>>  If \fB\-s\fR or \fB\-\-statistics\fR is specified, then
>> diff --git a/tests/dpctl.at b/tests/dpctl.at
>> index deec54959..7454a51ec 100644
>> --- a/tests/dpctl.at
>> +++ b/tests/dpctl.at
>> @@ -85,3 +85,53 @@ OVS_VSWITCHD_STOP(["/dummy@br0: port_del failed/d
>>  /dummy@br0: failed to add vif1.0 as port/d
>>  /Dropped 1 log messages in last/d"])
>>  AT_CLEANUP
>> +
>> +AT_SETUP([dpctl - add/mod/del-flows])
>> +OVS_VSWITCHD_START
>> +AT_CHECK([ovs-appctl dpctl/add-dp dummy@br0])
>> +AT_DATA([flows.txt], [dnl
>> +in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234) 
>> 2
>> +])
>> +AT_CHECK([ovs-appctl dpctl/add-flows dummy@br0 flows.txt], [0], [dnl
>> +])
>> +AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
>> +flow-dump from the main thread:
>> +recirc_id(0),in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234), 
>> packets:0, bytes:0, used:never, actions:2
>> +])
>> +AT_DATA([flows.txt], [dnl
>> +in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234) 
>> 3
>> +])
>> +AT_CHECK([ovs-appctl dpctl/mod-flows dummy@br0 flows.txt], [0], [dnl
>> +])
>> +AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
>> +flow-dump from the main thread:
>> +recirc_id(0),in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234), 
>> packets:0, bytes:0, used:never, actions:3
>> +])
>> +AT_DATA([flows.txt], [dnl
>> +in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234)
>> +])
>> +AT_CHECK([ovs-appctl dpctl/del-flows dummy@br0 flows.txt], [0], [dnl
>> +])
>> +AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
>> +])
>> +AT_DATA([flows.txt], [dnl
>> +add 
>> in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234) 
>> 2
>> +add 
>> in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:03),eth_type(0x1234) 
>> 2
>> +add 
>> in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:04),eth_type(0x1234) 
>> 2
>> +modify 
>> in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234) 
>> 1
>> +delete 
>> in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:03),eth_type(0x1234)
>> +])
>> +AT_CHECK([ovs-appctl dpctl/add-flows dummy@br0 flows.txt], [0], [dnl
>> +])
>> +AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
>> +flow-dump from the main thread:
>> +recirc_id(0),in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234), 
>> packets:0, bytes:0, used:never, actions:1
>> +recirc_id(0),in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:04),eth_type(0x1234), 
>> packets:0, bytes:0, used:never, actions:2
>> +])
>> +AT_CHECK([ovs-appctl dpctl/del-flows dummy@br0], [0], [dnl
>> +])
>> +AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
>> +])
>> +AT_CHECK([ovs-appctl dpctl/del-dp dummy@br0])
>> +OVS_VSWITCHD_STOP
>> +AT_CLEANUP
>> diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
>> index 7d99607f4..f616995c3 100644
>> --- a/utilities/ovs-dpctl.c
>> +++ b/utilities/ovs-dpctl.c
>> @@ -191,10 +191,13 @@ usage(void *userdata OVS_UNUSED)
>>             "  show DP...               show basic info on each DP\n"
>>             "  dump-flows [DP]          display flows in DP\n"
>>             "  add-flow [DP] FLOW ACTIONS add FLOW with ACTIONS to 
>> DP\n"
>> +           "  add-flows [DP] FILE        add flows from FILE\n"
>>             "  mod-flow [DP] FLOW ACTIONS change FLOW actions to 
>> ACTIONS in DP\n"
>> +           "  mod-flows [DP] FILE        change flows from FILE\n"
>>             "  get-flow [DP] ufid:UFID    fetch flow corresponding to 
>> UFID\n"
>>             "  del-flow [DP] FLOW         delete FLOW from DP\n"
>> -           "  del-flows [DP]             delete all flows from DP\n"
>> +           "  del-flows [DP] [FILE]      " \
>> +               "delete all or specified flows from DP\n"
>>             "  dump-conntrack [DP] [zone=ZONE]  " \
>>                 "display conntrack entries for ZONE\n"
>>             "  flush-conntrack [DP] [zone=ZONE] [ct-tuple]" \
>> @@ -205,8 +208,9 @@ usage(void *userdata OVS_UNUSED)
>>             "Each IFACE on add-dp, add-if, and set-if may be followed 
>> by\n"
>>             "comma-separated options.  See ovs-dpctl(8) for syntax, 
>> or the\n"
>>             "Interface table in ovs-vswitchd.conf.db(5) for an 
>> options list.\n"
>> -           "For COMMAND dump-flows, add-flow, mod-flow, del-flow 
>> and\n"
>> -           "del-flows, DP is optional if there is only one 
>> datapath.\n",
>> +           "For COMMAND dump-flows, add-flow, add-flows, 
>> mod-flow,\n"
>> +           "mod-flows, del-flow and del-flows, DP is optional if 
>> there is\n"
>> +           "only one datapath.\n",
>>             program_name, program_name);
>>      vlog_usage();
>>      printf("\nOptions for show and mod-flow:\n"
>>
>> _______________________________________________
>> dev mailing list
>> dev@openvswitch.org
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Ilya Maximets Jan. 5, 2021, 8:20 p.m. UTC | #3
On 12/22/20 4:48 PM, Eelco Chaudron wrote:
> 
> 
> On 22 Dec 2020, at 16:16, Paolo Valerio wrote:
> 
>> Eelco Chaudron <echaudro@redhat.com> writes:
>>
>>> When you would like to add, modify, or delete a lot of flows in the
>>> datapath, for example when you want to measure performance, adding
>>> one flow at the time won't scale. This as it takes a decent amount
>>> of time to set up the datapath connection.
>>>
>>> This new command is in-line with the same command available in
>>> ovs-ofctl which allows the same thing, with the only difference that
>>> we do not verify all lines before we start execution. This allows for
>>> a continuous add/delete stream. For example with a command like this:
>>>
>>> python3 -c 'while True:
>>>   for i in range(0, 1000):
>>>     print("add in_port(0),eth(),eth_type(0x800),ipv4(src=100.1.{}.{}) 1".format(int(i / 256), i % 256))
>>>   for i in range(0, 1000):
>>>     print("delete in_port(0),eth(),eth_type(0x800),ipv4(src=100.1.{}.{})".format(int(i / 256), i % 256))' \
>>> |  sudo utilities/ovs-dpctl add-flows -
>>>
>>>
>>> Signed-off-by: Eelco Chaudron <echaudro@redhat.com>
>>> ---
>>> v3: - Fixed NEWS section
>>>     - Added/update mod-flows/del-flows command to support FILE input
>>>     - Added some selftests
>>> v2: - Added change to NEWS
>>>     - Updated man page to be more clear
>>>
>>>  NEWS                  |    3 +
>>>  lib/dpctl.c           |  195 ++++++++++++++++++++++++++++++++++++++++++++-----
>>>  lib/dpctl.man         |   16 ++++
>>>  tests/dpctl.at        |   50 +++++++++++++
>>>  utilities/ovs-dpctl.c |   10 ++-
>>>  5 files changed, 251 insertions(+), 23 deletions(-)
>>>
>>> diff --git a/NEWS b/NEWS
>>> index 4619e73bf..fed222765 100644
>>> --- a/NEWS
>>> +++ b/NEWS
>>> @@ -7,6 +7,9 @@ Post-v2.14.0
>>>       * Removed support for vhost-user dequeue zero-copy.
>>>     - The environment variable OVS_UNBOUND_CONF, if set, is now used
>>>       as the DNS resolver's (unbound) configuration file.
>>> +  - ovs-dpctl and 'ovs-appctl dpctl/':
>>> +    * New commands where added, which allow adding, deleting, or modifying
>>> +      flows based on information read from a file.
>>>
>>
>> Tested it successfully.
> 
> Thanks Paolo!
> 
>> NEWS has conflicts, besides that, it would be nice to add the commands
>> ({add,mod,del}-flows) explicitly in the description for consistency with
>> the other dpctl entries.
> 
> Guess you mean in the NEWS file itself, like:
> 
> +  - ‘ovs-dpctl {add,mod,del}-flows’ and 'ovs-appctl dpctl/{add,mod,del}-flows':
> +    * New commands were added, which allow adding, deleting, or modifying
> +      flows based on information read from a file.
> 
> Ilya let me know if you need a new revision if this is all that needs changing?

No need for a new version.  I slightly modified this text, though.

> 
>> Otherwise,
>>
>> Acked-by: Paolo Valerio <pvalerio@redhat.com>

Thanks!  Applied.

Best regards, Ilya Maximets.
Ilya Maximets Jan. 6, 2021, 2:46 p.m. UTC | #4
On 1/5/21 9:20 PM, Ilya Maximets wrote:
> On 12/22/20 4:48 PM, Eelco Chaudron wrote:
>>
>>
>> On 22 Dec 2020, at 16:16, Paolo Valerio wrote:
>>
>>> Eelco Chaudron <echaudro@redhat.com> writes:
>>>
>>>> When you would like to add, modify, or delete a lot of flows in the
>>>> datapath, for example when you want to measure performance, adding
>>>> one flow at the time won't scale. This as it takes a decent amount
>>>> of time to set up the datapath connection.
>>>>
>>>> This new command is in-line with the same command available in
>>>> ovs-ofctl which allows the same thing, with the only difference that
>>>> we do not verify all lines before we start execution. This allows for
>>>> a continuous add/delete stream. For example with a command like this:
>>>>
>>>> python3 -c 'while True:
>>>>   for i in range(0, 1000):
>>>>     print("add in_port(0),eth(),eth_type(0x800),ipv4(src=100.1.{}.{}) 1".format(int(i / 256), i % 256))
>>>>   for i in range(0, 1000):
>>>>     print("delete in_port(0),eth(),eth_type(0x800),ipv4(src=100.1.{}.{})".format(int(i / 256), i % 256))' \
>>>> |  sudo utilities/ovs-dpctl add-flows -
>>>>
>>>>
>>>> Signed-off-by: Eelco Chaudron <echaudro@redhat.com>
>>>> ---
>>>> v3: - Fixed NEWS section
>>>>     - Added/update mod-flows/del-flows command to support FILE input
>>>>     - Added some selftests
>>>> v2: - Added change to NEWS
>>>>     - Updated man page to be more clear
>>>>
>>>>  NEWS                  |    3 +
>>>>  lib/dpctl.c           |  195 ++++++++++++++++++++++++++++++++++++++++++++-----
>>>>  lib/dpctl.man         |   16 ++++
>>>>  tests/dpctl.at        |   50 +++++++++++++
>>>>  utilities/ovs-dpctl.c |   10 ++-
>>>>  5 files changed, 251 insertions(+), 23 deletions(-)
>>>>
>>>> diff --git a/NEWS b/NEWS
>>>> index 4619e73bf..fed222765 100644
>>>> --- a/NEWS
>>>> +++ b/NEWS
>>>> @@ -7,6 +7,9 @@ Post-v2.14.0
>>>>       * Removed support for vhost-user dequeue zero-copy.
>>>>     - The environment variable OVS_UNBOUND_CONF, if set, is now used
>>>>       as the DNS resolver's (unbound) configuration file.
>>>> +  - ovs-dpctl and 'ovs-appctl dpctl/':
>>>> +    * New commands where added, which allow adding, deleting, or modifying
>>>> +      flows based on information read from a file.
>>>>
>>>
>>> Tested it successfully.
>>
>> Thanks Paolo!
>>
>>> NEWS has conflicts, besides that, it would be nice to add the commands
>>> ({add,mod,del}-flows) explicitly in the description for consistency with
>>> the other dpctl entries.
>>
>> Guess you mean in the NEWS file itself, like:
>>
>> +  - ‘ovs-dpctl {add,mod,del}-flows’ and 'ovs-appctl dpctl/{add,mod,del}-flows':
>> +    * New commands were added, which allow adding, deleting, or modifying
>> +      flows based on information read from a file.
>>
>> Ilya let me know if you need a new revision if this is all that needs changing?
> 
> No need for a new version.  I slightly modified this text, though.
> 
>>
>>> Otherwise,
>>>
>>> Acked-by: Paolo Valerio <pvalerio@redhat.com>
> 
> Thanks!  Applied.
> 
> Best regards, Ilya Maximets.
> 

Ugh.  This patch broke windows build:
lib/dpctl.c(1433): error C4013: 'strndup' undefined; assuming extern returning int

Eelco, could you, please, take a look.

I need to configure appveyor for my account to avoid this kind of issues.
Sorry about that.

Best regards, Ilya Maximets.
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 4619e73bf..fed222765 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,9 @@  Post-v2.14.0
      * Removed support for vhost-user dequeue zero-copy.
    - The environment variable OVS_UNBOUND_CONF, if set, is now used
      as the DNS resolver's (unbound) configuration file.
+  - ovs-dpctl and 'ovs-appctl dpctl/':
+    * New commands where added, which allow adding, deleting, or modifying
+      flows based on information read from a file.
 
 
 v2.14.0 - 17 Aug 2020
diff --git a/lib/dpctl.c b/lib/dpctl.c
index 2f859a753..6fdcfd26f 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -52,6 +52,12 @@ 
 #include "openvswitch/ofp-flow.h"
 #include "openvswitch/ofp-port.h"
 
+enum {
+    DPCTL_FLOWS_ADD = 0,
+    DPCTL_FLOWS_DEL,
+    DPCTL_FLOWS_MOD
+};
+
 typedef int dpctl_command_handler(int argc, const char *argv[],
                                   struct dpctl_params *);
 struct dpctl_command {
@@ -1102,28 +1108,22 @@  out_free:
 }
 
 static int
-dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
-               struct dpctl_params *dpctl_p)
+dpctl_put_flow_dpif(struct dpif *dpif, const char *key_s,
+                    const char *actions_s,
+                    enum dpif_flow_put_flags flags,
+                    struct dpctl_params *dpctl_p)
 {
-    const char *key_s = argv[argc - 2];
-    const char *actions_s = argv[argc - 1];
     struct dpif_flow_stats stats;
     struct dpif_port dpif_port;
     struct dpif_port_dump port_dump;
     struct ofpbuf actions;
     struct ofpbuf key;
     struct ofpbuf mask;
-    struct dpif *dpif;
     ovs_u128 ufid;
     bool ufid_present;
     struct simap port_names;
     int n, error;
 
-    error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
-    if (error) {
-        return error;
-    }
-
     ufid_present = false;
     n = odp_ufid_from_string(key_s, &ufid);
     if (n < 0) {
@@ -1195,6 +1195,24 @@  out_freeactions:
 out_freekeymask:
     ofpbuf_uninit(&mask);
     ofpbuf_uninit(&key);
+    return error;
+}
+
+static int
+dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
+               struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int error;
+
+    error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
+    if (error) {
+        return error;
+    }
+
+    error = dpctl_put_flow_dpif(dpif, argv[argc - 2], argv[argc - 1], flags,
+                                dpctl_p);
+
     dpif_close(dpif);
     return error;
 }
@@ -1268,26 +1286,21 @@  out:
 }
 
 static int
-dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+dpctl_del_flow_dpif(struct dpif *dpif, const char *key_s,
+                    struct dpctl_params *dpctl_p)
 {
-    const char *key_s = argv[argc - 1];
     struct dpif_flow_stats stats;
     struct dpif_port dpif_port;
     struct dpif_port_dump port_dump;
     struct ofpbuf key;
     struct ofpbuf mask; /* To be ignored. */
-    struct dpif *dpif;
+
     ovs_u128 ufid;
     bool ufid_generated;
     bool ufid_present;
     struct simap port_names;
     int n, error;
 
-    error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
-    if (error) {
-        return error;
-    }
-
     ufid_present = false;
     n = odp_ufid_from_string(key_s, &ufid);
     if (n < 0) {
@@ -1353,16 +1366,156 @@  out:
     ofpbuf_uninit(&mask);
     ofpbuf_uninit(&key);
     simap_destroy(&port_names);
+    return error;
+}
+
+static int
+dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+{
+    const char *key_s = argv[argc - 1];
+    struct dpif *dpif;
+    int error;
+
+    error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
+    if (error) {
+        return error;
+    }
+
+    error = dpctl_del_flow_dpif(dpif, key_s, dpctl_p);
+
     dpif_close(dpif);
     return error;
 }
 
+static int
+dpctl_parse_flow_line(int command, struct ds *s, char **flow, char **action)
+{
+    const char *line = ds_cstr(s);
+    size_t len;
+
+    /* First figure out the command, or fallback to FLOWS_ADD. */
+    line += strspn(line, " \t\r\n");
+    len = strcspn(line, ", \t\r\n");
+
+    if (!strncmp(line, "add", len)) {
+         command = DPCTL_FLOWS_ADD;
+    } else if (!strncmp(line, "delete", len)) {
+        command = DPCTL_FLOWS_DEL;
+    } else if (!strncmp(line, "modify", len)) {
+        command = DPCTL_FLOWS_MOD;
+    } else {
+        len = 0;
+    }
+    line += len;
+
+    /* Isolate flow and action (for add/modify). */
+    line += strspn(line, " \t\r\n");
+    len = strcspn(line, " \t\r\n");
+
+    if (len == 0) {
+        *flow = NULL;
+        *action = NULL;
+        return command;
+    }
+
+    *flow = strndup(line, len);
+
+    line += len;
+    line += strspn(line, " \t\r\n");
+    if (strlen(line)) {
+        *action = xstrdup(line);
+    } else {
+        *action = NULL;
+    }
+
+    return command;
+}
+
+static int
+dpctl_process_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
+{
+    const char *file_name = argv[argc - 1];
+    int line_number = 0;
+    struct dpif *dpif;
+    struct ds line;
+    FILE *stream;
+    int error;
+    int def_cmd = DPCTL_FLOWS_ADD;
+
+    if (strstr(argv[0], "mod-flows")) {
+        def_cmd = DPCTL_FLOWS_MOD;
+    } else if (strstr(argv[0], "del-flows")) {
+        def_cmd = DPCTL_FLOWS_DEL;
+    }
+
+    error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
+    if (error) {
+        return error;
+    }
+
+    stream = !strcmp(file_name, "-") ? stdin : fopen(file_name, "r");
+    if (!stream) {
+        error = errno;
+        dpctl_error(dpctl_p, error, "Opening file \"%s\" failed", file_name);
+        goto out_close_dpif;
+    }
+
+    ds_init(&line);
+    while (!ds_get_preprocessed_line(&line, stream, &line_number)) {
+        /* We do not process all the lines first and then execute the actions
+         * as we would like to take commands as a continuous stream of
+         * commands from stdin.
+         */
+        char *flow = NULL;
+        char *action = NULL;
+        int cmd = dpctl_parse_flow_line(def_cmd, &line, &flow, &action);
+
+        if ((!flow && !action)
+            || ((cmd == DPCTL_FLOWS_ADD || cmd == DPCTL_FLOWS_MOD) && !action)
+            || (cmd == DPCTL_FLOWS_DEL && action)) {
+            dpctl_error(dpctl_p, 0,
+                        "Failed parsing line number %u, skipped!",
+                        line_number);
+        } else {
+            switch (cmd) {
+            case DPCTL_FLOWS_ADD:
+                dpctl_put_flow_dpif(dpif, flow, action,
+                                    DPIF_FP_CREATE, dpctl_p);
+                break;
+            case DPCTL_FLOWS_MOD:
+                dpctl_put_flow_dpif(dpif, flow, action,
+                                    DPIF_FP_MODIFY, dpctl_p);
+                break;
+            case DPCTL_FLOWS_DEL:
+                dpctl_del_flow_dpif(dpif, flow, dpctl_p);
+                break;
+            }
+        }
+
+        free(flow);
+        free(action);
+    }
+
+    ds_destroy(&line);
+    if (stream != stdin) {
+        fclose(stream);
+    }
+out_close_dpif:
+    dpif_close(dpif);
+    return 0;
+}
+
 static int
 dpctl_del_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
 {
     struct dpif *dpif;
+    int error;
 
-    int error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
+    if ((!dp_arg_exists(argc, argv) && argc == 2) || argc > 2) {
+        return dpctl_process_flows(argc, argv, dpctl_p);
+    }
+
+    error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
     if (error) {
         return error;
     }
@@ -2528,7 +2681,9 @@  static const struct dpctl_command all_commands[] = {
     { "mod-flow", "[dp] flow actions", 2, 3, dpctl_mod_flow, DP_RW },
     { "get-flow", "[dp] ufid", 1, 2, dpctl_get_flow, DP_RO },
     { "del-flow", "[dp] flow", 1, 2, dpctl_del_flow, DP_RW },
-    { "del-flows", "[dp]", 0, 1, dpctl_del_flows, DP_RW },
+    { "add-flows", "[dp] file", 1, 2, dpctl_process_flows, DP_RW },
+    { "mod-flows", "[dp] file", 1, 2, dpctl_process_flows, DP_RW },
+    { "del-flows", "[dp] [file]", 0, 2, dpctl_del_flows, DP_RW },
     { "dump-conntrack", "[dp] [zone=N]", 0, 2, dpctl_dump_conntrack, DP_RO },
     { "flush-conntrack", "[dp] [zone=N] [ct-tuple]", 0, 3,
       dpctl_flush_conntrack, DP_RW },
diff --git a/lib/dpctl.man b/lib/dpctl.man
index 727d1f7be..50f7379a8 100644
--- a/lib/dpctl.man
+++ b/lib/dpctl.man
@@ -194,6 +194,22 @@  ovs-dpctl add-flow myDP \\
 .
 .RE
 .TP
+\*(DX\fBadd\-flows\fR [\fIdp\fR] \fIfile\fR
+.TQ
+\*(DX\fBmod\-flows\fR [\fIdp\fR] \fIfile\fR
+.TQ
+\*(DX\fBdel\-flows\fR [\fIdp\fR] \fIfile\fR
+Reads flow entries from \fIfile\fR (or \fBstdin\fR if \fIfile\fR is
+\fB\-\fR) and adds, modifies, or deletes each entry to the datapath.
+.
+Each flow specification (e.g., each line in \fIfile\fR) may start with
+\fBadd\fR, \fBmodify\fR, or \fBdelete\fR keyword to specify whether a
+flow is to be added, modified, or deleted. A flow specification without
+one of these keywords is treated based on the used command.  All flow
+modifications are executed as individual transactions in the order
+specified.
+.
+.TP
 .DO "[\fB\-s\fR | \fB\-\-statistics\fR]" "\*(DX\fBdel\-flow\fR" "[\fIdp\fR] \fIflow\fR"
 Deletes the flow from \fIdp\fR's flow table that matches \fIflow\fR.
 If \fB\-s\fR or \fB\-\-statistics\fR is specified, then
diff --git a/tests/dpctl.at b/tests/dpctl.at
index deec54959..7454a51ec 100644
--- a/tests/dpctl.at
+++ b/tests/dpctl.at
@@ -85,3 +85,53 @@  OVS_VSWITCHD_STOP(["/dummy@br0: port_del failed/d
 /dummy@br0: failed to add vif1.0 as port/d
 /Dropped 1 log messages in last/d"])
 AT_CLEANUP
+
+AT_SETUP([dpctl - add/mod/del-flows])
+OVS_VSWITCHD_START
+AT_CHECK([ovs-appctl dpctl/add-dp dummy@br0])
+AT_DATA([flows.txt], [dnl
+in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234) 2
+])
+AT_CHECK([ovs-appctl dpctl/add-flows dummy@br0 flows.txt], [0], [dnl
+])
+AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
+flow-dump from the main thread:
+recirc_id(0),in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234), packets:0, bytes:0, used:never, actions:2
+])
+AT_DATA([flows.txt], [dnl
+in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234) 3
+])
+AT_CHECK([ovs-appctl dpctl/mod-flows dummy@br0 flows.txt], [0], [dnl
+])
+AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
+flow-dump from the main thread:
+recirc_id(0),in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234), packets:0, bytes:0, used:never, actions:3
+])
+AT_DATA([flows.txt], [dnl
+in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234)
+])
+AT_CHECK([ovs-appctl dpctl/del-flows dummy@br0 flows.txt], [0], [dnl
+])
+AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
+])
+AT_DATA([flows.txt], [dnl
+add in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234) 2
+add in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:03),eth_type(0x1234) 2
+add in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:04),eth_type(0x1234) 2
+modify in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234) 1
+delete in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:03),eth_type(0x1234)
+])
+AT_CHECK([ovs-appctl dpctl/add-flows dummy@br0 flows.txt], [0], [dnl
+])
+AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
+flow-dump from the main thread:
+recirc_id(0),in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:02),eth_type(0x1234), packets:0, bytes:0, used:never, actions:1
+recirc_id(0),in_port(1),eth(src=00:00:00:00:00:01,dst=00:00:00:00:00:04),eth_type(0x1234), packets:0, bytes:0, used:never, actions:2
+])
+AT_CHECK([ovs-appctl dpctl/del-flows dummy@br0], [0], [dnl
+])
+AT_CHECK([ovs-appctl dpctl/dump-flows dummy@br0 | sort], [0], [dnl
+])
+AT_CHECK([ovs-appctl dpctl/del-dp dummy@br0])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
index 7d99607f4..f616995c3 100644
--- a/utilities/ovs-dpctl.c
+++ b/utilities/ovs-dpctl.c
@@ -191,10 +191,13 @@  usage(void *userdata OVS_UNUSED)
            "  show DP...               show basic info on each DP\n"
            "  dump-flows [DP]          display flows in DP\n"
            "  add-flow [DP] FLOW ACTIONS add FLOW with ACTIONS to DP\n"
+           "  add-flows [DP] FILE        add flows from FILE\n"
            "  mod-flow [DP] FLOW ACTIONS change FLOW actions to ACTIONS in DP\n"
+           "  mod-flows [DP] FILE        change flows from FILE\n"
            "  get-flow [DP] ufid:UFID    fetch flow corresponding to UFID\n"
            "  del-flow [DP] FLOW         delete FLOW from DP\n"
-           "  del-flows [DP]             delete all flows from DP\n"
+           "  del-flows [DP] [FILE]      " \
+               "delete all or specified flows from DP\n"
            "  dump-conntrack [DP] [zone=ZONE]  " \
                "display conntrack entries for ZONE\n"
            "  flush-conntrack [DP] [zone=ZONE] [ct-tuple]" \
@@ -205,8 +208,9 @@  usage(void *userdata OVS_UNUSED)
            "Each IFACE on add-dp, add-if, and set-if may be followed by\n"
            "comma-separated options.  See ovs-dpctl(8) for syntax, or the\n"
            "Interface table in ovs-vswitchd.conf.db(5) for an options list.\n"
-           "For COMMAND dump-flows, add-flow, mod-flow, del-flow and\n"
-           "del-flows, DP is optional if there is only one datapath.\n",
+           "For COMMAND dump-flows, add-flow, add-flows, mod-flow,\n"
+           "mod-flows, del-flow and del-flows, DP is optional if there is\n"
+           "only one datapath.\n",
            program_name, program_name);
     vlog_usage();
     printf("\nOptions for show and mod-flow:\n"