diff options
author | Ben Pfaff <blp@ovn.org> | 2016-12-28 09:17:51 -0800 |
---|---|---|
committer | Ben Pfaff <blp@ovn.org> | 2016-12-28 09:17:51 -0800 |
commit | c80eac1f85df91babe5d4739810a80f7e748c3fe (patch) | |
tree | e8994e5edabbbbe59d5bda06a234b81b3231107e /ovn | |
parent | 92043ab8ffd449dfd50c3e716d6db06d04af70d7 (diff) | |
download | openvswitch-c80eac1f85df91babe5d4739810a80f7e748c3fe.tar.gz |
ovn-controller: Tie OpenFlow and logical flows using OpenFlow cookie.
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>
Acked-by: Justin Pettit <jpettit@ovn.org>
Diffstat (limited to 'ovn')
-rw-r--r-- | ovn/controller/lflow.c | 8 | ||||
-rw-r--r-- | ovn/controller/ofctrl.c | 12 | ||||
-rw-r--r-- | ovn/controller/ofctrl.h | 4 | ||||
-rw-r--r-- | ovn/controller/physical.c | 38 | ||||
-rw-r--r-- | ovn/ovn-architecture.7.xml | 28 | ||||
-rw-r--r-- | ovn/utilities/ovn-sbctl.8.in | 20 | ||||
-rw-r--r-- | ovn/utilities/ovn-sbctl.c | 145 |
7 files changed, 201 insertions, 54 deletions
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c index d913998a3..9f5341a73 100644 --- a/ovn/controller/lflow.c +++ b/ovn/controller/lflow.c @@ -265,8 +265,8 @@ consider_logical_flow(const struct lport_index *lports, m->match.flow.conj_id += *conj_id_ofs_p; } if (!m->n) { - ofctrl_add_flow(flow_table, ptable, lflow->priority, &m->match, - &ofpacts); + ofctrl_add_flow(flow_table, ptable, lflow->priority, + lflow->header_.uuid.parts[0], &m->match, &ofpacts); } else { uint64_t conj_stubs[64 / 8]; struct ofpbuf conj; @@ -281,7 +281,7 @@ consider_logical_flow(const struct lport_index *lports, dst->clause = src->clause; dst->n_clauses = src->n_clauses; } - ofctrl_add_flow(flow_table, ptable, lflow->priority, &m->match, + ofctrl_add_flow(flow_table, ptable, lflow->priority, 0, &m->match, &conj); ofpbuf_uninit(&conj); } @@ -350,7 +350,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, 0, &match, &ofpacts); ofpbuf_uninit(&ofpacts); } diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c index 1d8fbf308..c79660bf4 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 *); @@ -592,8 +593,8 @@ ofctrl_recv(const struct ofp_header *oh, enum ofptype type) /* Flow table interfaces to the rest of ovn-controller. */ /* Adds a flow to 'desired_flows' with the specified 'match' and 'actions' to - * the OpenFlow table numbered 'table_id' with the given 'priority'. The - * caller retains ownership of 'match' and 'actions'. + * the OpenFlow table numbered 'table_id' with the given 'priority' and + * OpenFlow 'cookie'. The caller retains ownership of 'match' and 'actions'. * * This just assembles the desired flow table in memory. Nothing is actually * sent to the switch until a later call to ofctrl_run(). @@ -601,8 +602,9 @@ ofctrl_recv(const struct ofp_header *oh, enum ofptype type) * The caller should initialize its own hmap to hold the flows. */ void ofctrl_add_flow(struct hmap *desired_flows, - uint8_t table_id, uint16_t priority, - const struct match *match, const struct ofpbuf *actions) + uint8_t table_id, uint16_t priority, uint64_t cookie, + const struct match *match, + 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 5308b61b5..79f0cb780 100644 --- a/ovn/controller/ofctrl.h +++ b/ovn/controller/ofctrl.h @@ -45,8 +45,8 @@ 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); + uint16_t priority, uint64_t cookie, + const struct match *, const struct ofpbuf *ofpacts); void ofctrl_flow_table_clear(void); diff --git a/ovn/controller/physical.c b/ovn/controller/physical.c index 9d3741051..9cc7eb682 100644 --- a/ovn/controller/physical.c +++ b/ovn/controller/physical.c @@ -218,7 +218,7 @@ put_local_common_flows(uint32_t dp_key, uint32_t port_key, /* Resubmit to table 34. */ put_resubmit(OFTABLE_CHECK_LOOPBACK, ofpacts_p); - ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, + ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0, &match, ofpacts_p); /* Table 34, Priority 100. @@ -233,7 +233,7 @@ put_local_common_flows(uint32_t dp_key, uint32_t port_key, 0, MLF_ALLOW_LOOPBACK); 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, + ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 100, 0, &match, ofpacts_p); /* Table 64, Priority 100. @@ -257,7 +257,8 @@ put_local_common_flows(uint32_t dp_key, uint32_t port_key, put_load(0, MFF_IN_PORT, 0, 16, ofpacts_p); 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); + ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 100, 0, + &match, ofpacts_p); } static void @@ -346,7 +347,7 @@ consider_port_binding(enum mf_field_id mff_ovn_geneve, ofpacts_p->header = clone; ofpact_finish_CLONE(ofpacts_p, &clone); - ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100, + ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100, 0, &match, ofpacts_p); return; } @@ -471,7 +472,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, 0, &match, ofpacts_p); if (!tag && (!strcmp(binding->type, "localnet") || !strcmp(binding->type, "l2gateway"))) { @@ -481,7 +482,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, 0, &match, ofpacts_p); } /* Table 65, Priority 100. @@ -508,7 +509,7 @@ consider_port_binding(enum mf_field_id mff_ovn_geneve, * switch will also contain the tag. */ ofpact_put_STRIP_VLAN(ofpacts_p); } - ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100, + ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100, 0, &match, ofpacts_p); } else if (!tun) { /* Remote port connected by localnet port */ @@ -531,7 +532,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, + ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0, &match, ofpacts_p); } else { /* Remote port connected by tunnel */ @@ -555,7 +556,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, + ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100, 0, &match, ofpacts_p); } } @@ -643,7 +644,7 @@ consider_mc_group(enum mf_field_id mff_ovn_geneve, * group as the logical output port. */ put_load(mc->tunnel_key, MFF_LOG_OUTPORT, 0, 32, ofpacts_p); - ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, + ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0, &match, ofpacts_p); } @@ -681,7 +682,7 @@ consider_mc_group(enum mf_field_id mff_ovn_geneve, if (local_ports) { put_resubmit(OFTABLE_LOCAL_OUTPUT, remote_ofpacts_p); } - ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100, + ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100, 0, &match, remote_ofpacts_p); } } @@ -882,7 +883,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, 0, &match, + &ofpacts); } /* Add flows for VXLAN encapsulations. Due to the limited amount of @@ -914,7 +916,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, 0, &match, &ofpacts); } } @@ -935,7 +937,8 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve, /* Resubmit to table 33. */ put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts); - ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, &match, &ofpacts); + ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0, + &match, &ofpacts); /* Table 32, Priority 0. * ======================= @@ -945,7 +948,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, 0, &match, &ofpacts); /* Table 34, Priority 0. * ======================= @@ -959,7 +962,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, 0, &match, + &ofpacts); /* Table 64, Priority 0. * ======================= @@ -969,7 +973,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, 0, &match, &ofpacts); ofpbuf_uninit(&ofpacts); diff --git a/ovn/ovn-architecture.7.xml b/ovn/ovn-architecture.7.xml index d96e4b141..b049e51f0 100644 --- a/ovn/ovn-architecture.7.xml +++ b/ovn/ovn-architecture.7.xml @@ -846,6 +846,34 @@ </p> <p> + Each logical flow maps to one or more OpenFlow flows. An actual packet + ordinarily matches only one of these, although in some cases it can + match more than one of these flows (which is not a problem because all + of them have the same actions). <code>ovn-controller</code> uses the + first 32 bits of the logical flow's UUID as the cookie for its OpenFlow + flow or flows. (This is not necessarily unique, since the first 32 + bits of a logical flow's UUID is not necessarily unique.) + </p> + + <p> + Some logical flows can map to the Open vSwitch ``conjunctive match'' + extension (see <code>ovs-ofctl</code>(8)). Flows with a + <code>conjunction</code> action use an OpenFlow cookie of 0, because + they can correspond to multiple logical flows. The OpenFlow flow for a + conjunctive match includes a match on <code>conj_id</code>. + </p> + + <p> + Some logical flows may not be represented in the OpenFlow tables on a + given hypervisor, if they could not be used on that hypervisor. For + example, if no VIF in a logical switch resides on a given hypervisor, + and the logical switch is not otherwise reachable on that hypervisor + (e.g. over a series of hops through logical switches and routers + starting from a VIF on the hypervisor), then the logical flow may not + be represented there. + </p> + + <p> Most OVN actions have fairly obvious implementations in OpenFlow (with OVS extensions), e.g. <code>next;</code> is implemented as <code>resubmit</code>, <code><var>field</var> = diff --git a/ovn/utilities/ovn-sbctl.8.in b/ovn/utilities/ovn-sbctl.8.in index 783afc772..a87635de8 100644 --- a/ovn/utilities/ovn-sbctl.8.in +++ b/ovn/utilities/ovn-sbctl.8.in @@ -160,11 +160,25 @@ to unbind logical port that is not bound has no effect. . .SS "Logical Flow Commands" . -.IP "\fBlflow\-list\fR [\fIlogical-datapath\fR]" +.IP "[\fB\-\-uuid\fR] \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 (reporting an error if multiple +datapaths have the same 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 +If \fB\-\-uuid\fR is specified, the output includes the first 32 bits +of each logical flow's UUID. This makes it easier to find the +OpenFlow flows that correspond to a given logical flow. . -.IP "\fBdump\-flows\fR [\fIlogical-datapath\fR]" +.IP "[\fB\-\-uuid\fR] \fBdump\-flows\fR [\fIlogical-datapath\fR]" Alias for \fBlflow\-list\fB. . .SS "Remote Connectivity Commands" diff --git a/ovn/utilities/ovn-sbctl.c b/ovn/utilities/ovn-sbctl.c index c190635b0..9e3016b65 100644 --- a/ovn/utilities/ovn-sbctl.c +++ b/ovn/utilities/ovn-sbctl.c @@ -304,8 +304,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\ Connection commands:\n\ get-connection print the connections\n\ @@ -695,53 +695,148 @@ 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) +{ + struct uuid uuid; + if (uuid_from_string(&uuid, s)) { + const struct sbrec_datapath_binding *datapath; + datapath = sbrec_datapath_binding_get_for_uuid(idl, &uuid); + if (datapath) { + return datapath; + } + } + + const struct sbrec_datapath_binding *found = NULL; + const struct sbrec_datapath_binding *datapath; + SBREC_DATAPATH_BINDING_FOR_EACH (datapath, idl) { + const char *name = smap_get(&datapath->external_ids, "name"); + if (name && !strcmp(name, s)) { + if (!found) { + found = datapath; + } else { + ctl_fatal("%s: multiple datapaths with this name", s); + } + } + } + return found; +} + 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++) { + bool print_uuid = shash_find(&ctx->options, "--uuid") != NULL; + + 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; } - printf(" table=%-2" PRId64 "(%-19s), priority=%-5" PRId64 + /* Print the flow. */ + printf(" "); + if (print_uuid) { + printf("uuid=0x%08"PRIx32", ", lflow->header_.uuid.parts[0]); + } + 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); @@ -1243,10 +1338,12 @@ 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, - "", RO}, - {"dump-flows", 0, 1, "[DATAPATH]", pre_get_info, cmd_lflow_list, NULL, - "", RO}, /* Friendly alias for lflow-list */ + {"lflow-list", 0, INT_MAX, "[DATAPATH] [LFLOW...]", + pre_get_info, cmd_lflow_list, NULL, + "--uuid", RO}, + {"dump-flows", 0, INT_MAX, "[DATAPATH] [LFLOW...]", + pre_get_info, cmd_lflow_list, NULL, + "--uuid", RO}, /* Friendly alias for lflow-list */ /* Connection commands. */ {"get-connection", 0, 0, "", pre_connection, cmd_get_connection, NULL, "", RO}, |