@@ -166,6 +166,7 @@ Murphy McCauley murphy.mccauley@gmail.com
Natasha Gude natasha@nicira.com
Neil McKee neil.mckee@inmon.com
Neil Zhu zhuj@centecnetworks.com
+Nimay Desai nimaydesai1@gmail.com
Nithin Raju nithin@vmware.com
Niti Rohilla niti.rohilla@tcs.com
Numan Siddique nusiddiq@redhat.com
@@ -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
@@ -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"}},
@@ -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
@@ -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). "
@@ -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
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@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(-)