diff mbox

[ovs-dev,v2,1/2] ovn-controller: Tie OpenFlow and logical flows using OpenFlow cookie.

Message ID 20161210065633.32634-2-blp@ovn.org
State Superseded
Headers show

Commit Message

Ben Pfaff Dec. 10, 2016, 6:56 a.m. UTC
This makes it easy to find the logical flow that generated a particular
OpenFlow flow, by running "ovn-sbctl dump-flows <cookie>".

Later, this can be refined (and automated for "ofproto/trace"), but this
is still a significant advance.

Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 lib/uuid.c                   |  22 +++++++-
 lib/uuid.h                   |   3 +-
 ovn/controller/lflow.c       |   6 +-
 ovn/controller/ofctrl.c      |   6 +-
 ovn/controller/ofctrl.h      |   2 +-
 ovn/controller/physical.c    |  34 ++++++------
 ovn/utilities/ovn-sbctl.8.in |  13 ++++-
 ovn/utilities/ovn-sbctl.c    | 129 ++++++++++++++++++++++++++++++++++++-------
 8 files changed, 169 insertions(+), 46 deletions(-)

Comments

Gurucharan Shetty Dec. 12, 2016, 8:59 p.m. UTC | #1
On 9 December 2016 at 22:56, Ben Pfaff <blp@ovn.org> wrote:

> This makes it easy to find the logical flow that generated a particular
> OpenFlow flow, by running "ovn-sbctl dump-flows <cookie>".
>
> Later, this can be refined (and automated for "ofproto/trace"), but this
> is still a significant advance.
>
> Signed-off-by: Ben Pfaff <blp@ovn.org>
> ---
>  lib/uuid.c                   |  22 +++++++-
>  lib/uuid.h                   |   3 +-
>  ovn/controller/lflow.c       |   6 +-
>  ovn/controller/ofctrl.c      |   6 +-
>  ovn/controller/ofctrl.h      |   2 +-
>  ovn/controller/physical.c    |  34 ++++++------
>  ovn/utilities/ovn-sbctl.8.in |  13 ++++-
>  ovn/utilities/ovn-sbctl.c    | 129 ++++++++++++++++++++++++++++++
> ++++++-------
>  8 files changed, 169 insertions(+), 46 deletions(-)
>
> diff --git a/lib/uuid.c b/lib/uuid.c
> index 0f2a58e..bd98d40 100644
> --- a/lib/uuid.c
> +++ b/lib/uuid.c
> @@ -1,4 +1,4 @@
> -/* Copyright (c) 2008, 2009, 2010, 2011, 2013 Nicira, Inc.
> +/* Copyright (c) 2008, 2009, 2010, 2011, 2013, 2016 Nicira, Inc.
>   *
>   * Licensed under the Apache License, Version 2.0 (the "License");
>   * you may not use this file except in compliance with the License.
> @@ -210,6 +210,26 @@ error:
>      uuid_zero(uuid);
>      return false;
>  }
> +
> +/* Returns the number of characters at the beginning of 's' that are
> valid for
> + * a UUID.  For example, the "123" at the beginning of "123xyzzy" could
> begin a
> + * UUID, so uuid_is_partial_string() would return 3; for "xyzzy", this
> function
> + * would return 0, since "x" can't start a UUID. */
> +int
> +uuid_is_partial_string(const char *s)
> +{
> +    static const char tmpl[UUID_LEN] = "xxxxxxxx-xxxx-xxxx-xxxx-
> xxxxxxxxxxxx";
> +    size_t i;
> +    for (i = 0; i < UUID_LEN; i++) {
> +        if (tmpl[i] == 'x'
> +            ? hexit_value(s[i]) < 0
> +            : s[i] != '-') {
> +            break;
> +        }
> +    }
> +    return i;
> +}
> +
>
>  static void
>  sha1_update_int(struct sha1_ctx *sha1_ctx, uintmax_t x)
> diff --git a/lib/uuid.h b/lib/uuid.h
> index a5792b0..113574c 100644
> --- a/lib/uuid.h
> +++ b/lib/uuid.h
> @@ -1,4 +1,4 @@
> -/* Copyright (c) 2008, 2009, 2010 Nicira, Inc.
> +/* Copyright (c) 2008, 2009, 2010, 2016 Nicira, Inc.
>   *
>   * Licensed under the Apache License, Version 2.0 (the "License");
>   * you may not use this file except in compliance with the License.
> @@ -61,6 +61,7 @@ bool uuid_is_zero(const struct uuid *);
>  int uuid_compare_3way(const struct uuid *, const struct uuid *);
>  bool uuid_from_string(struct uuid *, const char *);
>  bool uuid_from_string_prefix(struct uuid *, const char *);
> +int uuid_is_partial_string(const char *);
>  void uuid_set_bits_v4(struct uuid *);
>
>  #endif /* uuid.h */
> diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
> index 4e67365..5f49183 100644
> --- a/ovn/controller/lflow.c
> +++ b/ovn/controller/lflow.c
> @@ -302,7 +302,7 @@ consider_logical_flow(const struct lport_index *lports,
>          }
>          if (!m->n) {
>              ofctrl_add_flow(flow_table, ptable, lflow->priority,
> &m->match,
> -                            &ofpacts);
> +                            lflow->header_.uuid.parts[0], &ofpacts);
>          } else {
>              uint64_t conj_stubs[64 / 8];
>              struct ofpbuf conj;
> @@ -318,7 +318,7 @@ consider_logical_flow(const struct lport_index *lports,
>                  dst->n_clauses = src->n_clauses;
>              }
>              ofctrl_add_flow(flow_table, ptable, lflow->priority,
> &m->match,
> -                            &conj);
> +                            0, &conj);
>

If I understand it right, a logical flow of the form ip4.src == {a, b, c}
after it is split into multiple openflow flows won't have a cookie?


