From d4cdc6b4c45e5ca6a44bccf90f856b76ef936fac Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 1 Oct 2010 13:41:40 -0700 Subject: [PATCH] ovs-controller: Improve QoS abilities. This makes it a little easier to test Open vSwitch QoS features using ovs-controller, by making it possible to assign queues on the basis of input port, instead of just allowing a single queue for a whole switch. CC: Michael Mao --- lib/learning-switch.c | 78 +++++++++++++++++++++++++++++++++-- lib/learning-switch.h | 9 ++-- utilities/ovs-controller.8.in | 24 ++++++++++- utilities/ovs-controller.c | 63 ++++++++++++++++++++++++---- 4 files changed, 158 insertions(+), 16 deletions(-) diff --git a/lib/learning-switch.c b/lib/learning-switch.c index 90749c01..1e5d25bc 100644 --- a/lib/learning-switch.c +++ b/lib/learning-switch.c @@ -24,6 +24,7 @@ #include #include "flow.h" +#include "hmap.h" #include "mac-learning.h" #include "ofpbuf.h" #include "ofp-parse.h" @@ -33,6 +34,7 @@ #include "poll-loop.h" #include "queue.h" #include "rconn.h" +#include "shash.h" #include "timeval.h" #include "vconn.h" #include "vlog.h" @@ -40,6 +42,12 @@ VLOG_DEFINE_THIS_MODULE(learning_switch) +struct lswitch_port { + struct hmap_node hmap_node; /* Hash node for port number. */ + uint16_t port_no; /* OpenFlow port number, in host byte order. */ + uint32_t queue_id; /* OpenFlow queue number. */ +}; + struct lswitch { /* If nonnegative, the switch sets up flows that expire after the given * number of seconds (or never expire, if the value is OFP_FLOW_PERMANENT). @@ -51,7 +59,11 @@ struct lswitch { struct mac_learning *ml; /* NULL to act as hub instead of switch. */ uint32_t wildcards; /* Wildcards to apply to flows. */ bool action_normal; /* Use OFPP_NORMAL? */ - uint32_t queue; /* OpenFlow queue to use, or UINT32_MAX. */ + + /* Queue distribution. */ + uint32_t default_queue; /* Default OpenFlow queue, or UINT32_MAX. */ + struct hmap queue_numbers; /* Map from port number to lswitch_port. */ + struct shash queue_names; /* Map from port name to lswitch_port. */ /* Number of outgoing queued packets on the rconn. */ struct rconn_packet_counter *queued; @@ -95,7 +107,21 @@ lswitch_create(struct rconn *rconn, const struct lswitch_config *cfg) sw->wildcards = (OFPFW_DL_TYPE | OFPFW_NW_SRC_MASK | OFPFW_NW_DST_MASK | OFPFW_NW_PROTO | OFPFW_TP_SRC | OFPFW_TP_DST); } - sw->queue = cfg->queue_id; + + sw->default_queue = cfg->default_queue; + hmap_init(&sw->queue_numbers); + shash_init(&sw->queue_names); + if (cfg->port_queues) { + struct shash_node *node; + + SHASH_FOR_EACH (node, cfg->port_queues) { + struct lswitch_port *port = xmalloc(sizeof *port); + hmap_node_nullify(&port->hmap_node); + port->queue_id = (uintptr_t) node->data; + shash_add(&sw->queue_names, node->name, port); + } + } + sw->queued = rconn_packet_counter_create(); send_features_request(sw, rconn); @@ -111,6 +137,13 @@ void lswitch_destroy(struct lswitch *sw) { if (sw) { + struct lswitch_port *node, *next; + + HMAP_FOR_EACH_SAFE (node, next, hmap_node, &sw->queue_numbers) { + hmap_remove(&sw->queue_numbers, &node->hmap_node); + free(node); + } + shash_destroy(&sw->queue_names); mac_learning_destroy(sw->ml); rconn_packet_counter_destroy(sw->queued); free(sw); @@ -247,8 +280,28 @@ process_switch_features(struct lswitch *sw, struct rconn *rconn OVS_UNUSED, void *osf_) { struct ofp_switch_features *osf = osf_; + size_t n_ports; + size_t i; + + if (check_ofp_message_array(&osf->header, OFPT_FEATURES_REPLY, + sizeof *osf, sizeof *osf->ports, &n_ports)) { + return; + } sw->datapath_id = ntohll(osf->datapath_id); + + for (i = 0; i < n_ports; i++) { + struct ofp_phy_port *opp = &osf->ports[i]; + struct lswitch_port *lp; + + opp->name[OFP_MAX_PORT_NAME_LEN - 1] = '\0'; + lp = shash_find_data(&sw->queue_names, (char *) opp->name); + if (lp && hmap_node_is_null(&lp->hmap_node)) { + lp->port_no = ntohs(opp->port_no); + hmap_insert(&sw->queue_numbers, &lp->hmap_node, + hash_int(lp->port_no, 0)); + } + } } static uint16_t @@ -291,11 +344,27 @@ lswitch_choose_destination(struct lswitch *sw, const flow_t *flow) return out_port; } +static uint32_t +get_queue_id(const struct lswitch *sw, uint16_t in_port) +{ + const struct lswitch_port *port; + + HMAP_FOR_EACH_WITH_HASH (port, hmap_node, hash_int(in_port, 0), + &sw->queue_numbers) { + if (port->port_no == in_port) { + return port->queue_id; + } + } + + return sw->default_queue; +} + static void process_packet_in(struct lswitch *sw, struct rconn *rconn, void *opi_) { struct ofp_packet_in *opi = opi_; uint16_t in_port = ntohs(opi->in_port); + uint32_t queue_id; uint16_t out_port; struct ofp_action_header actions[2]; @@ -323,9 +392,10 @@ process_packet_in(struct lswitch *sw, struct rconn *rconn, void *opi_) out_port = lswitch_choose_destination(sw, &flow); /* Make actions. */ + queue_id = get_queue_id(sw, in_port); if (out_port == OFPP_NONE) { actions_len = 0; - } else if (sw->queue == UINT32_MAX || out_port >= OFPP_MAX) { + } else if (queue_id == UINT32_MAX || out_port >= OFPP_MAX) { struct ofp_action_output oao; memset(&oao, 0, sizeof oao); @@ -342,7 +412,7 @@ process_packet_in(struct lswitch *sw, struct rconn *rconn, void *opi_) oae.type = htons(OFPAT_ENQUEUE); oae.len = htons(sizeof oae); oae.port = htons(out_port); - oae.queue_id = htonl(sw->queue); + oae.queue_id = htonl(queue_id); memcpy(actions, &oae, sizeof oae); actions_len = sizeof oae; diff --git a/lib/learning-switch.h b/lib/learning-switch.h index 2ce49e61..d0892576 100644 --- a/lib/learning-switch.h +++ b/lib/learning-switch.h @@ -46,9 +46,12 @@ struct lswitch_config { * requests to set up the flow table. */ const struct ofpbuf *default_flows; - /* The OpenFlow queue used by packets and flows set up by 'sw'. Use - * UINT32_MAX to avoid specifying a particular queue. */ - uint32_t queue_id; + /* The OpenFlow queue to use by default. Use UINT32_MAX to avoid + * specifying a particular queue. */ + uint32_t default_queue; + + /* Maps from a port name to a queue_id (cast to void *). */ + const struct shash *port_queues; }; struct lswitch *lswitch_create(struct rconn *, const struct lswitch_config *); diff --git a/utilities/ovs-controller.8.in b/utilities/ovs-controller.8.in index 24f3a5cd..aa5751f9 100644 --- a/utilities/ovs-controller.8.in +++ b/utilities/ovs-controller.8.in @@ -98,7 +98,29 @@ sending packets and setting up flows. Use one of these options, supplying \fIid\fR as an OpenFlow queue ID as a decimal number, to instead use that specific queue. .IP -This option may be useful for debugging quality of service setups. +This option is incompatible with \fB\-N\fR or \fB\-\-normal\fR and +with \fB\-H\fR or \fB\-\-hub\fR. If more than one is specified then +this option takes precedence. +.IP +This option may be useful for testing or debugging quality of service +setups. +. +.IP "\fB\-Q \fIport-name\fB:\fIqueue-id\fR" +.IP "\fB\-\-port\-queue \fIport-name\fB:\fIqueue-id\fR" +Configures packets received on the port named \fIport-name\fR +(e.g. \fBeth0\fR) to be output on OpenFlow queue ID \fIqueue-id\fR +(specified as a decimal number). For the specified port, this option +overrides the default specified on \fB\-q\fR or \fB\-\-queue\fR. +.IP +This option may be specified any number of times with different +\Iport-name\fR arguments. +.IP +This option is incompatible with \fB\-N\fR or \fB\-\-normal\fR and +with \fB\-H\fR or \fB\-\-hub\fR. If more than one is specified then +this option takes precedence. +.IP +This option may be useful for testing or debugging quality of service +setups. . .IP "\fB\-\-with\-flows \fIfile\fR" When a switch connects, push the flow entries as described in diff --git a/utilities/ovs-controller.c b/utilities/ovs-controller.c index 9892abe4..26a1fc3f 100644 --- a/utilities/ovs-controller.c +++ b/utilities/ovs-controller.c @@ -33,6 +33,7 @@ #include "openflow/openflow.h" #include "poll-loop.h" #include "rconn.h" +#include "shash.h" #include "stream-ssl.h" #include "timeval.h" #include "unixctl.h" @@ -50,10 +51,11 @@ struct switch_ { struct rconn *rconn; }; -/* Learn the ports on which MAC addresses appear? */ +/* -H, --hub: Learn the ports on which MAC addresses appear? */ static bool learn_macs = true; -/* Set up flows? (If not, every packet is processed at the controller.) */ +/* -n, --noflow: Set up flows? (If not, every packet is processed at the + * controller.) */ static bool set_up_flows = true; /* -N, --normal: Use "NORMAL" action instead of explicit port? */ @@ -69,8 +71,11 @@ static int max_idle = 60; * of their messages (for debugging fail-open mode). */ static bool mute = false; -/* -q, --queue: OpenFlow queue to use, or the default queue if UINT32_MAX. */ -static uint32_t queue_id = UINT32_MAX; +/* -q, --queue: default OpenFlow queue, none if UINT32_MAX. */ +static uint32_t default_queue = UINT32_MAX; + +/* -Q, --port-queue: map from port name to port number (cast to void *). */ +static struct shash port_queues = SHASH_INITIALIZER(&port_queues); /* --with-flows: File with flows to send to switch, or null to not load * any default flows. */ @@ -225,7 +230,8 @@ new_switch(struct switch_ *sw, struct vconn *vconn) : LSW_FLOOD); cfg.max_idle = set_up_flows ? max_idle : -1; cfg.default_flows = default_flows.head; - cfg.queue_id = queue_id; + cfg.default_queue = default_queue; + cfg.port_queues = &port_queues; sw->lswitch = lswitch_create(sw->rconn, &cfg); } @@ -269,6 +275,27 @@ read_flow_file(const char *name) fclose(stream); } +static void +add_port_queue(char *s) +{ + char *save_ptr = NULL; + char *port_name; + char *queue_id; + + port_name = strtok_r(s, ":", &save_ptr); + queue_id = strtok_r(NULL, "", &save_ptr); + if (!queue_id) { + ovs_fatal(0, "argument to -Q or --port-queue should take the form " + "\":\""); + } + + if (!shash_add_once(&port_queues, port_name, + (void *) (uintptr_t) atoi(queue_id))) { + ovs_fatal(0, " arguments for -Q or --port-queue must " + "be unique"); + } +} + static void parse_options(int argc, char *argv[]) { @@ -288,6 +315,7 @@ parse_options(int argc, char *argv[]) {"max-idle", required_argument, 0, OPT_MAX_IDLE}, {"mute", no_argument, 0, OPT_MUTE}, {"queue", required_argument, 0, 'q'}, + {"port-queue", required_argument, 0, 'Q'}, {"with-flows", required_argument, 0, OPT_WITH_FLOWS}, {"unixctl", required_argument, 0, OPT_UNIXCTL}, {"help", no_argument, 0, 'h'}, @@ -345,7 +373,11 @@ parse_options(int argc, char *argv[]) break; case 'q': - queue_id = atoi(optarg); + default_queue = atoi(optarg); + break; + + case 'Q': + add_port_queue(optarg); break; case OPT_WITH_FLOWS: @@ -382,6 +414,20 @@ parse_options(int argc, char *argv[]) } } free(short_options); + + if (!shash_is_empty(&port_queues) || default_queue != UINT32_MAX) { + if (action_normal) { + ovs_error(0, "queue IDs are incompatible with -N or --normal; " + "not using OFPP_NORMAL"); + action_normal = false; + } + + if (!learn_macs) { + ovs_error(0, "queue IDs are incompatible with -H or --hub; " + "not acting as hub"); + learn_macs = true; + } + } } static void @@ -398,9 +444,10 @@ usage(void) " -H, --hub act as hub instead of learning switch\n" " -n, --noflow pass traffic, but don't add flows\n" " --max-idle=SECS max idle time for new flows\n" - " -N, --normal use OFPAT_NORMAL action\n" + " -N, --normal use OFPP_NORMAL action\n" " -w, --wildcard use wildcards, not exact-match rules\n" - " -q, --queue=QUEUE OpenFlow queue ID to use for output\n" + " -q, --queue=QUEUE-ID OpenFlow queue ID to use for output\n" + " -Q PORT-NAME:QUEUE-ID use QUEUE-ID for frames from PORT-NAME\n" " --with-flows FILE use the flows from FILE\n" " --unixctl=SOCKET override default control socket name\n" " -h, --help display this help message\n" -- 2.30.2