From patchwork Wed Jul 27 18:28:24 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nimay Desai X-Patchwork-Id: 653447 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from archives.nicira.com (archives.nicira.com [96.126.127.54]) by ozlabs.org (Postfix) with ESMTP id 3s03Qw1w1Hz9t0G for ; Thu, 28 Jul 2016 04:28:40 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b=gkvWimqf; dkim-atps=neutral Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id 45E8410C5D; Wed, 27 Jul 2016 11:28:39 -0700 (PDT) X-Original-To: dev@openvswitch.org Delivered-To: dev@openvswitch.org Received: from mx1e3.cudamail.com (mx1.cudamail.com [69.90.118.67]) by archives.nicira.com (Postfix) with ESMTPS id 941F810B2E for ; Wed, 27 Jul 2016 11:28:37 -0700 (PDT) Received: from bar5.cudamail.com (localhost [127.0.0.1]) by mx1e3.cudamail.com (Postfix) with ESMTPS id 2852C420287 for ; Wed, 27 Jul 2016 12:28:37 -0600 (MDT) X-ASG-Debug-ID: 1469644115-09eadd7aea173ef0001-byXFYA Received: from mx1-pf1.cudamail.com ([192.168.24.1]) by bar5.cudamail.com with ESMTP id aU4bIqqHhFg4MUhF (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO) for ; Wed, 27 Jul 2016 12:28:35 -0600 (MDT) X-Barracuda-Envelope-From: nimaydesai1@gmail.com X-Barracuda-RBL-Trusted-Forwarder: 192.168.24.1 Received: from unknown (HELO mail-pa0-f65.google.com) (209.85.220.65) by mx1-pf1.cudamail.com with ESMTPS (AES128-SHA encrypted); 27 Jul 2016 18:28:35 -0000 Received-SPF: pass (mx1-pf1.cudamail.com: SPF record at _netblocks.google.com designates 209.85.220.65 as permitted sender) X-Barracuda-Apparent-Source-IP: 209.85.220.65 X-Barracuda-RBL-IP: 209.85.220.65 Received: by mail-pa0-f65.google.com with SMTP id hh10so2147503pac.1 for ; Wed, 27 Jul 2016 11:28:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:subject:date:message-id; bh=w6xnsc2iRQbZyVaGWc9x6bm7l2MIFx2RQsBssRdIwcs=; b=gkvWimqfmM7RalStRR9X8pO/MchvoGdRoVM1QOVqid9aogOe4R3PmNzvEp1pjYW5Nq 4H+ZYbUsul15b2YVO7iHmeTmBEz3Rqb2U4pCqEq9VBzBoq5dBL7DsUpqLeGIYbHK81b/ MyxuA7r7CJu8T63f4WsIhsY4WRldwmd/QlKGgtMGFhwBv7AvPBkbF8Ab17H7PYRtl890 K4BeALonNw0G6XINQF5rRCLxtNkWYGYTnXfQ9rVBqV4lrup4y56o48maIk9DkcfdTjCn SjK+H1lGkrKFmCoQ0jFThvJU4KxSE9B1qs7FNexwyPlaZe1Lath2LHVu3fdfdUDUmlhp KpfA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:subject:date:message-id; bh=w6xnsc2iRQbZyVaGWc9x6bm7l2MIFx2RQsBssRdIwcs=; b=mk0NwtzMIBGQ3pz4EL6/s0O17FRfWdLqqKlKPyDSliOH23XMP4gchAGUgq6x5kyEK0 e9yi1tg4Lu+f7b6W/0EmacmlbzVktA2XTV2ejL28vPar1+vzFD6smiBY80HXsWch9qEj 05MkdsIpyaB6xsB0o44VlX1C/kyzbDf5zYNAyg4cdpnMs1aUtZ+x437k6ve8VjLOmK2l Fx2qIbYGQR9Sem5ElKt+ayG7B80oWgysXbAQxUGtFioeZwSkQvJzdwD/O6u9zL207vfj I8qWk45M4+RMRENkIjcYt3UL3+HryFm7nPr8oGhXV8U5thoGSxRrqcDZMGx4Y0kkC6rm opOg== X-Gm-Message-State: AEkoouurmGyfArk6/2YdIlmdFVlU/0w+WEIDe49TuXcFckK4MOZGxKdWbClEpVYuXUC5Cg== X-Received: by 10.66.236.9 with SMTP id uq9mr52834973pac.145.1469644113709; Wed, 27 Jul 2016 11:28:33 -0700 (PDT) Received: from sc9-mailhost2.vmware.com ([208.91.1.34]) by smtp.gmail.com with ESMTPSA id s12sm10862346pfj.73.2016.07.27.11.28.32 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 27 Jul 2016 11:28:32 -0700 (PDT) X-CudaMail-Envelope-Sender: nimaydesai1@gmail.com From: Nimay Desai To: dev@openvswitch.org X-CudaMail-MID: CM-E1-726056435 X-CudaMail-DTE: 072716 X-CudaMail-Originating-IP: 209.85.220.65 Date: Wed, 27 Jul 2016 11:28:24 -0700 X-ASG-Orig-Subj: [##CM-E1-726056435##][PATCH v4] ovn-northd, tests: Adding IPAM to ovn-northd. Message-Id: <1469644104-46077-1-git-send-email-nimaydesai1@gmail.com> X-Mailer: git-send-email 1.9.1 X-Barracuda-Connect: UNKNOWN[192.168.24.1] X-Barracuda-Start-Time: 1469644115 X-Barracuda-Encrypted: ECDHE-RSA-AES256-GCM-SHA384 X-Barracuda-URL: https://web.cudamail.com:443/cgi-mod/mark.cgi X-Virus-Scanned: by bsmtpd at cudamail.com X-Barracuda-BRTS-Status: 1 X-Barracuda-Spam-Score: 0.60 X-Barracuda-Spam-Status: No, SCORE=0.60 using global scores of TAG_LEVEL=3.5 QUARANTINE_LEVEL=1000.0 KILL_LEVEL=4.0 tests=BSF_SC5_MJ1963, DKIM_SIGNED, RDNS_NONE X-Barracuda-Spam-Report: Code version 3.2, rules version 3.2.3.31559 Rule breakdown below pts rule name description ---- ---------------------- -------------------------------------------------- 0.00 DKIM_SIGNED Domain Keys Identified Mail: message has a signature 0.10 RDNS_NONE Delivered to trusted network by a host with no rDNS 0.50 BSF_SC5_MJ1963 Custom Rule MJ1963 Subject: [ovs-dev] [PATCH v4] ovn-northd, tests: Adding IPAM to ovn-northd. X-BeenThere: dev@openvswitch.org X-Mailman-Version: 2.1.16 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: dev-bounces@openvswitch.org Sender: "dev" 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 --- 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@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 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. + +

+ Additional configuration options for the logical switch. +

+ + + 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 + column. + +
+ See External IDs at the beginning of this document. @@ -422,9 +436,30 @@ ports) whose columns include unknown. + +
dynamic
+
+ 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 + 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. +
+ +

+ Addresses assigned to the logical port by the IPAM. Addresses will + be of the same format as those that populate the + 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. +

+
+

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