mirror of
https://github.com/openvswitch/ovs
synced 2025-09-02 07:15:17 +00:00
ovs-ofctl: New commands "replace-flows" and "diff-flows".
Requested-by: Paul Ingram <paul@nicira.com>
This commit is contained in:
@@ -726,7 +726,7 @@ parse_reg_value(struct cls_rule *rule, int reg_idx, const char *value)
|
|||||||
/* Convert 'string' (as described in the Flow Syntax section of the ovs-ofctl
|
/* Convert 'string' (as described in the Flow Syntax section of the ovs-ofctl
|
||||||
* man page) into 'pf'. If 'actions' is specified, an action must be in
|
* man page) into 'pf'. If 'actions' is specified, an action must be in
|
||||||
* 'string' and may be expanded or reallocated. */
|
* 'string' and may be expanded or reallocated. */
|
||||||
static void
|
void
|
||||||
parse_ofp_str(struct flow_mod *fm, uint8_t *table_idx,
|
parse_ofp_str(struct flow_mod *fm, uint8_t *table_idx,
|
||||||
struct ofpbuf *actions, char *string)
|
struct ofpbuf *actions, char *string)
|
||||||
{
|
{
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2010 Nicira Networks.
|
* Copyright (c) 2010, 2011 Nicira Networks.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -29,6 +29,9 @@ struct flow_stats_request;
|
|||||||
struct list;
|
struct list;
|
||||||
struct ofpbuf;
|
struct ofpbuf;
|
||||||
|
|
||||||
|
void parse_ofp_str(struct flow_mod *, uint8_t *table_idx,
|
||||||
|
struct ofpbuf *actions, char *string);
|
||||||
|
|
||||||
void parse_ofp_flow_mod_str(struct list *packets, enum nx_flow_format *cur,
|
void parse_ofp_flow_mod_str(struct list *packets, enum nx_flow_format *cur,
|
||||||
char *string, uint16_t command);
|
char *string, uint16_t command);
|
||||||
bool parse_ofp_add_flow_file(struct list *packets, enum nx_flow_format *cur,
|
bool parse_ofp_add_flow_file(struct list *packets, enum nx_flow_format *cur,
|
||||||
|
@@ -146,6 +146,33 @@ omitted and the \fB\-\-strict\fR option is not used, all flows in the
|
|||||||
switch's tables are removed. See \fBFlow Syntax\fR, below, for the
|
switch's tables are removed. See \fBFlow Syntax\fR, below, for the
|
||||||
syntax of \fIflows\fR.
|
syntax of \fIflows\fR.
|
||||||
.
|
.
|
||||||
|
.IP "\fBreplace\-flows \fIswitch file\fR"
|
||||||
|
Reads flow entries from \fIfile\fR (or \fBstdin\fR if \fIfile\fR is
|
||||||
|
\fB\-\fR) and queries the flow table from \fIswitch\fR. Then it fixes
|
||||||
|
up any differences, adding flows from \fIflow\fR that are missing on
|
||||||
|
\fIswitch\fR, deleting flows from \fIswitch\fR that are not in
|
||||||
|
\fIfile\fR, and updating flows in \fIswitch\fR whose actions, cookie,
|
||||||
|
or timeouts differ in \fIfile\fR.
|
||||||
|
.
|
||||||
|
.IP "\fBdiff\-flows \fIsource1 source2\fR"
|
||||||
|
Reads flow entries from \fIsource1\fR and \fIsource2\fR and prints the
|
||||||
|
differences. A flow that is in \fIsource1\fR but not in \fIsource2\fR
|
||||||
|
is printed preceded by a \fB\-\fR, and a flow that is in \fIsource2\fR
|
||||||
|
but not in \fIsource1\fR is printed preceded by a \fB+\fR. If a flow
|
||||||
|
exists in both \fIsource1\fR and \fIsource2\fR with different actions,
|
||||||
|
cookie, or timeouts, then both versions are printed preceded by
|
||||||
|
\fB\-\fR and \fB+\fR, respectively.
|
||||||
|
.IP
|
||||||
|
\fIsource1\fR and \fIsource2\fR may each name a file or a switch. If
|
||||||
|
a name begins with \fB/\fR or \fB.\fR, then it is considered to be a
|
||||||
|
file name. A name that contains \fB:\fR is considered to be a switch.
|
||||||
|
Otherwise, it is a file if a file by that name exists, a switch if
|
||||||
|
not.
|
||||||
|
.IP
|
||||||
|
For this command, an exit status of 0 means that no differences were
|
||||||
|
found, 1 means that an error occurred, and 2 means that some
|
||||||
|
differences were found.
|
||||||
|
.
|
||||||
.IP "\fBsnoop \fIswitch\fR"
|
.IP "\fBsnoop \fIswitch\fR"
|
||||||
Connects to \fIswitch\fR and prints to the console all OpenFlow
|
Connects to \fIswitch\fR and prints to the console all OpenFlow
|
||||||
messages received. Unlike other \fBovs\-ofctl\fR commands, if
|
messages received. Unlike other \fBovs\-ofctl\fR commands, if
|
||||||
|
@@ -506,12 +506,9 @@ set_flow_format(struct vconn *vconn, enum nx_flow_format flow_format)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static enum nx_flow_format
|
static enum nx_flow_format
|
||||||
negotiate_highest_flow_format(struct vconn *vconn, const struct cls_rule *rule,
|
negotiate_highest_flow_format(struct vconn *vconn,
|
||||||
bool cookie_support, ovs_be64 cookie)
|
enum nx_flow_format min_format)
|
||||||
{
|
{
|
||||||
enum nx_flow_format min_format;
|
|
||||||
|
|
||||||
min_format = ofputil_min_flow_format(rule, cookie_support, cookie);
|
|
||||||
if (preferred_flow_format != -1) {
|
if (preferred_flow_format != -1) {
|
||||||
if (preferred_flow_format < min_format) {
|
if (preferred_flow_format < min_format) {
|
||||||
ovs_fatal(0, "%s: cannot use requested flow format %s for "
|
ovs_fatal(0, "%s: cannot use requested flow format %s for "
|
||||||
@@ -547,7 +544,7 @@ negotiate_highest_flow_format(struct vconn *vconn, const struct cls_rule *rule,
|
|||||||
static void
|
static void
|
||||||
do_dump_flows__(int argc, char *argv[], bool aggregate)
|
do_dump_flows__(int argc, char *argv[], bool aggregate)
|
||||||
{
|
{
|
||||||
enum nx_flow_format flow_format;
|
enum nx_flow_format min_flow_format, flow_format;
|
||||||
struct flow_stats_request fsr;
|
struct flow_stats_request fsr;
|
||||||
struct ofpbuf *request;
|
struct ofpbuf *request;
|
||||||
struct vconn *vconn;
|
struct vconn *vconn;
|
||||||
@@ -555,7 +552,8 @@ do_dump_flows__(int argc, char *argv[], bool aggregate)
|
|||||||
parse_ofp_flow_stats_request_str(&fsr, aggregate, argc > 2 ? argv[2] : "");
|
parse_ofp_flow_stats_request_str(&fsr, aggregate, argc > 2 ? argv[2] : "");
|
||||||
|
|
||||||
open_vconn(argv[1], &vconn);
|
open_vconn(argv[1], &vconn);
|
||||||
flow_format = negotiate_highest_flow_format(vconn, &fsr.match, false, 0);
|
min_flow_format = ofputil_min_flow_format(&fsr.match, false, 0);
|
||||||
|
flow_format = negotiate_highest_flow_format(vconn, min_flow_format);
|
||||||
request = ofputil_encode_flow_stats_request(&fsr, flow_format);
|
request = ofputil_encode_flow_stats_request(&fsr, flow_format);
|
||||||
dump_stats_transaction(argv[1], request);
|
dump_stats_transaction(argv[1], request);
|
||||||
vconn_close(vconn);
|
vconn_close(vconn);
|
||||||
@@ -626,7 +624,7 @@ set_initial_format_for_flow_mod(struct list *requests)
|
|||||||
static void
|
static void
|
||||||
check_final_format_for_flow_mod(enum nx_flow_format flow_format)
|
check_final_format_for_flow_mod(enum nx_flow_format flow_format)
|
||||||
{
|
{
|
||||||
if (preferred_flow_format >= 0 && flow_format != preferred_flow_format) {
|
if (preferred_flow_format >= 0 && flow_format > preferred_flow_format) {
|
||||||
ovs_fatal(0, "flow cannot be expressed in flow format %s "
|
ovs_fatal(0, "flow cannot be expressed in flow format %s "
|
||||||
"(flow format %s or better is required)",
|
"(flow format %s or better is required)",
|
||||||
ofputil_flow_format_to_string(preferred_flow_format),
|
ofputil_flow_format_to_string(preferred_flow_format),
|
||||||
@@ -899,6 +897,396 @@ do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
|
|||||||
usage();
|
usage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* replace-flows and diff-flows commands. */
|
||||||
|
|
||||||
|
/* A flow table entry, possibly with two different versions. */
|
||||||
|
struct fte {
|
||||||
|
struct cls_rule rule; /* Within a "struct classifier". */
|
||||||
|
struct fte_version *versions[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* One version of a Flow Table Entry. */
|
||||||
|
struct fte_version {
|
||||||
|
ovs_be64 cookie;
|
||||||
|
uint16_t idle_timeout;
|
||||||
|
uint16_t hard_timeout;
|
||||||
|
uint16_t flags;
|
||||||
|
union ofp_action *actions;
|
||||||
|
size_t n_actions;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Frees 'version' and the data that it owns. */
|
||||||
|
static void
|
||||||
|
fte_version_free(struct fte_version *version)
|
||||||
|
{
|
||||||
|
if (version) {
|
||||||
|
free(version->actions);
|
||||||
|
free(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns true if 'a' and 'b' are the same, false if they differ.
|
||||||
|
*
|
||||||
|
* Ignores differences in 'flags' because there's no way to retrieve flags from
|
||||||
|
* an OpenFlow switch. We have to assume that they are the same. */
|
||||||
|
static bool
|
||||||
|
fte_version_equals(const struct fte_version *a, const struct fte_version *b)
|
||||||
|
{
|
||||||
|
return (a->cookie == b->cookie
|
||||||
|
&& a->idle_timeout == b->idle_timeout
|
||||||
|
&& a->hard_timeout == b->hard_timeout
|
||||||
|
&& a->n_actions == b->n_actions
|
||||||
|
&& !memcmp(a->actions, b->actions,
|
||||||
|
a->n_actions * sizeof *a->actions));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prints 'version' on stdout. Expects the caller to have printed the rule
|
||||||
|
* associated with the version. */
|
||||||
|
static void
|
||||||
|
fte_version_print(const struct fte_version *version)
|
||||||
|
{
|
||||||
|
struct ds s;
|
||||||
|
|
||||||
|
if (version->cookie != htonll(0)) {
|
||||||
|
printf(" cookie=0x%"PRIx64, ntohll(version->cookie));
|
||||||
|
}
|
||||||
|
if (version->idle_timeout != OFP_FLOW_PERMANENT) {
|
||||||
|
printf(" idle_timeout=%"PRIu16, version->idle_timeout);
|
||||||
|
}
|
||||||
|
if (version->hard_timeout != OFP_FLOW_PERMANENT) {
|
||||||
|
printf(" hard_timeout=%"PRIu16, version->hard_timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
ds_init(&s);
|
||||||
|
ofp_print_actions(&s, (const struct ofp_action_header *) version->actions,
|
||||||
|
version->n_actions * sizeof *version->actions);
|
||||||
|
printf(" %s\n", ds_cstr(&s));
|
||||||
|
ds_destroy(&s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct fte *
|
||||||
|
fte_from_cls_rule(const struct cls_rule *cls_rule)
|
||||||
|
{
|
||||||
|
return cls_rule ? CONTAINER_OF(cls_rule, struct fte, rule) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Frees 'fte' and its versions. */
|
||||||
|
static void
|
||||||
|
fte_free(struct fte *fte)
|
||||||
|
{
|
||||||
|
if (fte) {
|
||||||
|
fte_version_free(fte->versions[0]);
|
||||||
|
fte_version_free(fte->versions[1]);
|
||||||
|
free(fte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Frees all of the FTEs within 'cls'. */
|
||||||
|
static void
|
||||||
|
fte_free_all(struct classifier *cls)
|
||||||
|
{
|
||||||
|
struct cls_cursor cursor;
|
||||||
|
struct fte *fte, *next;
|
||||||
|
|
||||||
|
cls_cursor_init(&cursor, cls, NULL);
|
||||||
|
CLS_CURSOR_FOR_EACH_SAFE (fte, next, rule, &cursor) {
|
||||||
|
classifier_remove(cls, &fte->rule);
|
||||||
|
fte_free(fte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Searches 'cls' for an FTE matching 'rule', inserting a new one if
|
||||||
|
* necessary. Sets 'version' as the version of that rule with the given
|
||||||
|
* 'index', replacing any existing version, if any.
|
||||||
|
*
|
||||||
|
* Takes ownership of 'version'. */
|
||||||
|
static void
|
||||||
|
fte_insert(struct classifier *cls, const struct cls_rule *rule,
|
||||||
|
struct fte_version *version, int index)
|
||||||
|
{
|
||||||
|
struct fte *old, *fte;
|
||||||
|
|
||||||
|
fte = xzalloc(sizeof *fte);
|
||||||
|
fte->rule = *rule;
|
||||||
|
fte->versions[index] = version;
|
||||||
|
|
||||||
|
old = fte_from_cls_rule(classifier_insert(cls, &fte->rule));
|
||||||
|
if (old) {
|
||||||
|
fte_version_free(old->versions[index]);
|
||||||
|
fte->versions[!index] = old->versions[!index];
|
||||||
|
free(old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reads the flows in 'filename' as flow table entries in 'cls' for the version
|
||||||
|
* with the specified 'index'. Returns the minimum flow format required to
|
||||||
|
* represent the flows that were read. */
|
||||||
|
static enum nx_flow_format
|
||||||
|
read_flows_from_file(const char *filename, struct classifier *cls, int index)
|
||||||
|
{
|
||||||
|
enum nx_flow_format min_flow_format;
|
||||||
|
struct ds s;
|
||||||
|
FILE *file;
|
||||||
|
|
||||||
|
file = !strcmp(filename, "-") ? stdin : fopen(filename, "r");
|
||||||
|
if (file == NULL) {
|
||||||
|
ovs_fatal(errno, "%s: open", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
ds_init(&s);
|
||||||
|
min_flow_format = NXFF_OPENFLOW10;
|
||||||
|
while (!ds_get_preprocessed_line(&s, file)) {
|
||||||
|
struct fte_version *version;
|
||||||
|
enum nx_flow_format min_ff;
|
||||||
|
struct ofpbuf actions;
|
||||||
|
struct flow_mod fm;
|
||||||
|
uint8_t table_idx;
|
||||||
|
|
||||||
|
ofpbuf_init(&actions, 64);
|
||||||
|
parse_ofp_str(&fm, &table_idx, &actions, ds_cstr(&s));
|
||||||
|
|
||||||
|
version = xmalloc(sizeof *version);
|
||||||
|
version->cookie = fm.cookie;
|
||||||
|
version->idle_timeout = fm.idle_timeout;
|
||||||
|
version->hard_timeout = fm.hard_timeout;
|
||||||
|
version->flags = fm.flags & (OFPFF_SEND_FLOW_REM | OFPFF_EMERG);
|
||||||
|
version->n_actions = actions.size / sizeof *version->actions;
|
||||||
|
version->actions = ofpbuf_steal_data(&actions);
|
||||||
|
|
||||||
|
min_ff = ofputil_min_flow_format(&fm.cr, true, fm.cookie);
|
||||||
|
min_flow_format = MAX(min_flow_format, min_ff);
|
||||||
|
check_final_format_for_flow_mod(min_flow_format);
|
||||||
|
|
||||||
|
fte_insert(cls, &fm.cr, version, index);
|
||||||
|
}
|
||||||
|
ds_destroy(&s);
|
||||||
|
|
||||||
|
if (file != stdin) {
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return min_flow_format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reads the OpenFlow flow table from 'vconn', which has currently active flow
|
||||||
|
* format 'flow_format', and adds them as flow table entries in 'cls' for the
|
||||||
|
* version with the specified 'index'. */
|
||||||
|
static void
|
||||||
|
read_flows_from_switch(struct vconn *vconn, enum nx_flow_format flow_format,
|
||||||
|
struct classifier *cls, int index)
|
||||||
|
{
|
||||||
|
struct flow_stats_request fsr;
|
||||||
|
struct ofpbuf *request;
|
||||||
|
ovs_be32 send_xid;
|
||||||
|
bool done;
|
||||||
|
|
||||||
|
fsr.aggregate = false;
|
||||||
|
cls_rule_init_catchall(&fsr.match, 0);
|
||||||
|
fsr.out_port = OFPP_NONE;
|
||||||
|
fsr.table_id = 0xff;
|
||||||
|
request = ofputil_encode_flow_stats_request(&fsr, flow_format);
|
||||||
|
send_xid = ((struct ofp_header *) request->data)->xid;
|
||||||
|
send_openflow_buffer(vconn, request);
|
||||||
|
|
||||||
|
done = false;
|
||||||
|
while (!done) {
|
||||||
|
ovs_be32 recv_xid;
|
||||||
|
struct ofpbuf *reply;
|
||||||
|
|
||||||
|
run(vconn_recv_block(vconn, &reply), "OpenFlow packet receive failed");
|
||||||
|
recv_xid = ((struct ofp_header *) reply->data)->xid;
|
||||||
|
if (send_xid == recv_xid) {
|
||||||
|
const struct ofputil_msg_type *type;
|
||||||
|
const struct ofp_stats_reply *osr;
|
||||||
|
enum ofputil_msg_code code;
|
||||||
|
|
||||||
|
ofputil_decode_msg_type(reply->data, &type);
|
||||||
|
code = ofputil_msg_type_code(type);
|
||||||
|
if (code != OFPUTIL_OFPST_FLOW_REPLY &&
|
||||||
|
code != OFPUTIL_NXST_FLOW_REPLY) {
|
||||||
|
ovs_fatal(0, "received bad reply: %s",
|
||||||
|
ofp_to_string(reply->data, reply->size,
|
||||||
|
verbosity + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
osr = reply->data;
|
||||||
|
if (!(osr->flags & htons(OFPSF_REPLY_MORE))) {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
struct fte_version *version;
|
||||||
|
struct ofputil_flow_stats fs;
|
||||||
|
int retval;
|
||||||
|
|
||||||
|
retval = ofputil_decode_flow_stats_reply(&fs, reply,
|
||||||
|
flow_format);
|
||||||
|
if (retval) {
|
||||||
|
if (retval != EOF) {
|
||||||
|
ovs_fatal(0, "parse error in reply");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
version = xmalloc(sizeof *version);
|
||||||
|
version->cookie = fs.cookie;
|
||||||
|
version->idle_timeout = fs.idle_timeout;
|
||||||
|
version->hard_timeout = fs.hard_timeout;
|
||||||
|
version->flags = 0;
|
||||||
|
version->n_actions = fs.n_actions;
|
||||||
|
version->actions = xmemdup(fs.actions,
|
||||||
|
fs.n_actions * sizeof *fs.actions);
|
||||||
|
|
||||||
|
fte_insert(cls, &fs.rule, version, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
osr = ofpbuf_at(reply, 0, sizeof *osr);
|
||||||
|
} else {
|
||||||
|
VLOG_DBG("received reply with xid %08"PRIx32" "
|
||||||
|
"!= expected %08"PRIx32, recv_xid, send_xid);
|
||||||
|
}
|
||||||
|
ofpbuf_delete(reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fte_make_flow_mod(const struct fte *fte, int index, uint16_t command,
|
||||||
|
enum nx_flow_format flow_format, struct list *packets)
|
||||||
|
{
|
||||||
|
const struct fte_version *version = fte->versions[index];
|
||||||
|
struct flow_mod fm;
|
||||||
|
struct ofpbuf *ofm;
|
||||||
|
|
||||||
|
fm.cr = fte->rule;
|
||||||
|
fm.cookie = version->cookie;
|
||||||
|
fm.command = command;
|
||||||
|
fm.idle_timeout = version->idle_timeout;
|
||||||
|
fm.hard_timeout = version->hard_timeout;
|
||||||
|
fm.buffer_id = UINT32_MAX;
|
||||||
|
fm.out_port = OFPP_NONE;
|
||||||
|
fm.flags = version->flags;
|
||||||
|
if (command == OFPFC_ADD || command == OFPFC_MODIFY ||
|
||||||
|
command == OFPFC_MODIFY_STRICT) {
|
||||||
|
fm.actions = version->actions;
|
||||||
|
fm.n_actions = version->n_actions;
|
||||||
|
} else {
|
||||||
|
fm.actions = NULL;
|
||||||
|
fm.n_actions = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ofm = ofputil_encode_flow_mod(&fm, flow_format);
|
||||||
|
list_push_back(packets, &ofm->list_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
do_replace_flows(int argc OVS_UNUSED, char *argv[])
|
||||||
|
{
|
||||||
|
enum { FILE_IDX = 0, SWITCH_IDX = 1 };
|
||||||
|
enum nx_flow_format min_flow_format, flow_format;
|
||||||
|
struct cls_cursor cursor;
|
||||||
|
struct classifier cls;
|
||||||
|
struct list requests;
|
||||||
|
struct vconn *vconn;
|
||||||
|
struct fte *fte;
|
||||||
|
|
||||||
|
classifier_init(&cls);
|
||||||
|
min_flow_format = read_flows_from_file(argv[2], &cls, FILE_IDX);
|
||||||
|
|
||||||
|
open_vconn(argv[1], &vconn);
|
||||||
|
flow_format = negotiate_highest_flow_format(vconn, min_flow_format);
|
||||||
|
read_flows_from_switch(vconn, flow_format, &cls, SWITCH_IDX);
|
||||||
|
|
||||||
|
list_init(&requests);
|
||||||
|
|
||||||
|
/* Delete flows that exist on the switch but not in the file. */
|
||||||
|
cls_cursor_init(&cursor, &cls, NULL);
|
||||||
|
CLS_CURSOR_FOR_EACH (fte, rule, &cursor) {
|
||||||
|
struct fte_version *file_ver = fte->versions[FILE_IDX];
|
||||||
|
struct fte_version *sw_ver = fte->versions[SWITCH_IDX];
|
||||||
|
|
||||||
|
if (sw_ver && !file_ver) {
|
||||||
|
fte_make_flow_mod(fte, SWITCH_IDX, OFPFC_DELETE_STRICT,
|
||||||
|
flow_format, &requests);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add flows that exist in the file but not on the switch.
|
||||||
|
* Update flows that exist in both places but differ. */
|
||||||
|
cls_cursor_init(&cursor, &cls, NULL);
|
||||||
|
CLS_CURSOR_FOR_EACH (fte, rule, &cursor) {
|
||||||
|
struct fte_version *file_ver = fte->versions[FILE_IDX];
|
||||||
|
struct fte_version *sw_ver = fte->versions[SWITCH_IDX];
|
||||||
|
|
||||||
|
if (file_ver && (!sw_ver || !fte_version_equals(sw_ver, file_ver))) {
|
||||||
|
fte_make_flow_mod(fte, FILE_IDX, OFPFC_ADD, flow_format,
|
||||||
|
&requests);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transact_multiple_noreply(vconn, &requests);
|
||||||
|
vconn_close(vconn);
|
||||||
|
|
||||||
|
fte_free_all(&cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
read_flows_from_source(const char *source, struct classifier *cls, int index)
|
||||||
|
{
|
||||||
|
struct stat s;
|
||||||
|
|
||||||
|
if (source[0] == '/' || source[0] == '.'
|
||||||
|
|| (!strchr(source, ':') && !stat(source, &s))) {
|
||||||
|
read_flows_from_file(source, cls, index);
|
||||||
|
} else {
|
||||||
|
enum nx_flow_format flow_format;
|
||||||
|
struct vconn *vconn;
|
||||||
|
|
||||||
|
open_vconn(source, &vconn);
|
||||||
|
flow_format = negotiate_highest_flow_format(vconn, NXFF_OPENFLOW10);
|
||||||
|
read_flows_from_switch(vconn, flow_format, cls, index);
|
||||||
|
vconn_close(vconn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
do_diff_flows(int argc OVS_UNUSED, char *argv[])
|
||||||
|
{
|
||||||
|
bool differences = false;
|
||||||
|
struct cls_cursor cursor;
|
||||||
|
struct classifier cls;
|
||||||
|
struct fte *fte;
|
||||||
|
|
||||||
|
classifier_init(&cls);
|
||||||
|
read_flows_from_source(argv[1], &cls, 0);
|
||||||
|
read_flows_from_source(argv[2], &cls, 1);
|
||||||
|
|
||||||
|
cls_cursor_init(&cursor, &cls, NULL);
|
||||||
|
CLS_CURSOR_FOR_EACH (fte, rule, &cursor) {
|
||||||
|
struct fte_version *a = fte->versions[0];
|
||||||
|
struct fte_version *b = fte->versions[1];
|
||||||
|
|
||||||
|
if (!a || !b || !fte_version_equals(a, b)) {
|
||||||
|
char *rule_s = cls_rule_to_string(&fte->rule);
|
||||||
|
if (a) {
|
||||||
|
printf("-%s", rule_s);
|
||||||
|
fte_version_print(a);
|
||||||
|
}
|
||||||
|
if (b) {
|
||||||
|
printf("+%s", rule_s);
|
||||||
|
fte_version_print(b);
|
||||||
|
}
|
||||||
|
free(rule_s);
|
||||||
|
|
||||||
|
differences = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fte_free_all(&cls);
|
||||||
|
|
||||||
|
if (differences) {
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Undocumented commands for unit testing. */
|
/* Undocumented commands for unit testing. */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -1043,6 +1431,8 @@ static const struct command all_commands[] = {
|
|||||||
{ "add-flows", 2, 2, do_add_flows },
|
{ "add-flows", 2, 2, do_add_flows },
|
||||||
{ "mod-flows", 2, 2, do_mod_flows },
|
{ "mod-flows", 2, 2, do_mod_flows },
|
||||||
{ "del-flows", 1, 2, do_del_flows },
|
{ "del-flows", 1, 2, do_del_flows },
|
||||||
|
{ "replace-flows", 2, 2, do_replace_flows },
|
||||||
|
{ "diff-flows", 2, 2, do_diff_flows },
|
||||||
{ "dump-ports", 1, 2, do_dump_ports },
|
{ "dump-ports", 1, 2, do_dump_ports },
|
||||||
{ "mod-port", 3, 3, do_mod_port },
|
{ "mod-port", 3, 3, do_mod_port },
|
||||||
{ "probe", 1, 1, do_probe },
|
{ "probe", 1, 1, do_probe },
|
||||||
|
Reference in New Issue
Block a user