* New "monitor_cond" "monitor_cond_update" and "update2" extensions to
RFC 7047.
- OpenFlow:
+ * OpenFlow 1.3+ bundles are now supported for group mods as well as
+ flow mods and port mods. Both 'atomic' and 'ordered' bundle
+ flags are supported for group mods as well as flow mods.
* OpenFlow 1.1+ OFPT_QUEUE_GET_CONFIG_REQUEST now supports OFPP_ANY.
* OpenFlow 1.4+ OFPMP_QUEUE_DESC is now supported.
* OpenFlow 1.4+ OFPT_TABLE_STATUS is now supported.
properly translated to OpenFlow 1.0.
- ovs-ofctl:
* queue-get-config command now allows a queue ID to be specified.
- * '--bundle' option can now be used with OpenFlow 1.3.
+ * '--bundle' option can now be used with OpenFlow 1.3 and with group mods.
+ * New "bundle" command allows executing a mixture of flow and group mods
+ as a single atomic transaction.
* New option "--color" to produce colorized output for some commands.
* New option '--may-create' to use OFPGC_ADD_OR_MOD in mod-group command.
- IPFIX:
struct ofputil_group_mod;
struct ofputil_meter_mod;
struct ofputil_table_mod;
+struct ofputil_bundle_msg;
struct ofputil_tlv_table_mod;
struct simap;
enum ofputil_protocol;
enum ofputil_protocol *usable_protocols)
OVS_WARN_UNUSED_RESULT;
-char *parse_ofp_group_mod_file(const char *file_name, uint16_t command,
+char *parse_ofp_group_mod_file(const char *file_name, int command,
struct ofputil_group_mod **gms, size_t *n_gms,
enum ofputil_protocol *usable_protocols)
OVS_WARN_UNUSED_RESULT;
-char *parse_ofp_group_mod_str(struct ofputil_group_mod *, uint16_t command,
+char *parse_ofp_group_mod_str(struct ofputil_group_mod *, int command,
const char *string,
enum ofputil_protocol *usable_protocols)
OVS_WARN_UNUSED_RESULT;
+char *parse_ofp_bundle_file(const char *file_name,
+ struct ofputil_bundle_msg **, size_t *n_bms,
+ enum ofputil_protocol *)
+ OVS_WARN_UNUSED_RESULT;
+
char *parse_ofp_tlv_table_mod_str(struct ofputil_tlv_table_mod *,
uint16_t command, const char *string,
enum ofputil_protocol *usable_protocols)
#include "openvswitch/netdev.h"
#include "openflow/netronome-ext.h"
#include "openflow/nicira-ext.h"
+#include "openvswitch/ofp-msgs.h"
#include "openvswitch/ofpbuf.h"
#include "openvswitch/types.h"
#include "openvswitch/type-props.h"
const struct ofp_header *msg;
};
-enum ofptype;
-
enum ofperr ofputil_decode_bundle_ctrl(const struct ofp_header *,
struct ofputil_bundle_ctrl_msg *);
struct ofputil_bundle_add_msg *,
enum ofptype *type);
+struct ofputil_bundle_msg {
+ enum ofptype type;
+ union {
+ struct ofputil_flow_mod fm;
+ struct ofputil_group_mod gm;
+ };
+};
+
+/* Destroys 'bms'. */
+void ofputil_encode_bundle_msgs(struct ofputil_bundle_msg *bms, size_t n_bms,
+ struct ovs_list *requests,
+ enum ofputil_protocol);
+
struct ofputil_tlv_map {
struct ovs_list list_node;
}
static char * OVS_WARN_UNUSED_RESULT
-parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, uint16_t command,
+parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, int command,
char *string,
enum ofputil_protocol *usable_protocols)
{
*usable_protocols = OFPUTIL_P_OF11_UP;
+ if (command == -2) {
+ size_t len;
+
+ string += strspn(string, " \t\r\n"); /* Skip white space. */
+ len = strcspn(string, ", \t\r\n"); /* Get length of the first token. */
+
+ if (!strncmp(string, "add", len)) {
+ command = OFPGC11_ADD;
+ } else if (!strncmp(string, "delete", len)) {
+ command = OFPGC11_DELETE;
+ } else if (!strncmp(string, "modify", len)) {
+ command = OFPGC11_MODIFY;
+ } else if (!strncmp(string, "add_or_mod", len)) {
+ command = OFPGC11_ADD_OR_MOD;
+ } else if (!strncmp(string, "insert_bucket", len)) {
+ command = OFPGC15_INSERT_BUCKET;
+ } else if (!strncmp(string, "remove_bucket", len)) {
+ command = OFPGC15_REMOVE_BUCKET;
+ } else {
+ len = 0;
+ command = OFPGC11_ADD;
+ }
+ string += len;
+ }
+
switch (command) {
case OFPGC11_ADD:
fields = F_GROUP_TYPE | F_BUCKETS;
return error;
}
+/* If 'command' is given as -2, each line may start with a command name ("add",
+ * "modify", "add_or_mod", "delete", "insert_bucket", or "remove_bucket"). A
+ * missing command name is treated as "add".
+ */
char * OVS_WARN_UNUSED_RESULT
-parse_ofp_group_mod_str(struct ofputil_group_mod *gm, uint16_t command,
+parse_ofp_group_mod_str(struct ofputil_group_mod *gm, int command,
const char *str_,
enum ofputil_protocol *usable_protocols)
{
return error;
}
+/* If 'command' is given as -2, each line may start with a command name ("add",
+ * "modify", "add_or_mod", "delete", "insert_bucket", or "remove_bucket"). A
+ * missing command name is treated as "add".
+ */
char * OVS_WARN_UNUSED_RESULT
-parse_ofp_group_mod_file(const char *file_name, uint16_t command,
+parse_ofp_group_mod_file(const char *file_name, int command,
struct ofputil_group_mod **gms, size_t *n_gms,
enum ofputil_protocol *usable_protocols)
{
return NULL;
}
+static void
+free_bundle_msgs(struct ofputil_bundle_msg **bms, size_t *n_bms)
+{
+ for (size_t i = 0; i < *n_bms; i++) {
+ switch ((int)(*bms)[i].type) {
+ case OFPTYPE_FLOW_MOD:
+ free(CONST_CAST(struct ofpact *, (*bms)[i].fm.ofpacts));
+ break;
+ case OFPTYPE_GROUP_MOD:
+ ofputil_uninit_group_mod(&(*bms)[i].gm);
+ break;
+ default:
+ break;
+ }
+ }
+ free(*bms);
+ *bms = NULL;
+ *n_bms = 0;
+}
+
+/* Opens file 'file_name' and reads each line as a flow_mod or a group_mod,
+ * depending on the first keyword on each line. Stores each flow and group
+ * mods in '*bms', an array allocated on the caller's behalf, and the number of
+ * messages in '*n_bms'.
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error. The caller is responsible for freeing the returned string. */
+char * OVS_WARN_UNUSED_RESULT
+parse_ofp_bundle_file(const char *file_name,
+ struct ofputil_bundle_msg **bms, size_t *n_bms,
+ enum ofputil_protocol *usable_protocols)
+{
+ size_t allocated_bms;
+ char *error = NULL;
+ int line_number;
+ FILE *stream;
+ struct ds ds;
+
+ *usable_protocols = OFPUTIL_P_ANY;
+
+ *bms = NULL;
+ *n_bms = 0;
+
+ stream = !strcmp(file_name, "-") ? stdin : fopen(file_name, "r");
+ if (stream == NULL) {
+ return xasprintf("%s: open failed (%s)",
+ file_name, ovs_strerror(errno));
+ }
+
+ allocated_bms = *n_bms;
+ ds_init(&ds);
+ line_number = 0;
+ while (!ds_get_preprocessed_line(&ds, stream, &line_number)) {
+ enum ofputil_protocol usable;
+ char *s = ds_cstr(&ds);
+ size_t len;
+
+ if (*n_bms >= allocated_bms) {
+ struct ofputil_bundle_msg *new_bms;
+
+ new_bms = x2nrealloc(*bms, &allocated_bms, sizeof **bms);
+ for (size_t i = 0; i < *n_bms; i++) {
+ if (new_bms[i].type == OFPTYPE_GROUP_MOD) {
+ ovs_list_moved(&new_bms[i].gm.buckets,
+ &(*bms)[i].gm.buckets);
+ }
+ }
+ *bms = new_bms;
+ }
+
+ s += strspn(s, " \t\r\n"); /* Skip white space. */
+ len = strcspn(s, ", \t\r\n"); /* Get length of the first token. */
+
+ if (!strncmp(s, "flow", len)) {
+ s += len;
+ error = parse_ofp_flow_mod_str(&(*bms)[*n_bms].fm, s, -2, &usable);
+ if (error) {
+ break;
+ }
+ (*bms)[*n_bms].type = OFPTYPE_FLOW_MOD;
+ } else if (!strncmp(s, "group", len)) {
+ s += len;
+ error = parse_ofp_group_mod_str(&(*bms)[*n_bms].gm, -2, s,
+ &usable);
+ if (error) {
+ break;
+ }
+ (*bms)[*n_bms].type = OFPTYPE_GROUP_MOD;
+ } else {
+ error = xasprintf("Unsupported bundle message type: %.*s",
+ (int)len, s);
+ break;
+ }
+
+ *usable_protocols &= usable; /* Each line can narrow the set. */
+ *n_bms += 1;
+ }
+
+ ds_destroy(&ds);
+ if (stream != stdin) {
+ fclose(stream);
+ }
+
+ if (error) {
+ char *err_msg = xasprintf("%s:%d: %s", file_name, line_number, error);
+ free(error);
+
+ free_bundle_msgs(bms, n_bms);
+ return err_msg;
+ }
+ return NULL;
+}
+
char * OVS_WARN_UNUSED_RESULT
parse_ofp_tlv_table_mod_str(struct ofputil_tlv_table_mod *ttm,
uint16_t command, const char *s,
return 0;
}
+/* Destroys 'bms'. */
+void
+ofputil_encode_bundle_msgs(struct ofputil_bundle_msg *bms, size_t n_bms,
+ struct ovs_list *requests,
+ enum ofputil_protocol protocol)
+{
+ enum ofp_version version = ofputil_protocol_to_ofp_version(protocol);
+
+ for (size_t i = 0; i < n_bms; i++) {
+ struct ofpbuf *request = NULL;
+
+ switch ((int)bms[i].type) {
+ case OFPTYPE_FLOW_MOD:
+ request = ofputil_encode_flow_mod(&bms[i].fm, protocol);
+ free(CONST_CAST(struct ofpact *, bms[i].fm.ofpacts));
+ break;
+ case OFPTYPE_GROUP_MOD:
+ request = ofputil_encode_group_mod(version, &bms[i].gm);
+ ofputil_uninit_group_mod(&bms[i].gm);
+ break;
+ default:
+ break;
+ }
+ if (request) {
+ ovs_list_push_back(requests, &request->list_node);
+ }
+ }
+ free(bms);
+}
+
/* Parse a queue status request message into 'oqsr'.
* Returns 0 if successful, otherwise an OFPERR_* number. */
enum ofperr
/* Minimum required by OpenFlow 1.4. */
case OFPTYPE_PORT_MOD:
case OFPTYPE_FLOW_MOD:
+ /* Other supported types. */
+ case OFPTYPE_GROUP_MOD:
return true;
/* Nice to have later. */
case OFPTYPE_FLOW_MOD_TABLE_ID:
- case OFPTYPE_GROUP_MOD:
case OFPTYPE_TABLE_MOD:
case OFPTYPE_METER_MOD:
case OFPTYPE_PACKET_OUT:
/*
* Copyright (c) 2013, 2014 Alexandru Copot <alex.mihai.c@gmail.com>, with support from IXIA.
* Copyright (c) 2013, 2014 Daniel Baluta <dbaluta@ixiacom.com>
- * Copyright (c) 2014, 2015 Nicira, Inc.
+ * Copyright (c) 2014, 2015, 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.
struct ofp_bundle_entry {
struct ovs_list node;
- enum ofptype type; /* OFPTYPE_FLOW_MOD or OFPTYPE_PORT_MOD. */
+ enum ofptype type; /* OFPTYPE_FLOW_MOD, OFPTYPE_PORT_MOD, or
+ * OFPTYPE_GROUP_MOD. */
union {
struct ofproto_flow_mod ofm; /* ofm.fm.ofpacts must be malloced. */
struct ofproto_port_mod opm;
+ struct ofproto_group_mod ogm;
};
/* OpenFlow header and some of the message contents for error reporting. */
if (entry) {
if (entry->type == OFPTYPE_FLOW_MOD) {
free(entry->ofm.fm.ofpacts);
+ } else if (entry->type == OFPTYPE_GROUP_MOD) {
+ ofputil_uninit_group_mod(&entry->ogm.gm);
}
free(entry);
}
}
}
+static void
+delete_flows_revert__(struct ofproto *ofproto,
+ const struct rule_collection *rules)
+ OVS_REQUIRES(ofproto_mutex)
+{
+ struct rule *rule;
+
+ RULE_COLLECTION_FOR_EACH (rule, rules) {
+ struct oftable *table = &ofproto->tables[rule->table_id];
+
+ /* Add rule back to ofproto data structures. */
+ ofproto_rule_insert__(ofproto, rule);
+
+ /* Restore table's rule count. */
+ table->n_flows++;
+
+ /* Restore the original visibility of the rule. */
+ cls_rule_restore_visibility(&rule->cr);
+ }
+}
+
static void
delete_flows_finish__(struct ofproto *ofproto,
struct rule_collection *rules,
delete_flows_revert(struct ofproto *ofproto, struct ofproto_flow_mod *ofm)
OVS_REQUIRES(ofproto_mutex)
{
- struct rule_collection *rules = &ofm->old_rules;
- struct rule *rule;
-
- RULE_COLLECTION_FOR_EACH (rule, rules) {
- struct oftable *table = &ofproto->tables[rule->table_id];
-
- /* Add rule back to ofproto data structures. */
- ofproto_rule_insert__(ofproto, rule);
-
- /* Restore table's rule count. */
- table->n_flows++;
-
- /* Restore the original visibility of the rule. */
- cls_rule_restore_visibility(&rule->cr);
- }
- rule_collection_destroy(rules);
+ delete_flows_revert__(ofproto, &ofm->old_rules);
+ rule_collection_destroy(&ofm->old_rules);
}
static void
return error;
}
+static void
+ofproto_group_mod_revert(struct ofproto *ofproto,
+ struct ofproto_group_mod *ogm)
+ OVS_REQUIRES(ofproto_mutex)
+{
+ struct ofgroup *new_group = ogm->new_group;
+ struct ofgroup *old_group;
+
+ /* Restore replaced or deleted groups. */
+ GROUP_COLLECTION_FOR_EACH (old_group, &ogm->old_groups) {
+ ofproto->n_groups[old_group->type]++;
+ if (new_group) {
+ ovs_assert(group_collection_n(&ogm->old_groups) == 1);
+ /* Transfer rules back. */
+ rule_collection_move(&old_group->rules, &new_group->rules);
+ } else {
+ old_group->being_deleted = false;
+ /* Revert rule deletion. */
+ delete_flows_revert__(ofproto, &old_group->rules);
+ }
+ /* Restore visibility. */
+ versions_set_remove_version(&old_group->versions,
+ OVS_VERSION_NOT_REMOVED);
+ }
+ if (new_group) {
+ /* Remove the new group immediately. It was never visible to
+ * lookups. */
+ cmap_remove(&ofproto->groups, &new_group->cmap_node,
+ hash_int(new_group->group_id, 0));
+ ofproto->n_groups[new_group->type]--;
+ ofproto_group_unref(new_group);
+ }
+}
+
static void
ofproto_group_mod_finish(struct ofproto *ofproto,
struct ofproto_group_mod *ogm,
prev_is_port_mod = true;
error = port_mod_start(ofconn, &be->opm.pm, &be->opm.port);
}
- } else if (be->type == OFPTYPE_FLOW_MOD) {
- /* Flow mods between port mods are applied as a single
- * version, but the versions are published only after
- * we know the commit is successful. */
+ } else {
+ /* Flow & group mods between port mods are applied as a single
+ * version, but the versions are published only after we know
+ * the commit is successful. */
if (prev_is_port_mod) {
+ prev_is_port_mod = false;
++version;
}
- prev_is_port_mod = false;
- /* Store the version in which the changes should take
- * effect. */
- be->ofm.version = version;
- error = ofproto_flow_mod_start(ofproto, &be->ofm);
- } else {
- OVS_NOT_REACHED();
+ if (be->type == OFPTYPE_FLOW_MOD) {
+ /* Store the version in which the changes should take
+ * effect. */
+ be->ofm.version = version;
+ error = ofproto_flow_mod_start(ofproto, &be->ofm);
+ } else if (be->type == OFPTYPE_GROUP_MOD) {
+ /* Store the version in which the changes should take
+ * effect. */
+ be->ogm.version = version;
+ error = ofproto_group_mod_start(ofproto, &be->ogm);
+ } else {
+ OVS_NOT_REACHED();
+ }
}
if (error) {
break;
LIST_FOR_EACH_REVERSE_CONTINUE(be, node, &bundle->msg_list) {
if (be->type == OFPTYPE_FLOW_MOD) {
ofproto_flow_mod_revert(ofproto, &be->ofm);
+ } else if (be->type == OFPTYPE_GROUP_MOD) {
+ ofproto_group_mod_revert(ofproto, &be->ogm);
}
+
/* Nothing needs to be reverted for a port mod. */
}
} else {
/* 4. Finish. */
LIST_FOR_EACH (be, node, &bundle->msg_list) {
- if (be->type == OFPTYPE_FLOW_MOD) {
- struct openflow_mod_requester req = { ofconn,
- be->ofp_msg };
-
- /* Bump the lookup version to the one of the current
- * message. This makes all the changes in the bundle at
- * this version visible to lookups at once. */
- if (ofproto->tables_version < be->ofm.version) {
- ofproto->tables_version = be->ofm.version;
- ofproto->ofproto_class->set_tables_version(
- ofproto, ofproto->tables_version);
- }
-
- ofproto_flow_mod_finish(ofproto, &be->ofm, &req);
- } else if (be->type == OFPTYPE_PORT_MOD) {
+ if (be->type == OFPTYPE_PORT_MOD) {
/* Perform the actual port mod. This is not atomic, i.e.,
* the effects will be immediately seen by upcall
* processing regardless of the lookup version. It should
* also from OVSDB changes asynchronously to all upcall
* processing. */
port_mod_finish(ofconn, &be->opm.pm, be->opm.port);
+ } else {
+ struct openflow_mod_requester req = { ofconn,
+ be->ofp_msg };
+ if (be->type == OFPTYPE_FLOW_MOD) {
+ /* Bump the lookup version to the one of the current
+ * message. This makes all the changes in the bundle
+ * at this version visible to lookups at once. */
+ if (ofproto->tables_version < be->ofm.version) {
+ ofproto->tables_version = be->ofm.version;
+ ofproto->ofproto_class->set_tables_version(
+ ofproto, ofproto->tables_version);
+ }
+
+ ofproto_flow_mod_finish(ofproto, &be->ofm, &req);
+ } else if (be->type == OFPTYPE_GROUP_MOD) {
+ /* Bump the lookup version to the one of the current
+ * message. This makes all the changes in the bundle
+ * at this version visible to lookups at once. */
+ if (ofproto->tables_version < be->ogm.version) {
+ ofproto->tables_version = be->ogm.version;
+ ofproto->ofproto_class->set_tables_version(
+ ofproto, ofproto->tables_version);
+ }
+
+ ofproto_group_mod_finish(ofproto, &be->ogm, &req);
+ }
}
}
}
ofproto->n_tables);
/* Move actions to heap. */
bmsg->ofm.fm.ofpacts = ofpbuf_steal_data(&ofpacts);
+ } else if (type == OFPTYPE_GROUP_MOD) {
+ error = ofputil_decode_group_mod(badd.msg, &bmsg->ogm.gm);
} else {
OVS_NOT_REACHED();
}
s/ hard_age=[0-9]*,//
s/dp_hash=0x[0-9a-f]*\//dp_hash=0x0\//
s/recirc_id=0x[0-9a-f]*,/recirc_id=0x0,/
+s/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z|//
+s/dir\/[0-9]*\/br0.mgmt/dir\/XXXX\/br0.mgmt/
'
}
OVS_VSWITCHD_STOP
AT_CLEANUP
-AT_SETUP([ofproto - group_mod with mod and add_or_mod command])
+AT_SETUP([ofproto - group mod with mod and add_or_mod command])
OVS_VSWITCHD_START
dnl Check that mod-group for non-existing group fails without --may-create
AT_DATA([stderr], [dnl
dnl This is really bare-bones.
dnl It at least checks request and reply serialization and deserialization.
dnl Actions definition listed in both supported formats (w/ actions=)
-AT_SETUP([ofproto - insert buckets])
+AT_SETUP([ofproto - insert group buckets])
OVS_VSWITCHD_START
# Add group with no buckets.
AT_DATA([groups.txt], [dnl
dnl This is really bare-bones.
dnl It at least checks request and reply serialization and deserialization.
dnl Actions definition listed in both supported formats (w/ actions=)
-AT_SETUP([ofproto - remove buckets])
+AT_SETUP([ofproto - remove group buckets])
OVS_VSWITCHD_START
AT_DATA([groups.txt], [dnl
group_id=1234,type=all,bucket=bucket_id:10,actions=output:10,bucket=bucket_id:11,actions=output:11,bucket=bucket_id:12,actions=output:12,bucket=bucket_id:13,actions=output:13,bucket=bucket_id:14,actions=output:14,bucket=bucket_id:15,actions=output:15,bucket=bucket_id:16,actions=output:16
OVS_VSWITCHD_STOP
AT_CLEANUP
+AT_SETUP([ofproto - bundle del group (OpenFlow 1.3)])
+OVS_VSWITCHD_START
+AT_DATA([groups.txt], [dnl
+group_id=1234,type=all,bucket=output:10
+group_id=1235,type=all,bucket=actions=output:10
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow13 -vwarn add-groups br0 groups.txt])
+AT_CHECK([ovs-ofctl -O OpenFlow13 -vwarn dump-groups br0 ], [0], [stdout])
+AT_CHECK([strip_xids < stdout | sort], [0], [dnl
+ group_id=1234,type=all,bucket=actions=output:10
+ group_id=1235,type=all,bucket=actions=output:10
+OFPST_GROUP_DESC reply (OF1.3):
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow13 -vwarn del-groups br0 group_id=1234])
+AT_CHECK([ovs-ofctl -O OpenFlow13 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.3):
+ group_id=1235,type=all,bucket=actions=output:10
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow13 -vwarn del-groups br0 group_id=1234])
+AT_CHECK([ovs-ofctl -O OpenFlow13 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.3):
+ group_id=1235,type=all,bucket=actions=output:10
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow13 -vwarn del-groups br0], [0])
+AT_CHECK([ovs-ofctl -O OpenFlow13 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.3):
+])
+
+# Negative test.
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow13 -vwarn del-groups br0 group_id=0xfffffff0],
+ [1], [], [ovs-ofctl: invalid group id 4294967280
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto - bundle add indirect group])
+OVS_VSWITCHD_START
+dnl indirect group must have exactly one bucket
+AT_DATA([stderr], [dnl
+OFPT_ERROR (OF1.4) (xid=0x2): OFPGMFC_INVALID_GROUP
+OFPT_BUNDLE_ADD_MESSAGE (OF1.4) (xid=0x2):
+ bundle_id=0 flags=atomic ordered
+OFPT_GROUP_MOD (OF1.4) (xid=0x2): ***decode error: OFPGMFC_INVALID_GROUP***
+])
+AT_CHECK([ovs-ofctl --bundle -vwarn add-group br0 'group_id=1234,type=indirect'], [1], , [stderr])
+AT_CHECK([ovs-ofctl --bundle -vwarn add-group br0 'group_id=1235,type=indirect,bucket=output:10'])
+AT_CHECK([ovs-ofctl --bundle -vwarn add-group br0 'group_id=1236,type=indirect,bucket=output:10,bucket=output:11'], [1], , [stderr])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto - bundle group mod with mod and add_or_mod command])
+OVS_VSWITCHD_START
+dnl Check that mod-group for non-existing group fails without --may-create
+AT_DATA([stderr], [dnl
+OFPT_ERROR (OF1.4) (xid=0x2): OFPGMFC_UNKNOWN_GROUP
+OFPT_GROUP_MOD (OF1.4) (xid=0x2):
+ MOD group_id=1234,type=indirect,bucket=actions=output:2
+])
+AT_CHECK([ovs-ofctl --bundle -vwarn mod-group br0 'group_id=1234,type=indirect,bucket=actions=2'], [1], , [stderr])
+dnl Check that mod-group for non-existing group succeeds with --may-create
+AT_CHECK([ovs-ofctl --bundle -vwarn --may-create mod-group br0 'group_id=1234,type=indirect,bucket=actions=2'])
+AT_CHECK([ovs-ofctl -O OpenFlow14 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.4):
+ group_id=1234,type=indirect,bucket=actions=output:2
+])
+dnl Check that mod-group for existing group succeeds with --may-create
+AT_CHECK([ovs-ofctl --bundle -vwarn --may-create mod-group br0 'group_id=1234,type=indirect,bucket=actions=3'])
+AT_CHECK([ovs-ofctl -O OpenFlow14 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.4):
+ group_id=1234,type=indirect,bucket=actions=output:3
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto - bundle del group (OpenFlow 1.5)])
+OVS_VSWITCHD_START
+AT_DATA([groups.txt], [dnl
+group_id=1233,type=select,selection_method=hash,bucket=output:10,bucket=output:11
+group_id=1234,type=select,selection_method=hash,fields(eth_dst,ip_dst,tcp_dst),bucket=output:10,bucket=output:11
+group_id=1235,type=all,bucket=actions=output:12,bucket=bucket_id:0,actions=output:10,bucket=bucket_id:1,actions=output:11
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn add-groups br0 groups.txt])
+AT_CHECK([ovs-ofctl -F OXM-OpenFlow15 -O OpenFlow15 -vwarn dump-groups br0 1234], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=select,selection_method=hash,fields(eth_dst,ip_dst,tcp_dst),bucket=bucket_id:0,actions=output:10,bucket=bucket_id:1,actions=output:11
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn del-groups br0 group_id=1234])
+AT_CHECK([ovs-ofctl -O OpenFlow15 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout | sort], [0], [dnl
+ group_id=1233,type=select,selection_method=hash,bucket=bucket_id:0,actions=output:10,bucket=bucket_id:1,actions=output:11
+ group_id=1235,type=all,bucket=bucket_id:2,actions=output:12,bucket=bucket_id:0,actions=output:10,bucket=bucket_id:1,actions=output:11
+OFPST_GROUP_DESC reply (OF1.5):
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn del-groups br0 group_id=1234])
+AT_CHECK([ovs-ofctl -O OpenFlow15 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout | sort], [0], [dnl
+ group_id=1233,type=select,selection_method=hash,bucket=bucket_id:0,actions=output:10,bucket=bucket_id:1,actions=output:11
+ group_id=1235,type=all,bucket=bucket_id:2,actions=output:12,bucket=bucket_id:0,actions=output:10,bucket=bucket_id:1,actions=output:11
+OFPST_GROUP_DESC reply (OF1.5):
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn del-groups br0], [0])
+AT_CHECK([ovs-ofctl -O OpenFlow15 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto - bundle del group deletes flows])
+OVS_VSWITCHD_START
+AT_DATA([groups.txt], [dnl
+group_id=1234,type=all,bucket=output:10
+group_id=1235,type=all,bucket=output:10
+])
+AT_CHECK([ovs-ofctl --bundle -vwarn add-groups br0 groups.txt])
+AT_DATA([flows.txt], [dnl
+tcp actions=group:1234
+table=2 udp actions=group:1235
+])
+AT_CHECK([ovs-ofctl --bundle -vwarn add-flows br0 flows.txt])
+AT_CHECK([ovs-ofctl -O OpenFlow14 -vwarn dump-flows br0 | ofctl_strip | sort],
+[0], [dnl
+ table=2, udp actions=group:1235
+ tcp actions=group:1234
+OFPST_FLOW reply (OF1.4):
+])
+AT_CHECK([ovs-ofctl --bundle -vwarn del-groups br0 group_id=1234])
+AT_CHECK([ovs-ofctl -O OpenFlow14 -vwarn dump-flows br0 | ofctl_strip | sort],
+[0], [dnl
+ table=2, udp actions=group:1235
+OFPST_FLOW reply (OF1.4):
+])
+AT_CHECK([ovs-ofctl --bundle -vwarn del-groups br0 group_id=1234])
+AT_CHECK([ovs-ofctl -O OpenFlow14 -vwarn dump-flows br0 | ofctl_strip | sort],
+[0], [dnl
+ table=2, udp actions=group:1235
+OFPST_FLOW reply (OF1.4):
+])
+AT_CHECK([ovs-ofctl --bundle -vwarn del-groups br0])
+AT_CHECK([ovs-ofctl -O OpenFlow14 -vwarn dump-flows br0 | ofctl_strip | sort],
+[0], [dnl
+OFPST_FLOW reply (OF1.4):
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+dnl This is really bare-bones.
+dnl It at least checks request and reply serialization and deserialization.
+dnl Actions definition listed in both supported formats (w/ actions=)
+AT_SETUP([ofproto - bundle insert group buckets])
+OVS_VSWITCHD_START
+# Add group with no buckets.
+AT_DATA([groups.txt], [dnl
+group_id=1234,type=all
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn add-groups br0 groups.txt])
+AT_CHECK([ovs-ofctl -F OXM-OpenFlow15 -O OpenFlow15 -vwarn dump-groups br0 1234], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=all
+])
+
+# Add two buckets, using "last".
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn insert-buckets br0 group_id=1234,command_bucket_id=last,bucket=bucket_id:10,actions=output:10,bucket=bucket_id:11,actions=output:11])
+AT_CHECK([ovs-ofctl -F OXM-OpenFlow15 -O OpenFlow15 -vwarn dump-groups br0 1234], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=all,bucket=bucket_id:10,actions=output:10,bucket=bucket_id:11,actions=output:11
+])
+
+# Start over again, then add two buckets using "first".
+AT_DATA([groups.txt], [dnl
+delete group_id=1234
+add group_id=1234,type=all
+insert_bucket group_id=1234,command_bucket_id=first,bucket=bucket_id:10,actions=output:10,bucket=bucket_id:11,actions=output:11
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn add-groups br0 groups.txt])
+AT_CHECK([ovs-ofctl -F OXM-OpenFlow15 -O OpenFlow15 -vwarn dump-groups br0 1234], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=all,bucket=bucket_id:10,actions=output:10,bucket=bucket_id:11,actions=output:11
+])
+
+# Add two more buckets before the existing ones.
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn insert-buckets br0 group_id=1234,command_bucket_id=first,bucket=bucket_id:0,actions=output:0,bucket=bucket_id:1,actions=output:1])
+AT_CHECK([ovs-ofctl -O OpenFlow15 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=all,bucket=bucket_id:0,actions=output:0,bucket=bucket_id:1,actions=output:1,bucket=bucket_id:10,actions=output:10,bucket=bucket_id:11,actions=output:11
+])
+
+# Add another bucket at the end.
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn insert-buckets br0 group_id=1234,command_bucket_id=last,bucket=bucket_id:14,actions=output:14,bucket=bucket_id:15,actions=output:15])
+AT_CHECK([ovs-ofctl -O OpenFlow15 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=all,bucket=bucket_id:0,actions=output:0,bucket=bucket_id:1,actions=output:1,bucket=bucket_id:10,actions=output:10,bucket=bucket_id:11,actions=output:11,bucket=bucket_id:14,actions=output:14,bucket=bucket_id:15,actions=output:15
+])
+
+# Verify that duplicate bucket IDs are rejected.
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn insert-buckets br0 group_id=1234,command_bucket_id=last,bucket=bucket_id:14,actions=output:14,bucket=bucket_id:15,actions=output:15], [1], [], [stderr])
+AT_CHECK([strip_xids < stderr | sed '/talking to/,$d'], [0], [dnl
+Error OFPGMFC_BUCKET_EXISTS for: OFPT_GROUP_MOD (OF1.5):
+ INSERT_BUCKET command_bucket_id:last,group_id=1234,bucket=bucket_id:14,actions=output:14,bucket=bucket_id:15,actions=output:15
+Error OFPBFC_MSG_FAILED for: OFPT_BUNDLE_CONTROL (OF1.5):
+ bundle_id=0 type=COMMIT_REQUEST flags=atomic ordered
+])
+
+# Add another bucket just before bucket 15.
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn insert-buckets br0 group_id=1234,command_bucket_id=15])
+AT_CHECK([ovs-ofctl -O OpenFlow15 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=all,bucket=bucket_id:0,actions=output:0,bucket=bucket_id:1,actions=output:1,bucket=bucket_id:10,actions=output:10,bucket=bucket_id:11,actions=output:11,bucket=bucket_id:14,actions=output:14,bucket=bucket_id:15,actions=output:15
+])
+
+# Add two more buckets just before bucket 11,
+# getting the command from a file.
+AT_DATA([buckets.txt], [dnl
+group_id=1234,command_bucket_id=11,bucket=bucket_id:12,actions=output:12,bucket=bucket_id:13,actions=output:13
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn insert-buckets br0 - < buckets.txt])
+AT_CHECK([ovs-ofctl -O OpenFlow15 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=all,bucket=bucket_id:0,actions=output:0,bucket=bucket_id:1,actions=output:1,bucket=bucket_id:10,actions=output:10,bucket=bucket_id:11,actions=output:11,bucket=bucket_id:12,actions=output:12,bucket=bucket_id:13,actions=output:13,bucket=bucket_id:14,actions=output:14,bucket=bucket_id:15,actions=output:15
+])
+
+# Add yet two more buckets.
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn insert-buckets br0 group_id=1234,command_bucket_id=15,bucket=bucket_id:20,actions=output:20,bucket=bucket_id:21,actions=output:21])
+AT_CHECK([ovs-ofctl -O OpenFlow15 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=all,bucket=bucket_id:0,actions=output:0,bucket=bucket_id:1,actions=output:1,bucket=bucket_id:10,actions=output:10,bucket=bucket_id:11,actions=output:11,bucket=bucket_id:12,actions=output:12,bucket=bucket_id:13,actions=output:13,bucket=bucket_id:14,actions=output:14,bucket=bucket_id:15,actions=output:15,bucket=bucket_id:20,actions=output:20,bucket=bucket_id:21,actions=output:21
+])
+
+# Negative tests.
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn insert-buckets br0 group_id=1234,command_bucket_id=0xffffff01,bucket=bucket_id:0,actions=output:0,bucket=bucket_id:1,actions=output:1], [1], [],
+ [ovs-ofctl: invalid command bucket id 4294967041
+])
+AT_CHECK([ovs-ofctl --bundle -vwarn insert-buckets br0 group_id=1234,command_bucket_id=first,bucket=bucket_id:0,actions=output:0,bucket=bucket_id:1,actions=output:1], [1], [],
+ [ovs-ofctl: insert-bucket needs OpenFlow 1.5 or later ('-O OpenFlow15')
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto - bundle remove group buckets])
+OVS_VSWITCHD_START
+AT_DATA([groups.txt], [dnl
+group_id=1234,type=all,bucket=bucket_id:10,actions=output:10,bucket=bucket_id:11,actions=output:11,bucket=bucket_id:12,actions=output:12,bucket=bucket_id:13,actions=output:13,bucket=bucket_id:14,actions=output:14,bucket=bucket_id:15,actions=output:15,bucket=bucket_id:16,actions=output:16
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn add-groups br0 groups.txt])
+AT_CHECK([ovs-ofctl -F OXM-OpenFlow15 -O OpenFlow15 -vwarn dump-groups br0 1234], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=all,bucket=bucket_id:10,actions=output:10,bucket=bucket_id:11,actions=output:11,bucket=bucket_id:12,actions=output:12,bucket=bucket_id:13,actions=output:13,bucket=bucket_id:14,actions=output:14,bucket=bucket_id:15,actions=output:15,bucket=bucket_id:16,actions=output:16
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn remove-buckets br0 group_id=1234,command_bucket_id=first])
+AT_CHECK([ovs-ofctl -O OpenFlow15 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=all,bucket=bucket_id:11,actions=output:11,bucket=bucket_id:12,actions=output:12,bucket=bucket_id:13,actions=output:13,bucket=bucket_id:14,actions=output:14,bucket=bucket_id:15,actions=output:15,bucket=bucket_id:16,actions=output:16
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn remove-buckets br0 group_id=1234,command_bucket_id=last])
+AT_CHECK([ovs-ofctl -O OpenFlow15 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=all,bucket=bucket_id:11,actions=output:11,bucket=bucket_id:12,actions=output:12,bucket=bucket_id:13,actions=output:13,bucket=bucket_id:14,actions=output:14,bucket=bucket_id:15,actions=output:15
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn remove-buckets br0 group_id=1234,command_bucket_id=13])
+AT_CHECK([ovs-ofctl -O OpenFlow15 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=all,bucket=bucket_id:11,actions=output:11,bucket=bucket_id:12,actions=output:12,bucket=bucket_id:14,actions=output:14,bucket=bucket_id:15,actions=output:15
+])
+AT_DATA([buckets.txt], [dnl
+group_id=1234
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn remove-buckets br0 - < buckets.txt])
+AT_CHECK([ovs-ofctl -O OpenFlow15 -vwarn dump-groups br0], [0], [stdout])
+AT_CHECK([strip_xids < stdout], [0], [dnl
+OFPST_GROUP_DESC reply (OF1.5):
+ group_id=1234,type=all
+])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn remove-buckets br0 group_id=1234,command_bucket_id=first])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn remove-buckets br0 group_id=1234,command_bucket_id=last])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn remove-buckets br0 group_id=1234,command_bucket_id=all])
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow15 -vwarn remove-buckets br0 group_id=1234,command_bucket_id=1], [1], [], [stderr])
+AT_CHECK([ofctl_strip < stderr | sed '/talking to/,$d'], [0], [dnl
+Error OFPGMFC_UNKNOWN_BUCKET for: OFPT_GROUP_MOD (OF1.5):
+ REMOVE_BUCKET command_bucket_id:1,group_id=1234
+Error OFPBFC_MSG_FAILED for: OFPT_BUNDLE_CONTROL (OF1.5):
+ bundle_id=0 type=COMMIT_REQUEST flags=atomic ordered
+])
+# Negative test.
+AT_CHECK([ovs-ofctl --bundle -O OpenFlow11 -vwarn remove-buckets br0 group_id=1234,command_bucket_id=last], [1], [],
+ [ovs-ofctl: remove-bucket needs OpenFlow 1.5 or later ('-O OpenFlow15')
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
dnl This is really bare-bones.
dnl It at least checks request and reply serialization and deserialization.
AT_SETUP([ofproto - flow mod checks group availability])
OVS_VSWITCHD_STOP
AT_CLEANUP
+AT_SETUP([ofproto - bundle flow mod checks group availability])
+OVS_VSWITCHD_START
+AT_DATA([bundle.txt], [dnl
+group add group_id=1234,type=all,bucket=output:10
+flow add tcp actions=group:1234
+flow add udp actions=group:1235
+])
+AT_CHECK([ovs-ofctl -vwarn bundle br0 bundle.txt], [1], [], [stderr])
+
+# The output should look like this:
+#
+# 00000000 02 0e 00 98 00 00 00 02-00 00 00 00 00 00 00 00 |................|
+# 00000010 00 00 00 00 00 00 00 00-ff 00 00 00 00 00 80 00 |................|
+# 00000020 ff ff ff ff ff ff ff ff-ff ff ff ff 00 00 00 00 |................|
+# 00000030 00 00 00 58 00 00 00 00-00 00 03 d7 00 00 00 00 |...X............|
+#
+# This 'sed' command captures the error message but drops details.
+AT_CHECK([sed '/truncated/d
+/^000000.0/d' stderr | ofctl_strip | sed '/talking to/,$d'], [0],
+ [dnl
+Error OFPBAC_BAD_OUT_GROUP for: OFPT_FLOW_MOD (OF1.4): ADD udp actions=group:1235
+Error OFPBFC_MSG_FAILED for: OFPT_BUNDLE_CONTROL (OF1.4):
+ bundle_id=0 type=COMMIT_REQUEST flags=atomic ordered
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
dnl This is really bare-bones.
dnl It at least checks request and reply serialization and deserialization.
AT_SETUP([ofproto - group description])
OVS_VSWITCHD_STOP
AT_CLEANUP
+AT_SETUP([ofproto - bundle flow_mod with out group matching (OpenFlow 1.4)])
+OVS_VSWITCHD_START
+AT_DATA([bundle.txt], [dnl
+group group_id=1,type=all,bucket=output:10
+group group_id=2,type=all,bucket=output:10
+group group_id=3,type=all,bucket=output:10
+flow in_port=1 actions=group:2,output:5
+flow in_port=2 actions=group:1,write_actions(group:2,group:3,output:6)
+flow in_port=3 actions=group:3,group:1,write_actions(group:2,output:3)
+flow in_port=4 actions=output:4
+flow in_port=5 actions=write_actions(output:4,group:3)
+flow in_port=6 actions=group:1
+])
+AT_CHECK([ovs-ofctl bundle br0 bundle.txt])
+
+# for checking
+AT_DATA([flows.txt], [dnl
+ in_port=1 actions=group:2,output:5
+ in_port=2 actions=group:1,write_actions(group:2,group:3,output:6)
+ in_port=3 actions=group:3,group:1,write_actions(group:2,output:3)
+ in_port=4 actions=output:4
+ in_port=5 actions=write_actions(output:4,group:3)
+ in_port=6 actions=group:1
+])
+
+(cat flows.txt; echo 'OFPST_FLOW reply (OF1.4):') > expout
+AT_CHECK([ovs-ofctl -O OpenFlow14 dump-flows br0 | ofctl_strip | sort], [0],
+ [expout])
+
+(grep 'output:3' flows.txt | grep 'group:2'; echo 'OFPST_FLOW reply (OF1.4):') > expout
+AT_CHECK([ovs-ofctl -O OpenFlow14 dump-flows br0 out_port=3,out_group=2 | ofctl_strip | sort], [0], [expout])
+
+AT_CHECK([ovs-ofctl -O OpenFlow14 del-flows br0 out_group=2])
+(grep -v 'group:2' flows.txt; echo 'OFPST_FLOW reply (OF1.4):') > expout
+AT_CHECK([ovs-ofctl -O OpenFlow14 dump-flows br0 | ofctl_strip | sort], [0],
+ [expout])
+
+AT_CHECK([ovs-ofctl -O OpenFlow14 del-flows br0 out_group=3])
+(grep -v 'group:[[23]]' flows.txt; echo 'OFPST_FLOW reply (OF1.4):') > expout
+AT_CHECK([ovs-ofctl -O OpenFlow14 dump-flows br0 | ofctl_strip | sort], [0],
+ [expout])
+
+AT_CHECK([ovs-ofctl -O OpenFlow14 del-flows br0 out_group=1])
+(grep -v 'group:[[123]]' flows.txt; echo 'OFPST_FLOW reply (OF1.4):') > expout
+AT_CHECK([ovs-ofctl -O OpenFlow14 dump-flows br0 | ofctl_strip | sort], [0],
+ [expout])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
AT_SETUP([ofproto - flow table configuration (OpenFlow 1.0)])
OVS_VSWITCHD_START
# Check the default configuration.
OVS_VSWITCHD_STOP
AT_CLEANUP
+
+
+AT_SETUP([ofproto - failing bundle commit (OpenFlow 1.3)])
+AT_KEYWORDS([monitor])
+OVS_VSWITCHD_START
+
+AT_CHECK([ovs-ofctl del-flows br0])
+
+# Invalid group numbers are detected at commit time
+AT_DATA([flows.txt], [dnl
+add idle_timeout=50 in_port=2 dl_src=00:66:77:88:99:aa actions=group:1
+add idle_timeout=60 in_port=2 dl_src=00:77:88:99:aa:bb actions=group:2
+add idle_timeout=70 in_port=2 dl_src=00:88:99:aa:bb:cc actions=group:3
+])
+
+AT_CHECK([ovs-ofctl -O OpenFlow13 --bundle add-flows br0 flows.txt 2>&1 | sed '/talking to/,$d'],
+[0], [dnl
+Error OFPBAC_BAD_OUT_GROUP for: OFPT_FLOW_MOD (OF1.3) (xid=0x2): ADD in_port=2,dl_src=00:66:77:88:99:aa idle:50 actions=group:1
+Error OFPBFC_MSG_FAILED for: ONFT_BUNDLE_CONTROL (OF1.3) (xid=0x7):
+ bundle_id=0 type=COMMIT_REQUEST flags=atomic ordered
+])
+
+AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
+NXST_FLOW reply:
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
These commands manage the group table in an OpenFlow switch. In each
case, \fIgroup\fR specifies a group entry in the format described in
\fBGroup Syntax\fR, below, and \fIfile\fR is a text file that contains
-zero or more groups in the same syntax, one per line.
+zero or more groups in the same syntax, one per line, and the optional
+\fB\-\-bundle\fR option operates the command as a single atomic
+transation, see option \fB\-\-bundle\fR, below.
-.IP "\fBadd\-group \fIswitch group\fR"
-.IQ "\fBadd\-group \fIswitch \fB\- < \fIfile\fR"
-.IQ "\fBadd\-groups \fIswitch file\fR"
+.IP "[\fB\-\-bundle\fR] \fBadd\-group \fIswitch group\fR"
+.IQ "[\fB\-\-bundle\fR] \fBadd\-group \fIswitch \fB\- < \fIfile\fR"
+.IQ "[\fB\-\-bundle\fR] \fBadd\-groups \fIswitch file\fR"
Add each group entry to \fIswitch\fR's tables.
.
-.IP "[\fB\-\-may\-create\fR] \fBmod\-group \fIswitch group\fR"
-.IQ "[\fB\-\-may\-create\fR] \fBmod\-group \fIswitch \fB\- < \fIfile\fR"
+Each group specification (e.g., each line in \fIfile\fR) may start
+with \fBadd\fR, \fBmodify\fR, \fBadd_or_mod\fR, \fBdelete\fR,
+\fBinsert_bucket\fR, or \fBremove_bucket\fR keyword to specify whether
+a flow is to be added, modified, or deleted, or whether a group bucket
+is to be added or removed. For backwards compatibility a group
+specification without one of these keywords is treated as a group add.
+All group mods are executed in the order specified.
+.
+.IP "[\fB\-\-bundle\fR] [\fB\-\-may\-create\fR] \fBmod\-group \fIswitch group\fR"
+.IQ "[\fB\-\-bundle\fR] [\fB\-\-may\-create\fR] \fBmod\-group \fIswitch \fB\- < \fIfile\fR"
Modify the action buckets in entries from \fIswitch\fR's tables for
each group entry. If a specified group does not already exist, then
without \fB\-\-may\-create\fR, this command has no effect; with
\fB\-\-may\-create\fR option uses an Open vSwitch extension to
OpenFlow only implemented in Open vSwitch 2.6 and later.
.
-.IP "\fBdel\-groups \fIswitch\fR"
-.IQ "\fBdel\-groups \fIswitch \fR[\fIgroup\fR]"
-.IQ "\fBdel\-groups \fIswitch \fB\- < \fIfile\fR"
+.IP "[\fB\-\-bundle\fR] \fBdel\-groups \fIswitch\fR"
+.IQ "[\fB\-\-bundle\fR] \fBdel\-groups \fIswitch \fR[\fIgroup\fR]"
+.IQ "[\fB\-\-bundle\fR] \fBdel\-groups \fIswitch \fB\- < \fIfile\fR"
Deletes entries from \fIswitch\fR's group table. With only a
\fIswitch\fR argument, deletes all groups. Otherwise, deletes the group
for each group entry.
.
-.IP "\fBinsert\-buckets \fIswitch group\fR"
-.IQ "\fBinsert\-buckets \fIswitch \fB\- < \fIfile\fR"
+.IP "[\fB\-\-bundle\fR] \fBinsert\-buckets \fIswitch group\fR"
+.IQ "[\fB\-\-bundle\fR] \fBinsert\-buckets \fIswitch \fB\- < \fIfile\fR"
Add buckets to an existing group present in the \fIswitch\fR's group table.
If no \fIcommand_bucket_id\fR is present in the group specification then all
buckets of the group are removed.
.
-.IP "\fBremove\-buckets \fIswitch group\fR"
-.IQ "\fBremove\-buckets \fIswitch \fB\- < \fIfile\fR"
+.IP "[\fB\-\-bundle\fR] \fBremove\-buckets \fIswitch group\fR"
+.IQ "[\fB\-\-bundle\fR] \fBremove\-buckets \fIswitch \fB\- < \fIfile\fR"
Remove buckets to an existing group present in the \fIswitch\fR's group table.
If no \fIcommand_bucket_id\fR is present in the group specification then all
buckets of the group are removed.
.
+.SS OpenFlow Switch Bundle Command
+.
+Transactional updates to both flow and group tables can be made with
+the \fBbundle\fR command. \fIfile\fR is a text file that contains
+zero or more flows and groups in either \fBFlow Syntax\fR or \fBGroup
+Syntax\fR, each line preceded by either \fBflow\fR or \fBgroup\fR
+keyword. The \fBflow\fR keyword may be optionally followed by one of
+the keywords \fBadd\fR, \fBmodify\fR, \fBmodify_strict\fR,
+\fBdelete\fR, or \fBdelete_strict\fR, of which the \fBadd\fR is
+assumed if a bare \fBflow\fR is given. Similarly, the \fBgroup\fR
+keyword may be optionally followed by one of the keywords \fBadd\fR,
+\fBmodify\fR, \fBadd_or_mod\fR, \fBdelete\fR, \fBinsert_bucket\fR, or
+\fBremove_bucket\fR, of which the \fBadd\fR is assumed if a bare
+\fBgroup\fR is given.
+.
+.IP "\fBbundle \fIswitch file\fR"
+Execute all flow and group mods in \fIfile\fR as a single atomic
+transaction against \fIswitch\fR's tables. All bundled mods are
+executed in the order specified.
+.
.SS "OpenFlow Switch Tunnel TLV Table Commands"
.
Open vSwitch maintains a mapping table between tunnel option TLVs (defined
dump_trivial_transaction(ctx->argv[1], OFPRAW_NXST_IPFIX_FLOW_REQUEST);
}
+static void
+bundle_group_mod__(const char *remote, struct ofputil_group_mod *gms,
+ size_t n_gms, enum ofputil_protocol usable_protocols)
+{
+ enum ofputil_protocol protocol;
+ enum ofp_version version;
+ struct vconn *vconn;
+ struct ovs_list requests;
+ size_t i;
+
+ ovs_list_init(&requests);
+
+ /* Bundles need OpenFlow 1.3+. */
+ usable_protocols &= OFPUTIL_P_OF13_UP;
+ protocol = open_vconn_for_flow_mod(remote, &vconn, usable_protocols);
+ version = ofputil_protocol_to_ofp_version(protocol);
+
+ for (i = 0; i < n_gms; i++) {
+ struct ofputil_group_mod *gm = &gms[i];
+ struct ofpbuf *request = ofputil_encode_group_mod(version, gm);
+
+ ovs_list_push_back(&requests, &request->list_node);
+ ofputil_uninit_group_mod(gm);
+ }
+
+ bundle_transact(vconn, &requests, OFPBF_ORDERED | OFPBF_ATOMIC);
+ ofpbuf_list_delete(&requests);
+ vconn_close(vconn);
+}
+
static void
ofctl_group_mod__(const char *remote, struct ofputil_group_mod *gms,
size_t n_gms, enum ofputil_protocol usable_protocols)
struct vconn *vconn;
size_t i;
+ if (bundle) {
+ bundle_group_mod__(remote, gms, n_gms, usable_protocols);
+ return;
+ }
+
protocol = open_vconn_for_flow_mod(remote, &vconn, usable_protocols);
version = ofputil_protocol_to_ofp_version(protocol);
for (i = 0; i < n_gms; i++) {
gm = &gms[i];
request = ofputil_encode_group_mod(version, gm);
- if (request) {
- transact_noreply(vconn, request);
- }
+ transact_noreply(vconn, request);
ofputil_uninit_group_mod(gm);
}
vconn_close(vconn);
}
-
static void
-ofctl_group_mod_file(int argc OVS_UNUSED, char *argv[], uint16_t command)
+ofctl_group_mod_file(int argc OVS_UNUSED, char *argv[], int command)
{
struct ofputil_group_mod *gms = NULL;
enum ofputil_protocol usable_protocols;
size_t n_gms = 0;
char *error;
+ if (command == OFPGC11_ADD) {
+ /* Allow the file to specify a mix of commands. If none specified at
+ * the beginning of any given line, then the default is OFPGC11_ADD, so
+ * this is backwards compatible. */
+ command = -2;
+ }
error = parse_ofp_group_mod_file(argv[2], command, &gms, &n_gms,
&usable_protocols);
if (error) {
vconn_close(vconn);
}
+static void
+ofctl_bundle(struct ovs_cmdl_context *ctx)
+{
+ enum ofputil_protocol protocol, usable_protocols;
+ struct ofputil_bundle_msg *bms;
+ struct ovs_list requests;
+ struct vconn *vconn;
+ size_t n_bms;
+ char *error;
+
+ error = parse_ofp_bundle_file(ctx->argv[2], &bms, &n_bms,
+ &usable_protocols);
+ if (error) {
+ ovs_fatal(0, "%s", error);
+ }
+
+ /* Implicit OpenFlow 1.4. */
+ if (!(get_allowed_ofp_versions() &
+ ofputil_protocols_to_version_bitmap(OFPUTIL_P_OF13_UP))) {
+
+ /* Add implicit allowance for OpenFlow 1.4. */
+ add_allowed_ofp_versions(ofputil_protocols_to_version_bitmap(
+ OFPUTIL_P_OF14_OXM));
+ /* Remove all versions that do not support bundles. */
+ mask_allowed_ofp_versions(ofputil_protocols_to_version_bitmap(
+ OFPUTIL_P_OF13_UP));
+ allowed_protocols = ofputil_protocols_from_version_bitmap(
+ get_allowed_ofp_versions());
+ }
+
+ /* Bundles need OpenFlow 1.3+. */
+ usable_protocols &= OFPUTIL_P_OF13_UP;
+ protocol = open_vconn_for_flow_mod(ctx->argv[1], &vconn, usable_protocols);
+
+ ovs_list_init(&requests);
+ ofputil_encode_bundle_msgs(bms, n_bms, &requests, protocol);
+ bundle_transact(vconn, &requests, OFPBF_ORDERED | OFPBF_ATOMIC);
+ ofpbuf_list_delete(&requests);
+
+ vconn_close(vconn);
+}
+
static void
ofctl_tlv_mod(struct ovs_cmdl_context *ctx, uint16_t command)
{
1, 2, ofctl_dump_group_stats },
{ "dump-group-features", "switch",
1, 1, ofctl_dump_group_features },
+
+ { "bundle", "switch file",
+ 2, 2, ofctl_bundle },
+
{ "add-tlv-map", "switch map",
2, 2, ofctl_add_tlv_map },
{ "del-tlv-map", "switch [map]",