]> git.proxmox.com Git - ovs.git/commitdiff
ovn-nbctl: Add lrouter and lrport related commands.
authorNirapada Ghosh <nghosh@us.ibm.com>
Fri, 3 Jun 2016 18:48:49 +0000 (11:48 -0700)
committerBen Pfaff <blp@ovn.org>
Fri, 3 Jun 2016 18:49:07 +0000 (11:49 -0700)
ovn-nbctl provides a shortcut to perform commands related lswitch, lport
and such but it doesn't have similar commands related to logical routers
and logical router ports. Also, 'ovn-nbctl show' is supposed to show an
overview of database contents, which means it should show the routers
as well. "ovn-nbctl show LSWITCH" shows the switch details, similarly
"ovn-nbctl show LROUTER" should show the router details too. This patch
takes care of all of these.

Modifications;
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

Unit test cases have been added to test all of these modifications and
additions.

Signed-off-by: Nirapada Ghosh <nghosh@us.ibm.com>
[blp@ovn.org added features to match the lswitch and lport commands]
Co-authored-by: Ben Pfaff <blp@ovn.org>
Signed-off-by: Ben Pfaff <blp@ovn.org>
ovn/utilities/ovn-nbctl.8.xml
ovn/utilities/ovn-nbctl.c
tests/ovn-nbctl.at

index 8375ab7cde60eaef44291a44a4d43425b4fa2b0a..f5920363b00e2d932a9e807531c0fdc51bae728c 100644 (file)
     <h1>General Commands</h1>
 
     <dl>
-      <dt><code>show [<var>lswitch</var>]</code></dt>
+      <dt><code>show [<var>lswitch</var> | <var>lrouter</var>]</code></dt>
       <dd>
         Prints a brief overview of the database contents.  If
         <var>lswitch</var> is provided, only records related to that
-        logical switch are shown.
+        logical switch are shown. If
+        <var>lrouter</var> is provided, only records related to that
+        logical router are shown.
       </dd>
     </dl>
 
+    <h1>Logical Router Commands</h1>
+
+    <dl>
+      <dt>[<code>--may-exist</code> | <code>--add-duplicate</code>] <code>lrouter-add</code> <var>lrouter</var></dt> 
+      <dd>
+        <p>
+          Creates a new logical router named <var>lrouter</var>, which
+          initially has no router ports.
+        </p>
+
+        <p>
+          The OVN northbound database schema does not require logical router
+          names to be unique, but the whole point to the names is to provide an
+          easy way for humans to refer to the routers, making duplicate names
+          unhelpful.  Thus, without any options, this command regards it as an
+          error if <var>lrouter</var> is a duplicate name.  With
+          <code>--may-exist</code>, adding a duplicate name succeeds but does
+          not create a new logical router.  With <code>--add-duplicate</code>,
+          the command really creates a new logical router with a duplicate
+          name.  It is an error to specify both options.
+        </p>
+      </dd>
+
+      <dt>[<code>--if-exists</code>] <code>lrouter-del</code> <var>lrouter</var></dt>
+      <dd>
+        Deletes <var>lrouter</var>.  It is an error if <var>lrouter</var> does
+        not exist, unless <code>--if-exists</code> is specified.
+      </dd>
+
+      <dt><code>lrouter-list</code></dt>
+      <dd>
+        Lists all existing logical routers on standard output, one per line.
+      </dd>
+    </dl>
     <h1>Logical Switch Commands</h1>
 
     <dl>
         Lists the ACLs on <var>lswitch</var>.
       </dd>
     </dl>
+    <h1>Logical Router Port Commands</h1>
+    <dl>
+      <dt>[<code>--may-exist</code>] <code>lrport-add</code> <var>lrouter</var> <var>lrport</var></dt>
+      <dd>
+        <p>
+          Creates on <var>lrouter</var> a new logical router port named
+          <var>lrport</var>.
+        </p>
+
+        <p>
+          It is an error if a logical router port named <var>lrport</var>
+          already exists, unless <code>--may-exist</code> is specified.
+          Regardless of <code>--may-exist</code>, it is an error if the
+          existing router port is in some logical router other than
+          <var>lrouter</var>.
+        </p>
+      </dd>
+
+      <dt>[<code>--if-exists</code>] <code>lrport-del</code> <var>lrport</var></dt>
+      <dd>
+        Deletes <var>lrport</var>.  It is an error if <var>lrport</var> does
+        not exist, unless <code>--if-exists</code> is specified.
+      </dd>
+
+      <dt><code>lrport-list</code> <var>lrouter</var></dt>
+      <dd>
+        Lists all the logical router ports within <var>lrouter</var> on
+        standard output, one per line.
+      </dd>
 
