[ovs-dev] [PATCH v4] ovn-northd, tests: Adding IPAM to ovn-northd.

Nimay Desai nimaydesai1 at gmail.com
Wed Jul 27 18:28:24 UTC 2016


Added an IPv4 and MAC addresses management system to ovn-northd. When a logical
switch's other_config:subnet field is set, logical ports attached to that
switch that have the keyword "dynamic" in their addresses column will
automatically be allocated a globally unique MAC address/unused IPv4 address
within the provided subnet. The allocated address will populate the
dynamic_addresses column. This can be useful for a user who wants to deploy
many VM's or containers with networking capabilities, but does not care about
the specific MAC/IPv4 addresses that are assigned.

Added tests in ovn.at for ipam.

Signed-off-by: Nimay Desai <nimaydesai1 at gmail.com>
---
 AUTHORS                   |   1 +
 ovn/northd/ovn-northd.c   | 390 +++++++++++++++++++++++++++++++++++++++++++++-
 ovn/ovn-nb.ovsschema      |  10 +-
 ovn/ovn-nb.xml            |  35 +++++
 ovn/utilities/ovn-nbctl.c |   2 +-
 tests/ovn.at              | 294 ++++++++++++++++++++++++++++++++++
 6 files changed, 728 insertions(+), 4 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 334e17f..8e04e75 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -166,6 +166,7 @@ Murphy McCauley         murphy.mccauley at gmail.com
 Natasha Gude            natasha at nicira.com
 Neil McKee              neil.mckee at inmon.com
 Neil Zhu                zhuj at centecnetworks.com
+Nimay Desai             nimaydesai1 at gmail.com
 Nithin Raju             nithin at vmware.com
 Niti Rohilla            niti.rohilla at tcs.com
 Numan Siddique          nusiddiq at redhat.com
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index a836881..5183d15 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -58,6 +58,13 @@ static const char *ovnsb_db;
 
 static const char *default_nb_db(void);
 static const char *default_sb_db(void);
+
+#define MAC_ADDR_PREFIX 0x0A0000000000ULL
+#define MAC_ADDR_SPACE 0xffffff
+
+/* MAC address table of "struct eth_addr"s, that holds the MAC addresses
+ * allocated by the ipam. */
+static struct hmap macam = HMAP_INITIALIZER(&macam);
 
 /* Pipeline stages. */
 
@@ -288,8 +295,40 @@ struct ovn_datapath {
     uint32_t port_key_hint;
 
     bool has_unknown;
+
+    /* IPAM data. */
+    struct hmap ipam;
+};
+
+struct macam_node {
+    struct hmap_node hmap_node;
+    struct eth_addr mac_addr; /* Allocated MAC address. */
 };
 
+static void
+cleanup_macam(struct hmap *macam)
+{
+    struct macam_node *node;
+    HMAP_FOR_EACH_POP (node, hmap_node, macam) {
+        free(node);
+    }
+}
+
+struct ipam_node {
+    struct hmap_node hmap_node;
+    uint32_t ip_addr; /* Allocated IP address. */
+};
+
+static void
+destroy_ipam(struct hmap *ipam)
+{
+    struct ipam_node *node;
+    HMAP_FOR_EACH_POP (node, hmap_node, ipam) {
+        free(node);
+    }
+    hmap_destroy(ipam);
+}
+
 static struct ovn_datapath *
 ovn_datapath_create(struct hmap *datapaths, const struct uuid *key,
                     const struct nbrec_logical_switch *nbs,
@@ -302,6 +341,7 @@ ovn_datapath_create(struct hmap *datapaths, const struct uuid *key,
     od->nbs = nbs;
     od->nbr = nbr;
     hmap_init(&od->port_tnlids);
+    hmap_init(&od->ipam);
     od->port_key_hint = 0;
     hmap_insert(datapaths, &od->key_node, uuid_hash(&od->key));
     return od;
@@ -316,6 +356,7 @@ ovn_datapath_destroy(struct hmap *datapaths, struct ovn_datapath *od)
          * use it. */
         hmap_remove(datapaths, &od->key_node);
         destroy_tnlids(&od->port_tnlids);
+        destroy_ipam(&od->ipam);
         free(od->router_ports);
         free(od);
     }
@@ -600,6 +641,318 @@ ovn_port_allocate_key(struct ovn_datapath *od)
                           (1u << 15) - 1, &od->port_key_hint);
 }
 
