2
0
mirror of https://github.com/openvswitch/ovs synced 2025-09-05 00:35:33 +00:00

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>
This commit is contained in:
Ben Pfaff
2016-12-28 09:17:51 -08:00
parent 92043ab8ff
commit c80eac1f85
9 changed files with 225 additions and 57 deletions

View File

@@ -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)

View File

@@ -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 */

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -845,6 +845,34 @@
through 31).
</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

View File

@@ -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"

View File

@@ -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;
if (datapath && !uuid_from_string(&datapath_uuid, datapath)) {
VLOG_ERR("Invalid format of datapath UUID");
return;
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++;
}
}
lflows = xmalloc(sizeof *lflows * n_capacity);
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;
}
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},