+      <dt><code>lrport-set-mac-address</code> <var>lrport</var>
+                <var>address</var>...</dt>
+      <dd>
+        Sets the address associated with <var>lrport</var> to
+        <var>address</var>.  <var>address</var> should be either an
+        Ethernet address or an Ethernet address followed by an IP address
+        (separated by a space and quoted to form a single command-line
+        argument).  The special form <code>unknown</code> is also valid.
+      </dd>
+
+      <dt><code>lrport-get-mac-address</code> <var>lrport</var></dt>
+      <dd>
+        Lists the mac address associated with <var>lrport</var> on standard
+        output.
+      </dd>
+
+      <dt><code>lrport-set-enabled</code> <var>lrport</var> <var>state</var></dt>
+      <dd>
+        Set the administrative state of <var>lrport</var>,
+        either <code>enabled</code> or <code>disabled</code>.
+        When a port is disabled, no traffic is allowed into
+        or out of the port.
+      </dd>
+
+      <dt><code>lrport-get-enabled</code> <var>lrport</var></dt>
+      <dd>
+        Prints the administrative state of <var>lrport</var>,
+        either <code>enabled</code> or <code>disabled</code>.
+      </dd>
+
+    </dl>
     <h1>Logical Port Commands</h1>
     <dl>
       <dt>[<code>--may-exist</code>] <code>lport-add</code> <var>lswitch</var> <var>lport</var></dt>
index d267829c3a4bb32b5e85c354932df7fdb8f4c44b..321040e8c21acd6ef29fb5c401e42fb7a3c868d7 100644 (file)
@@ -293,12 +293,18 @@ usage: %s [OPTIONS] COMMAND [ARG...]\n\
 General commands:\n\
   show                      print overview of database contents\n\
   show LSWITCH              print overview of database contents for LSWITCH\n\
+  show LROUTER              print overview of database contents for LROUTER\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\
@@ -306,6 +312,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\
@@ -352,6 +371,44 @@ Other options:\n\
     exit(EXIT_SUCCESS);
 }
 \f
+
+/* Find an lrouter given its id. */
+static const struct nbrec_logical_router *
+lrouter_by_name_or_uuid(struct ctl_context *ctx, const char *id,
+                        bool must_exist)
+{
+    const struct nbrec_logical_router *lrouter = NULL;
+    bool is_uuid = 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) {
+                ctl_fatal("Multiple logical routers named '%s'.  "
+                          "Use a UUID.", id);
+            }
+            lrouter = iter;
+        }
+    }
+
+    if (!lrouter && must_exist) {
+        ctl_fatal("%s: lrouter %s not found", id, is_uuid ? "UUID" : "name");
+    }
+
+    return lrouter;
+}
+
 static const struct nbrec_logical_switch *
 lswitch_by_name_or_uuid(struct ctl_context *ctx, const char *id,
                         bool must_exist)