+static bool
+ipam_is_duplicate_mac(struct eth_addr *ea, uint64_t mac64, bool warn)
+{
+    struct macam_node *macam_node;
+    HMAP_FOR_EACH_WITH_HASH (macam_node, hmap_node, hash_uint64(mac64),
+                             &macam) {
+        if (eth_addr_equals(*ea, macam_node->mac_addr)) {
+            if (warn) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+                VLOG_WARN_RL(&rl, "Duplicate MAC set: "ETH_ADDR_FMT,
+                             ETH_ADDR_ARGS(macam_node->mac_addr));
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool
+ipam_is_duplicate_ip(struct ovn_datapath *od, uint32_t ip, bool warn)
+{
+    struct ipam_node *ipam_node;
+    HMAP_FOR_EACH_WITH_HASH (ipam_node, hmap_node, hash_int(ip, 0),
+                             &od->ipam) {
+        if (ipam_node->ip_addr == ip) {
+            if (warn) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+                VLOG_WARN_RL(&rl, "Duplicate IP set: "IP_FMT,
+                             IP_ARGS(htonl(ip)));
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+static void
+ipam_insert_mac(struct eth_addr *ea, bool check)
+{
+    if (!ea) {
+        return;
+    }
+
+    uint64_t mac64 = eth_addr_to_uint64(*ea);
+    /* If the new MAC was not assigned by this address management system or
+     * check is true and the new MAC is a duplicate, do not insert it into the
+     * macam hmap. */
+    if (((mac64 ^ MAC_ADDR_PREFIX) >> 24)
+        || (check && ipam_is_duplicate_mac(ea, mac64, true))) {
+        return;
+    }
+
+    struct macam_node *new_macam_node = xmalloc(sizeof *new_macam_node);
+    new_macam_node->mac_addr = *ea;
+    hmap_insert(&macam, &new_macam_node->hmap_node, hash_uint64(mac64));
+}
+
+static void
+ipam_insert_ip(struct ovn_datapath *od, uint32_t ip, bool check)
+{
+    if (!od) {
+        return;
+    }
+
+    if (check && ipam_is_duplicate_ip(od, ip, true)) {
+        return;
+    }
+
+    struct ipam_node *new_ipam_node = xmalloc(sizeof *new_ipam_node);
+    new_ipam_node->ip_addr = ip;
+    hmap_insert(&od->ipam, &new_ipam_node->hmap_node, hash_int(ip, 0));
+}
+
+static void
+ipam_insert_lsp_addresses(struct ovn_datapath *od, struct ovn_port *op,
+                          char *address)
+{
+    if (!od || !op || !address || !strcmp(address, "unknown")
+        || !strcmp(address, "dynamic")) {
+        return;
+    }
+
+    struct lport_addresses laddrs;
+    if (!extract_lsp_addresses(address, &laddrs)) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+        VLOG_WARN_RL(&rl, "Extract addresses failed.");
+        return;
+    }
+    ipam_insert_mac(&laddrs.ea, true);
+
+    /* IP is only added to IPAM if the switch's subnet option
+     * is set, whereas MAC is always added to MACAM. */
+    if (!smap_get(&od->nbs->other_config, "subnet")) {
+        destroy_lport_addresses(&laddrs);
+        return;
+    }
+
+    for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
+        uint32_t ip = ntohl(laddrs.ipv4_addrs[j].addr);
+        ipam_insert_ip(od, ip, true);
+    }
+
+    destroy_lport_addresses(&laddrs);
+}
+
+static void
+ipam_add_port_addresses(struct ovn_datapath *od, struct ovn_port *op)
+{
+    if (!od || !op) {
+        return;
+    }
+
+    if (op->nbsp) {
+        /* Add all the port's addresses to address data structures. */
+        for (size_t i = 0; i < op->nbsp->n_addresses; i++) {
+            ipam_insert_lsp_addresses(od, op, op->nbsp->addresses[i]);
+        }
+        if (op->nbsp->dynamic_addresses) {
+            ipam_insert_lsp_addresses(od, op, op->nbsp->dynamic_addresses);
+        }
+    } else if (op->nbrp) {
+        struct lport_addresses lrp_networks;
+        if (!extract_lrp_networks(op->nbrp, &lrp_networks)) {
+            static struct vlog_rate_limit rl
+                = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_WARN_RL(&rl, "Extract addresses failed.");
+            return;
+        }
+        ipam_insert_mac(&lrp_networks.ea, true);
+
+        if (!op->peer || !op->peer->nbsp || !op->peer->od || !op->peer->od->nbs
+            || !smap_get(&op->peer->od->nbs->other_config, "subnet")) {
+            destroy_lport_addresses(&lrp_networks);
+            return;
+        }
+
+        for (size_t i = 0; i < lrp_networks.n_ipv4_addrs; i++) {
+            uint32_t ip = ntohl(lrp_networks.ipv4_addrs[i].addr);
+            ipam_insert_ip(op->peer->od, ip, true);
+        }
+
+        destroy_lport_addresses(&lrp_networks);
+    }
+}
+
+static uint64_t
+ipam_get_unused_mac(void)
+{
+    /* Stores the suffix of the most recently ipam-allocated MAC address. */
+    static uint32_t last_mac;
+
+    uint64_t mac64;
+    struct eth_addr mac;
+    uint32_t mac_addr_suffix, i;
+    for (i = 0; i < MAC_ADDR_SPACE - 1; i++) {
+        /* The tentative MAC's suffix will be in the interval (1, 0xfffffe). */
+        mac_addr_suffix = ((last_mac + i) % (MAC_ADDR_SPACE - 1)) + 1;
+        mac64 = MAC_ADDR_PREFIX | mac_addr_suffix;
+        eth_addr_from_uint64(mac64, &mac);
+        if (!ipam_is_duplicate_mac(&mac, mac64, false)) {
+            last_mac = mac_addr_suffix;
+            break;
+        }
+    }
+
+    if (i == MAC_ADDR_SPACE) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+        VLOG_WARN_RL(&rl, "MAC address space exhausted.");
+        mac64 = 0;
+    }
+
+    return mac64;
+}
+
+static uint32_t
+ipam_get_unused_ip(struct ovn_datapath *od, uint32_t subnet, uint32_t mask)
+{
+    if (!od) {
+        return 0;
+    }
+
+    uint32_t ip = 0;
+
+    /* Find an unused IP address in subnet. x.x.x.1 is reserved for a
+     * logical router port. */
+    for (uint32_t i = 2; i < ~mask; i++) {
+        uint32_t tentative_ip = subnet + i;
+        if (!ipam_is_duplicate_ip(od, tentative_ip, false)) {
+            ip = tentative_ip;
+            break;
+        }
+    }
+
+    if (!ip) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+        VLOG_WARN_RL( &rl, "Subnet address space has been exhausted.");
+    }
+
+    return ip;
+}
+
+static bool
+ipam_allocate_addresses(struct ovn_datapath *od, struct ovn_port *op,
+                        ovs_be32 subnet, ovs_be32 mask)
+{
+    if (!od || !op || !op->nbsp) {
+        return false;
+    }
+
+    uint32_t ip = ipam_get_unused_ip(od, ntohl(subnet), ntohl(mask));
+    if (!ip) {
+        return false;
+    }
+
+    struct eth_addr mac;
+    uint64_t mac64 = ipam_get_unused_mac();
+    if (!mac64) {
+        return false;
+    }
+    eth_addr_from_uint64(mac64, &mac);
+
+    /* Add MAC/IP to MACAM/IPAM hmaps if both addresses were allocated
+     * successfully. */
+    ipam_insert_ip(od, ip, false);
+    ipam_insert_mac(&mac, false);
+
+    /* Convert IP to string form. */
+    struct ds ip_ds;
+    ds_init(&ip_ds);
+    ds_put_format(&ip_ds, IP_FMT, IP_ARGS(htonl(ip)));
+
+    /* Convert MAC to string form. */
+    struct ds mac_ds;
+    ds_init(&mac_ds);
+    ds_put_format(&mac_ds, ETH_ADDR_FMT, ETH_ADDR_ARGS(mac));
+
+    char *new_addr = xasprintf("%s %s", mac_ds.string, ip_ds.string);
+    nbrec_logical_switch_port_set_dynamic_addresses(op->nbsp,
+                                                    (const char*) new_addr);
+    ds_destroy(&ip_ds);
+    ds_destroy(&mac_ds);
+    free(new_addr);
+
+    return true;
+}
+
+static void
+build_ipam(struct northd_context *ctx, struct hmap *datapaths,
+           struct hmap *ports)
+{
+    if (!ctx->ovnnb_txn) {
+        return;
+    }
+
+    /* If the switch's other_config:subnet is set, allocate new addresses for
+     * ports that have the "dynamic" keyword in their addresses column. */
+    struct ovn_datapath *od;
+    HMAP_FOR_EACH (od, key_node, datapaths) {
+        if (od->nbs) {
+            const char *subnet_str = smap_get(&od->nbs->other_config,
+                                              "subnet");
+            if (!subnet_str) {
+                continue;
+            }
+
+            ovs_be32 subnet, mask;
+            char *error = ip_parse_masked(subnet_str, &subnet, &mask);
+            if (error || mask == OVS_BE32_MAX || !ip_is_cidr(mask)) {
+                static struct vlog_rate_limit rl
+                    = VLOG_RATE_LIMIT_INIT(5, 1);
+                VLOG_WARN_RL(&rl, "bad 'subnet' %s", subnet_str);
+                free(error);
+                continue;
+            }
+
+            struct ovn_port *op;
+            for (size_t i = 0; i < od->nbs->n_ports; i++) {
+                const struct nbrec_logical_switch_port *nbsp =
+                    od->nbs->ports[i];
+
+                if (!nbsp) {
+                    continue;
+                }
+
+                op = ovn_port_find(ports, nbsp->name);
+                if (!op || (op->nbsp && op->peer)) {
+                    /* Do not allocate addresses for logical switch ports that
+                     * have a peer. */
+                    continue;
+                }
+
+                for (size_t j = 0; j < nbsp->n_addresses; j++) {
+                    if (!strcmp(nbsp->addresses[j], "dynamic")
+                        && !nbsp->dynamic_addresses) {
+                        if (!ipam_allocate_addresses(od, op, subnet, mask)
+                            || !extract_lsp_addresses(nbsp->dynamic_addresses,
+                                            &op->lsp_addrs[op->n_lsp_addrs])) {
+                            static struct vlog_rate_limit rl
+                                = VLOG_RATE_LIMIT_INIT(1, 1);
+                            VLOG_INFO_RL(&rl, "Failed to allocate address.");
+                        } else {
+                            op->n_lsp_addrs++;
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
+
+
 static void
 join_logical_ports(struct northd_context *ctx,
                    struct hmap *datapaths, struct hmap *ports,
@@ -651,7 +1004,23 @@ join_logical_ports(struct northd_context *ctx,
                     if (!strcmp(nbsp->addresses[j], "unknown")) {
                         continue;
                     }
-                    if (!extract_lsp_addresses(nbsp->addresses[j],
+                    if (!strcmp(nbsp->addresses[j], "dynamic")) {
+                        if (nbsp->dynamic_addresses) {
+                            if (!extract_lsp_addresses(nbsp->dynamic_addresses,
+                                            &op->lsp_addrs[op->n_lsp_addrs])) {
+                                static struct vlog_rate_limit rl
+                                    = VLOG_RATE_LIMIT_INIT(1, 1);
+                                VLOG_INFO_RL(&rl, "invalid syntax '%s' in "
+                                                  "logical switch port "
+                                                  "dynamic_addresses. No "
+                                                  "MAC address found",
+                                                  op->nbsp->dynamic_addresses);
+                                continue;
+                            }
+                        } else {
+                            continue;
+                        }
+                    } else if (!extract_lsp_addresses(nbsp->addresses[j],
                                            &op->lsp_addrs[op->n_lsp_addrs])) {
                         static struct vlog_rate_limit rl
                             = VLOG_RATE_LIMIT_INIT(1, 1);
@@ -680,6 +1049,7 @@ join_logical_ports(struct northd_context *ctx,
                 }
 
                 op->od = od;
+                ipam_add_port_addresses(od, op);
             }
         } else {
             for (size_t i = 0; i < od->nbr->n_ports; i++) {
@@ -722,6 +1092,7 @@ join_logical_ports(struct northd_context *ctx,
 
                 op->lrp_networks = lrp_networks;
                 op->od = od;
+                ipam_add_port_addresses(op->od, op);
             }
         }
     }
@@ -2261,6 +2632,20 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                     ovn_multicast_add(mcgroups, &mc_unknown, op);
                     op->od->has_unknown = true;
                 }
+            } else if (!strcmp(op->nbsp->addresses[i], "dynamic")) {
+                if (!op->nbsp->dynamic_addresses
+                    || !eth_addr_from_string(op->nbsp->dynamic_addresses,
+                                            &mac)) {
+                    continue;
+                }
+                ds_clear(&match);
+                ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT,
+                              ETH_ADDR_ARGS(mac));
+
+                ds_clear(&actions);
+                ds_put_format(&actions, "outport = %s; output;", op->json_key);
+                ovn_lflow_add(lflows, op->od, S_SWITCH_IN_L2_LKUP, 50,
+                              ds_cstr(&match), ds_cstr(&actions));
             } else {
                 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
 
@@ -3179,6 +3564,7 @@ ovnnb_db_run(struct northd_context *ctx, struct ovsdb_idl_loop *sb_loop)
     struct hmap datapaths, ports;
     build_datapaths(ctx, &datapaths);
     build_ports(ctx, &datapaths, &ports);
+    build_ipam(ctx, &datapaths, &ports);
     build_lflows(ctx, &datapaths, &ports);
 
     sync_address_sets(ctx);
@@ -3204,6 +3590,8 @@ ovnnb_db_run(struct northd_context *ctx, struct ovsdb_idl_loop *sb_loop)
         sbrec_sb_global_set_nb_cfg(sb, nb->nb_cfg);
         sb_loop->next_cfg = nb->nb_cfg;
     }
+
+    cleanup_macam(&macam);
 }
 
 /* Handle changes to the 'chassis' column of the 'Port_Binding' table.  When
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index a5dc669..660db76 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
-    "version": "5.2.0",
-    "cksum": "650844440 8727",
+    "version": "5.3.0",
+    "cksum": "1305504870 9051",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -31,6 +31,9 @@
                                                   "refType": "strong"},
                                            "min": 0,
                                            "max": 1}},
+                "other_config": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}},
                 "external_ids": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
@@ -53,6 +56,9 @@
                 "addresses": {"type": {"key": "string",
                                        "min": 0,
                                        "max": "unlimited"}},
+                "dynamic_addresses": {"type": {"key": "string",
+                                       "min": 0,
+                                       "max": 1}},
                 "port_security": {"type": {"key": "string",
                                            "min": 0,
                                            "max": "unlimited"}},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 4b61bbc..249d3c5 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -119,6 +119,20 @@
       Access control rules that apply to packets within the logical switch.
     </column>
 
+    <group title="other_config">
+      <p>
+        Additional configuration options for the logical switch.
+      </p>
+
+      <column name="other_config" key="subnet">
+        If set, logical ports that are attached to this switch that have the
+        "dynamic" keyword in their addresses column will automatically be
+        allocated a globally unique MAC address/unused IPv4 address within the
+        provided IPv4 subnet.  The allocated address will populate the
+        <ref column="dynamic_addresses"/> column.
+      </column>
+    </group>
+
     <group title="Common Columns">
       <column name="external_ids">
         See <em>External IDs</em> at the beginning of this document.
@@ -422,9 +436,30 @@
             ports) whose <ref column="addresses"/> columns include
             <code>unknown</code>.
           </dd>
+
+          <dt><code>dynamic</code></dt>
+          <dd>
+            This indicates that the logical port should be automatically
+            assigned a globally unique MAC address and an unused IPv4 address
+            within the subnet that this logical port belongs to.  The assigned
+            addresses will populate the <ref column="dynamic_addresses"/>
+            column.  For this keyword to work properly, the other_config:subnet
+            of the logical switch that this logical port is attached to must be
+            set.
+          </dd>
         </dl>
       </column>
 
+      <column name="dynamic_addresses">
+        <p>
+          Addresses assigned to the logical port by the IPAM.  Addresses will
+          be of the same format as those that populate the
+          <ref column="addresses"/> column.  Note that these addresses are
+          constructed and managed locally in ovn-northd, so they cannot be
+          reconstructed in the event that the database is lost.
+        </p>
+      </column>
+
       <column name="port_security">
         <p>
           This column controls the addresses from which the host attached to the
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index 591ff3b..1066429 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -933,7 +933,7 @@ nbctl_lsp_set_addresses(struct ctl_context *ctx)
     for (i = 2; i < ctx->argc; i++) {
         struct eth_addr ea;
 
-        if (strcmp(ctx->argv[i], "unknown")
+        if (strcmp(ctx->argv[i], "unknown") && strcmp(ctx->argv[i], "dynamic")
             && !ovs_scan(ctx->argv[i], ETH_ADDR_SCAN_FMT,
                          ETH_ADDR_SCAN_ARGS(ea))) {
             ctl_fatal("%s: Invalid address format. See ovn-nb(5). "
diff --git a/tests/ovn.at b/tests/ovn.at
index 4af46b5..cb06208 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -3716,3 +3716,297 @@ AT_CHECK([ovs-appctl -t ovn-controller version], [0], [ignore])
 OVN_CLEANUP([hv1])
 
 AT_CLEANUP
+
+AT_SETUP([ovn -- ipam])
+AT_KEYWORDS([ovnipam])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+# Add a port to a switch that does not have a subnet set, then set the
+# subnet which should result in an address being allocated for the port.
+ovn-nbctl ls-add sw0
+ovn-nbctl lsp-add sw0 p0 -- lsp-set-addresses p0 dynamic
+ovn-nbctl add Logical-Switch sw0 other_config subnet=192.168.1.0/24
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p0 dynamic_addresses], [0],
+    ["0a:00:00:00:00:01 192.168.1.2"
+])
+
+# Add 9 more ports to sw0, addresses should all be unique.
+for n in `seq 1 9`; do
+    ovn-nbctl lsp-add sw0 "p$n" -- lsp-set-addresses "p$n" dynamic
+done
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p1 dynamic_addresses], [0],
+    ["0a:00:00:00:00:02 192.168.1.3"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p2 dynamic_addresses], [0],
+    ["0a:00:00:00:00:03 192.168.1.4"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p3 dynamic_addresses], [0],
+    ["0a:00:00:00:00:04 192.168.1.5"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p4 dynamic_addresses], [0],
+    ["0a:00:00:00:00:05 192.168.1.6"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p5 dynamic_addresses], [0],
+    ["0a:00:00:00:00:06 192.168.1.7"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p6 dynamic_addresses], [0],
+    ["0a:00:00:00:00:07 192.168.1.8"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p7 dynamic_addresses], [0],
+    ["0a:00:00:00:00:08 192.168.1.9"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p8 dynamic_addresses], [0],
+    ["0a:00:00:00:00:09 192.168.1.10"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p9 dynamic_addresses], [0],
+    ["0a:00:00:00:00:0a 192.168.1.11"
+])
+
+# Trying similar tests with a second switch. MAC addresses should be unique
+# across both switches but IP's only need to be unique within the same switch.
+ovn-nbctl ls-add sw1
+ovn-nbctl lsp-add sw1 p10 -- lsp-set-addresses p10 dynamic
+ovn-nbctl add Logical-Switch sw1 other_config subnet=192.168.1.0/24
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p10 dynamic_addresses], [0],
+     ["0a:00:00:00:00:0b 192.168.1.2"
+])
+
+for n in `seq 11 19`; do
+    ovn-nbctl lsp-add sw1 "p$n" -- lsp-set-addresses "p$n" dynamic
+done
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p11 dynamic_addresses], [0],
+     ["0a:00:00:00:00:0c 192.168.1.3"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p12 dynamic_addresses], [0],
+     ["0a:00:00:00:00:0d 192.168.1.4"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p13 dynamic_addresses], [0],
+     ["0a:00:00:00:00:0e 192.168.1.5"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p14 dynamic_addresses], [0],
+     ["0a:00:00:00:00:0f 192.168.1.6"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p15 dynamic_addresses], [0],
+     ["0a:00:00:00:00:10 192.168.1.7"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p16 dynamic_addresses], [0],
+     ["0a:00:00:00:00:11 192.168.1.8"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p17 dynamic_addresses], [0],
+     ["0a:00:00:00:00:12 192.168.1.9"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p18 dynamic_addresses], [0],
+     ["0a:00:00:00:00:13 192.168.1.10"
+])
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p19 dynamic_addresses], [0],
+     ["0a:00:00:00:00:14 192.168.1.11"
+])
+
+# Change a port's address to test for multiple ip's for a single address entry
+# and addresses set by the user.
+ovn-nbctl lsp-set-addresses p0 "0a:00:00:00:00:15 192.168.1.12 192.168.1.14"
+ovn-nbctl lsp-add sw0 p20 -- lsp-set-addresses p20 dynamic
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p20 dynamic_addresses], [0],
+     ["0a:00:00:00:00:16 192.168.1.13"
+])
+
+# Test for logical router port address management.
+ovn-nbctl create Logical_Router name=R1
+ovn-nbctl -- --id=@lrp create Logical_Router_port name=sw0 \
+network="192.168.1.1/24" mac=\"0a:00:00:00:00:17\" \
+-- add Logical_Router R1 ports @lrp -- lsp-add sw0 rp-sw0 \
+-- set Logical_Switch_Port rp-sw0 type=router options:router-port=sw0
+ovn-nbctl lsp-add sw0 p21 -- lsp-set-addresses p21 dynamic
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p21 dynamic_addresses], [0],
+     ["0a:00:00:00:00:18 192.168.1.15"
+])
+
+# Test for address reuse after logical port is deleted.
+ovn-nbctl lsp-del p0
+ovn-nbctl lsp-add sw0 p23 -- lsp-set-addresses p23 dynamic
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p23 dynamic_addresses], [0],
+     ["0a:00:00:00:00:19 192.168.1.2"
+])
+
+# Test for multiple addresses to one logical port.
+ovn-nbctl lsp-add sw0 p25 -- lsp-set-addresses p25 \
+"0a:00:00:00:00:1a 192.168.1.12" "0a:00:00:00:00:1b 192.168.1.14"
+ovn-nbctl lsp-add sw0 p26 -- lsp-set-addresses p26 dynamic
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p26 dynamic_addresses], [0],
+     ["0a:00:00:00:00:1c 192.168.1.16"
+])
+
+# Test for exhausting subnet address space.
+ovn-nbctl ls-add sw2 -- add Logical-Switch sw2 other_config subnet=172.16.1.0/30
+ovn-nbctl lsp-add sw2 p27 -- lsp-set-addresses p27 dynamic
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p27 dynamic_addresses], [0],
+     ["0a:00:00:00:00:1d 172.16.1.2"
+])
+
+ovn-nbctl lsp-add sw2 p28 -- lsp-set-addresses p28 dynamic
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p28 dynamic_addresses], [0],
+     [[[]]
+])
+
+# Test that address management does not add duplicate MAC for lsp/lrp peers.
+ovn-nbctl create Logical_Router name=R2
+ovn-nbctl ls-add sw3
+ovn-nbctl lsp-add sw3 p29 -- lsp-set-addresses p29 \
+"0a:00:00:00:00:1e"
+ovn-nbctl -- --id=@lrp create Logical_Router_port name=sw3 \
+network="192.168.2.1/24" mac=\"0a:00:00:00:00:1f\" \
+-- add Logical_Router R2 ports @lrp -- lsp-add sw3 rp-sw3 \
+-- set Logical_Switch_Port rp-sw3 type=router options:router-port=sw3
+ovn-nbctl lsp-add sw0 p30 -- lsp-set-addresses p30 dynamic
+AT_CHECK([ovn-nbctl get Logical-Switch-Port p30 dynamic_addresses], [0],
+     ["0a:00:00:00:00:20 192.168.1.17"
+])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+AT_CLEANUP
+
+AT_SETUP([ovn -- ipam connectivity])
+AT_KEYWORDS([ovnipamconnectivity])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+ovn-nbctl lr-add R1
+
+# Test for a ping using dynamically allocated addresses.
+ovn-nbctl ls-add foo -- add Logical_Switch foo other_config subnet=192.168.1.0/24
+ovn-nbctl ls-add alice -- add Logical_Switch alice other_config subnet=192.168.2.0/24
+
+# Connect foo to R1
+ovn-nbctl lrp-add R1 foo 00:00:00:01:02:03 192.168.1.1/24
+ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \
+          options:router-port=foo addresses=\"00:00:00:01:02:03\"
+
+# Connect alice to R1
+ovn-nbctl lrp-add R1 alice 00:00:00:01:02:04 192.168.2.1/24
+ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice type=router \
+          options:router-port=alice addresses=\"00:00:00:01:02:04\"
+
+# Create logical port foo1 in foo
+ovn-nbctl lsp-add foo foo1 \
+-- lsp-set-addresses foo1 "dynamic"
+AT_CHECK([ovn-nbctl get Logical-Switch-Port foo1 dynamic_addresses], [0],
+     ["0a:00:00:00:00:01 192.168.1.2"
+])
+
+# Create logical port alice1 in alice
+ovn-nbctl lsp-add alice alice1 \
+-- lsp-set-addresses alice1 "dynamic"
+AT_CHECK([ovn-nbctl get Logical-Switch-Port alice1 dynamic_addresses], [0],
+     ["0a:00:00:00:00:02 192.168.2.2"
+])
+
+# Create logical port foo2 in foo
+ovn-nbctl lsp-add foo foo2 \
+-- lsp-set-addresses foo2 "dynamic"
+AT_CHECK([ovn-nbctl get Logical-Switch-Port foo2 dynamic_addresses], [0],
+     ["0a:00:00:00:00:03 192.168.1.3"
+])
+
+# Create a hypervisor and create OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=foo1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=1
+
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
+    set interface hv1-vif2 external-ids:iface-id=foo2 \
+    options:tx_pcap=hv1/vif2-tx.pcap \
+    options:rxq_pcap=hv1/vif2-rx.pcap \
+    ofport-request=2
+
+ovs-vsctl -- add-port br-int hv1-vif3 -- \
+    set interface hv1-vif3 external-ids:iface-id=alice1 \
+    options:tx_pcap=hv1/vif3-tx.pcap \
+    options:rxq_pcap=hv1/vif3-rx.pcap \
+    ofport-request=3
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+# XXX This should be more systematic.
+sleep 1
+
+ip_to_hex() {
+    printf "%02x%02x%02x%02x" "$@"
+}
+trim_zeros() {
+    sed 's/\(00\)\{1,\}$//'
+}
+
+# Send ip packets between foo1 and foo2
+src_mac="0a0000000001"
+dst_mac="0a0000000003"
+src_ip=`ip_to_hex 192 168 1 2`
+dst_ip=`ip_to_hex 192 168 1 3`
+packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+
+# Send ip packets between foo1 and alice1
+src_mac="0a0000000001"
+dst_mac="000000010203"
+src_ip=`ip_to_hex 192 168 1 2`
+dst_ip=`ip_to_hex 192 168 2 2`
+packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+
+echo "---------NB dump-----"
+ovn-nbctl show
+echo "---------------------"
+ovn-nbctl list logical_router
+echo "---------------------"
+ovn-nbctl list logical_router_port
+echo "---------------------"
+
+echo "---------SB dump-----"
+ovn-sbctl list datapath_binding
+echo "---------------------"
+ovn-sbctl list port_binding
+echo "---------------------"
+
+echo "------ hv1 dump ----------"
+as hv1 ovs-ofctl dump-flows br-int
+
+# Packet to Expect at foo2
+src_mac="0a0000000001"
+dst_mac="0a0000000003"
+src_ip=`ip_to_hex 192 168 1 2`
+dst_ip=`ip_to_hex 192 168 1 3`
+expected=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap | trim_zeros > received1.packets
+echo $expected | trim_zeros > expout
+AT_CHECK([cat received1.packets], [0], [expout])
+
+# Packet to Expect at alice1
+src_mac="000000010204"
+dst_mac="0a0000000002"
+src_ip=`ip_to_hex 192 168 1 2`
+dst_ip=`ip_to_hex 192 168 2 2`
+expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap | trim_zeros > received2.packets
+echo $expected | trim_zeros > expout
+AT_CHECK([cat received2.packets], [0], [expout])
+
+OVN_CLEANUP([hv1])
+
+AT_CLEANUP
-- 
1.9.1




More information about the dev mailing list