>              ofpbuf_uninit(&conj);
>          }
>      }
> @@ -386,7 +386,7 @@ consider_neighbor_flow(const struct lport_index
> *lports,
>      uint64_t stub[1024 / 8];
>      struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
>      put_load(mac.ea, sizeof mac.ea, MFF_ETH_DST, 0, 48, &ofpacts);
> -    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, &match,
> &ofpacts);
> +    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, &match, 0,
> &ofpacts);
>      ofpbuf_uninit(&ofpacts);
>  }
>
> diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c
> index 1d8fbf3..7449293 100644
> --- a/ovn/controller/ofctrl.c
> +++ b/ovn/controller/ofctrl.c
> @@ -57,6 +57,7 @@ struct ovn_flow {
>      /* Data. */
>      struct ofpact *ofpacts;
>      size_t ofpacts_len;
> +    uint64_t cookie;
>  };
>
>  static uint32_t ovn_flow_hash(const struct ovn_flow *);
> @@ -602,7 +603,8 @@ ofctrl_recv(const struct ofp_header *oh, enum ofptype
> type)
>  void
>  ofctrl_add_flow(struct hmap *desired_flows,
>                  uint8_t table_id, uint16_t priority,
> -                const struct match *match, const struct ofpbuf *actions)
> +                const struct match *match, uint64_t cookie,
> +                const struct ofpbuf *actions)
>  {
>      struct ovn_flow *f = xmalloc(sizeof *f);
>      f->table_id = table_id;
> @@ -611,6 +613,7 @@ ofctrl_add_flow(struct hmap *desired_flows,
>      f->ofpacts = xmemdup(actions->data, actions->size);
>      f->ofpacts_len = actions->size;
>      f->hmap_node.hash = ovn_flow_hash(f);
> +    f->cookie = cookie;
>
>      if (ovn_flow_lookup(desired_flows, f)) {
>          static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
> @@ -935,6 +938,7 @@ ofctrl_put(struct hmap *flow_table, struct shash
> *pending_ct_zones,
>              .table_id = d->table_id,
>              .ofpacts = d->ofpacts,
>              .ofpacts_len = d->ofpacts_len,
> +            .new_cookie = htonll(d->cookie),
>              .command = OFPFC_ADD,
>          };
>          add_flow_mod(&fm, &msgs);
> diff --git a/ovn/controller/ofctrl.h b/ovn/controller/ofctrl.h
> index 5308b61..67d6c59 100644
> --- a/ovn/controller/ofctrl.h
> +++ b/ovn/controller/ofctrl.h
> @@ -46,7 +46,7 @@ void ofctrl_ct_flush_zone(uint16_t zone_id);
>  /* Flow table interfaces to the rest of ovn-controller. */
>  void ofctrl_add_flow(struct hmap *desired_flows, uint8_t table_id,
>                       uint16_t priority, const struct match *,
> -                     const struct ofpbuf *ofpacts);
> +                     uint64_t cookie, const struct ofpbuf *ofpacts);
>
>  void ofctrl_flow_table_clear(void);
>
> diff --git a/ovn/controller/physical.c b/ovn/controller/physical.c
> index 48adb78..17a4126 100644
> --- a/ovn/controller/physical.c
> +++ b/ovn/controller/physical.c
> @@ -331,7 +331,7 @@ consider_port_binding(enum mf_field_id mff_ovn_geneve,
>          /* Resubmit to first logical ingress pipeline table. */
>          put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, ofpacts_p);
>          ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG,
> -                        tag ? 150 : 100, &match, ofpacts_p);
> +                        tag ? 150 : 100, &match, 0, ofpacts_p);
>
>          if (!tag && (!strcmp(binding->type, "localnet")
>                       || !strcmp(binding->type, "l2gateway"))) {
> @@ -341,7 +341,7 @@ consider_port_binding(enum mf_field_id mff_ovn_geneve,
>               * action. */
>              ofpbuf_pull(ofpacts_p, ofpacts_orig_size);
>              match_set_dl_tci_masked(&match, 0, htons(VLAN_CFI));
> -            ofctrl_add_flow(flow_table, 0, 100, &match, ofpacts_p);
> +            ofctrl_add_flow(flow_table, 0, 100, &match, 0, ofpacts_p);
>          }
>
>          /* Table 33, priority 100.
> @@ -372,7 +372,7 @@ consider_port_binding(enum mf_field_id mff_ovn_geneve,
>          /* Resubmit to table 34. */
>          put_resubmit(OFTABLE_CHECK_LOOPBACK, ofpacts_p);
>          ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100,
> -                        &match, ofpacts_p);
> +                        &match, 0, ofpacts_p);
>
>          /* Table 34, Priority 100.
>           * =======================
> @@ -387,7 +387,7 @@ consider_port_binding(enum mf_field_id mff_ovn_geneve,
>          match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0, port_key);
>          match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
>          ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 100,
> -                        &match, ofpacts_p);
> +                        &match, 0, ofpacts_p);
>
>          /* Table 64, Priority 100.
>           * =======================
> @@ -411,7 +411,7 @@ consider_port_binding(enum mf_field_id mff_ovn_geneve,
>          put_resubmit(OFTABLE_LOG_TO_PHY, ofpacts_p);
>          put_stack(MFF_IN_PORT, ofpact_put_STACK_POP(ofpacts_p));
>          ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 100,
> -                        &match, ofpacts_p);
> +                        &match, 0, ofpacts_p);
>
>          /* Table 65, Priority 100.
>           * =======================
> @@ -438,7 +438,7 @@ consider_port_binding(enum mf_field_id mff_ovn_geneve,
>              ofpact_put_STRIP_VLAN(ofpacts_p);
>          }
>          ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100,
> -                        &match, ofpacts_p);
> +                        &match, 0, ofpacts_p);
>      } else if (!tun) {
>          /* Remote port connected by localnet port */
>          /* Table 33, priority 100.
> @@ -461,7 +461,7 @@ consider_port_binding(enum mf_field_id mff_ovn_geneve,
>          /* Resubmit to table 33. */
>          put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p);
>          ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100,
> -                        &match, ofpacts_p);
> +                        &match, 0, ofpacts_p);
>      } else {
>          /* Remote port connected by tunnel */
>
> @@ -486,7 +486,7 @@ consider_port_binding(enum mf_field_id mff_ovn_geneve,
>
>          /* Resubmit to table 33. */
>          put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p);
> -        ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, &match,
> +        ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, &match, 0,
>                          ofpacts_p);
>
>
> @@ -503,7 +503,7 @@ consider_port_binding(enum mf_field_id mff_ovn_geneve,
>          /* Output to tunnel. */
>          ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
>          ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100,
> -                        &match, ofpacts_p);
> +                        &match, 0, ofpacts_p);
>      }
>  }
>
> @@ -583,7 +583,7 @@ consider_mc_group(enum mf_field_id mff_ovn_geneve,
>          put_load(mc->tunnel_key, MFF_LOG_OUTPORT, 0, 32, ofpacts_p);
>
>          ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100,
> -                        &match, ofpacts_p);
> +                        &match, 0, ofpacts_p);
>      }
>
>      /* Table 32, priority 100.
> @@ -621,7 +621,7 @@ consider_mc_group(enum mf_field_id mff_ovn_geneve,
>                  put_resubmit(OFTABLE_LOCAL_OUTPUT, remote_ofpacts_p);
>              }
>              ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100,
> -                            &match, remote_ofpacts_p);
> +                            &match, 0, remote_ofpacts_p);
>          }
>      }
>      sset_destroy(&remote_chassis);
> @@ -833,7 +833,8 @@ physical_run(struct controller_ctx *ctx, enum
> mf_field_id mff_ovn_geneve,
>
>          put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
>
> -        ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, &match,
> &ofpacts);
> +        ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, &match, 0,
> +                        &ofpacts);
>      }
>
>      /* Add flows for VXLAN encapsulations.  Due to the limited amount of
> @@ -865,7 +866,7 @@ physical_run(struct controller_ctx *ctx, enum
> mf_field_id mff_ovn_geneve,
>              put_load(1, MFF_LOG_FLAGS, MLF_RCV_FROM_VXLAN_BIT, 1,
> &ofpacts);
>              put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, &ofpacts);
>
> -            ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, &match,
> +            ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, &match,
> 0,
>                              &ofpacts);
>          }
>      }
> @@ -879,7 +880,7 @@ physical_run(struct controller_ctx *ctx, enum
> mf_field_id mff_ovn_geneve,
>      match_init_catchall(&match);
>      ofpbuf_clear(&ofpacts);
>      put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
> -    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 0, &match,
> &ofpacts);
> +    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 0, &match, 0,
> &ofpacts);
>
>      /* Table 34, Priority 0.
>       * =======================
> @@ -893,7 +894,8 @@ physical_run(struct controller_ctx *ctx, enum
> mf_field_id mff_ovn_geneve,
>          put_load(0, MFF_REG0 + i, 0, 32, &ofpacts);
>      }
>      put_resubmit(OFTABLE_LOG_EGRESS_PIPELINE, &ofpacts);
> -    ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 0, &match,
> &ofpacts);
> +    ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 0, &match, 0,
> +                    &ofpacts);
>
>      /* Table 64, Priority 0.
>       * =======================
> @@ -903,7 +905,7 @@ physical_run(struct controller_ctx *ctx, enum
> mf_field_id mff_ovn_geneve,
>      match_init_catchall(&match);
>      ofpbuf_clear(&ofpacts);
>      put_resubmit(OFTABLE_LOG_TO_PHY, &ofpacts);
> -    ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 0, &match, &ofpacts);
> +    ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 0, &match, 0,
> &ofpacts);
>
>      ofpbuf_uninit(&ofpacts);
>
> diff --git a/ovn/utilities/ovn-sbctl.8.in b/ovn/utilities/ovn-sbctl.8.in
> index 458a643..2f274ae 100644
> --- a/ovn/utilities/ovn-sbctl.8.in
> +++ b/ovn/utilities/ovn-sbctl.8.in
> @@ -159,9 +159,18 @@ to unbind logical port that is not bound has no
> effect.
>  .
>  .SS "Logical Flow Commands"
>  .
> -.IP "\fBlflow\-list\fR [\fIlogical-datapath\fR]"
> +.IP "\fBlflow\-list\fR [\fIlogical-datapath\fR] [\fIlflow\fR...]"
>  List logical flows.  If \fIlogical-datapath\fR is specified, only list
> -flows for that logical datapath.
> +flows for that logical datapath.  The \fIlogical-datapath\fR may be
> +given as a UUID or as a datapath name.
> +.IP
> +If at least one \fIlflow\fR is given, only matching logical flows, if
> +any, are listed.  Each \fIlflow\fR may be specified as a UUID or the
> +first few characters of a UUID, optionally prefixed by \fB0x\fR.
> +(Because \fBovn\-controller\fR sets OpenFlow flow cookies to the first
> +32 bits of the corresponding logical flow's UUID, this makes it easy
> +to look up the logical flow that generated a particular OpenFlow
> +flow.)
>  .
>  .IP "\fBdump\-flows\fR [\fIlogical-datapath\fR]"
>  Alias for \fBlflow\-list\fB.
> diff --git a/ovn/utilities/ovn-sbctl.c b/ovn/utilities/ovn-sbctl.c
> index 0763b79..f51a2ed 100644
> --- a/ovn/utilities/ovn-sbctl.c
> +++ b/ovn/utilities/ovn-sbctl.c
> @@ -306,8 +306,8 @@ Port binding commands:\n\
>    lsp-unbind PORT             reset the port binding of logical port
> PORT\n\
>  \n\
>  Logical flow commands:\n\
> -  lflow-list [DATAPATH]       List logical flows for all or a single
> datapath\n\
> -  dump-flows [DATAPATH]       Alias for lflow-list\n\
> +  lflow-list [DATAPATH] [LFLOW...] List logical flows for DATAPATH\n\
> +  dump-flows [DATAPATH] [LFLOW...] Alias for lflow-list\n\
>  \n\
>  %s\
>  \n\
> @@ -687,53 +687,138 @@ lflow_cmp(const void *lf1_, const void *lf2_)
>      return 0;
>  }
>
> +static char *
> +parse_partial_uuid(char *s)
> +{
> +    /* Accept a full or partial UUID. */
> +    if (uuid_is_partial_string(s) == strlen(s)) {
> +        return s;
> +    }
> +
> +    /* Accept a full or partial UUID prefixed by 0x, since "ovs-ofctl
> +     * dump-flows" prints cookies prefixed by 0x. */
> +    if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
> +        && uuid_is_partial_string(s + 2) == strlen(s + 2)) {
> +        return s + 2;
> +    }
> +
> +    /* Not a (partial) UUID. */
> +    return NULL;
> +}
> +
> +static bool
> +is_partial_uuid_match(const struct uuid *uuid, const char *match)
> +{
> +    char uuid_s[UUID_LEN + 1];
> +    snprintf(uuid_s, sizeof uuid_s, UUID_FMT, UUID_ARGS(uuid));
> +
> +    return !strncmp(uuid_s, match, strlen(match));
> +}
> +
> +static const struct sbrec_datapath_binding *
> +lookup_datapath(struct ovsdb_idl *idl, const char *s)
> +{
> +    const struct sbrec_datapath_binding *datapath;
> +
> +    struct uuid uuid;
> +    if (uuid_from_string(&uuid, s)) {
> +        datapath = sbrec_datapath_binding_get_for_uuid(idl, &uuid);
> +        if (datapath) {
> +            return datapath;
> +        }
> +    }
> +
> +    SBREC_DATAPATH_BINDING_FOR_EACH (datapath, idl) {
> +        const char *name = smap_get(&datapath->external_ids, "name");
> +        if (name && !strcmp(name, s)) {
> +            return datapath;
> +        }
> +    }
> +
> +    return NULL;
> +}
> +
>  static void
>  cmd_lflow_list(struct ctl_context *ctx)
>  {
> -    const char *datapath = ctx->argc == 2 ? ctx->argv[1] : NULL;
> -    struct uuid datapath_uuid = { .parts = { 0, }};
> -    const struct sbrec_logical_flow **lflows;
> -    const struct sbrec_logical_flow *lflow;
> -    size_t n_flows = 0, n_capacity = 64;
> +    const struct sbrec_datapath_binding *datapath = NULL;
> +    if (ctx->argc > 1) {
> +        datapath = lookup_datapath(ctx->idl, ctx->argv[1]);
> +        if (datapath) {
> +            ctx->argc--;
> +            ctx->argv++;
> +        }
> +    }
>
> -    if (datapath && !uuid_from_string(&datapath_uuid, datapath)) {
> -        VLOG_ERR("Invalid format of datapath UUID");
> -        return;
> +    for (size_t i = 1; i < ctx->argc; i++) {
> +        char *s = parse_partial_uuid(ctx->argv[i]);
> +        if (!s) {
> +            ctl_fatal("%s is not a UUID or the beginning of a UUID",
> +                      ctx->argv[i]);
> +        }
> +        ctx->argv[i] = s;
>      }
>
> -    lflows = xmalloc(sizeof *lflows * n_capacity);
> +    const struct sbrec_logical_flow **lflows = NULL;
> +    size_t n_flows = 0;
> +    size_t n_capacity = 0;
> +    const struct sbrec_logical_flow *lflow;
>      SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->idl) {
> +        if (datapath && lflow->logical_datapath != datapath) {
> +            continue;
> +        }
> +
>          if (n_flows == n_capacity) {
>              lflows = x2nrealloc(lflows, &n_capacity, sizeof *lflows);
>          }
>          lflows[n_flows] = lflow;
>          n_flows++;
>      }
> -
>      qsort(lflows, n_flows, sizeof *lflows, lflow_cmp);
>
> -    const char *cur_pipeline = "";
> -    size_t i;
> -    for (i = 0; i < n_flows; i++) {
> +    const struct sbrec_logical_flow *prev = NULL;
> +    for (size_t i = 0; i < n_flows; i++) {
>          lflow = lflows[i];
> -        if (datapath && !uuid_equals(&datapath_uuid,
> -                                     &lflow->logical_datapath->header_.uuid))
> {
> +
> +        /* Figure out whether to print this particular flow.  By default,
> we
> +         * print all flows, but if any UUIDs were listed on the command
> line
> +         * then we only print the matching ones. */
> +        bool include;
> +        if (ctx->argc > 1) {
> +            include = false;
> +            for (size_t j = 1; j < ctx->argc; j++) {
> +                if (is_partial_uuid_match(&lflow->header_.uuid,
> +                                          ctx->argv[j])) {
> +                    include = true;
> +                    break;
> +                }
> +            }
> +        } else {
> +            include = true;
> +        }
> +        if (!include) {
>              continue;
>          }
> -        if (strcmp(cur_pipeline, lflow->pipeline)) {
> +
> +        /* Print a header line for this datapath or pipeline, if we
> haven't
> +         * already done so. */
> +        if (!prev
> +            || prev->logical_datapath != lflow->logical_datapath
> +            || strcmp(prev->pipeline, lflow->pipeline)) {
>              printf("Datapath: \"%s\" ("UUID_FMT")  Pipeline: %s\n",
>                     smap_get_def(&lflow->logical_datapath->external_ids,
>                                  "name", ""),
>                     UUID_ARGS(&lflow->logical_datapath->header_.uuid),
>                     lflow->pipeline);
> -            cur_pipeline = lflow->pipeline;
>          }
>
> +        /* Print the flow. */
>          printf("  table=%-2" PRId64 "(%-19s), priority=%-5" PRId64
>                 ", match=(%s), action=(%s)\n",
>                 lflow->table_id,
>                 smap_get_def(&lflow->external_ids, "stage-name", ""),
>                 lflow->priority, lflow->match, lflow->actions);
>

I wonder whether we should also print extenral_ids:source in the dump-flows
output?

Acked-by: Gurucharan Shetty <guru@ovn.org>



> +        prev = lflow;
>      }
>
>      free(lflows);
> @@ -1039,9 +1124,11 @@ static const struct ctl_command_syntax
> sbctl_commands[] = {
>       "--if-exists", RW},
>
>      /* Logical flow commands */
> -    {"lflow-list", 0, 1, "[DATAPATH]", pre_get_info, cmd_lflow_list, NULL,
> +    {"lflow-list", 0, INT_MAX, "[DATAPATH] [LFLOW...]",
> +     pre_get_info, cmd_lflow_list, NULL,
>       "", RO},
> -    {"dump-flows", 0, 1, "[DATAPATH]", pre_get_info, cmd_lflow_list, NULL,
> +    {"dump-flows", 0, INT_MAX, "[DATAPATH] [LFLOW...]",
> +     pre_get_info, cmd_lflow_list, NULL,
>       "", RO}, /* Friendly alias for lflow-list */
>
>      /* SSL commands (To Be Added). */
> --
> 2.10.2
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
Ben Pfaff Dec. 12, 2016, 9:31 p.m. UTC | #2
On Mon, Dec 12, 2016 at 12:59:06PM -0800, Guru Shetty wrote:
> On 9 December 2016 at 22:56, Ben Pfaff <blp@ovn.org> wrote:
> 
> > This makes it easy to find the logical flow that generated a particular
> > OpenFlow flow, by running "ovn-sbctl dump-flows <cookie>".
> >
> > Later, this can be refined (and automated for "ofproto/trace"), but this
> > is still a significant advance.
> >
> > Signed-off-by: Ben Pfaff <blp@ovn.org>

...

> > diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
> > index 4e67365..5f49183 100644
> > --- a/ovn/controller/lflow.c
> > +++ b/ovn/controller/lflow.c
> > @@ -302,7 +302,7 @@ consider_logical_flow(const struct lport_index *lports,
> >          }
> >          if (!m->n) {
> >              ofctrl_add_flow(flow_table, ptable, lflow->priority,
> > &m->match,
> > -                            &ofpacts);
> > +                            lflow->header_.uuid.parts[0], &ofpacts);
> >          } else {
> >              uint64_t conj_stubs[64 / 8];
> >              struct ofpbuf conj;
> > @@ -318,7 +318,7 @@ consider_logical_flow(const struct lport_index *lports,
> >                  dst->n_clauses = src->n_clauses;
> >              }
> >              ofctrl_add_flow(flow_table, ptable, lflow->priority,
> > &m->match,
> > -                            &conj);
> > +                            0, &conj);
> >
> 
> If I understand it right, a logical flow of the form ip4.src == {a, b, c}
> after it is split into multiple openflow flows won't have a cookie?

Such a flow generates OpenFlow flows that look something like this, if
necessary (not actually in this case since there's only one field):

    ip,nw_src=a, actions=conjunction(123)
    ip,nw_src=b, actions=conjunction(123)
    ip,nw_src=c, actions=conjunction(123)
    conj_id=123, actions=...real actions...

In such a case, the flows with "conjunction" actions indeed will not get
a cookie, but really they can't because these might be shared by
multiple logical flows.  That is, if another OVN logical flow comes
along with ip4.src == {c,d,e} and other criteria, then you'd end up
with:

    ip,nw_src=a, actions=conjunction(123)
    ip,nw_src=b, actions=conjunction(123)
    ip,nw_src=c, actions=conjunction(123),conjunction(456)
    ip,nw_src=d, actions=conjunction(456)
    ip,nw_src=e, actions=conjunction(456)
    conj_id=123, actions=...real actions...
    conj_id=456, actions=...real actions...

and so in a case like that, which cookie should get used?

The flows with "conjunction" actions won't ever show up in a trace,
because they direct packet classification and never get executed like
normal actions.

The text added to the ovn-trace manpage in patch 2/2 describes this,
although it doesn't have an example like above.

> > +        /* Print the flow. */
> >          printf("  table=%-2" PRId64 "(%-19s), priority=%-5" PRId64
> >                 ", match=(%s), action=(%s)\n",
> >                 lflow->table_id,
> >                 smap_get_def(&lflow->external_ids, "stage-name", ""),
> >                 lflow->priority, lflow->match, lflow->actions);
> >
> 
> I wonder whether we should also print extenral_ids:source in the dump-flows
> output?

Maybe.  These lines are already pretty wide though.

> Acked-by: Gurucharan Shetty <guru@ovn.org>

Thanks!  I'm going to ask Justin whether he wants to review this before
I push it, because I know that he has opinions on how to provide this
extra information.
diff mbox

Patch

diff --git a/lib/uuid.c b/lib/uuid.c
index 0f2a58e..bd98d40 100644
--- a/lib/uuid.c
+++ b/lib/uuid.c
@@ -1,4 +1,4 @@ 
-/* Copyright (c) 2008, 2009, 2010, 2011, 2013 Nicira, Inc.
+/* Copyright (c) 2008, 2009, 2010, 2011, 2013, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -210,6 +210,26 @@  error:
     uuid_zero(uuid);
     return false;
 }
+
+/* Returns the number of characters at the beginning of 's' that are valid for
+ * a UUID.  For example, the "123" at the beginning of "123xyzzy" could begin a
+ * UUID, so uuid_is_partial_string() would return 3; for "xyzzy", this function
+ * would return 0, since "x" can't start a UUID. */
+int
+uuid_is_partial_string(const char *s)
+{
+    static const char tmpl[UUID_LEN] = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
+    size_t i;
+    for (i = 0; i < UUID_LEN; i++) {
+        if (tmpl[i] == 'x'
+            ? hexit_value(s[i]) < 0
+            : s[i] != '-') {
+            break;
+        }
+    }
+    return i;
+}
+
 
 static void
 sha1_update_int(struct sha1_ctx *sha1_ctx, uintmax_t x)
diff --git a/lib/uuid.h b/lib/uuid.h
index a5792b0..113574c 100644
--- a/lib/uuid.h
+++ b/lib/uuid.h
@@ -1,4 +1,4 @@ 
-/* Copyright (c) 2008, 2009, 2010 Nicira, Inc.
+/* Copyright (c) 2008, 2009, 2010, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -61,6 +61,7 @@  bool uuid_is_zero(const struct uuid *);
 int uuid_compare_3way(const struct uuid *, const struct uuid *);
 bool uuid_from_string(struct uuid *, const char *);
 bool uuid_from_string_prefix(struct uuid *, const char *);
+int uuid_is_partial_string(const char *);
 void uuid_set_bits_v4(struct uuid *);
 
 #endif /* uuid.h */
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
index 4e67365..5f49183 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -302,7 +302,7 @@  consider_logical_flow(const struct lport_index *lports,
         }
         if (!m->n) {
             ofctrl_add_flow(flow_table, ptable, lflow->priority, &m->match,
-                            &ofpacts);
+                            lflow->header_.uuid.parts[0], &ofpacts);
         } else {
             uint64_t conj_stubs[64 / 8];
             struct ofpbuf conj;
@@ -318,7 +318,7 @@  consider_logical_flow(const struct lport_index *lports,
                 dst->n_clauses = src->n_clauses;
             }
             ofctrl_add_flow(flow_table, ptable, lflow->priority, &m->match,
-                            &conj);
+                            0, &conj);
             ofpbuf_uninit(&conj);
         }
     }
@@ -386,7 +386,7 @@  consider_neighbor_flow(const struct lport_index *lports,
     uint64_t stub[1024 / 8];
     struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
     put_load(mac.ea, sizeof mac.ea, MFF_ETH_DST, 0, 48, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, &match, &ofpacts);
+    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, &match, 0, &ofpacts);
     ofpbuf_uninit(&ofpacts);
 }
 
diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c
index 1d8fbf3..7449293 100644
--- a/ovn/controller/ofctrl.c
+++ b/ovn/controller/ofctrl.c
@@ -57,6 +57,7 @@  struct ovn_flow {
     /* Data. */
     struct ofpact *ofpacts;
     size_t ofpacts_len;
+    uint64_t cookie;
 };
 
 static uint32_t ovn_flow_hash(const struct ovn_flow *);
@@ -602,7 +603,8 @@  ofctrl_recv(const struct ofp_header *oh, enum ofptype type)
 void
 ofctrl_add_flow(struct hmap *desired_flows,
                 uint8_t table_id, uint16_t priority,
-                const struct match *match, const struct ofpbuf *actions)
+                const struct match *match, uint64_t cookie,
+                const struct ofpbuf *actions)
 {
     struct ovn_flow *f = xmalloc(sizeof *f);
     f->table_id = table_id;
@@ -611,6 +613,7 @@  ofctrl_add_flow(struct hmap *desired_flows,
     f->ofpacts = xmemdup(actions->data, actions->size);
     f->ofpacts_len = actions->size;
     f->hmap_node.hash = ovn_flow_hash(f);
+    f->cookie = cookie;
 
     if (ovn_flow_lookup(desired_flows, f)) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
@@ -935,6 +938,7 @@  ofctrl_put(struct hmap *flow_table, struct shash *pending_ct_zones,
             .table_id = d->table_id,
             .ofpacts = d->ofpacts,
             .ofpacts_len = d->ofpacts_len,
+            .new_cookie = htonll(d->cookie),
             .command = OFPFC_ADD,
         };
         add_flow_mod(&fm, &msgs);
diff --git a/ovn/controller/ofctrl.h b/ovn/controller/ofctrl.h
index 5308b61..67d6c59 100644
--- a/ovn/controller/ofctrl.h
+++ b/ovn/controller/ofctrl.h
@@ -46,7 +46,7 @@  void ofctrl_ct_flush_zone(uint16_t zone_id);
 /* Flow table interfaces to the rest of ovn-controller. */
 void ofctrl_add_flow(struct hmap *desired_flows, uint8_t table_id,
                      uint16_t priority, const struct match *,
-                     const struct ofpbuf *ofpacts);
+                     uint64_t cookie, const struct ofpbuf *ofpacts);
 
 void ofctrl_flow_table_clear(void);
 
diff --git a/ovn/controller/physical.c b/ovn/controller/physical.c
index 48adb78..17a4126 100644
--- a/ovn/controller/physical.c
+++ b/ovn/controller/physical.c
@@ -331,7 +331,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         /* Resubmit to first logical ingress pipeline table. */
         put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, ofpacts_p);
         ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG,
-                        tag ? 150 : 100, &match, ofpacts_p);
+                        tag ? 150 : 100, &match, 0, ofpacts_p);
 
         if (!tag && (!strcmp(binding->type, "localnet")
                      || !strcmp(binding->type, "l2gateway"))) {
@@ -341,7 +341,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
              * action. */
             ofpbuf_pull(ofpacts_p, ofpacts_orig_size);
             match_set_dl_tci_masked(&match, 0, htons(VLAN_CFI));
-            ofctrl_add_flow(flow_table, 0, 100, &match, ofpacts_p);
+            ofctrl_add_flow(flow_table, 0, 100, &match, 0, ofpacts_p);
         }
 
         /* Table 33, priority 100.
@@ -372,7 +372,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         /* Resubmit to table 34. */
         put_resubmit(OFTABLE_CHECK_LOOPBACK, ofpacts_p);
         ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100,