@@ -386,6 +443,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)
 {
@@ -429,6 +504,93 @@ 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], false);
+        if (lrouter) {
+            print_lrouter(lrouter, &ctx->output);
+        }
+    } else {
+        NBREC_LOGICAL_ROUTER_FOR_EACH(lrouter, ctx->idl) {
+            print_lrouter(lrouter, &ctx->output);
+        }
+    }
+}
+
+/* Add an lrouter. */
+static void
+nbctl_lrouter_add(struct ctl_context *ctx)
+{
+    const char *lrouter_name = ctx->argc == 2 ? ctx->argv[1] : NULL;
+
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL;
+    if (may_exist && add_duplicate) {
+        ctl_fatal("--may-exist and --add-duplicate may not be used together");
+    }
+
+    if (lrouter_name) {
+        if (!add_duplicate) {
+            const struct nbrec_logical_router *lrouter;
+            NBREC_LOGICAL_ROUTER_FOR_EACH (lrouter, ctx->idl) {
+                if (!strcmp(lrouter->name, lrouter_name)) {
+                    if (may_exist) {
+                        return;
+                    }
+                    ctl_fatal("%s: an lrouter with this name already exists",
+                              lrouter_name);
+                }
+            }
+        }
+    } else if (may_exist) {
+        ctl_fatal("--may-exist requires specifying a name");
+    } else if (add_duplicate) {
+        ctl_fatal("--add-duplicate requires specifying a name");
+    }
+
+    struct nbrec_logical_router *lrouter;
+    lrouter = nbrec_logical_router_insert(ctx->txn);
+    if (lrouter_name) {
+        nbrec_logical_router_set_name(lrouter, lrouter_name);
+    }
+}
+
+/* Delete an lrouter. */
+static void
+nbctl_lrouter_del(struct ctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    const char *id = ctx->argv[1];
+    const struct nbrec_logical_router *lrouter;
+
+    lrouter = lrouter_by_name_or_uuid(ctx, id, must_exist);
+    if (!lrouter) {
+        return;
+    }
+
+    nbrec_logical_router_delete(lrouter);
+}
+
+/* Print list of lrouters. */
+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
@@ -503,6 +665,37 @@ nbctl_lswitch_list(struct ctl_context *ctx)
     free(nodes);
 }
 \f
+/* Find the lrport given its id. */
+static const struct nbrec_logical_router_port *
+lrport_by_name_or_uuid(struct ctl_context *ctx, const char *id,
+                       bool must_exist)
+{
+    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 && must_exist) {
+        ctl_fatal("%s: lrport with this %s not found",
+                  id, is_uuid ? "name" : "UUID");
+    }
+
+    return lrport;
+}
+
 static const struct nbrec_logical_port *
 lport_by_name_or_uuid(struct ctl_context *ctx, const char *id,
                       bool must_exist)
@@ -549,6 +742,25 @@ lport_to_lswitch(const struct ovsdb_idl *idl,
               lport->name);
 }
 
+/* Returns the lrouter that contains 'lport'. */
+static const struct nbrec_logical_router *
+lrport_to_lrouter(const struct ovsdb_idl *idl,
+                  const struct nbrec_logical_router_port *lrport)
+{
+    const struct nbrec_logical_router *lrouter;
+    NBREC_LOGICAL_ROUTER_FOR_EACH (lrouter, idl) {
+        for (size_t i = 0; i < lrouter->n_ports; i++) {
+            if (lrouter->ports[i] == lrport) {
+                return lrouter;
+            }
+        }
+    }
+
+    /* Can't happen because of the database schema */
+    ctl_fatal("logical port %s is not part of any logical router",
+              lrport->name);
+}
+
 static const char *
 lswitch_get_name(const struct nbrec_logical_switch *lswitch,
                  char uuid_s[UUID_LEN + 1], size_t uuid_s_size)
@@ -560,6 +772,17 @@ lswitch_get_name(const struct nbrec_logical_switch *lswitch,
     return uuid_s;
 }
 
+static const char *
+lrouter_get_name(const struct nbrec_logical_router *lrouter,
+                 char uuid_s[UUID_LEN + 1], size_t uuid_s_size)
+{
+    if (lrouter->name[0]) {
+        return lrouter->name;
+    }
+    snprintf(uuid_s, uuid_s_size, UUID_FMT, UUID_ARGS(&lrouter->header_.uuid));
+    return uuid_s;
+}
+
 static void
 nbctl_lport_add(struct ctl_context *ctx)
 {
@@ -646,6 +869,26 @@ 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. */
+    nbrec_logical_router_port_delete(lrport);
+}
+
 /* Removes lport 'lswitch->ports[idx]'. */
 static void
 remove_lport(const struct nbrec_logical_switch *lswitch, size_t idx)
@@ -742,6 +985,26 @@ nbctl_lport_get_tag(struct ctl_context *ctx)
     }
 }
 
