From patchwork Sat Dec 10 06:56:32 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ben Pfaff X-Patchwork-Id: 704699 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3tbKfK0fCfz9tld for ; Sat, 10 Dec 2016 17:57:13 +1100 (AEDT) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 71287978; Sat, 10 Dec 2016 06:56:45 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id 4CB6994B for ; Sat, 10 Dec 2016 06:56:44 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from relay2-d.mail.gandi.net (relay2-d.mail.gandi.net [217.70.183.194]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id B3950A1 for ; Sat, 10 Dec 2016 06:56:42 +0000 (UTC) Received: from mfilter17-d.gandi.net (mfilter17-d.gandi.net [217.70.178.145]) by relay2-d.mail.gandi.net (Postfix) with ESMTP id 85FDEC5A43; Sat, 10 Dec 2016 07:56:41 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at mfilter17-d.gandi.net Received: from relay2-d.mail.gandi.net ([IPv6:::ffff:217.70.183.194]) by mfilter17-d.gandi.net (mfilter17-d.gandi.net [::ffff:10.0.15.180]) (amavisd-new, port 10024) with ESMTP id 3IyqtSswQsrr; Sat, 10 Dec 2016 07:56:39 +0100 (CET) X-Originating-IP: 173.228.112.229 Received: from sigabrt.gateway.sonic.net (173-228-112-229.dsl.dynamic.fusionbroadband.com [173.228.112.229]) (Authenticated sender: blp@ovn.org) by relay2-d.mail.gandi.net (Postfix) with ESMTPSA id 5BC0AC5A56; Sat, 10 Dec 2016 07:56:38 +0100 (CET) From: Ben Pfaff To: dev@openvswitch.org Date: Fri, 9 Dec 2016 22:56:32 -0800 Message-Id: <20161210065633.32634-2-blp@ovn.org> X-Mailer: git-send-email 2.10.2 In-Reply-To: <20161210065633.32634-1-blp@ovn.org> References: <20161210065633.32634-1-blp@ovn.org> X-Spam-Status: No, score=-2.6 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_LOW autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Cc: Ben Pfaff Subject: [ovs-dev] [PATCH v2 1/2] ovn-controller: Tie OpenFlow and logical flows using OpenFlow cookie. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org This makes it easy to find the logical flow that generated a particular OpenFlow flow, by running "ovn-sbctl dump-flows ". Later, this can be refined (and automated for "ofproto/trace"), but this is still a significant advance. Signed-off-by: Ben Pfaff Acked-by: Gurucharan Shetty --- 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); 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). */