-                        &match, ofpacts_p);
+                        &match, 0, ofpacts_p);
 
         /* Table 34, Priority 100.
          * =======================
@@ -387,7 +387,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0, port_key);
         match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
         ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 100,
-                        &match, ofpacts_p);
+                        &match, 0, ofpacts_p);
 
         /* Table 64, Priority 100.
          * =======================
@@ -411,7 +411,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         put_resubmit(OFTABLE_LOG_TO_PHY, ofpacts_p);
         put_stack(MFF_IN_PORT, ofpact_put_STACK_POP(ofpacts_p));
         ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 100,
-                        &match, ofpacts_p);
+                        &match, 0, ofpacts_p);
 
         /* Table 65, Priority 100.
          * =======================
@@ -438,7 +438,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
             ofpact_put_STRIP_VLAN(ofpacts_p);
         }
         ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100,
-                        &match, ofpacts_p);
+                        &match, 0, ofpacts_p);
     } else if (!tun) {
         /* Remote port connected by localnet port */
         /* Table 33, priority 100.
@@ -461,7 +461,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         /* Resubmit to table 33. */
         put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p);
         ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100,
-                        &match, ofpacts_p);
+                        &match, 0, ofpacts_p);
     } else {
         /* Remote port connected by tunnel */
 
@@ -486,7 +486,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
 
         /* Resubmit to table 33. */
         put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p);