+/* Set 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, true);
+
+    const char *mac = ctx->argc > 2 ? ctx->argv[2] : "";
+    if (mac[0] && !ovs_scan(ctx->argv[2], ETH_ADDR_SCAN_FMT,
+                            ETH_ADDR_SCAN_ARGS(ea))) {
+        ctl_fatal("%s: invalid MAC address format", mac);
+        return;
+    }
+
+    nbrec_logical_router_port_set_mac(lrport, mac);
+}
+
 static void
 nbctl_lport_set_addresses(struct ctl_context *ctx)
 {
@@ -768,6 +1031,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, true);
+    if (!lrport) {
+        return;
+    }
+    ds_put_format(&ctx->output, "%s\n", lrport->mac);
+}
+
 static void
 nbctl_lport_get_addresses(struct ctl_context *ctx)
 {
@@ -833,6 +1110,35 @@ nbctl_lport_get_up(struct ctl_context *ctx)
                   "%s\n", (lport->up && *lport->up) ? "up" : "down");
 }
 
+static bool
+parse_enabled(const char *state)
+{
+    if (!strcasecmp(state, "enabled")) {
+        return true;
+    } else if (!strcasecmp(state, "disabled")) {
+        return false;
+    } else {
+        ctl_fatal("%s: state must be \"enabled\" or \"disabled\"", state);
+    }
+}
+
+/* Set the lrport admin-enabled state. */
+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, true);
+    if (!lrport) {
+        return;
+    }
+
+    bool enabled = parse_enabled(state);
+    nbrec_logical_router_port_set_enabled(lrport, &enabled, 1);
+}
+
 static void
 nbctl_lport_set_enabled(struct ctl_context *ctx)
 {
@@ -841,16 +1147,25 @@ nbctl_lport_set_enabled(struct ctl_context *ctx)
     const struct nbrec_logical_port *lport;
 
     lport = lport_by_name_or_uuid(ctx, id, true);
-    if (!strcasecmp(state, "enabled")) {
-        bool enabled = true;
-        nbrec_logical_port_set_enabled(lport, &enabled, 1);
-    } else if (!strcasecmp(state, "disabled")) {
-        bool enabled = false;
-        nbrec_logical_port_set_enabled(lport, &enabled, 1);
-    } else {
-        ctl_fatal("%s: state must be \"enabled\" or \"disabled\"",
-                  state);
+    bool enabled = parse_enabled(state);
+    nbrec_logical_port_set_enabled(lport, &enabled, 1);
+}
+
+/* Print admin-enabled state for lrport. */
+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, true);
+    if (!lrport) {
+        return;
     }
+
+    ds_put_format(&ctx->output, "%s\n",
+                  !lrport->enabled ||
+                  *lrport->enabled ? "enabled" : "disabled");
 }
 
 static void
@@ -1336,6 +1651,105 @@ nbctl_exit(int status)
     exit(status);
 }
 
