[ovs-dev] [PATCH v1] Add logical-router and logical-router-port related commands to ovn-nbctl
Nirapada Ghosh
nghosh at us.ibm.com
Mon Apr 11 21:14:41 UTC 2016
Just to clarify:
1) Most of these commands can be achieved through other ovn-nbctl commands
[e.g
"ovn-nbctl create Logical_router .." will do the same thing as what
"ovn-nbctl lrouter-add" does.
But the same is true for other existing commands like "ovn-nbctl
lport-add". These commands are
added as "shortcuts" to achieve the same thing as what the longer
version of the commands do.
2) Commands like "set-enabled" or "get-enabled" on a logical router port
are added just to add a shortcut
to be able to get/set the field in database, such commands are added
for all the fields in that table as a
matter of fact. They were not added with some specific requirement in
mind.
3) I am working on adding some test cases for the commands I added, will
submit it as soon as they are ready.
Thanks,
Nirapada
"dev" <dev-bounces at openvswitch.org> wrote on 04/08/2016 10:48:33 PM:
> From: Nirapada Ghosh/San Jose/IBM at IBMUS
> To: dev at openvswitch.org
> Date: 04/08/2016 10:48 PM
> Subject: [ovs-dev] [PATCH v1] Add logical-router and logical-router-
> port related commands to ovn-nbctl
> Sent by: "dev" <dev-bounces at openvswitch.org>
>
> This patch introduces the following to ovn-nbctl:
>
> 1) ovn-nbctl show -- will now show lrouters as well
> 2) ovn-nbctl show <lrouter> -- will show the router now
>
> New commands added:
> 3) ovn-nbctl lrouter-add [LROUTER]
> 4) ovn-nbctl lrouter-del LROUTER
> 5) ovn-nbctl lrouter-list
> 6) lrport-add LROUTER LRPORT
> 7) lrport-del LRPORT
> 8) lrport-list LROUTER
> 9) lrport-set-mac-address LRPORT [ADDRESS]
> 10) lrport-get-mac-address LRPORT
> 11) lrport-set-enabled LRPORT STATE
> 12) lrport-get-enabled LRPORT
>
> diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
> index bdad368..ed4916d 100644
> --- a/ovn/utilities/ovn-nbctl.c
> +++ b/ovn/utilities/ovn-nbctl.c
> @@ -290,12 +290,18 @@ usage: %s [OPTIONS] COMMAND [ARG...]\n\
> General commands:\n\
> show print overview of database contents\n\
> show LSWITCH print overview of database contents
forLSWITCH\n\
> + show LROUTER print overview of database contents
forLROUTER\n\
> \n\
> Logical switch commands:\n\
> lswitch-add [LSWITCH] create a logical switch named LSWITCH\n\
> lswitch-del LSWITCH delete LSWITCH and all its ports\n\
> lswitch-list print the names of all logical switches\n\
> \n\
> +Logical router commands:\n\
> + lrouter-add [LROUTER] create a logical router named LROUTER\n\
> + lrouter-del LROUTER delete LROUTER and all its ports\n\
> + lrouter-list print the names of all logical routers\n\
> +\n\
> ACL commands:\n\
> acl-add LSWITCH DIRECTION PRIORITY MATCH ACTION [log]\n\
> add an ACL to LSWITCH\n\
> @@ -303,6 +309,19 @@ ACL commands:\n\
> remove ACLs from LSWITCH\n\
> acl-list LSWITCH print ACLs for LSWITCH\n\
> \n\
> +Logical router port commands:\n\
> + lrport-add LROUTER LRPORT add logical router port LRPORT to LROUTER
\n\
> + lrport-del LRPORT delete LRPORT from its attached router\n\
> + lrport-list LROUTER print the names of all logical ports
> on LROUTER\n\
> + lrport-set-mac-address LRPORT [ADDRESS]\n\
> + set MAC address for LRPORT.\n\
> + lrport-get-mac-address LRPORT get MAC addresses on LRPORT\n\
> + lrport-set-enabled LRPORT STATE\n\
> + set administrative state LRPORT\n\
> + ('enabled' or 'disabled')\n\
> + lrport-get-enabled LRPORT get administrative state LRPORT\n\
> + ('enabled' or 'disabled')\n\
> +\n\
> Logical port commands:\n\
> lport-add LSWITCH LPORT add logical port LPORT on LSWITCH\n\
> lport-add LSWITCH LPORT PARENT TAG\n\
> @@ -349,8 +368,49 @@ Other options:\n\
> exit(EXIT_SUCCESS);
> }
>
> +
> +/* Following function finds out the lrouter given it's id. */
> +static const struct nbrec_logical_router *
> +lrouter_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool
silent)
> +{
> + const struct nbrec_logical_router *lrouter = NULL;
> + bool is_uuid = false;
> + bool duplicate = false;
> + struct uuid lrouter_uuid;
> +
> + if (uuid_from_string(&lrouter_uuid, id)) {
> + is_uuid = true;
> + lrouter = nbrec_logical_router_get_for_uuid(ctx->idl,
> + &lrouter_uuid);
> + }
> +
> + if (!lrouter) {
> + const struct nbrec_logical_router *iter;
> +
> + NBREC_LOGICAL_ROUTER_FOR_EACH(iter, ctx->idl) {
> + if (strcmp(iter->name, id)) {
> + continue;
> + }
> + if (lrouter) {
> + VLOG_WARN("There is more than one logical router
> named '%s'. "
> + "Use a UUID.", id);
> + lrouter = NULL;
> + duplicate = true;
> + break;
> + }
> + lrouter = iter;
> + }
> + }
> +
> + if (!lrouter && !duplicate && !silent) {
> + VLOG_WARN("lrouter not found for %s: '%s'",
> + is_uuid ? "UUID" : "name", id);
> + }
> +
> + return lrouter;
> +}
> static const struct nbrec_logical_switch *
> -lswitch_by_name_or_uuid(struct ctl_context *ctx, const char *id)
> +lswitch_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool
silent)
> {
> const struct nbrec_logical_switch *lswitch = NULL;
> bool is_uuid = false;
> @@ -381,7 +441,7 @@ lswitch_by_name_or_uuid(struct ctl_context *ctx,
> const char *id)
> }
> }
>
> - if (!lswitch && !duplicate) {
> + if (!lswitch && !duplicate && !silent) {
> VLOG_WARN("lswitch not found for %s: '%s'",
> is_uuid ? "UUID" : "name", id);
> }
> @@ -389,6 +449,24 @@ lswitch_by_name_or_uuid(struct ctl_context
> *ctx, const char *id)
> return lswitch;
> }
>
> +/* Given pointer to lrouter, this routine prints the lrouter
information. */
> +static void
> +print_lrouter(const struct nbrec_logical_router *lrouter, struct ds *s)
> +{
> + ds_put_format(s, " lrouter "UUID_FMT" (%s)\n",
> + UUID_ARGS(&lrouter->header_.uuid), lrouter->name);
> +
> + for (size_t i = 0; i < lrouter->n_ports; i++) {
> + const struct nbrec_logical_router_port *lrport = lrouter->ports
[i];
> + ds_put_format(s, " lrport %s\n", lrport->name);
> + if (lrport->mac) {
> + ds_put_cstr(s, " mac: ");
> + ds_put_format(s, "\"%s\"", lrport->mac);
> + }
> + }
> + ds_put_format(s, "\n");
> +}
> +
> static void
> print_lswitch(const struct nbrec_logical_switch *lswitch, struct ds *s)
> {
> @@ -423,7 +501,7 @@ nbctl_show(struct ctl_context *ctx)
> const struct nbrec_logical_switch *lswitch;
>
> if (ctx->argc == 2) {
> - lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1]);
> + lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1], true);
> if (lswitch) {
> print_lswitch(lswitch, &ctx->output);
> }
> @@ -432,6 +510,66 @@ nbctl_show(struct ctl_context *ctx)
> print_lswitch(lswitch, &ctx->output);
> }
> }
> + const struct nbrec_logical_router *lrouter;
> +
> + if (ctx->argc == 2) {
> + lrouter = lrouter_by_name_or_uuid(ctx, ctx->argv[1], true);
> + if (lrouter) {
> + print_lrouter(lrouter, &ctx->output);
> + }
> + } else {
> + NBREC_LOGICAL_ROUTER_FOR_EACH(lrouter, ctx->idl) {
> + print_lrouter(lrouter, &ctx->output);
> + }
> + }
> +}
> +
> +/* Following function adds a lrouter to NB */
> +static void
> +nbctl_lrouter_add(struct ctl_context *ctx)
> +{
> + struct nbrec_logical_router *lrouter;
> +
> + lrouter = nbrec_logical_router_insert(ctx->txn);
> + if (ctx->argc == 2) {
> + nbrec_logical_router_set_name(lrouter, ctx->argv[1]);
> + }
> +}
> +
> +/* Following function deletes a lrouter from NB */
> +static void
> +nbctl_lrouter_del(struct ctl_context *ctx)
> +{
> + const char *id = ctx->argv[1];
> + const struct nbrec_logical_router *lrouter;
> +
> + lrouter = lrouter_by_name_or_uuid(ctx, id, false);
> + if (!lrouter) {
> + return;
> + }
> +
> + nbrec_logical_router_delete(lrouter);
> +}
> +
> +/* Following function prints list of lrouters in NB */
> +static void
> +nbctl_lrouter_list(struct ctl_context *ctx)
> +{
> + const struct nbrec_logical_router *lrouter;
> + struct smap lrouters;
> +
> + smap_init(&lrouters);
> + NBREC_LOGICAL_ROUTER_FOR_EACH(lrouter, ctx->idl) {
> + smap_add_format(&lrouters, lrouter->name, UUID_FMT " (%s)",
> + UUID_ARGS(&lrouter->header_.uuid), lrouter->
name);
> + }
> + const struct smap_node **nodes = smap_sort(&lrouters);
> + for (size_t i = 0; i < smap_count(&lrouters); i++) {
> + const struct smap_node *node = nodes[i];
> + ds_put_format(&ctx->output, "%s\n", node->value);
> + }
> + smap_destroy(&lrouters);
> + free(nodes);
> }
>
> static void
> @@ -451,7 +589,7 @@ nbctl_lswitch_del(struct ctl_context *ctx)
> const char *id = ctx->argv[1];
> const struct nbrec_logical_switch *lswitch;
>
> - lswitch = lswitch_by_name_or_uuid(ctx, id);
> + lswitch = lswitch_by_name_or_uuid(ctx, id, false);
> if (!lswitch) {
> return;
> }
> @@ -479,6 +617,36 @@ nbctl_lswitch_list(struct ctl_context *ctx)
> free(nodes);
> }
>
> +
> +/* Following function finds out the lrport given it's id. */
> +static const struct nbrec_logical_router_port *
> +lrport_by_name_or_uuid(struct ctl_context *ctx, const char *id)
> +{
> + const struct nbrec_logical_router_port *lrport = NULL;
> + bool is_uuid = false;
> + struct uuid lrport_uuid;
> +
> + if (uuid_from_string(&lrport_uuid, id)) {
> + is_uuid = true;
> + lrport = nbrec_logical_router_port_get_for_uuid(ctx->idl,
> &lrport_uuid);
> + }
> +
> + if (!lrport) {
> + NBREC_LOGICAL_ROUTER_PORT_FOR_EACH(lrport, ctx->idl) {
> + if (!strcmp(lrport->name, id)) {
> + break;
> + }
> + }
> + }
> +
> + if (!lrport) {
> + VLOG_WARN("lrport not found for %s: '%s'",
> + is_uuid ? "UUID" : "name", id);
> + }
> +
> + return lrport;
> +}
> +
> static const struct nbrec_logical_port *
> lport_by_name_or_uuid(struct ctl_context *ctx, const char *id)
> {
> @@ -514,7 +682,7 @@ nbctl_lport_add(struct ctl_context *ctx)
> const struct nbrec_logical_switch *lswitch;
> int64_t tag;
>
> - lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1]);
> + lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1], false);
> if (!lswitch) {
> return;
> }
> @@ -551,6 +719,28 @@ nbctl_lport_add(struct ctl_context *ctx)
> free(new_ports);
> }
>
> +/* Removes lrport 'lrouter->ports[idx]' from lrouter. */
> +static void
> +remove_lrport(const struct nbrec_logical_router *lrouter, size_t idx)
> +{
> + const struct nbrec_logical_router_port *lrport = lrouter->ports
[idx];
> +
> + /* First remove 'lrport' from the array of ports. This is what will
> + * actually cause the logical port to be deleted when the
transaction is
> + * sent to the database server (due to garbage collection). */
> + struct nbrec_logical_router_port **new_ports
> + = xmemdup(lrouter->ports, sizeof *new_ports * lrouter->n_ports);
> + new_ports[idx] = new_ports[lrouter->n_ports - 1];
> + nbrec_logical_router_verify_ports(lrouter);
> + nbrec_logical_router_set_ports(lrouter, new_ports, lrouter->n_ports
- 1);
> + free(new_ports);
> +
> + /* Delete 'lrport' from the IDL. This won't have a real effect on
the
> + * database server (the IDL will suppress it in fact) but it
> means that it
> + * won't show up when we iterate with
> NBREC_LOGICAL_ROUTER_PORT_FOR_EACH later. */
> + nbrec_logical_router_port_delete(lrport);
> +}
> +
> /* Removes lport 'lswitch->ports[idx]'. */
> static void
> remove_lport(const struct nbrec_logical_switch *lswitch, size_t idx)
> @@ -606,7 +796,7 @@ nbctl_lport_list(struct ctl_context *ctx)
> struct smap lports;
> size_t i;
>
> - lswitch = lswitch_by_name_or_uuid(ctx, id);
> + lswitch = lswitch_by_name_or_uuid(ctx, id, false);
> if (!lswitch) {
> return;
> }
> @@ -656,6 +846,32 @@ nbctl_lport_get_tag(struct ctl_context *ctx)
> }
> }
>
> +/* Following function sets the mac address of lrport. */
> +static void
> +nbctl_lrport_set_mac(struct ctl_context *ctx)
> +{
> + struct eth_addr ea;
> + const char *id = ctx->argv[1];
> + const struct nbrec_logical_router_port *lrport;
> +
> + lrport = lrport_by_name_or_uuid(ctx, id);
> + if (!lrport) {
> + return;
> + }
> + if (ctx->argc != 3) {
> + VLOG_ERR("Invalid usage");
> + return;
> + }
> +
> + if (strcmp(ctx->argv[2], "unknown")
> + && !ovs_scan(ctx->argv[2], ETH_ADDR_SCAN_FMT,
> + ETH_ADDR_SCAN_ARGS(ea))) {
> + VLOG_ERR("Invalid MAC address format, See ovn-nb(5)");
> + return;
> + }
> +
> + nbrec_logical_router_port_set_mac(lrport, ctx->argv[2]);
> +}
> static void
> nbctl_lport_set_addresses(struct ctl_context *ctx)
> {
> @@ -686,6 +902,20 @@ nbctl_lport_set_addresses(struct ctl_context *ctx)
> (const char **) ctx->argv + 2, ctx->argc - 2);
> }
>
> +/* Following function prints the mac address of the lrport. */
> +static void
> +nbctl_lrport_get_mac(struct ctl_context *ctx)
> +{
> + const char *id = ctx->argv[1];
> + const struct nbrec_logical_router_port *lrport;
> +
> + lrport = lrport_by_name_or_uuid(ctx, id);
> + if (!lrport) {
> + return;
> + }
> + ds_put_format(&ctx->output, "%s\n", lrport->mac);
> +}
> +
> static void
> nbctl_lport_get_addresses(struct ctl_context *ctx)
> {
> @@ -766,6 +996,29 @@ nbctl_lport_get_up(struct ctl_context *ctx)
> "%s\n", (lport->up && *lport->up) ? "up" : "down");
> }
>
> +/* Following function sets the lrport to admin-enabled. */
> +static void
> +nbctl_lrport_set_enabled(struct ctl_context *ctx)
> +{
> + const char *id = ctx->argv[1];
> + const char *state = ctx->argv[2];
> + const struct nbrec_logical_router_port *lrport;
> +
> + lrport = lrport_by_name_or_uuid(ctx, id);
> + if (!lrport) {
> + return;
> + }
> +
> + if (!strcasecmp(state, "enabled")) {
> + bool enabled = true;
> + nbrec_logical_router_port_set_enabled(lrport, &enabled, 1);
> + } else if (!strcasecmp(state, "disabled")) {
> + bool enabled = false;
> + nbrec_logical_router_port_set_enabled(lrport, &enabled, 1);
> + } else {
> + VLOG_ERR("Invalid state '%s' provided to lport-set-enabled",
state);
> + }
> +}
> static void
> nbctl_lport_set_enabled(struct ctl_context *ctx)
> {
> @@ -789,6 +1042,21 @@ nbctl_lport_set_enabled(struct ctl_context *ctx)
> }
> }
>
> +/* Following function prints admin-enabled state. */
> +static void
> +nbctl_lrport_get_enabled(struct ctl_context *ctx)
> +{
> + const char *id = ctx->argv[1];
> + const struct nbrec_logical_router_port *lrport;
> +
> + lrport = lrport_by_name_or_uuid(ctx, id);
> + if (!lrport) {
> + return;
> + }
> +
> + ds_put_format(&ctx->output, "%s\n",
> + !lrport->enabled || *lrport->enabled ? "enabled"
> : "disabled");
> +}
> static void
> nbctl_lport_get_enabled(struct ctl_context *ctx)
> {
> @@ -922,7 +1190,7 @@ nbctl_acl_list(struct ctl_context *ctx)
> const struct nbrec_acl **acls;
> size_t i;
>
> - lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1]);
> + lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1], false);
> if (!lswitch) {
> return;
> }
> @@ -951,7 +1219,7 @@ nbctl_acl_add(struct ctl_context *ctx)
> const char *direction;
> int64_t priority;
>
> - lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1]);
> + lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1], false);
> if (!lswitch) {
> return;
> }
> @@ -1007,7 +1275,7 @@ nbctl_acl_del(struct ctl_context *ctx)
> const char *direction;
> int64_t priority = 0;
>
> - lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1]);
> + lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1], false);
> if (!lswitch) {
> return;
> }
> @@ -1302,6 +1570,92 @@ nbctl_exit(int status)
> exit(status);
> }
>
> +/* Following function prints the list of lrports. */
> +static void
> +nbctl_lrport_list(struct ctl_context *ctx)
> +{
> + const char *id = ctx->argv[1];
> + const struct nbrec_logical_router *lrouter;
> + struct smap lports;
> + size_t i;
> +
> + lrouter = lrouter_by_name_or_uuid(ctx, id, false);
> + if (!lrouter) {
> + return;
> + }
> +
> + smap_init(&lports);
> + for (i = 0; i < lrouter->n_ports; i++) {
> + const struct nbrec_logical_router_port *lport = lrouter->ports
[i];
> + smap_add_format(&lports, lport->name, UUID_FMT " (%s)",
> + UUID_ARGS(&lport->header_.uuid), lport->name);
> + }
> + const struct smap_node **nodes = smap_sort(&lports);
> + for (i = 0; i < smap_count(&lports); i++) {
> + const struct smap_node *node = nodes[i];
> + ds_put_format(&ctx->output, "%s\n", node->value);
> + }
> + smap_destroy(&lports);
> + free(nodes);
> +}
> +
> +/* Following function adds an lrport to the lrouter. */
> +static void
> +nbctl_lrport_add(struct ctl_context *ctx)
> +{
> + struct nbrec_logical_router_port *lport;
> + const struct nbrec_logical_router *lrouter;
> +
> + lrouter = lrouter_by_name_or_uuid(ctx, ctx->argv[1], false);
> + if (!lrouter) {
> + return;
> + }
> +
> + if (ctx->argc != 3) {
> + VLOG_WARN("Invalid arguments to lrport-add.");
> + return;
> + }
> +
> + /* Create the logical router port. */
> + lport = nbrec_logical_router_port_insert(ctx->txn);
> + nbrec_logical_router_port_set_name(lport, ctx->argv[2]);
> +
> + /* Insert the logical port into the logical router. */
> + nbrec_logical_router_verify_ports(lrouter);
> + struct nbrec_logical_router_port **new_ports = xmalloc(sizeof
> *new_ports *
> + (lrouter->n_ports +
1));
> + memcpy(new_ports, lrouter->ports, sizeof *new_ports * lrouter->
n_ports);
> + new_ports[lrouter->n_ports] = lport;
> + nbrec_logical_router_set_ports(lrouter, new_ports, lrouter->n_ports
+ 1);
> + free(new_ports);
> +}
> +
> +/* Following function deletes the lrport from lrouter. */
> +static void
> +nbctl_lrport_del(struct ctl_context *ctx)
> +{
> + const struct nbrec_logical_router_port *lrport;
> +
> + lrport = lrport_by_name_or_uuid(ctx, ctx->argv[1]);
> + if (!lrport) {
> + return;
> + }
> +
> + /* Find the router that contains 'lport', then delete it. */
> + const struct nbrec_logical_router *lrouter;
> + NBREC_LOGICAL_ROUTER_FOR_EACH (lrouter, ctx->idl) {
> + for (size_t i = 0; i < lrouter->n_ports; i++) {
> + if (lrouter->ports[i] == lrport) {
> + remove_lrport(lrouter, i);
> + return;
> + }
> + }
> + }
> +
> + VLOG_WARN("logical router port %s is not part of any logical
router",
> + ctx->argv[1]);
> +}
> +
> static const struct ctl_command_syntax nbctl_commands[] = {
> { "show", 0, 1, "[LSWITCH]", NULL, nbctl_show, NULL, "", RO },
>
> @@ -1312,6 +1666,13 @@ static const struct ctl_command_syntax
> nbctl_commands[] = {
> NULL, "", RW },
> { "lswitch-list", 0, 0, "", NULL, nbctl_lswitch_list, NULL, "",
RO },
>
> + /* lrouter commands. */
> + { "lrouter-add", 0, 1, "[LROUTER]", NULL, nbctl_lrouter_add,
> + NULL, "", RW },
> + { "lrouter-del", 1, 1, "LROUTER", NULL, nbctl_lrouter_del,
> + NULL, "", RW },
> + { "lrouter-list", 0, 0, "", NULL, nbctl_lrouter_list, NULL, "",
RO },
> +
> /* acl commands. */
> { "acl-add", 5, 5, "LSWITCH DIRECTION PRIORITY MATCH ACTION", NULL,
> nbctl_acl_add, NULL, "--log", RW },
> @@ -1319,6 +1680,20 @@ static const struct ctl_command_syntax
> nbctl_commands[] = {
> nbctl_acl_del, NULL, "", RW },
> { "acl-list", 1, 1, "LSWITCH", NULL, nbctl_acl_list, NULL, "", RO },
>
> + /* lrport commands. */
> + { "lrport-add", 2, 4, "LROUTER LRPORT ", NULL, nbctl_lrport_add,
> + NULL, "", RW },
> + { "lrport-del", 1, 1, "LRPORT", NULL, nbctl_lrport_del, NULL, "",
RO },
> + { "lrport-list", 1, 1, "LROUTER", NULL, nbctl_lrport_list,
> NULL, "", RO },
> + { "lrport-set-mac-address", 1, INT_MAX, "LRPORT [ADDRESS]", NULL,
> + nbctl_lrport_set_mac, NULL, "", RW },
> + { "lrport-get-mac-address", 1, 1, "LRPORT", NULL,
> + nbctl_lrport_get_mac, NULL,
> + "", RO },
> + { "lrport-set-enabled", 2, 2, "LRPORT STATE", NULL,
> nbctl_lrport_set_enabled,
> + NULL, "", RW },
> + { "lrport-get-enabled", 1, 1, "LRPORT", NULL,
> nbctl_lrport_get_enabled, NULL,
> + "", RO },
> /* lport commands. */
> { "lport-add", 2, 4, "LSWITCH LPORT [PARENT] [TAG]", NULL,
> nbctl_lport_add,
> NULL, "", RW },
>
> _______________________________________________
> dev mailing list
> dev at openvswitch.org
> http://openvswitch.org/mailman/listinfo/dev
More information about the dev
mailing list