-        ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, &match,
+        ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, &match, 0,
                         ofpacts_p);
 
 
@@ -503,7 +503,7 @@  consider_port_binding(enum mf_field_id mff_ovn_geneve,
         /* Output to tunnel. */
         ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
         ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100,
-                        &match, ofpacts_p);
+                        &match, 0, ofpacts_p);
     }
 }
 
@@ -583,7 +583,7 @@  consider_mc_group(enum mf_field_id mff_ovn_geneve,
         put_load(mc->tunnel_key, MFF_LOG_OUTPORT, 0, 32, ofpacts_p);
 
         ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100,
-                        &match, ofpacts_p);
+                        &match, 0, ofpacts_p);
     }
 
     /* Table 32, priority 100.
@@ -621,7 +621,7 @@  consider_mc_group(enum mf_field_id mff_ovn_geneve,
                 put_resubmit(OFTABLE_LOCAL_OUTPUT, remote_ofpacts_p);
             }
             ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100,
-                            &match, remote_ofpacts_p);
+                            &match, 0, remote_ofpacts_p);
         }
     }
     sset_destroy(&remote_chassis);
@@ -833,7 +833,8 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
 
         put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
 
-        ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, &match, &ofpacts);
+        ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, &match, 0,
+                        &ofpacts);
     }
 
     /* Add flows for VXLAN encapsulations.  Due to the limited amount of
@@ -865,7 +866,7 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
             put_load(1, MFF_LOG_FLAGS, MLF_RCV_FROM_VXLAN_BIT, 1, &ofpacts);
             put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, &ofpacts);
 
-            ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, &match,
+            ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, &match, 0,
                             &ofpacts);
         }
     }
@@ -879,7 +880,7 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
     match_init_catchall(&match);
     ofpbuf_clear(&ofpacts);
     put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 0, &match, &ofpacts);
+    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 0, &match, 0, &ofpacts);
 
     /* Table 34, Priority 0.
      * =======================
@@ -893,7 +894,8 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
         put_load(0, MFF_REG0 + i, 0, 32, &ofpacts);
     }
     put_resubmit(OFTABLE_LOG_EGRESS_PIPELINE, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 0, &match, &ofpacts);
+    ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 0, &match, 0,
+                    &ofpacts);
 
     /* Table 64, Priority 0.
      * =======================
@@ -903,7 +905,7 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
     match_init_catchall(&match);
     ofpbuf_clear(&ofpacts);
     put_resubmit(OFTABLE_LOG_TO_PHY, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 0, &match, &ofpacts);
+    ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 0, &match, 0, &ofpacts);
 
     ofpbuf_uninit(&ofpacts);
 
diff --git a/ovn/utilities/ovn-sbctl.8.in b/ovn/utilities/ovn-sbctl.8.in
index 458a643..2f274ae 100644
--- a/ovn/utilities/ovn-sbctl.8.in
+++ b/ovn/utilities/ovn-sbctl.8.in
@@ -159,9 +159,18 @@  to unbind logical port that is not bound has no effect.
 .
 .SS "Logical Flow Commands"
 .
-.IP "\fBlflow\-list\fR [\fIlogical-datapath\fR]"
+.IP "\fBlflow\-list\fR [\fIlogical-datapath\fR] [\fIlflow\fR...]"
 List logical flows.  If \fIlogical-datapath\fR is specified, only list
-flows for that logical datapath.
+flows for that logical datapath.  The \fIlogical-datapath\fR may be
+given as a UUID or as a datapath name.
+.IP
+If at least one \fIlflow\fR is given, only matching logical flows, if
+any, are listed.  Each \fIlflow\fR may be specified as a UUID or the
+first few characters of a UUID, optionally prefixed by \fB0x\fR.
+(Because \fBovn\-controller\fR sets OpenFlow flow cookies to the first
+32 bits of the corresponding logical flow's UUID, this makes it easy
+to look up the logical flow that generated a particular OpenFlow
+flow.)
 .
 .IP "\fBdump\-flows\fR [\fIlogical-datapath\fR]"
 Alias for \fBlflow\-list\fB.
diff --git a/ovn/utilities/ovn-sbctl.c b/ovn/utilities/ovn-sbctl.c
index 0763b79..f51a2ed 100644
--- a/ovn/utilities/ovn-sbctl.c
+++ b/ovn/utilities/ovn-sbctl.c
@@ -306,8 +306,8 @@  Port binding commands:\n\
   lsp-unbind PORT             reset the port binding of logical port PORT\n\
 \n\
 Logical flow commands:\n\
-  lflow-list [DATAPATH]       List logical flows for all or a single datapath\n\
-  dump-flows [DATAPATH]       Alias for lflow-list\n\
+  lflow-list [DATAPATH] [LFLOW...] List logical flows for DATAPATH\n\
+  dump-flows [DATAPATH] [LFLOW...] Alias for lflow-list\n\
 \n\
 %s\
 \n\
@@ -687,53 +687,138 @@  lflow_cmp(const void *lf1_, const void *lf2_)
     return 0;
 }
 
+static char *
+parse_partial_uuid(char *s)
+{
+    /* Accept a full or partial UUID. */
+    if (uuid_is_partial_string(s) == strlen(s)) {
+        return s;
+    }
+
+    /* Accept a full or partial UUID prefixed by 0x, since "ovs-ofctl
+     * dump-flows" prints cookies prefixed by 0x. */
+    if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
+        && uuid_is_partial_string(s + 2) == strlen(s + 2)) {
+        return s + 2;
+    }
+
+    /* Not a (partial) UUID. */
+    return NULL;
+}
+
+static bool
+is_partial_uuid_match(const struct uuid *uuid, const char *match)
+{
+    char uuid_s[UUID_LEN + 1];
+    snprintf(uuid_s, sizeof uuid_s, UUID_FMT, UUID_ARGS(uuid));
+
+    return !strncmp(uuid_s, match, strlen(match));
+}
+
+static const struct sbrec_datapath_binding *
+lookup_datapath(struct ovsdb_idl *idl, const char *s)
+{
+    const struct sbrec_datapath_binding *datapath;
+
+    struct uuid uuid;
+    if (uuid_from_string(&uuid, s)) {
+        datapath = sbrec_datapath_binding_get_for_uuid(idl, &uuid);
+        if (datapath) {
+            return datapath;
+        }
+    }
+
+    SBREC_DATAPATH_BINDING_FOR_EACH (datapath, idl) {
+        const char *name = smap_get(&datapath->external_ids, "name");
+        if (name && !strcmp(name, s)) {
+            return datapath;
+        }
+    }
+
+    return NULL;
+}
+
 static void
 cmd_lflow_list(struct ctl_context *ctx)
 {
-    const char *datapath = ctx->argc == 2 ? ctx->argv[1] : NULL;
-    struct uuid datapath_uuid = { .parts = { 0, }};
-    const struct sbrec_logical_flow **lflows;
-    const struct sbrec_logical_flow *lflow;
-    size_t n_flows = 0, n_capacity = 64;
+    const struct sbrec_datapath_binding *datapath = NULL;
+    if (ctx->argc > 1) {
+        datapath = lookup_datapath(ctx->idl, ctx->argv[1]);
+        if (datapath) {
+            ctx->argc--;
+            ctx->argv++;
+        }
+    }
 
-    if (datapath && !uuid_from_string(&datapath_uuid, datapath)) {
-        VLOG_ERR("Invalid format of datapath UUID");
-        return;
+    for (size_t i = 1; i < ctx->argc; i++) {
+        char *s = parse_partial_uuid(ctx->argv[i]);
+        if (!s) {
+            ctl_fatal("%s is not a UUID or the beginning of a UUID",
+                      ctx->argv[i]);
+        }
+        ctx->argv[i] = s;
     }
 
-    lflows = xmalloc(sizeof *lflows * n_capacity);
+    const struct sbrec_logical_flow **lflows = NULL;
+    size_t n_flows = 0;
+    size_t n_capacity = 0;
+    const struct sbrec_logical_flow *lflow;
     SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->idl) {
+        if (datapath && lflow->logical_datapath != datapath) {
+            continue;
+        }
+
         if (n_flows == n_capacity) {
             lflows = x2nrealloc(lflows, &n_capacity, sizeof *lflows);
         }
         lflows[n_flows] = lflow;
         n_flows++;
     }
-
     qsort(lflows, n_flows, sizeof *lflows, lflow_cmp);
 
-    const char *cur_pipeline = "";
-    size_t i;
-    for (i = 0; i < n_flows; i++) {
+    const struct sbrec_logical_flow *prev = NULL;
+    for (size_t i = 0; i < n_flows; i++) {
         lflow = lflows[i];
-        if (datapath && !uuid_equals(&datapath_uuid,
-                                     &lflow->logical_datapath->header_.uuid)) {
+
+        /* Figure out whether to print this particular flow.  By default, we
+         * print all flows, but if any UUIDs were listed on the command line
+         * then we only print the matching ones. */
+        bool include;
+        if (ctx->argc > 1) {
+            include = false;
+            for (size_t j = 1; j < ctx->argc; j++) {
+                if (is_partial_uuid_match(&lflow->header_.uuid,
+                                          ctx->argv[j])) {
+                    include = true;
+                    break;
+                }
+            }
+        } else {
+            include = true;
+        }
+        if (!include) {
             continue;
         }
-        if (strcmp(cur_pipeline, lflow->pipeline)) {
+
+        /* Print a header line for this datapath or pipeline, if we haven't
+         * already done so. */
+        if (!prev
+            || prev->logical_datapath != lflow->logical_datapath
+            || strcmp(prev->pipeline, lflow->pipeline)) {
             printf("Datapath: \"%s\" ("UUID_FMT")  Pipeline: %s\n",
                    smap_get_def(&lflow->logical_datapath->external_ids,
                                 "name", ""),
                    UUID_ARGS(&lflow->logical_datapath->header_.uuid),
                    lflow->pipeline);
-            cur_pipeline = lflow->pipeline;
         }
 
+        /* Print the flow. */
         printf("  table=%-2" PRId64 "(%-19s), priority=%-5" PRId64
                ", match=(%s), action=(%s)\n",
                lflow->table_id,
                smap_get_def(&lflow->external_ids, "stage-name", ""),
                lflow->priority, lflow->match, lflow->actions);
+        prev = lflow;
     }
 
     free(lflows);
@@ -1039,9 +1124,11 @@  static const struct ctl_command_syntax sbctl_commands[] = {
      "--if-exists", RW},
 
     /* Logical flow commands */
-    {"lflow-list", 0, 1, "[DATAPATH]", pre_get_info, cmd_lflow_list, NULL,
+    {"lflow-list", 0, INT_MAX, "[DATAPATH] [LFLOW...]",
+     pre_get_info, cmd_lflow_list, NULL,
      "", RO},
-    {"dump-flows", 0, 1, "[DATAPATH]", pre_get_info, cmd_lflow_list, NULL,
+    {"dump-flows", 0, INT_MAX, "[DATAPATH] [LFLOW...]",
+     pre_get_info, cmd_lflow_list, NULL,
      "", RO}, /* Friendly alias for lflow-list */
 
     /* SSL commands (To Be Added). */