+/* Print a 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, true);
+
+    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);
+}
+
+/* Add an lrport to the lrouter. */
+static void
+nbctl_lrport_add(struct ctl_context *ctx)
+{
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+
+    const struct nbrec_logical_router *lrouter;
+    lrouter = lrouter_by_name_or_uuid(ctx, ctx->argv[1], true);
+
+    const char *lrport_name = ctx->argv[2];
+    const struct nbrec_logical_router_port *lrport;
+
+    lrport = lrport_by_name_or_uuid(ctx, lrport_name, false);
+    if (lrport) {
+        if (!may_exist) {
+            ctl_fatal("%s: an lrport with this name already exists",
+                      lrport_name);
+        }
+
+        const struct nbrec_logical_router *lr;
+        lr = lrport_to_lrouter(ctx->idl, lrport);
+        if (lr != lrouter) {
+            char uuid_s[UUID_LEN + 1];
+            ctl_fatal("%s: lrport already exists but in lrouter %s",
+                      lrport_name,
+                      lrouter_get_name(lr, uuid_s, sizeof uuid_s));
+        }
+
+        return;
+    }
+
+    /* Create the logical router port. */
+    lrport = nbrec_logical_router_port_insert(ctx->txn);
+    nbrec_logical_router_port_set_name(lrport, 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] = CONST_CAST(
+        struct nbrec_logical_router_port *, lrport);
+    nbrec_logical_router_set_ports(lrouter, new_ports, lrouter->n_ports + 1);
+    free(new_ports);
+}
+
+/* Deletes an lrport from an lrouter. */
+static void
+nbctl_lrport_del(struct ctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    const struct nbrec_logical_router_port *lrport;
+
+    lrport = lrport_by_name_or_uuid(ctx, ctx->argv[1], must_exist);
+    if (!lrport) {
+        return;
+    }
+
+    /* Find the router that contains 'lrport', 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;
+            }
+        }
+    }
+
+    ctl_fatal("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 },
 
@@ -1346,6 +1760,13 @@ static const struct ctl_command_syntax nbctl_commands[] = {
       NULL, "--if-exists", RW },
     { "lswitch-list", 0, 0, "", NULL, nbctl_lswitch_list, NULL, "", RO },
 
+    /* lrouter commands. */
+    { "lrouter-add", 0, 1, "[LROUTER]", NULL, nbctl_lrouter_add,
+      NULL, "--may-exist,--add-duplicate", RW },
+    { "lrouter-del", 1, 1, "LROUTER", NULL, nbctl_lrouter_del,
+      NULL, "--if-exists", 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 },
@@ -1353,6 +1774,21 @@ 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, 2, "LROUTER LRPORT", NULL, nbctl_lrport_add,
+      NULL, "--may-exist", 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, 2, "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, "--may-exist", RW },
index 47ec1ef4fa44a6754a36ab4bf8a6588785a2746b..faedf25d783f66c290b0e7e377384d66d3c87641 100644 (file)
@@ -139,7 +139,7 @@ AT_CLEANUP
 
 dnl ---------------------------------------------------------------------
 
-AT_SETUP([ovn-nbctl - addresses])
+AT_SETUP([ovn-nbctl - lport addresses])
 OVN_NBCTL_TEST_START
 
 AT_CHECK([ovn-nbctl lswitch-add ls0])
@@ -231,3 +231,144 @@ from-lport   200 (ip) drop
 
 OVN_NBCTL_TEST_STOP
 AT_CLEANUP
+
+dnl ---------------------------------------------------------------------
+
+AT_SETUP([ovn-nbctl - basic lrouter commands])
+OVN_NBCTL_TEST_START
+
+AT_CHECK([ovn-nbctl lrouter-add lr0])
+AT_CHECK([ovn-nbctl lrouter-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+<0> (lr0)
+])
+
+AT_CHECK([ovn-nbctl lrouter-add lr1])
+AT_CHECK([ovn-nbctl lrouter-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+<0> (lr0)
+<1> (lr1)
+])
+
+AT_CHECK([ovn-nbctl lrouter-del lr0])
+AT_CHECK([ovn-nbctl lrouter-list | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+<0> (lr1)
+])
+
+AT_CHECK([ovn-nbctl show lr0])
+AT_CHECK([ovn-nbctl lrouter-add lr0])
+AT_CHECK([ovn-nbctl show lr0 | ${PERL} $srcdir/uuidfilt.pl], [0],
+  [    lrouter <0> (lr0)
+])
+AT_CHECK([ovn-nbctl lrouter-add lr0], [1], [],
+  [ovn-nbctl: lr0: an lrouter with this name already exists
+])
+AT_CHECK([ovn-nbctl --may-exist lrouter-add lr0])
+AT_CHECK([ovn-nbctl show lr0 | ${PERL} $srcdir/uuidfilt.pl], [0],
+  [    lrouter <0> (lr0)
+])
+AT_CHECK([ovn-nbctl --add-duplicate lrouter-add lr0])
+AT_CHECK([ovn-nbctl --may-exist --add-duplicate lrouter-add lr0], [1], [],
+  [ovn-nbctl: --may-exist and --add-duplicate may not be used together
+])
+AT_CHECK([ovn-nbctl lrouter-del lr0], [1], [],
+  [ovn-nbctl: Multiple logical routers named 'lr0'.  Use a UUID.
+])
+
+AT_CHECK([ovn-nbctl lrouter-del lr2], [1], [],
+  [ovn-nbctl: lr2: lrouter name not found
+])
+AT_CHECK([ovn-nbctl --if-exists lrouter-del lr2])
+
+AT_CHECK([ovn-nbctl lrouter-add])
+AT_CHECK([ovn-nbctl lrouter-add])
+AT_CHECK([ovn-nbctl --add-duplicate lrouter-add], [1], [],
+  [ovn-nbctl: --add-duplicate requires specifying a name
+])
+AT_CHECK([ovn-nbctl --may-exist lrouter-add], [1], [],
+  [ovn-nbctl: --may-exist requires specifying a name
+])
+
+OVN_NBCTL_TEST_STOP
+AT_CLEANUP
+
+dnl ---------------------------------------------------------------------
+
+AT_SETUP([ovn-nbctl - basic lrport commands])
+OVN_NBCTL_TEST_START
+
+AT_CHECK([ovn-nbctl lrouter-add lr0])
+AT_CHECK([ovn-nbctl lrport-add lr0 lrp0])
+AT_CHECK([ovn-nbctl lrport-add lr0 lrp0], [1], [],
+  [ovn-nbctl: lrp0: an lrport with this name already exists
+])
+AT_CHECK([ovn-nbctl --may-exist lrport-add lr0 lrp0])
+AT_CHECK([ovn-nbctl lrport-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+<0> (lrp0)
+])
+
+AT_CHECK([ovn-nbctl lrport-add lr0 lrp1])
+AT_CHECK([ovn-nbctl lrport-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+<0> (lrp0)
+<1> (lrp1)
+])
+
+AT_CHECK([ovn-nbctl lrouter-add lr1])
+AT_CHECK([ovn-nbctl lrport-add lr0 lrp1], [1], [],
+  [ovn-nbctl: lrp1: an lrport with this name already exists
+])
+AT_CHECK([ovn-nbctl --may-exist lrport-add lr1 lrp1], [1], [],
+  [ovn-nbctl: lrp1: lrport already exists but in lrouter lr0
+])
+
+AT_CHECK([ovn-nbctl lrport-del lrp1])
+AT_CHECK([ovn-nbctl lrport-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl
+<0> (lrp0)
+])
+
+OVN_NBCTL_TEST_STOP
+AT_CLEANUP
+
+dnl ---------------------------------------------------------------------
+
+AT_SETUP([ovn-nbctl - lrport addresses])
+OVN_NBCTL_TEST_START
+
+AT_CHECK([ovn-nbctl lrouter-add lr0])
+AT_CHECK([ovn-nbctl lrport-add lr0 lrp0])
+AT_CHECK([ovn-nbctl lrport-get-mac-address lrp0], [0], [
+])
+
+AT_CHECK([ovn-nbctl lrport-set-mac-address lrp0 00:11:22:33:44:55])
+AT_CHECK([ovn-nbctl lrport-get-mac-address lrp0], [0], [00:11:22:33:44:55
+])
+
+AT_CHECK([ovn-nbctl lrport-set-mac-address lrp0])
+AT_CHECK([ovn-nbctl lrport-get-mac-address lrp0], [0], [
+])
+
+OVN_NBCTL_TEST_STOP
+AT_CLEANUP
+
+dnl ---------------------------------------------------------------------
+
+AT_SETUP([ovn-nbctl - lrport enable and disable])
+OVN_NBCTL_TEST_START
+
+AT_CHECK([ovn-nbctl lrouter-add lr0])
+AT_CHECK([ovn-nbctl lrport-add lr0 lrp0])
+AT_CHECK([ovn-nbctl lrport-get-enabled lrp0], [0], [enabled
+])
+
+AT_CHECK([ovn-nbctl lrport-set-enabled lrp0 disabled])
+AT_CHECK([ovn-nbctl lrport-get-enabled lrp0], [0], [disabled
+])
+
+AT_CHECK([ovn-nbctl lrport-set-enabled lrp0 enabled])
+AT_CHECK([ovn-nbctl lrport-get-enabled lrp0], [0], [enabled
+])
+
+AT_CHECK([ovn-nbctl lrport-set-enabled lrp0 xyzzy], [1], [],
+  [ovn-nbctl: xyzzy: state must be "enabled" or "disabled"
+])
+
+OVN_NBCTL_TEST_STOP
+AT_CLEANUP