From patchwork Mon May 12 20:58:05 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Michelson X-Patchwork-Id: 2084692 X-Patchwork-Delegate: amusil@redhat.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Og8fxc1O; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::133; helo=smtp2.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4ZxBlR0phSz1yPv for ; Tue, 13 May 2025 06:58:10 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 3F67841095; Mon, 12 May 2025 20:58:25 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp2.osuosl.org ([127.0.0.1]) by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 4jdIwRUh5xoV; Mon, 12 May 2025 20:58:22 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=140.211.9.56; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp2.osuosl.org CE7A740921 Authentication-Results: smtp2.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Og8fxc1O Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp2.osuosl.org (Postfix) with ESMTPS id CE7A740921; Mon, 12 May 2025 20:58:22 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id A879DC007B; Mon, 12 May 2025 20:58:22 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp3.osuosl.org (smtp3.osuosl.org [140.211.166.136]) by lists.linuxfoundation.org (Postfix) with ESMTP id EA1ECC0009 for ; Mon, 12 May 2025 20:58:21 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id CA52460E85 for ; Mon, 12 May 2025 20:58:21 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id E_9G1gNS52CX for ; Mon, 12 May 2025 20:58:21 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.133.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=mmichels@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp3.osuosl.org EF5B860E82 Authentication-Results: smtp3.osuosl.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org EF5B860E82 Authentication-Results: smtp3.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Og8fxc1O Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by smtp3.osuosl.org (Postfix) with ESMTPS id EF5B860E82 for ; Mon, 12 May 2025 20:58:20 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1747083499; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=vKa+eajOyRIRsMI6JldxlEp04BRH33/+zmXitUg1Pes=; b=Og8fxc1OHt3xnX/HHRd/J/5jS+OjBdbD2zlh426e7GIG+iIxy5k6IwGUa2Hqr4vIQZ7Rye b64tF8GcCgSBfcnZBNFM0gLkKAjnYgQRGYNcnofQ0U/L3iHeFw0O6C+OpCoygiv7vSllzx oS7eL9KdbzWAvOiLBsgtKJyNkifiKLw= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-194-ZJ_N_JucNzmklsImHTCGOA-1; Mon, 12 May 2025 16:58:18 -0400 X-MC-Unique: ZJ_N_JucNzmklsImHTCGOA-1 X-Mimecast-MFC-AGG-ID: ZJ_N_JucNzmklsImHTCGOA_1747083497 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 7AD761800258 for ; Mon, 12 May 2025 20:58:17 +0000 (UTC) Received: from localhost.localdomain.com (unknown [10.22.58.17]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 1D1941943282 for ; Mon, 12 May 2025 20:58:16 +0000 (UTC) To: dev@openvswitch.org Date: Mon, 12 May 2025 16:58:05 -0400 Message-ID: <20250512205815.870519-2-mmichels@redhat.com> In-Reply-To: <20250512205815.870519-1-mmichels@redhat.com> References: <20250512205815.870519-1-mmichels@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: B987TYWQZ8mf5g_TPR5tZz42I9d_5N-l6q8vgfDUEj4_1747083497 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH ovn v5 1/5] en-global-config: Cache vxlan mode and max tunnel key. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Mark Michelson via dev From: Mark Michelson Reply-To: Mark Michelson Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" There are going to be additional incremental nodes that wish to know if vxlan mode is enabled. Instead of having to traverse the chassis in each of those nodes, we can cache the vxlan status in the global config. We can do the same with the max tunnel key based on the vxlan setting. Signed-off-by: Mark Michelson Acked-by: Dumitru Ceara Acked-by: Ales Musil --- * v4 -> v5: * Rebased. * v3 -> v4: * Rebased. * v2 -> v3: * Rebased, but no other changes made. * v1 -> v2: * Fixed compilation error. * Fixed indentation of is_vxlan_mode declaration. * Added Dumitru's ack. --- northd/en-global-config.c | 50 ++++++++++++++++++++++++++------------- northd/en-global-config.h | 3 +++ northd/en-northd.c | 1 + northd/northd.c | 21 +--------------- northd/northd.h | 5 +--- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/northd/en-global-config.c b/northd/en-global-config.c index 98c2fd0cc..11513e31e 100644 --- a/northd/en-global-config.c +++ b/northd/en-global-config.c @@ -49,6 +49,8 @@ static bool check_nb_options_out_of_sync( const struct sampling_app_table *); static void update_sb_config_options_to_sbrec(struct ed_type_global_config *, const struct sbrec_sb_global *); +static bool is_vxlan_mode(const struct smap *nb_options, + const struct sbrec_chassis_table *); void * en_global_config_init(struct engine_node *node OVS_UNUSED, @@ -131,11 +133,11 @@ en_global_config_run(struct engine_node *node , void *data) break; } } - uint32_t max_dp_key = - get_ovn_max_dp_key_local(is_vxlan_mode(&nb->options, - sbrec_chassis_table), - ic_vxlan_mode); - char *max_tunid = xasprintf("%d", max_dp_key); + config_data->vxlan_mode = is_vxlan_mode(&nb->options, sbrec_chassis_table); + config_data->max_dp_tunnel_id = + get_ovn_max_dp_key_local(config_data->vxlan_mode, ic_vxlan_mode); + + char *max_tunid = xasprintf("%d", config_data->max_dp_tunnel_id); smap_replace(options, "max_tunid", max_tunid); free(max_tunid); @@ -269,6 +271,11 @@ global_config_nb_global_handler(struct engine_node *node, void *data) return EN_UNHANDLED; } + if (config_out_of_sync(&nb->options, &config_data->nb_options, + "vxlan_mode", false)) { + return false; + } + if (check_nb_options_out_of_sync(nb, config_data, sampling_apps)) { config_data->tracked_data.nb_options_changed = true; } @@ -390,8 +397,6 @@ global_config_nb_logical_switch_handler(struct engine_node *node, EN_OVSDB_GET(engine_get_input("NB_logical_switch", node)); const struct nbrec_nb_global *nb = nbrec_nb_global_table_first( EN_OVSDB_GET(engine_get_input("NB_nb_global", node))); - const struct sbrec_chassis_table *sbrec_chassis_table = - EN_OVSDB_GET(engine_get_input("SB_chassis", node)); enum engine_input_handler_result result; bool ic_vxlan_mode = false; @@ -402,11 +407,10 @@ global_config_nb_logical_switch_handler(struct engine_node *node, break; } } - uint32_t max_dp_key = - get_ovn_max_dp_key_local(is_vxlan_mode(&nb->options, - sbrec_chassis_table), + config_data->max_dp_tunnel_id = + get_ovn_max_dp_key_local(config_data->vxlan_mode, ic_vxlan_mode); - char *max_tunid = xasprintf("%d", max_dp_key); + char *max_tunid = xasprintf("%d", config_data->max_dp_tunnel_id); struct smap *options = &config_data->nb_options; const char *cur_max_tunid = smap_get(options, "max_tunid"); @@ -629,11 +633,6 @@ check_nb_options_out_of_sync( return true; } - if (config_out_of_sync(&nb->options, &config_data->nb_options, - "vxlan_mode", false)) { - return true; - } - if (config_out_of_sync(&nb->options, &config_data->nb_options, "always_tunnel", false)) { return true; @@ -695,3 +694,22 @@ chassis_features_changed(const struct chassis_features *present, return false; } + +static bool +is_vxlan_mode(const struct smap *nb_options, + const struct sbrec_chassis_table *sbrec_chassis_table) +{ + if (!smap_get_bool(nb_options, "vxlan_mode", true)) { + return false; + } + + const struct sbrec_chassis *chassis; + SBREC_CHASSIS_TABLE_FOR_EACH (chassis, sbrec_chassis_table) { + for (int i = 0; i < chassis->n_encaps; i++) { + if (!strcmp(chassis->encaps[i]->type, "vxlan")) { + return true; + } + } + } + return false; +} diff --git a/northd/en-global-config.h b/northd/en-global-config.h index 08da8a5ab..55a1e420b 100644 --- a/northd/en-global-config.h +++ b/northd/en-global-config.h @@ -49,6 +49,9 @@ struct ed_type_global_config { bool ovn_internal_version_changed; + bool vxlan_mode; + uint32_t max_dp_tunnel_id; + bool tracked; struct global_config_tracked_data tracked_data; }; diff --git a/northd/en-northd.c b/northd/en-northd.c index 3359d8d0e..02a27aac2 100644 --- a/northd/en-northd.c +++ b/northd/en-northd.c @@ -116,6 +116,7 @@ northd_get_input_data(struct engine_node *node, input_data->svc_monitor_mac = global_config->svc_monitor_mac; input_data->svc_monitor_mac_ea = global_config->svc_monitor_mac_ea; input_data->features = &global_config->features; + input_data->vxlan_mode = global_config->vxlan_mode; } enum engine_node_state diff --git a/northd/northd.c b/northd/northd.c index 7b05147b4..1a89b5224 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -984,24 +984,6 @@ join_datapaths(const struct nbrec_logical_switch_table *nbrec_ls_table, } } -bool -is_vxlan_mode(const struct smap *nb_options, - const struct sbrec_chassis_table *sbrec_chassis_table) -{ - if (!smap_get_bool(nb_options, "vxlan_mode", true)) { - return false; - } - - const struct sbrec_chassis *chassis; - SBREC_CHASSIS_TABLE_FOR_EACH (chassis, sbrec_chassis_table) { - for (int i = 0; i < chassis->n_encaps; i++) { - if (!strcmp(chassis->encaps[i]->type, "vxlan")) { - return true; - } - } - } - return false; -} uint32_t get_ovn_max_dp_key_local(bool _vxlan_mode, bool _vxlan_ic_mode) @@ -19326,8 +19308,7 @@ ovnnb_db_run(struct northd_input *input_data, use_common_zone = smap_get_bool(input_data->nb_options, "use_common_zone", false); - vxlan_mode = is_vxlan_mode(input_data->nb_options, - input_data->sbrec_chassis_table); + vxlan_mode = input_data->vxlan_mode; build_datapaths(ovnsb_txn, input_data->nbrec_logical_switch_table, diff --git a/northd/northd.h b/northd/northd.h index 5a698458f..d4feff63d 100644 --- a/northd/northd.h +++ b/northd/northd.h @@ -65,6 +65,7 @@ struct northd_input { const char *svc_monitor_mac; struct eth_addr svc_monitor_mac_ea; const struct chassis_features *features; + bool vxlan_mode; /* ACL ID inputs. */ const struct acl_id_data *acl_id_data; @@ -967,10 +968,6 @@ lr_has_multiple_gw_ports(const struct ovn_datapath *od) return vector_len(&od->l3dgw_ports) > 1 && !od->is_gw_router; } -bool -is_vxlan_mode(const struct smap *nb_options, - const struct sbrec_chassis_table *sbrec_chassis_table); - uint32_t get_ovn_max_dp_key_local(bool _vxlan_mode, bool ic_mode); /* Returns true if the logical router port 'enabled' column is empty or From patchwork Mon May 12 20:58:06 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Michelson X-Patchwork-Id: 2084693 X-Patchwork-Delegate: amusil@redhat.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=FKQboM9F; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::137; helo=smtp4.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4ZxBlS3cHZz1yPv for ; Tue, 13 May 2025 06:58:12 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id B566340BF4; Mon, 12 May 2025 20:58:25 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id GGS0Pz1EtSPR; Mon, 12 May 2025 20:58:24 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org B0F7D40BA8 Authentication-Results: smtp4.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=FKQboM9F Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp4.osuosl.org (Postfix) with ESMTPS id B0F7D40BA8; Mon, 12 May 2025 20:58:24 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 8CD14C007B; Mon, 12 May 2025 20:58:24 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137]) by lists.linuxfoundation.org (Postfix) with ESMTP id 5A0BFC0009 for ; Mon, 12 May 2025 20:58:23 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 2511940BA8 for ; Mon, 12 May 2025 20:58:23 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 2mQTv5ty1Cq5 for ; Mon, 12 May 2025 20:58:22 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.129.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=mmichels@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp4.osuosl.org 5D036406B3 Authentication-Results: smtp4.osuosl.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 5D036406B3 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by smtp4.osuosl.org (Postfix) with ESMTPS id 5D036406B3 for ; Mon, 12 May 2025 20:58:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1747083501; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=kd6p9lB2ez5TuLrr5gMLXyT15dQrueVpDdkW5d2dYcE=; b=FKQboM9FOFwo2q5rTi9eTAA0Ekc6ZU6FBuxdhsFNKc6gRdtFppzedojHx4DVciuvIxnZZJ ocXxPvdFvHxyh96gjuWMQFYdXhDh3fC82hoME08YG/OAcku3gQR8cyoCiKKbX/J4TNByzf b9V1qoCxkcuCPHAfZ23FshSSswRpquI= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-41-OPA6QidzNQSynTXlLQ_l_g-1; Mon, 12 May 2025 16:58:18 -0400 X-MC-Unique: OPA6QidzNQSynTXlLQ_l_g-1 X-Mimecast-MFC-AGG-ID: OPA6QidzNQSynTXlLQ_l_g_1747083498 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 2A2B218004AD for ; Mon, 12 May 2025 20:58:18 +0000 (UTC) Received: from localhost.localdomain.com (unknown [10.22.58.17]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id C096A1943282 for ; Mon, 12 May 2025 20:58:17 +0000 (UTC) To: dev@openvswitch.org Date: Mon, 12 May 2025 16:58:06 -0400 Message-ID: <20250512205815.870519-3-mmichels@redhat.com> In-Reply-To: <20250512205815.870519-1-mmichels@redhat.com> References: <20250512205815.870519-1-mmichels@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: hvYW8wBOVf-Xh4sng5ilW9zaGJOsgKvbWdDbjZX5-18_1747083498 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH ovn v5 2/5] northd: Remove lr_list from en_northd data. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Mark Michelson via dev From: Mark Michelson Reply-To: Mark Michelson Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" This structure is redundant since we already have the lr_datapaths that store the same set of ovn_datapaths. Signed-off-by: Mark Michelson Acked-by: Lorenzo Bianconi Acked-by: Dumitru Ceara Acked-by: Ales Musil --- * v4 -> v5: * Rebased. * v3 -> v4: * Rebased. * v2 -> v3: * Rebased, but no other changes made. * v1 -> v2: * Added Lorenzo's and Dumitru's acks. --- northd/northd.c | 30 ++++++++++++------------------ northd/northd.h | 2 -- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/northd/northd.c b/northd/northd.c index 1a89b5224..b0e957b30 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -883,8 +883,7 @@ join_datapaths(const struct nbrec_logical_switch_table *nbrec_ls_table, const struct sbrec_datapath_binding_table *sbrec_dp_table, struct ovsdb_idl_txn *ovnsb_txn, struct hmap *datapaths, struct ovs_list *sb_only, - struct ovs_list *nb_only, struct ovs_list *both, - struct ovs_list *lr_list) + struct ovs_list *nb_only, struct ovs_list *both) { ovs_list_init(sb_only); ovs_list_init(nb_only); @@ -980,7 +979,6 @@ join_datapaths(const struct nbrec_logical_switch_table *nbrec_ls_table, od->dynamic_routing_redistribute = parse_dynamic_routing_redistribute(&od->nbr->options, DRRM_NONE, od->nbr->name); - ovs_list_push_back(lr_list, &od->lr_list); } } @@ -1073,14 +1071,13 @@ build_datapaths(struct ovsdb_idl_txn *ovnsb_txn, const struct nbrec_logical_router_table *nbrec_lr_table, const struct sbrec_datapath_binding_table *sbrec_dp_table, struct ovn_datapaths *ls_datapaths, - struct ovn_datapaths *lr_datapaths, - struct ovs_list *lr_list) + struct ovn_datapaths *lr_datapaths) { struct ovs_list sb_only, nb_only, both; struct hmap *datapaths = &ls_datapaths->datapaths; join_datapaths(nbrec_ls_table, nbrec_lr_table, sbrec_dp_table, ovnsb_txn, - datapaths, &sb_only, &nb_only, &both, lr_list); + datapaths, &sb_only, &nb_only, &both); /* Assign explicitly requested tunnel ids first. */ struct hmap dp_tnlids = HMAP_INITIALIZER(&dp_tnlids); @@ -8854,7 +8851,7 @@ build_lrouter_groups__(struct hmap *lr_ports, struct ovn_datapath *od) * each other either directly or indirectly (via transit logical switches * in between). * - * Suppose if 'lr_list' has lr0, lr1, lr2, lr3, lr4, lr5 + * Suppose if 'lr_datapaths' has lr0, lr1, lr2, lr3, lr4, lr5 * and the topology is like * sw0 <-> lr0 <-> sw1 <-> lr1 <->sw2 <-> lr2 * sw3 <-> lr3 <-> lr4 <-> sw5 @@ -8871,12 +8868,12 @@ build_lrouter_groups__(struct hmap *lr_ports, struct ovn_datapath *od) * Each logical router can belong to only one group. */ static void -build_lrouter_groups(struct hmap *lr_ports, struct ovs_list *lr_list) +build_lrouter_groups(struct hmap *lr_ports, struct ovn_datapaths *lr_datapaths) { struct ovn_datapath *od; - size_t n_router_dps = ovs_list_size(lr_list); + size_t n_router_dps = hmap_count(&lr_datapaths->datapaths); - LIST_FOR_EACH (od, lr_list, lr_list) { + HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) { if (!od->lr_group) { od->lr_group = xzalloc(sizeof *od->lr_group); /* Each logical router group can have max @@ -19115,11 +19112,10 @@ ovn_datapaths_destroy(struct ovn_datapaths *datapaths) static void destroy_datapaths_and_ports(struct ovn_datapaths *ls_datapaths, struct ovn_datapaths *lr_datapaths, - struct hmap *ls_ports, struct hmap *lr_ports, - struct ovs_list *lr_list) + struct hmap *ls_ports, struct hmap *lr_ports) { struct ovn_datapath *router_dp; - LIST_FOR_EACH_POP (router_dp, lr_list, lr_list) { + HMAP_FOR_EACH (router_dp, key_node, &lr_datapaths->datapaths) { if (router_dp->lr_group) { struct lrouter_group *lr_group = router_dp->lr_group; @@ -19158,7 +19154,6 @@ northd_init(struct northd_data *data) hmap_init(&data->lr_ports); hmap_init(&data->lb_datapaths_map); hmap_init(&data->lb_group_datapaths_map); - ovs_list_init(&data->lr_list); sset_init(&data->svc_monitor_lsps); hmap_init(&data->svc_monitor_map); init_northd_tracked_data(data); @@ -19224,8 +19219,7 @@ northd_destroy(struct northd_data *data) cleanup_macam(); destroy_datapaths_and_ports(&data->ls_datapaths, &data->lr_datapaths, - &data->ls_ports, &data->lr_ports, - &data->lr_list); + &data->ls_ports, &data->lr_ports); sset_destroy(&data->svc_monitor_lsps); destroy_northd_tracked_data(data); @@ -19315,7 +19309,7 @@ ovnnb_db_run(struct northd_input *input_data, input_data->nbrec_logical_router_table, input_data->sbrec_datapath_binding_table, &data->ls_datapaths, - &data->lr_datapaths, &data->lr_list); + &data->lr_datapaths); build_lb_datapaths(input_data->lbs, input_data->lbgrps, &data->ls_datapaths, &data->lr_datapaths, &data->lb_datapaths_map, &data->lb_group_datapaths_map); @@ -19342,7 +19336,7 @@ ovnnb_db_run(struct northd_input *input_data, ods_size(&data->ls_datapaths), ods_size(&data->lr_datapaths)); build_ipam(&data->ls_datapaths.datapaths, &data->ls_ports); - build_lrouter_groups(&data->lr_ports, &data->lr_list); + build_lrouter_groups(&data->lr_ports, &data->lr_datapaths); build_ip_mcast(ovnsb_txn, input_data->sbrec_ip_multicast_table, input_data->sbrec_ip_mcast_by_dp, &data->ls_datapaths.datapaths); diff --git a/northd/northd.h b/northd/northd.h index d4feff63d..69143cd7a 100644 --- a/northd/northd.h +++ b/northd/northd.h @@ -170,7 +170,6 @@ struct northd_data { struct hmap lr_ports; struct hmap lb_datapaths_map; struct hmap lb_group_datapaths_map; - struct ovs_list lr_list; struct sset svc_monitor_lsps; struct hmap svc_monitor_map; @@ -411,7 +410,6 @@ struct ovn_datapath { struct vector localnet_ports; /* Vector of struct ovn_port *. */ - struct ovs_list lr_list; /* In list of logical router datapaths. */ /* The logical router group to which this datapath belongs. * Valid only if it is logical router datapath. NULL otherwise. */ struct lrouter_group *lr_group; From patchwork Mon May 12 20:58:07 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Michelson X-Patchwork-Id: 2084696 X-Patchwork-Delegate: amusil@redhat.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=CIgBrfmk; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.133; helo=smtp2.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp2.osuosl.org (smtp2.osuosl.org [140.211.166.133]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4ZxBlb22WXz1yPv for ; Tue, 13 May 2025 06:58:19 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 0B6444111C; Mon, 12 May 2025 20:58:33 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp2.osuosl.org ([127.0.0.1]) by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id qMU-V1ZsP0X7; Mon, 12 May 2025 20:58:28 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=140.211.9.56; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp2.osuosl.org D5B3B410E0 Authentication-Results: smtp2.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=CIgBrfmk Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp2.osuosl.org (Postfix) with ESMTPS id D5B3B410E0; Mon, 12 May 2025 20:58:27 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 60A42C0A45; Mon, 12 May 2025 20:58:27 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136]) by lists.linuxfoundation.org (Postfix) with ESMTP id 1BA65C007B for ; Mon, 12 May 2025 20:58:25 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id AA08060E88 for ; Mon, 12 May 2025 20:58:24 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id V8Ktlrhy6pjI for ; Mon, 12 May 2025 20:58:22 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.129.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=mmichels@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp3.osuosl.org 5703160E82 Authentication-Results: smtp3.osuosl.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org 5703160E82 Authentication-Results: smtp3.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=CIgBrfmk Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by smtp3.osuosl.org (Postfix) with ESMTPS id 5703160E82 for ; Mon, 12 May 2025 20:58:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1747083501; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=6wJEyVBc0uzYSsIPVAPGC6OsMfMmsAMcEGryGaWwR3Q=; b=CIgBrfmkV+KR7WgWfM7yDxT4a6kRWWcHXaJRbVMD0crrVTvPWxKMGP1Mi1zx+QqhX09C12 NcH3FSuDwuWgz0ze+QDw1DjPp6elcRPc0yNvuynBYNbVK2vnfeFllWJHzvLqBtAZoIrDIu 5odX+QobUOHMnLcimT7klFg20vi85JI= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-695-rVvZ49VxPkmLGxUfqB5qYg-1; Mon, 12 May 2025 16:58:19 -0400 X-MC-Unique: rVvZ49VxPkmLGxUfqB5qYg-1 X-Mimecast-MFC-AGG-ID: rVvZ49VxPkmLGxUfqB5qYg_1747083499 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id EBB501800446 for ; Mon, 12 May 2025 20:58:18 +0000 (UTC) Received: from localhost.localdomain.com (unknown [10.22.58.17]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 61DDA1943282 for ; Mon, 12 May 2025 20:58:18 +0000 (UTC) To: dev@openvswitch.org Date: Mon, 12 May 2025 16:58:07 -0400 Message-ID: <20250512205815.870519-4-mmichels@redhat.com> In-Reply-To: <20250512205815.870519-1-mmichels@redhat.com> References: <20250512205815.870519-1-mmichels@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: KUoT1AISYF1C2_TMkYg4uBdeFBRZiC3D8kFXIJQt_-0_1747083499 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH ovn v5 3/5] northd: Refactor datapath syncing. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Mark Michelson via dev From: Mark Michelson Reply-To: Mark Michelson Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" In current OVN, the en-northd node (via code in northd.c) takes full control of syncing logical switches and logical routers with southbound datapath bindings. This is fine, so long as: 1) These are the only datapath types to sync. 2) northd will always be the arbiter of datapath syncing. However, future commits will introduce new types of datapaths. These are not good fits for the en-northd node, since they have completely independent processing rules, and trying to shoehorn them into a struct ovn_datapath would be wasteful. This patch introduces a new way of syncing datapaths. Each datapath type has a node that is responsible for creating an ovn_unsynced_datapath_map for the type of datapath it creates. Then a type-agnostic datapath syncing node syncs these datapaths with the southbound Datapath_Binding type. Then, these synced datapaths are fed back into type-specific datapath nodes, which translate these synced datapaths into specific types. Nodes can then use these as inputs if they need synced datapaths (i.e. a northbound type with its corresponding southbound type). In this case, en_northd uses the synced logical switch and logical router types in order to create its ovn_datapath structures. Doing this will provide an easy way to sync new datapath types to the southbound database. Signed-off-by: Mark Michelson --- v4 -> v5: * Rebased. v3 -> v4: * Rebased. v2 -> v3: * Rebased * Fixed a typo in datapath_sync.h v1 -> v2: * Many formatting fixes (added newlines, removed unnecessary local variables, removed unnecessary parameter names, etc.). * Clarified language in TODO.rst regarding the eventual removal of the ovn_datapath structure. * Use ovsdb_idl_row's type field to find the northbound type when converting from generic synced datapath to logical switch or logical router. * Fixed several memory leaks. * Switched to allocating the input_maps dynamically in en-datapath-sync since sanitizers complain about stack-allocated variable-length arrays. * Changed name of function that deletes remaining candidate synced datapaths to make it less confusing (delete_candidates_with_no_tunnel_keys -> delete_candidates). * Used engine_noop_handler instead of defining new stubs that return true. Any review comments not addressed in this version should have been addressed by my responses to earlier reviews. --- TODO.rst | 12 + northd/automake.mk | 8 + northd/datapath_sync.c | 57 +++++ northd/datapath_sync.h | 77 ++++++ northd/en-datapath-logical-router.c | 175 ++++++++++++++ northd/en-datapath-logical-router.h | 49 ++++ northd/en-datapath-logical-switch.c | 176 ++++++++++++++ northd/en-datapath-logical-switch.h | 48 ++++ northd/en-datapath-sync.c | 319 +++++++++++++++++++++++++ northd/en-datapath-sync.h | 27 +++ northd/en-global-config.c | 11 + northd/en-northd.c | 6 + northd/inc-proc-northd.c | 40 ++++ northd/northd.c | 354 +++------------------------- northd/northd.h | 6 +- 15 files changed, 1045 insertions(+), 320 deletions(-) create mode 100644 northd/datapath_sync.c create mode 100644 northd/datapath_sync.h create mode 100644 northd/en-datapath-logical-router.c create mode 100644 northd/en-datapath-logical-router.h create mode 100644 northd/en-datapath-logical-switch.c create mode 100644 northd/en-datapath-logical-switch.h create mode 100644 northd/en-datapath-sync.c create mode 100644 northd/en-datapath-sync.h diff --git a/TODO.rst b/TODO.rst index e50b1bd76..78962bb92 100644 --- a/TODO.rst +++ b/TODO.rst @@ -156,3 +156,15 @@ OVN To-do List monitoring conditions to update before we actually try to learn routes. Otherwise we could try to add duplicated Learned_Routes and the ovnsb commit would fail. + +* Datapath sync nodes + + * Add incremental processing to the en-datapath-logical-switch, + en-datapath-logical-router, en-datapath-sync, and possibly the + en-datapath-synced-logical-router and en-datapath-synced-logical-switch + nodes. + + * Migrate data stored in the ovn\_datapath structure to + ovn\_synced\_logical_router and ovn\_synced\_logical\_switch. This will + allow for the eventual removal of the ovn\_datapath structure from the + codebase. diff --git a/northd/automake.mk b/northd/automake.mk index 9a7165529..bf9978dd2 100644 --- a/northd/automake.mk +++ b/northd/automake.mk @@ -3,11 +3,19 @@ bin_PROGRAMS += northd/ovn-northd northd_ovn_northd_SOURCES = \ northd/aging.c \ northd/aging.h \ + northd/datapath_sync.c \ + northd/datapath_sync.h \ northd/debug.c \ northd/debug.h \ northd/northd.c \ northd/northd.h \ northd/ovn-northd.c \ + northd/en-datapath-logical-switch.c \ + northd/en-datapath-logical-switch.h \ + northd/en-datapath-logical-router.c \ + northd/en-datapath-logical-router.h \ + northd/en-datapath-sync.c \ + northd/en-datapath-sync.h \ northd/en-ecmp-nexthop.c \ northd/en-ecmp-nexthop.h \ northd/en-global-config.c \ diff --git a/northd/datapath_sync.c b/northd/datapath_sync.c new file mode 100644 index 000000000..fcffb71aa --- /dev/null +++ b/northd/datapath_sync.c @@ -0,0 +1,57 @@ +/* Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "datapath_sync.h" + +struct ovn_unsynced_datapath * +ovn_unsynced_datapath_alloc(const char *name, uint32_t requested_tunnel_key, + const struct ovsdb_idl_row *nb_row) +{ + struct ovn_unsynced_datapath *dp = xzalloc(sizeof *dp); + dp->name = xstrdup(name); + dp->requested_tunnel_key = requested_tunnel_key; + dp->nb_row = nb_row; + smap_init(&dp->external_ids); + + return dp; +} + +void +ovn_unsynced_datapath_destroy(struct ovn_unsynced_datapath *dp) +{ + free(dp->name); + smap_destroy(&dp->external_ids); +} + +void +ovn_unsynced_datapath_map_init(struct ovn_unsynced_datapath_map *map, + const char *sb_key_name) +{ + hmap_init(&map->dps); + map->sb_key_name = sb_key_name; +} + +void +ovn_unsynced_datapath_map_destroy(struct ovn_unsynced_datapath_map *map) +{ + struct ovn_unsynced_datapath *dp; + HMAP_FOR_EACH_POP (dp, hmap_node, &map->dps) { + ovn_unsynced_datapath_destroy(dp); + free(dp); + } + hmap_destroy(&map->dps); +} diff --git a/northd/datapath_sync.h b/northd/datapath_sync.h new file mode 100644 index 000000000..78512f7d4 --- /dev/null +++ b/northd/datapath_sync.h @@ -0,0 +1,77 @@ +/* Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DATAPATH_SYNC_H +#define DATAPATH_SYNC_H 1 + +#include "openvswitch/hmap.h" +#include "openvswitch/list.h" +#include "smap.h" + +/* Datapath syncing API. This file consists of utility functions + * that can be used when syncing northbound datapath types (e.g. + * Logical_Router and Logical_Switch) to southbound Datapath_Bindings. + * + * The basic flow of data is as such. + * 1. A northbound type is converted into an ovn_unsynced_datapath. + * All ovn_unsynced_datapaths are placed into an ovn_unsynced_datapath_map. + * 2. The en_datapath_sync node takes all of the maps in as input and + * syncs them with southbound datapath bindings. This includes allocating + * tunnel keys across all datapath types. The output of this node is + * ovn_synced_datapaths, which contains a list of all synced datapaths. + * 3. A northbound type-aware node then takes the ovn_synced_datapaths, + * and decodes the generic synced datapaths back into a type-specific + * version (e.g. ovn_synced_logical_router). Later nodes can then consume + * these type-specific synced datapath types in order to perform + * further processing. + */ + +/* Represents a datapath from the northbound database + * that has not yet been synced with the southbound database. + */ +struct ovn_unsynced_datapath { + struct hmap_node hmap_node; + char *name; + uint32_t requested_tunnel_key; + struct smap external_ids; + const struct ovsdb_idl_row *nb_row; +}; + +struct ovn_unsynced_datapath_map { + /* ovn_unsynced_datapath */ + struct hmap dps; + const char *sb_key_name; +}; + +struct ovn_synced_datapath { + struct ovs_list list_node; + const struct ovsdb_idl_row *nb_row; + const struct sbrec_datapath_binding *sb_dp; +}; + +struct ovn_synced_datapaths { + struct ovs_list synced_dps; +}; + +struct ovn_unsynced_datapath *ovn_unsynced_datapath_alloc( + const char *name, uint32_t requested_tunnel_key, + const struct ovsdb_idl_row *nb_row); +void ovn_unsynced_datapath_destroy(struct ovn_unsynced_datapath *dp); + +void ovn_unsynced_datapath_map_init(struct ovn_unsynced_datapath_map *map, + const char *sb_key_name); +void ovn_unsynced_datapath_map_destroy(struct ovn_unsynced_datapath_map *map); + +#endif /* DATAPATH_SYNC_H */ diff --git a/northd/en-datapath-logical-router.c b/northd/en-datapath-logical-router.c new file mode 100644 index 000000000..343b2eec9 --- /dev/null +++ b/northd/en-datapath-logical-router.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "openvswitch/hmap.h" +#include "openvswitch/util.h" + +#include "ovn-nb-idl.h" +#include "aging.h" +#include "datapath_sync.h" +#include "en-datapath-logical-router.h" +#include "ovn-util.h" + +#define LR_SB_KEY_NAME "logical-router" + +void * +en_datapath_logical_router_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *args OVS_UNUSED) +{ + struct ovn_unsynced_datapath_map *map = xzalloc(sizeof *map); + ovn_unsynced_datapath_map_init(map, LR_SB_KEY_NAME); + return map; +} + +enum engine_node_state +en_datapath_logical_router_run(struct engine_node *node , void *data) +{ + const struct nbrec_logical_router_table *nb_lr_table = + EN_OVSDB_GET(engine_get_input("NB_logical_router", node)); + + struct ovn_unsynced_datapath_map *map = data; + + ovn_unsynced_datapath_map_destroy(map); + ovn_unsynced_datapath_map_init(map, LR_SB_KEY_NAME); + + const struct nbrec_logical_router *nbr; + NBREC_LOGICAL_ROUTER_TABLE_FOR_EACH (nbr, nb_lr_table) { + struct ovn_unsynced_datapath *dp; + dp = ovn_unsynced_datapath_alloc(nbr->name, + smap_get_int(&nbr->options, + "requested-tnl-key", 0), + &nbr->header_); + + smap_add_format(&dp->external_ids, LR_SB_KEY_NAME, UUID_FMT, + UUID_ARGS(&nbr->header_.uuid)); + smap_add(&dp->external_ids, "name", dp->name); + const char *neutron_router = smap_get(&nbr->options, + "neutron:router_name"); + if (neutron_router && neutron_router[0]) { + smap_add(&dp->external_ids, "name2", neutron_router); + } + + int64_t ct_zone_limit = ovn_smap_get_llong(&nbr->options, + "ct-zone-limit", -1); + if (ct_zone_limit > 0) { + smap_add_format(&dp->external_ids, "ct-zone-limit", "%"PRId64, + ct_zone_limit); + } + + int nat_default_ct = smap_get_int(&nbr->options, + "snat-ct-zone", -1); + if (nat_default_ct >= 0) { + smap_add_format(&dp->external_ids, "snat-ct-zone", "%d", + nat_default_ct); + } + + bool learn_from_arp_request = + smap_get_bool(&nbr->options, "always_learn_from_arp_request", + true); + if (!learn_from_arp_request) { + smap_add(&dp->external_ids, "always_learn_from_arp_request", + "false"); + } + + /* For timestamp refreshing, the smallest threshold of the option is + * set to SB to make sure all entries are refreshed in time. + * This approach simplifies processing in ovn-controller, but it + * may be enhanced, if necessary, to parse the complete CIDR-based + * threshold configurations to SB to reduce unnecessary refreshes. */ + uint32_t age_threshold = min_mac_binding_age_threshold( + smap_get(&nbr->options, + "mac_binding_age_threshold")); + if (age_threshold) { + smap_add_format(&dp->external_ids, "mac_binding_age_threshold", + "%u", age_threshold); + } + + hmap_insert(&map->dps, &dp->hmap_node, uuid_hash(&nbr->header_.uuid)); + } + + return EN_UPDATED; +} + + +void +en_datapath_logical_router_cleanup(void *data) +{ + struct ovn_unsynced_datapath_map *map = data; + ovn_unsynced_datapath_map_destroy(map); +} + +static void +synced_logical_router_map_init( + struct ovn_synced_logical_router_map *router_map) +{ + hmap_init(&router_map->synced_routers); +} + +static void +synced_logical_router_map_destroy( + struct ovn_synced_logical_router_map *router_map) +{ + struct ovn_synced_logical_router *lr; + HMAP_FOR_EACH_POP (lr, hmap_node, &router_map->synced_routers) { + free(lr); + } + hmap_destroy(&router_map->synced_routers); +} + +void * +en_datapath_synced_logical_router_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *args OVS_UNUSED) +{ + struct ovn_synced_logical_router_map *router_map; + router_map = xzalloc(sizeof *router_map); + synced_logical_router_map_init(router_map); + + return router_map; +} + +enum engine_node_state +en_datapath_synced_logical_router_run(struct engine_node *node , void *data) +{ + const struct ovn_synced_datapaths *dps = + engine_get_input_data("datapath_sync", node); + struct ovn_synced_logical_router_map *router_map = data; + + synced_logical_router_map_destroy(router_map); + synced_logical_router_map_init(router_map); + + struct ovn_synced_datapath *sdp; + LIST_FOR_EACH (sdp, list_node, &dps->synced_dps) { + if (sdp->nb_row->table->class_ != &nbrec_table_logical_router) { + continue; + } + struct ovn_synced_logical_router *lr = xzalloc(sizeof *lr); + lr->nb = CONTAINER_OF(sdp->nb_row, struct nbrec_logical_router, + header_); + lr->sb = sdp->sb_dp; + hmap_insert(&router_map->synced_routers, &lr->hmap_node, + uuid_hash(&lr->nb->header_.uuid)); + } + + return EN_UPDATED; +} + +void en_datapath_synced_logical_router_cleanup(void *data) +{ + struct ovn_synced_logical_router_map *router_map = data; + synced_logical_router_map_destroy(router_map); +} diff --git a/northd/en-datapath-logical-router.h b/northd/en-datapath-logical-router.h new file mode 100644 index 000000000..587de8393 --- /dev/null +++ b/northd/en-datapath-logical-router.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EN_DATAPATH_LOGICAL_ROUTER_H +#define EN_DATAPATH_LOGICAL_ROUTER_H + +#include "lib/inc-proc-eng.h" +#include "openvswitch/hmap.h" + + +void *en_datapath_logical_router_init(struct engine_node *, + struct engine_arg *); + +enum engine_node_state en_datapath_logical_router_run(struct engine_node *, + void *data); +void en_datapath_logical_router_cleanup(void *data); + +struct ovn_synced_logical_router { + struct hmap_node hmap_node; + const struct nbrec_logical_router *nb; + const struct sbrec_datapath_binding *sb; +}; + +struct ovn_synced_logical_router_map { + struct hmap synced_routers; +}; + +void *en_datapath_synced_logical_router_init(struct engine_node *, + struct engine_arg *); + +enum engine_node_state en_datapath_synced_logical_router_run( + struct engine_node *, void *data); + +void en_datapath_synced_logical_router_cleanup(void *data); + +#endif /* EN_DATAPATH_LOGICAL_ROUTER_H */ diff --git a/northd/en-datapath-logical-switch.c b/northd/en-datapath-logical-switch.c new file mode 100644 index 000000000..6395b1552 --- /dev/null +++ b/northd/en-datapath-logical-switch.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "openvswitch/hmap.h" +#include "openvswitch/util.h" +#include "openvswitch/vlog.h" + +#include "inc-proc-eng.h" +#include "ovn-nb-idl.h" +#include "datapath_sync.h" +#include "en-datapath-logical-switch.h" +#include "en-global-config.h" +#include "ovn-util.h" + +#define LS_SB_KEY_NAME "logical-switch" + +VLOG_DEFINE_THIS_MODULE(en_datapath_logical_switch); + +void * +en_datapath_logical_switch_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *args OVS_UNUSED) +{ + struct ovn_unsynced_datapath_map *map = xzalloc(sizeof *map); + ovn_unsynced_datapath_map_init(map, LS_SB_KEY_NAME); + return map; +} + +enum engine_node_state +en_datapath_logical_switch_run(struct engine_node *node , void *data) +{ + const struct nbrec_logical_switch_table *nb_ls_table = + EN_OVSDB_GET(engine_get_input("NB_logical_switch", node)); + const struct ed_type_global_config *global_config = + engine_get_input_data("global_config", node); + + struct ovn_unsynced_datapath_map *map = data; + + ovn_unsynced_datapath_map_destroy(map); + ovn_unsynced_datapath_map_init(map, LS_SB_KEY_NAME); + + const struct nbrec_logical_switch *nbs; + NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH (nbs, nb_ls_table) { + uint32_t requested_tunnel_key = smap_get_int(&nbs->other_config, + "requested-tnl-key", 0); + const char *ts = smap_get(&nbs->other_config, "interconn-ts"); + + if (!ts && global_config->vxlan_mode && + requested_tunnel_key >= 1 << 12) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "Tunnel key %"PRIu32" for datapath %s is " + "incompatible with VXLAN", requested_tunnel_key, + nbs->name); + requested_tunnel_key = 0; + } + + struct ovn_unsynced_datapath *dp; + dp = ovn_unsynced_datapath_alloc(nbs->name, requested_tunnel_key, + &nbs->header_); + + smap_init(&dp->external_ids); + smap_add_format(&dp->external_ids, LS_SB_KEY_NAME, UUID_FMT, + UUID_ARGS(&nbs->header_.uuid)); + smap_add(&dp->external_ids, "name", dp->name); + const char *neutron_network = smap_get(&nbs->other_config, + "neutron:network_name"); + if (neutron_network && neutron_network[0]) { + smap_add(&dp->external_ids, "name2", neutron_network); + } + + int64_t ct_zone_limit = ovn_smap_get_llong(&nbs->other_config, + "ct-zone-limit", -1); + if (ct_zone_limit > 0) { + smap_add_format(&dp->external_ids, "ct-zone-limit", "%"PRId64, + ct_zone_limit); + } + + if (ts) { + smap_add(&dp->external_ids, "interconn-ts", ts); + } + + uint32_t age_threshold = smap_get_uint(&nbs->other_config, + "fdb_age_threshold", 0); + if (age_threshold) { + smap_add_format(&dp->external_ids, "fdb_age_threshold", + "%u", age_threshold); + } + + hmap_insert(&map->dps, &dp->hmap_node, uuid_hash(&nbs->header_.uuid)); + } + + return EN_UPDATED; +} + + +void +en_datapath_logical_switch_cleanup(void *data) +{ + struct ovn_unsynced_datapath_map *map = data; + ovn_unsynced_datapath_map_destroy(map); +} + +static void +synced_logical_switch_map_init( + struct ovn_synced_logical_switch_map *switch_map) +{ + hmap_init(&switch_map->synced_switches); +} + +static void +synced_logical_switch_map_destroy( + struct ovn_synced_logical_switch_map *switch_map) +{ + struct ovn_synced_logical_switch *ls; + HMAP_FOR_EACH_POP (ls, hmap_node, &switch_map->synced_switches) { + free(ls); + } + hmap_destroy(&switch_map->synced_switches); +} + +void * +en_datapath_synced_logical_switch_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *args OVS_UNUSED) +{ + struct ovn_synced_logical_switch_map *switch_map; + switch_map = xzalloc(sizeof *switch_map); + synced_logical_switch_map_init(switch_map); + + return switch_map; +} + +enum engine_node_state +en_datapath_synced_logical_switch_run(struct engine_node *node , void *data) +{ + const struct ovn_synced_datapaths *dps = + engine_get_input_data("datapath_sync", node); + struct ovn_synced_logical_switch_map *switch_map = data; + + synced_logical_switch_map_destroy(switch_map); + synced_logical_switch_map_init(switch_map); + + struct ovn_synced_datapath *sdp; + LIST_FOR_EACH (sdp, list_node, &dps->synced_dps) { + if (sdp->nb_row->table->class_ != &nbrec_table_logical_switch) { + continue; + } + struct ovn_synced_logical_switch *lsw = xzalloc(sizeof *lsw); + lsw->nb = CONTAINER_OF(sdp->nb_row, struct nbrec_logical_switch, + header_); + lsw->sb = sdp->sb_dp; + hmap_insert(&switch_map->synced_switches, &lsw->hmap_node, + uuid_hash(&lsw->nb->header_.uuid)); + } + + return EN_UPDATED; +} + +void en_datapath_synced_logical_switch_cleanup(void *data) +{ + struct ovn_synced_logical_switch_map *switch_map = data; + synced_logical_switch_map_destroy(switch_map); +} diff --git a/northd/en-datapath-logical-switch.h b/northd/en-datapath-logical-switch.h new file mode 100644 index 000000000..1190b7be8 --- /dev/null +++ b/northd/en-datapath-logical-switch.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EN_DATAPATH_LOGICAL_SWITCH_H +#define EN_DATAPATH_LOGICAL_SWITCH_H + +#include "lib/inc-proc-eng.h" +#include "openvswitch/hmap.h" + + +void *en_datapath_logical_switch_init(struct engine_node *, + struct engine_arg *); + +enum engine_node_state en_datapath_logical_switch_run(struct engine_node *, + void *data); +void en_datapath_logical_switch_cleanup(void *data); + +struct ovn_synced_logical_switch { + struct hmap_node hmap_node; + const struct nbrec_logical_switch *nb; + const struct sbrec_datapath_binding *sb; +}; + +struct ovn_synced_logical_switch_map { + struct hmap synced_switches; +}; + +void *en_datapath_synced_logical_switch_init(struct engine_node *, + struct engine_arg *); + +enum engine_node_state en_datapath_synced_logical_switch_run( + struct engine_node *, void *data); +void en_datapath_synced_logical_switch_cleanup(void *data); + +#endif /* EN_DATAPATH_LOGICAL_SWITCH_H */ diff --git a/northd/en-datapath-sync.c b/northd/en-datapath-sync.c new file mode 100644 index 000000000..15e4ddc06 --- /dev/null +++ b/northd/en-datapath-sync.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "uuidset.h" + +#include "en-datapath-sync.h" +#include "en-global-config.h" +#include "datapath_sync.h" +#include "ovn-sb-idl.h" +#include "openvswitch/vlog.h" + +VLOG_DEFINE_THIS_MODULE(datapath_sync); + +void * +en_datapath_sync_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *args OVS_UNUSED) +{ + struct ovn_synced_datapaths *synced_datapaths + = xzalloc(sizeof *synced_datapaths); + ovs_list_init(&synced_datapaths->synced_dps); + + return synced_datapaths; +} + +static struct ovn_unsynced_datapath * +find_unsynced_datapath(const struct ovn_unsynced_datapath_map **maps, + size_t n_maps, + const struct sbrec_datapath_binding *sb_dp, + const char **map_key) +{ + struct uuid key; + const struct ovn_unsynced_datapath_map *map; + bool found_map = false; + *map_key = NULL; + for (size_t i = 0; i < n_maps; i++) { + map = maps[i]; + if (smap_get_uuid(&sb_dp->external_ids, map->sb_key_name, &key)) { + found_map = true; + break; + } + } + + if (!found_map) { + return NULL; + } + ovs_assert(map); + *map_key = map->sb_key_name; + + uint32_t hash = uuid_hash(&key); + struct ovn_unsynced_datapath *dp; + HMAP_FOR_EACH_WITH_HASH (dp, hmap_node, hash, &map->dps) { + if (uuid_equals(&key, &dp->nb_row->uuid)) { + return dp; + } + } + + return NULL; +} + +struct candidate_sdp { + struct ovs_list list_node; + struct ovn_synced_datapath *sdp; + uint32_t requested_tunnel_key; + uint32_t existing_tunnel_key; +}; + +static struct candidate_sdp * +candidate_sdp_alloc(const struct ovn_unsynced_datapath *udp, + const struct sbrec_datapath_binding *sb_dp) +{ + struct ovn_synced_datapath *sdp; + sdp = xzalloc(sizeof *sdp); + sdp->sb_dp = sb_dp; + sdp->nb_row = udp->nb_row; + sbrec_datapath_binding_set_external_ids(sb_dp, &udp->external_ids); + + struct candidate_sdp *candidate; + candidate = xzalloc(sizeof *candidate); + candidate->sdp = sdp; + candidate->requested_tunnel_key = udp->requested_tunnel_key; + candidate->existing_tunnel_key = sdp->sb_dp->tunnel_key; + + return candidate; +} + +static void +reset_synced_datapaths(struct ovn_synced_datapaths *synced_datapaths) +{ + struct ovn_synced_datapath *sdp; + LIST_FOR_EACH_POP (sdp, list_node, &synced_datapaths->synced_dps) { + free(sdp); + } + ovs_list_init(&synced_datapaths->synced_dps); +} + +static void +create_synced_datapath_candidates_from_sb( + const struct sbrec_datapath_binding_table *sb_dp_table, + struct uuidset *visited, + const struct ovn_unsynced_datapath_map **input_maps, + size_t n_input_maps, + struct ovs_list *candidate_sdps) +{ + const struct sbrec_datapath_binding *sb_dp; + SBREC_DATAPATH_BINDING_TABLE_FOR_EACH_SAFE (sb_dp, sb_dp_table) { + const char *map_key; + struct ovn_unsynced_datapath *udp; + udp = find_unsynced_datapath(input_maps, n_input_maps, sb_dp, + &map_key); + if (!udp) { + sbrec_datapath_binding_delete(sb_dp); + continue; + } + + if (uuidset_find(visited, &udp->nb_row->uuid)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_INFO_RL( + &rl, "deleting Datapath_Binding "UUID_FMT" with " + "duplicate external-ids:%s "UUID_FMT, + UUID_ARGS(&sb_dp->header_.uuid), map_key, + UUID_ARGS(&udp->nb_row->uuid)); + sbrec_datapath_binding_delete(sb_dp); + continue; + } + + struct candidate_sdp *candidate; + candidate = candidate_sdp_alloc(udp, sb_dp); + ovs_list_push_back(candidate_sdps, &candidate->list_node); + uuidset_insert(visited, &udp->nb_row->uuid); + } +} + +static void +create_synced_datapath_candidates_from_nb( + const struct ovn_unsynced_datapath_map **input_maps, + size_t n_input_maps, + struct ovsdb_idl_txn *ovnsb_idl_txn, + struct uuidset *visited, + struct ovs_list *candidate_sdps) +{ + for (size_t i = 0; i < n_input_maps; i++) { + const struct ovn_unsynced_datapath_map *map = input_maps[i]; + struct ovn_unsynced_datapath *udp; + HMAP_FOR_EACH (udp, hmap_node, &map->dps) { + if (uuidset_find(visited, &udp->nb_row->uuid)) { + continue; + } + struct sbrec_datapath_binding *sb_dp; + sb_dp = sbrec_datapath_binding_insert(ovnsb_idl_txn); + struct candidate_sdp *candidate; + candidate = candidate_sdp_alloc(udp, sb_dp); + ovs_list_push_back(candidate_sdps, &candidate->list_node); + } + } +} + +static void +assign_requested_tunnel_keys(struct ovs_list *candidate_sdps, + struct hmap *dp_tnlids, + struct ovn_synced_datapaths *synced_datapaths) +{ + struct candidate_sdp *candidate; + LIST_FOR_EACH_SAFE (candidate, list_node, candidate_sdps) { + if (!candidate->requested_tunnel_key) { + continue; + } + if (ovn_add_tnlid(dp_tnlids, candidate->requested_tunnel_key)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "Logical datapath "UUID_FMT" requests same " + "tunnel key %"PRIu32" as another logical datapath", + UUID_ARGS(&candidate->sdp->nb_row->uuid), + candidate->requested_tunnel_key); + } + sbrec_datapath_binding_set_tunnel_key(candidate->sdp->sb_dp, + candidate->requested_tunnel_key); + ovs_list_remove(&candidate->list_node); + ovs_list_push_back(&synced_datapaths->synced_dps, + &candidate->sdp->list_node); + free(candidate); + } +} + +static void +assign_existing_tunnel_keys(struct ovs_list *candidate_sdps, + struct hmap *dp_tnlids, + struct ovn_synced_datapaths *synced_datapaths) +{ + struct candidate_sdp *candidate; + LIST_FOR_EACH_SAFE (candidate, list_node, candidate_sdps) { + if (!candidate->existing_tunnel_key) { + continue; + } + /* Existing southbound DP. If this key is available, + * reuse it. + */ + if (ovn_add_tnlid(dp_tnlids, candidate->existing_tunnel_key)) { + ovs_list_remove(&candidate->list_node); + ovs_list_push_back(&synced_datapaths->synced_dps, + &candidate->sdp->list_node); + free(candidate); + } + } +} + +static void +allocate_tunnel_keys(struct ovs_list *candidate_sdps, + struct hmap *dp_tnlids, + uint32_t max_dp_tunnel_id, + struct ovn_synced_datapaths *synced_datapaths) +{ + uint32_t hint = 0; + struct candidate_sdp *candidate; + LIST_FOR_EACH_SAFE (candidate, list_node, candidate_sdps) { + uint32_t tunnel_key = + ovn_allocate_tnlid(dp_tnlids, "datapath", OVN_MIN_DP_KEY_LOCAL, + max_dp_tunnel_id, &hint); + if (!tunnel_key) { + continue; + } + sbrec_datapath_binding_set_tunnel_key(candidate->sdp->sb_dp, + tunnel_key); + ovs_list_remove(&candidate->list_node); + ovs_list_push_back(&synced_datapaths->synced_dps, + &candidate->sdp->list_node); + free(candidate); + } +} + +static void +delete_candidates(struct ovs_list *candidate_sdps) +{ + struct candidate_sdp *candidate; + LIST_FOR_EACH_POP (candidate, list_node, candidate_sdps) { + sbrec_datapath_binding_delete(candidate->sdp->sb_dp); + free(candidate->sdp); + free(candidate); + } +} + +enum engine_node_state +en_datapath_sync_run(struct engine_node *node , void *data) +{ + const struct sbrec_datapath_binding_table *sb_dp_table = + EN_OVSDB_GET(engine_get_input("SB_datapath_binding", node)); + const struct ed_type_global_config *global_config = + engine_get_input_data("global_config", node); + /* The inputs are: + * * Some number of input maps. + * * Southbound Datapath Binding table. + * * Global config data. + * + * Therefore, the number of inputs - 2 is the number of input + * maps from the datapath-specific nodes. + */ + size_t n_input_maps = node->n_inputs - 2; + const struct ovn_unsynced_datapath_map **input_maps = + xmalloc(n_input_maps * sizeof *input_maps); + struct ovn_synced_datapaths *synced_datapaths = data; + + for (size_t i = 0; i < n_input_maps; i++) { + input_maps[i] = engine_get_data(node->inputs[i].node); + } + + reset_synced_datapaths(synced_datapaths); + + struct uuidset visited = UUIDSET_INITIALIZER(&visited); + struct ovs_list candidate_sdps = OVS_LIST_INITIALIZER(&candidate_sdps); + create_synced_datapath_candidates_from_sb(sb_dp_table, &visited, + input_maps, n_input_maps, + &candidate_sdps); + + const struct engine_context *eng_ctx = engine_get_context(); + create_synced_datapath_candidates_from_nb(input_maps, n_input_maps, + eng_ctx->ovnsb_idl_txn, &visited, + &candidate_sdps); + uuidset_destroy(&visited); + + struct hmap dp_tnlids = HMAP_INITIALIZER(&dp_tnlids); + assign_requested_tunnel_keys(&candidate_sdps, &dp_tnlids, + synced_datapaths); + assign_existing_tunnel_keys(&candidate_sdps, &dp_tnlids, synced_datapaths); + allocate_tunnel_keys(&candidate_sdps, &dp_tnlids, + global_config->max_dp_tunnel_id, synced_datapaths); + + /* Any remaining candidates could not be synced due to tunnel key + * issues and need to be deleted. + */ + delete_candidates(&candidate_sdps); + + ovn_destroy_tnlids(&dp_tnlids); + free(input_maps); + + return EN_UPDATED; +} + +void en_datapath_sync_cleanup(void *data) +{ + struct ovn_synced_datapaths *synced_datapaths = data; + struct ovn_synced_datapath *sdp; + + LIST_FOR_EACH_POP (sdp, list_node, &synced_datapaths->synced_dps) { + free(sdp); + } +} diff --git a/northd/en-datapath-sync.h b/northd/en-datapath-sync.h new file mode 100644 index 000000000..3b3262304 --- /dev/null +++ b/northd/en-datapath-sync.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EN_DATAPATH_SYNC_H +#define EN_DATAPATH_SYNC_H + +#include "inc-proc-eng.h" + +void *en_datapath_sync_init(struct engine_node *, + struct engine_arg *); +enum engine_node_state en_datapath_sync_run(struct engine_node *, void *data); +void en_datapath_sync_cleanup(void *data); + +#endif /* EN_DATAPATH_SYNC_H */ diff --git a/northd/en-global-config.c b/northd/en-global-config.c index 11513e31e..7204462ee 100644 --- a/northd/en-global-config.c +++ b/northd/en-global-config.c @@ -63,6 +63,17 @@ en_global_config_init(struct engine_node *node OVS_UNUSED, return data; } +static uint32_t +get_ovn_max_dp_key_local(bool vxlan_mode, bool vxlan_ic_mode) +{ + if (vxlan_mode) { + /* OVN_MAX_DP_GLOBAL_NUM doesn't apply for VXLAN mode. */ + return vxlan_ic_mode ? OVN_MAX_DP_VXLAN_KEY_LOCAL + : OVN_MAX_DP_VXLAN_KEY; + } + return vxlan_ic_mode ? OVN_MAX_DP_VXLAN_KEY_LOCAL : OVN_MAX_DP_KEY_LOCAL; +} + enum engine_node_state en_global_config_run(struct engine_node *node , void *data) { diff --git a/northd/en-northd.c b/northd/en-northd.c index 02a27aac2..c4573f88f 100644 --- a/northd/en-northd.c +++ b/northd/en-northd.c @@ -117,6 +117,12 @@ northd_get_input_data(struct engine_node *node, input_data->svc_monitor_mac_ea = global_config->svc_monitor_mac_ea; input_data->features = &global_config->features; input_data->vxlan_mode = global_config->vxlan_mode; + + input_data->synced_lses = + engine_get_input_data("datapath_synced_logical_switch", node); + + input_data->synced_lrs = + engine_get_input_data("datapath_synced_logical_router", node); } enum engine_node_state diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c index b1e4994a4..bdc6c48df 100644 --- a/northd/inc-proc-northd.c +++ b/northd/inc-proc-northd.c @@ -47,6 +47,9 @@ #include "en-advertised-route-sync.h" #include "en-learned-route-sync.h" #include "en-group-ecmp-route.h" +#include "en-datapath-logical-router.h" +#include "en-datapath-logical-switch.h" +#include "en-datapath-sync.h" #include "unixctl.h" #include "util.h" @@ -179,6 +182,11 @@ static ENGINE_NODE(advertised_route_sync); static ENGINE_NODE(learned_route_sync, CLEAR_TRACKED_DATA); static ENGINE_NODE(dynamic_routes); static ENGINE_NODE(group_ecmp_route, CLEAR_TRACKED_DATA); +static ENGINE_NODE(datapath_logical_router); +static ENGINE_NODE(datapath_logical_switch); +static ENGINE_NODE(datapath_synced_logical_router); +static ENGINE_NODE(datapath_synced_logical_switch); +static ENGINE_NODE(datapath_sync); void inc_proc_northd_init(struct ovsdb_idl_loop *nb, struct ovsdb_idl_loop *sb) @@ -209,6 +217,21 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_acl_id, &en_nb_acl, NULL); engine_add_input(&en_acl_id, &en_sb_acl_id, NULL); + engine_add_input(&en_datapath_logical_switch, &en_nb_logical_switch, NULL); + engine_add_input(&en_datapath_logical_switch, &en_global_config, NULL); + + engine_add_input(&en_datapath_logical_router, &en_nb_logical_router, NULL); + + engine_add_input(&en_datapath_sync, &en_datapath_logical_switch, NULL); + engine_add_input(&en_datapath_sync, &en_datapath_logical_router, NULL); + engine_add_input(&en_datapath_sync, &en_sb_datapath_binding, NULL); + engine_add_input(&en_datapath_sync, &en_global_config, NULL); + + engine_add_input(&en_datapath_synced_logical_router, &en_datapath_sync, + NULL); + engine_add_input(&en_datapath_synced_logical_switch, &en_datapath_sync, + NULL); + engine_add_input(&en_northd, &en_nb_mirror, NULL); engine_add_input(&en_northd, &en_nb_mirror_rule, NULL); engine_add_input(&en_northd, &en_nb_static_mac_binding, NULL); @@ -247,6 +270,23 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, northd_nb_logical_router_handler); engine_add_input(&en_northd, &en_lb_data, northd_lb_data_handler); + /* Currently, northd handles logical router and switch changes in nodes + * that read directly from the northbound logical tables. Those nodes + * will trigger a recompute if conditions on changed logical routers + * or logical switches cannot be handled. From en-northd's perspective, + * synced logical switch and router changes are always handled. + * + * Once datapath syncing has incremental processing added, then + * en-northd can move its logical router and switch change handling to + * handlers defined here, and there will be no need for en_northd to + * read directly from the northbound database for incremental handling + * of these types. + */ + engine_add_input(&en_northd, &en_datapath_synced_logical_router, + engine_noop_handler); + engine_add_input(&en_northd, &en_datapath_synced_logical_switch, + engine_noop_handler); + engine_add_input(&en_lr_nat, &en_northd, lr_nat_northd_handler); engine_add_input(&en_lr_stateful, &en_northd, lr_stateful_northd_handler); diff --git a/northd/northd.c b/northd/northd.c index b0e957b30..47b54dbd9 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -52,6 +52,8 @@ #include "en-ls-stateful.h" #include "en-multicast.h" #include "en-sampling-app.h" +#include "en-datapath-logical-switch.h" +#include "en-datapath-logical-router.h" #include "lib/ovn-parallel-hmap.h" #include "ovn/actions.h" #include "ovn/features.h" @@ -96,8 +98,6 @@ static bool default_acl_drop; * and ports tunnel key allocation (12 bits for each instead of default 16). */ static bool vxlan_mode; -static bool vxlan_ic_mode; - #define MAX_OVN_TAGS 4096 @@ -482,6 +482,8 @@ struct lrouter_group { struct hmapx tmp_ha_ref_chassis; }; +static void init_mcast_info_for_datapath(struct ovn_datapath *od); + static struct ovn_datapath * ovn_datapath_create(struct hmap *datapaths, const struct uuid *key, const struct nbrec_logical_switch *nbs, @@ -503,6 +505,8 @@ ovn_datapath_create(struct hmap *datapaths, const struct uuid *key, od->router_ports = VECTOR_EMPTY_INITIALIZER(struct ovn_port *); od->l3dgw_ports = VECTOR_EMPTY_INITIALIZER(struct ovn_port *); od->localnet_ports = VECTOR_EMPTY_INITIALIZER(struct ovn_port *); + od->tunnel_key = sb->tunnel_key; + init_mcast_info_for_datapath(od); return od; } @@ -745,87 +749,6 @@ store_mcast_info_for_switch_datapath(const struct sbrec_ip_multicast *sb, } } -static void -ovn_datapath_update_external_ids(struct ovn_datapath *od) -{ - /* Get the logical-switch or logical-router UUID to set in - * external-ids. */ - char uuid_s[UUID_LEN + 1]; - sprintf(uuid_s, UUID_FMT, UUID_ARGS(&od->key)); - const char *key = od->nbs ? "logical-switch" : "logical-router"; - - /* Get names to set in external-ids. */ - const char *name = od->nbs ? od->nbs->name : od->nbr->name; - const char *name2 = (od->nbs - ? smap_get(&od->nbs->external_ids, - "neutron:network_name") - : smap_get(&od->nbr->external_ids, - "neutron:router_name")); - - /* Set external-ids. */ - struct smap ids = SMAP_INITIALIZER(&ids); - smap_add(&ids, key, uuid_s); - smap_add(&ids, "name", name); - if (name2 && name2[0]) { - smap_add(&ids, "name2", name2); - } - - int64_t ct_zone_limit = ovn_smap_get_llong(od->nbs ? - &od->nbs->other_config : - &od->nbr->options, - "ct-zone-limit", -1); - if (ct_zone_limit > 0) { - smap_add_format(&ids, "ct-zone-limit", "%"PRId64, ct_zone_limit); - } - - /* Set interconn-ts. */ - if (od->nbs) { - const char *ts = smap_get(&od->nbs->other_config, "interconn-ts"); - if (ts) { - smap_add(&ids, "interconn-ts", ts); - } - - uint32_t age_threshold = smap_get_uint(&od->nbs->other_config, - "fdb_age_threshold", 0); - if (age_threshold) { - smap_add_format(&ids, "fdb_age_threshold", - "%u", age_threshold); - } - } - - /* Set snat-ct-zone */ - if (od->nbr) { - int nat_default_ct = smap_get_int(&od->nbr->options, - "snat-ct-zone", -1); - if (nat_default_ct >= 0) { - smap_add_format(&ids, "snat-ct-zone", "%d", nat_default_ct); - } - - bool learn_from_arp_request = - smap_get_bool(&od->nbr->options, "always_learn_from_arp_request", - true); - if (!learn_from_arp_request) { - smap_add(&ids, "always_learn_from_arp_request", "false"); - } - - /* For timestamp refreshing, the smallest threshold of the option is - * set to SB to make sure all entries are refreshed in time. - * XXX: This approach simplifies processing in ovn-controller, but it - * may be enhanced, if necessary, to parse the complete CIDR-based - * threshold configurations to SB to reduce unnecessary refreshes. */ - uint32_t age_threshold = min_mac_binding_age_threshold( - smap_get(&od->nbr->options, - "mac_binding_age_threshold")); - if (age_threshold) { - smap_add_format(&ids, "mac_binding_age_threshold", - "%u", age_threshold); - } - } - - sbrec_datapath_binding_set_external_ids(od->sb, &ids); - smap_destroy(&ids); -} - static enum dynamic_routing_redistribute_mode parse_dynamic_routing_redistribute( const struct smap *options, @@ -877,170 +800,6 @@ parse_dynamic_routing_redistribute( return out; } -static void -join_datapaths(const struct nbrec_logical_switch_table *nbrec_ls_table, - const struct nbrec_logical_router_table *nbrec_lr_table, - const struct sbrec_datapath_binding_table *sbrec_dp_table, - struct ovsdb_idl_txn *ovnsb_txn, - struct hmap *datapaths, struct ovs_list *sb_only, - struct ovs_list *nb_only, struct ovs_list *both) -{ - ovs_list_init(sb_only); - ovs_list_init(nb_only); - ovs_list_init(both); - - const struct sbrec_datapath_binding *sb; - SBREC_DATAPATH_BINDING_TABLE_FOR_EACH_SAFE (sb, sbrec_dp_table) { - struct uuid key; - if (!smap_get_uuid(&sb->external_ids, "logical-switch", &key) && - !smap_get_uuid(&sb->external_ids, "logical-router", &key)) { - ovsdb_idl_txn_add_comment( - ovnsb_txn, - "deleting Datapath_Binding "UUID_FMT" that lacks " - "external-ids:logical-switch and " - "external-ids:logical-router", - UUID_ARGS(&sb->header_.uuid)); - sbrec_datapath_binding_delete(sb); - continue; - } - - if (ovn_datapath_find_(datapaths, &key)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_INFO_RL( - &rl, "deleting Datapath_Binding "UUID_FMT" with " - "duplicate external-ids:logical-switch/router "UUID_FMT, - UUID_ARGS(&sb->header_.uuid), UUID_ARGS(&key)); - sbrec_datapath_binding_delete(sb); - continue; - } - - struct ovn_datapath *od = ovn_datapath_create(datapaths, &key, - NULL, NULL, sb); - ovs_list_push_back(sb_only, &od->list); - } - - vxlan_ic_mode = false; - const struct nbrec_logical_switch *nbs; - NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH (nbs, nbrec_ls_table) { - struct ovn_datapath *od = ovn_datapath_find_(datapaths, - &nbs->header_.uuid); - if (od) { - od->nbs = nbs; - ovs_list_remove(&od->list); - ovs_list_push_back(both, &od->list); - ovn_datapath_update_external_ids(od); - } else { - od = ovn_datapath_create(datapaths, &nbs->header_.uuid, - nbs, NULL, NULL); - ovs_list_push_back(nb_only, &od->list); - } - - init_ipam_info_for_datapath(od); - init_mcast_info_for_datapath(od); - - if (smap_get_bool(&nbs->other_config, "ic-vxlan_mode", false)) { - vxlan_ic_mode = true; - } - } - - const struct nbrec_logical_router *nbr; - NBREC_LOGICAL_ROUTER_TABLE_FOR_EACH (nbr, nbrec_lr_table) { - if (!lrouter_is_enabled(nbr)) { - continue; - } - - struct ovn_datapath *od = ovn_datapath_find_(datapaths, - &nbr->header_.uuid); - if (od) { - if (!od->nbs) { - od->nbr = nbr; - ovs_list_remove(&od->list); - ovs_list_push_back(both, &od->list); - ovn_datapath_update_external_ids(od); - } else { - /* Can't happen! */ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, - "duplicate UUID "UUID_FMT" in OVN_Northbound", - UUID_ARGS(&nbr->header_.uuid)); - continue; - } - } else { - od = ovn_datapath_create(datapaths, &nbr->header_.uuid, - NULL, nbr, NULL); - ovs_list_push_back(nb_only, &od->list); - } - init_mcast_info_for_datapath(od); - if (smap_get(&od->nbr->options, "chassis")) { - od->is_gw_router = true; - } - od->dynamic_routing = smap_get_bool(&od->nbr->options, - "dynamic-routing", false); - od->dynamic_routing_redistribute = - parse_dynamic_routing_redistribute(&od->nbr->options, DRRM_NONE, - od->nbr->name); - } -} - - -uint32_t -get_ovn_max_dp_key_local(bool _vxlan_mode, bool _vxlan_ic_mode) -{ - if (_vxlan_mode) { - /* OVN_MAX_DP_GLOBAL_NUM doesn't apply for VXLAN mode. */ - return _vxlan_ic_mode ? OVN_MAX_DP_VXLAN_KEY_LOCAL - : OVN_MAX_DP_VXLAN_KEY; - } - return _vxlan_ic_mode ? OVN_MAX_DP_VXLAN_KEY_LOCAL : OVN_MAX_DP_KEY_LOCAL; -} - -static void -ovn_datapath_allocate_key(struct hmap *datapaths, struct hmap *dp_tnlids, - struct ovn_datapath *od, uint32_t *hint) -{ - if (!od->tunnel_key) { - od->tunnel_key = ovn_allocate_tnlid(dp_tnlids, "datapath", - OVN_MIN_DP_KEY_LOCAL, - get_ovn_max_dp_key_local(vxlan_mode, vxlan_ic_mode), hint); - if (!od->tunnel_key) { - if (od->sb) { - sbrec_datapath_binding_delete(od->sb); - } - ovs_list_remove(&od->list); - ovn_datapath_destroy(datapaths, od); - } - } -} - -static void -ovn_datapath_assign_requested_tnl_id( - struct hmap *dp_tnlids, struct ovn_datapath *od) -{ - const struct smap *other_config = (od->nbs - ? &od->nbs->other_config - : &od->nbr->options); - uint32_t tunnel_key = smap_get_int(other_config, "requested-tnl-key", 0); - if (tunnel_key) { - const char *interconn_ts = smap_get(other_config, "interconn-ts"); - if (!interconn_ts && vxlan_mode && tunnel_key >= 1 << 12) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Tunnel key %"PRIu32" for datapath %s is " - "incompatible with VXLAN", tunnel_key, - od->nbs ? od->nbs->name : od->nbr->name); - return; - } - if (ovn_add_tnlid(dp_tnlids, tunnel_key)) { - od->tunnel_key = tunnel_key; - } else { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Logical %s %s requests same tunnel key " - "%"PRIu32" as another logical switch or router", - od->nbs ? "switch" : "router", - od->nbs ? od->nbs->name : od->nbr->name, tunnel_key); - } - } -} - static void ods_build_array_index(struct ovn_datapaths *datapaths) { @@ -1060,82 +819,43 @@ ods_build_array_index(struct ovn_datapaths *datapaths) } } -/* Updates the southbound Datapath_Binding table so that it contains the - * logical switches and routers specified by the northbound database. - * - * Initializes 'datapaths' to contain a "struct ovn_datapath" for every logical - * switch and router. */ +/* Initializes 'ls_datapaths' to contain a "struct ovn_datapath" for every + * logical switch, and initializes 'lr_datapaths' to contain a + * "struct ovn_datapath" for every logical router. + */ static void -build_datapaths(struct ovsdb_idl_txn *ovnsb_txn, - const struct nbrec_logical_switch_table *nbrec_ls_table, - const struct nbrec_logical_router_table *nbrec_lr_table, - const struct sbrec_datapath_binding_table *sbrec_dp_table, +build_datapaths(const struct ovn_synced_logical_switch_map *ls_map, + const struct ovn_synced_logical_router_map *lr_map, struct ovn_datapaths *ls_datapaths, struct ovn_datapaths *lr_datapaths) { - struct ovs_list sb_only, nb_only, both; - - struct hmap *datapaths = &ls_datapaths->datapaths; - join_datapaths(nbrec_ls_table, nbrec_lr_table, sbrec_dp_table, ovnsb_txn, - datapaths, &sb_only, &nb_only, &both); - - /* Assign explicitly requested tunnel ids first. */ - struct hmap dp_tnlids = HMAP_INITIALIZER(&dp_tnlids); - struct ovn_datapath *od; - LIST_FOR_EACH (od, list, &both) { - ovn_datapath_assign_requested_tnl_id(&dp_tnlids, od); - } - LIST_FOR_EACH (od, list, &nb_only) { - ovn_datapath_assign_requested_tnl_id(&dp_tnlids, od); - } - - /* Keep nonconflicting tunnel IDs that are already assigned. */ - LIST_FOR_EACH (od, list, &both) { - if (!od->tunnel_key && ovn_add_tnlid(&dp_tnlids, od->sb->tunnel_key)) { - od->tunnel_key = od->sb->tunnel_key; - } - } - - /* Assign new tunnel ids where needed. */ - uint32_t hint = 0; - LIST_FOR_EACH_SAFE (od, list, &both) { - ovn_datapath_allocate_key(datapaths, &dp_tnlids, od, &hint); - } - LIST_FOR_EACH_SAFE (od, list, &nb_only) { - ovn_datapath_allocate_key(datapaths, &dp_tnlids, od, &hint); + struct ovn_synced_logical_switch *ls; + HMAP_FOR_EACH (ls, hmap_node, &ls_map->synced_switches) { + struct ovn_datapath *od = + ovn_datapath_create(&ls_datapaths->datapaths, + &ls->nb->header_.uuid, + ls->nb, NULL, ls->sb); + init_ipam_info_for_datapath(od); } - /* Sync tunnel ids from nb to sb. */ - LIST_FOR_EACH (od, list, &both) { - if (od->sb->tunnel_key != od->tunnel_key) { - sbrec_datapath_binding_set_tunnel_key(od->sb, od->tunnel_key); + struct ovn_synced_logical_router *lr; + HMAP_FOR_EACH (lr, hmap_node, &lr_map->synced_routers) { + if (!lrouter_is_enabled(lr->nb)) { + continue; } - ovn_datapath_update_external_ids(od); - } - LIST_FOR_EACH (od, list, &nb_only) { - od->sb = sbrec_datapath_binding_insert(ovnsb_txn); - ovn_datapath_update_external_ids(od); - sbrec_datapath_binding_set_tunnel_key(od->sb, od->tunnel_key); - } - ovn_destroy_tnlids(&dp_tnlids); - /* Delete southbound records without northbound matches. */ - LIST_FOR_EACH_SAFE (od, list, &sb_only) { - ovs_list_remove(&od->list); - sbrec_datapath_binding_delete(od->sb); - ovn_datapath_destroy(datapaths, od); - } - - /* Move lr datapaths to lr_datapaths, and ls datapaths will - * remain in datapaths/ls_datapaths. */ - HMAP_FOR_EACH_SAFE (od, key_node, datapaths) { - if (!od->nbr) { - ovs_assert(od->nbs); - continue; + struct ovn_datapath *od = + ovn_datapath_create(&lr_datapaths->datapaths, + &lr->nb->header_.uuid, + NULL, lr->nb, lr->sb); + if (smap_get(&od->nbr->options, "chassis")) { + od->is_gw_router = true; } - hmap_remove(datapaths, &od->key_node); - hmap_insert(&lr_datapaths->datapaths, &od->key_node, - od->key_node.hash); + od->dynamic_routing = smap_get_bool(&od->nbr->options, + "dynamic-routing", false); + od->dynamic_routing_redistribute = + parse_dynamic_routing_redistribute(&od->nbr->options, DRRM_NONE, + od->nbr->name); } ods_build_array_index(ls_datapaths); @@ -19304,10 +19024,8 @@ ovnnb_db_run(struct northd_input *input_data, vxlan_mode = input_data->vxlan_mode; - build_datapaths(ovnsb_txn, - input_data->nbrec_logical_switch_table, - input_data->nbrec_logical_router_table, - input_data->sbrec_datapath_binding_table, + build_datapaths(input_data->synced_lses, + input_data->synced_lrs, &data->ls_datapaths, &data->lr_datapaths); build_lb_datapaths(input_data->lbs, input_data->lbgrps, diff --git a/northd/northd.h b/northd/northd.h index 69143cd7a..e5a9cc775 100644 --- a/northd/northd.h +++ b/northd/northd.h @@ -70,6 +70,10 @@ struct northd_input { /* ACL ID inputs. */ const struct acl_id_data *acl_id_data; + /* Synced datapath inputs. */ + const struct ovn_synced_logical_switch_map *synced_lses; + const struct ovn_synced_logical_router_map *synced_lrs; + /* Indexes */ struct ovsdb_idl_index *sbrec_chassis_by_name; struct ovsdb_idl_index *sbrec_chassis_by_hostname; @@ -966,8 +970,6 @@ lr_has_multiple_gw_ports(const struct ovn_datapath *od) return vector_len(&od->l3dgw_ports) > 1 && !od->is_gw_router; } -uint32_t get_ovn_max_dp_key_local(bool _vxlan_mode, bool ic_mode); - /* Returns true if the logical router port 'enabled' column is empty or * set to true. Otherwise, returns false. */ static inline bool From patchwork Mon May 12 20:58:08 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Michelson X-Patchwork-Id: 2084695 X-Patchwork-Delegate: amusil@redhat.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Z5migtNy; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.136; helo=smtp3.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp3.osuosl.org (smtp3.osuosl.org [140.211.166.136]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4ZxBlW53Ssz1yPv for ; Tue, 13 May 2025 06:58:15 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 07F3760EC5; Mon, 12 May 2025 20:58:28 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id kV3eEkCQyqPU; Mon, 12 May 2025 20:58:27 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=140.211.9.56; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org BC36260EA1 Authentication-Results: smtp3.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Z5migtNy Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp3.osuosl.org (Postfix) with ESMTPS id BC36260EA1; Mon, 12 May 2025 20:58:26 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id A00CBC0AB7; Mon, 12 May 2025 20:58:26 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136]) by lists.linuxfoundation.org (Postfix) with ESMTP id 9EA49C0923 for ; Mon, 12 May 2025 20:58:24 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 93D6A60E89 for ; Mon, 12 May 2025 20:58:23 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id uNqf2AuSBCCh for ; Mon, 12 May 2025 20:58:23 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.129.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=mmichels@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp3.osuosl.org C3C5D60E85 Authentication-Results: smtp3.osuosl.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org C3C5D60E85 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by smtp3.osuosl.org (Postfix) with ESMTPS id C3C5D60E85 for ; Mon, 12 May 2025 20:58:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1747083501; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=DzodTXHNZhIS66Tl/5hHp/c9rBy+P2MmFPmyUKvoKwg=; b=Z5migtNyA83+H8K8VwfZNldlmH/fycQqsp3o9Y+Cqkt7qV55RYBjdKj6s1YQo8w/QKnpeb khfVlIVULEIH8CDKDeRCebtLSzvUr2EMAc6fDkCGGHYQpQAZaIqkDm+yQnotp553iSqeeh FOOZFfRD7VReaoJDy1lgY/Ph1ng3/CU= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-655-Aueodr61NYKeLXtRJnRpeA-1; Mon, 12 May 2025 16:58:20 -0400 X-MC-Unique: Aueodr61NYKeLXtRJnRpeA-1 X-Mimecast-MFC-AGG-ID: Aueodr61NYKeLXtRJnRpeA_1747083499 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id A65671956089 for ; Mon, 12 May 2025 20:58:19 +0000 (UTC) Received: from localhost.localdomain.com (unknown [10.22.58.17]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 3AF991943282 for ; Mon, 12 May 2025 20:58:19 +0000 (UTC) To: dev@openvswitch.org Date: Mon, 12 May 2025 16:58:08 -0400 Message-ID: <20250512205815.870519-5-mmichels@redhat.com> In-Reply-To: <20250512205815.870519-1-mmichels@redhat.com> References: <20250512205815.870519-1-mmichels@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: XwPKIl-II0NDMyQg8z0DtCb90eehd1qNSxQR2fo1AY0_1747083499 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH ovn v5 4/5] ovn.at: Change MAC Bindings used by check packet length test. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Mark Michelson via dev From: Mark Michelson Reply-To: Mark Michelson Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" The "check packet length" test creates two unorthodox MAC_Bindings. The two MAC Bindings' datapath values refer to southbound Datapath_Bindings for northbound Logical_Switches. Meanwhile, the MAC_Bindings' logical_port values refer to northbound Logical_Router_Ports. OVN makes the assumption that all MAC_Bindings' will refer to Datapath_Bindings corresponding to northbound Logical_Router_Ports. Therefore, if ovn-northd thinks it should clean up stale MAC Bindings, then it will delete any MAC_Binding whose datapath column refers to a Logical_Switch Datapath_Binding. The test passes because it never happens to trigger ovn-northd's MAC binding cleanup code. An upcoming patch is going to change the circumstances under which ovn-northd will clean up stale MAC Bindings, though. With this change, the test creates the MAC_Binding using the logical router Datapath_Binding instead. The test still passes, and now it will not have its MAC_Binding removed by ovn-northd when cleaning up stale MAC_Bindings. Signed-off-by: Mark Michelson Acked-by: Ales Musil --- v4 -> v5: * Rebased. v3 -> v4: * Rebased. v3: This is the first version with this patch present. --- tests/ovn.at | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ovn.at b/tests/ovn.at index 596a7be2b..9dd602adf 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -22206,7 +22206,7 @@ AT_CAPTURE_FILE([sbflows]) AT_CHECK([as hv1 ovs-ofctl dump-flows br-int \ | grep "check_pkt_larger" | wc -l], [0], [[0 ]]) -dp_uuid=$(ovn-sbctl find datapath_binding | grep sw0 -B2 | grep _uuid | \ +dp_uuid=$(ovn-sbctl find datapath_binding | grep lr0 -B2 | grep _uuid | \ awk '{print $3}') check_uuid ovn-sbctl create MAC_Binding ip=172.168.0.3 datapath=$dp_uuid \ logical_port=lr0-public mac="00\:00\:00\:12\:af\:11" @@ -22277,7 +22277,7 @@ check ovn-nbctl lr-nat-add lr1 snat 172.168.0.100 10.0.0.0/24 check ovn-nbctl lr-nat-add lr1 snat 2000::1 1000::/64 check ovn-nbctl --wait=sb sync -dp_uuid=$(ovn-sbctl find datapath_binding | grep sw0 -B2 | grep _uuid | \ +dp_uuid=$(ovn-sbctl find datapath_binding | grep lr1 -B2 | grep _uuid | \ awk '{print $3}') check_uuid ovn-sbctl create MAC_Binding ip=172.168.0.3 datapath=$dp_uuid \ logical_port=lr1-public mac="00\:00\:00\:12\:af\:11" From patchwork Mon May 12 20:58:09 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Michelson X-Patchwork-Id: 2084697 X-Patchwork-Delegate: amusil@redhat.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Atp61H3W; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::136; helo=smtp3.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4ZxBlp4fBrz1yPv for ; Tue, 13 May 2025 06:58:30 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 1068C60EC2; Mon, 12 May 2025 20:58:45 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 51YxQn1jQw_7; Mon, 12 May 2025 20:58:37 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org BA2B860EC7 Authentication-Results: smtp3.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Atp61H3W Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp3.osuosl.org (Postfix) with ESMTPS id BA2B860EC7; Mon, 12 May 2025 20:58:34 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 683BDC007B; Mon, 12 May 2025 20:58:34 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137]) by lists.linuxfoundation.org (Postfix) with ESMTP id 102BDC08A9 for ; Mon, 12 May 2025 20:58:33 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 672F240C0F for ; Mon, 12 May 2025 20:58:30 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 6-mOR3vrwiKo for ; Mon, 12 May 2025 20:58:26 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.133.124; helo=us-smtp-delivery-124.mimecast.com; envelope-from=mmichels@redhat.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp4.osuosl.org A15A540BF0 Authentication-Results: smtp4.osuosl.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org A15A540BF0 Authentication-Results: smtp4.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Atp61H3W Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by smtp4.osuosl.org (Postfix) with ESMTPS id A15A540BF0 for ; Mon, 12 May 2025 20:58:25 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1747083504; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=RbODVphijMfoMssFx4/bgtIIMwKGF+8afLGrPFqfAVA=; b=Atp61H3WEcFPdlmG+E9KLXRRf5233GZNE1gQq3uF7BPn0PvkBep+aIHzcC7JmEOfoTXZaH ZJcwg3fFW+Z8H+tzejWv7s2h3ceqFkx61gCSfRfMlHuqhRS+QUx/Mml0ar0LFoNkLYdIGe AIniSAx2J2hVHhezq39tXfWRxG4mfho= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-452-vOdcxs0POJi9g_Nm_FiDVw-1; Mon, 12 May 2025 16:58:21 -0400 X-MC-Unique: vOdcxs0POJi9g_Nm_FiDVw-1 X-Mimecast-MFC-AGG-ID: vOdcxs0POJi9g_Nm_FiDVw_1747083500 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 95F031800370 for ; Mon, 12 May 2025 20:58:20 +0000 (UTC) Received: from localhost.localdomain.com (unknown [10.22.58.17]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 035A81943282 for ; Mon, 12 May 2025 20:58:19 +0000 (UTC) To: dev@openvswitch.org Date: Mon, 12 May 2025 16:58:09 -0400 Message-ID: <20250512205815.870519-6-mmichels@redhat.com> In-Reply-To: <20250512205815.870519-1-mmichels@redhat.com> References: <20250512205815.870519-1-mmichels@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: P0Clgj7_v07GH5_utBy2tMQOhSctX8iZVWfv5ffNbpo_1747083500 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH ovn v5 5/5] northd: Refactor port binding pairing. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Mark Michelson via dev From: Mark Michelson Reply-To: Mark Michelson Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" This is similar to a recent change that refactored datapath syncing. This works in a very similar way. * Input nodes create type-agnostic ovn_unpaired_port_bindings. These are fed into the en-port-binding-pair node. * The en-port-binding-pair node ensures that a southbound Port_Binding is created for each unpaired port binding. Any remaining soutbound Port_Bindings are deleted. * Type-specific nodes then convert the paired port bindings into type-specific paired port bindings that can be consumed by other nodes. However, there are some important differences to note between this and the datapath sync patch: * This patch opts for the word "pair" instead of "sync". This is because en-port-binding-pair ensures that all southbound Port_Bindings are paired with some northbound structure. However, the columns in the southobund Port_Binding are not all synced with their northd counterpart. Other engine nodes are responsible for fully syncing the data. * Not all southbound Port_Bindings have a corresponding northbound row. Therefore, the engine nodes that create unpaired port bindings pass an opaque cookie pointer to the pairing node instead of a database row. * Port bindings tend to be identified by name. This means the code has to have several safeguards in place to catch scenarios such as a port "moving" from one datapath to another, or a port being deleted and re-added quickly. * Unlike with the datapath syncing code, this required changes to northd's incremental processing since northd was already incrementally processing some northbound logical switch port changes. Signed-off-by: Mark Michelson --- v4 -> v5: * Rebased. * Fixed some formatting anomalies in mirror port syncing. * Fixed checkpatch warnings. v3 -> v4: * Rebased. * Addressed newly-added mirror port functionality. * Fixed many memory leaks. v3: This is the first version with this patch present. --- TODO.rst | 12 + northd/automake.mk | 14 +- northd/en-global-config.c | 3 + northd/en-global-config.h | 1 + northd/en-northd.c | 49 +- northd/en-northd.h | 2 - northd/en-port-binding-chassisredirect.c | 319 ++++++++ northd/en-port-binding-chassisredirect.h | 53 ++ northd/en-port-binding-logical-router-port.c | 178 +++++ northd/en-port-binding-logical-router-port.h | 47 ++ northd/en-port-binding-logical-switch-port.c | 231 ++++++ northd/en-port-binding-logical-switch-port.h | 48 ++ northd/en-port-binding-mirror.c | 191 +++++ northd/en-port-binding-mirror.h | 48 ++ northd/en-port-binding-pair.c | 467 ++++++++++++ northd/en-port-binding-pair.h | 34 + northd/inc-proc-northd.c | 54 +- northd/northd.c | 746 +++++-------------- northd/northd.h | 17 +- northd/port_binding_pair.c | 81 ++ northd/port_binding_pair.h | 117 +++ 21 files changed, 2124 insertions(+), 588 deletions(-) create mode 100644 northd/en-port-binding-chassisredirect.c create mode 100644 northd/en-port-binding-chassisredirect.h create mode 100644 northd/en-port-binding-logical-router-port.c create mode 100644 northd/en-port-binding-logical-router-port.h create mode 100644 northd/en-port-binding-logical-switch-port.c create mode 100644 northd/en-port-binding-logical-switch-port.h create mode 100644 northd/en-port-binding-mirror.c create mode 100644 northd/en-port-binding-mirror.h create mode 100644 northd/en-port-binding-pair.c create mode 100644 northd/en-port-binding-pair.h create mode 100644 northd/port_binding_pair.c create mode 100644 northd/port_binding_pair.h diff --git a/TODO.rst b/TODO.rst index 78962bb92..60ae155c5 100644 --- a/TODO.rst +++ b/TODO.rst @@ -168,3 +168,15 @@ OVN To-do List ovn\_synced\_logical_router and ovn\_synced\_logical\_switch. This will allow for the eventual removal of the ovn\_datapath structure from the codebase. + +* Port Binding sync nodes + + * Southbound Port bindings are synced across three engine nodes: + - en_port_binding_pair + - en_northd + - en_sync_to_sb + It would be easier to work with if these were combined into a + single node instead. + + * Add incremental processing to the en-port-binding-pair node, as + well as derivative nodes. diff --git a/northd/automake.mk b/northd/automake.mk index bf9978dd2..f475e0cd9 100644 --- a/northd/automake.mk +++ b/northd/automake.mk @@ -54,6 +54,16 @@ northd_ovn_northd_SOURCES = \ northd/en-learned-route-sync.h \ northd/en-group-ecmp-route.c \ northd/en-group-ecmp-route.h \ + northd/en-port-binding-logical-router-port.c \ + northd/en-port-binding-logical-router-port.h \ + northd/en-port-binding-logical-switch-port.c \ + northd/en-port-binding-logical-switch-port.h \ + northd/en-port-binding-chassisredirect.c \ + northd/en-port-binding-chassisredirect.h \ + northd/en-port-binding-mirror.c \ + northd/en-port-binding-mirror.h \ + northd/en-port-binding-pair.c \ + northd/en-port-binding-pair.h \ northd/inc-proc-northd.c \ northd/inc-proc-northd.h \ northd/ipam.c \ @@ -61,7 +71,9 @@ northd_ovn_northd_SOURCES = \ northd/lflow-mgr.c \ northd/lflow-mgr.h \ northd/lb.c \ - northd/lb.h + northd/lb.h \ + northd/port_binding_pair.c \ + northd/port_binding_pair.h northd_ovn_northd_LDADD = \ lib/libovn.la \ $(OVSDB_LIBDIR)/libovsdb.la \ diff --git a/northd/en-global-config.c b/northd/en-global-config.c index 7204462ee..3a4bdbf87 100644 --- a/northd/en-global-config.c +++ b/northd/en-global-config.c @@ -148,6 +148,9 @@ en_global_config_run(struct engine_node *node , void *data) config_data->max_dp_tunnel_id = get_ovn_max_dp_key_local(config_data->vxlan_mode, ic_vxlan_mode); + uint8_t pb_tunnel_bits = config_data->vxlan_mode ? 12 : 16; + config_data->max_pb_tunnel_id = (1u << (pb_tunnel_bits - 1)) - 1; + char *max_tunid = xasprintf("%d", config_data->max_dp_tunnel_id); smap_replace(options, "max_tunid", max_tunid); free(max_tunid); diff --git a/northd/en-global-config.h b/northd/en-global-config.h index 55a1e420b..dbb06151c 100644 --- a/northd/en-global-config.h +++ b/northd/en-global-config.h @@ -51,6 +51,7 @@ struct ed_type_global_config { bool vxlan_mode; uint32_t max_dp_tunnel_id; + uint32_t max_pb_tunnel_id; bool tracked; struct global_config_tracked_data tracked_data; diff --git a/northd/en-northd.c b/northd/en-northd.c index c4573f88f..15840e361 100644 --- a/northd/en-northd.c +++ b/northd/en-northd.c @@ -123,6 +123,19 @@ northd_get_input_data(struct engine_node *node, input_data->synced_lrs = engine_get_input_data("datapath_synced_logical_router", node); + + input_data->paired_lsps = + engine_get_input_data("port_binding_paired_logical_switch_port", node); + + input_data->paired_lrps = + engine_get_input_data("port_binding_paired_logical_router_port", node); + + input_data->paired_crps = + engine_get_input_data("port_binding_paired_chassisredirect_port", + node); + + input_data->paired_mirrors = + engine_get_input_data("port_binding_paired_mirror", node); } enum engine_node_state @@ -477,42 +490,6 @@ en_northd_clear_tracked_data(void *data_) destroy_northd_data_tracked_changes(data); } -enum engine_input_handler_result -northd_sb_fdb_change_handler(struct engine_node *node, void *data) -{ - struct northd_data *nd = data; - const struct sbrec_fdb_table *sbrec_fdb_table = - EN_OVSDB_GET(engine_get_input("SB_fdb", node)); - - /* check if changed rows are stale and delete them */ - const struct sbrec_fdb *fdb_e, *fdb_prev_del = NULL; - SBREC_FDB_TABLE_FOR_EACH_TRACKED (fdb_e, sbrec_fdb_table) { - if (sbrec_fdb_is_deleted(fdb_e)) { - continue; - } - - if (fdb_prev_del) { - sbrec_fdb_delete(fdb_prev_del); - } - - fdb_prev_del = fdb_e; - struct ovn_datapath *od - = ovn_datapath_find_by_key(&nd->ls_datapaths.datapaths, - fdb_e->dp_key); - if (od) { - if (ovn_tnlid_present(&od->port_tnlids, fdb_e->port_key)) { - fdb_prev_del = NULL; - } - } - } - - if (fdb_prev_del) { - sbrec_fdb_delete(fdb_prev_del); - } - - return EN_HANDLED_UNCHANGED; -} - void en_route_policies_cleanup(void *data) { diff --git a/northd/en-northd.h b/northd/en-northd.h index b19b73270..58a524c5c 100644 --- a/northd/en-northd.h +++ b/northd/en-northd.h @@ -25,8 +25,6 @@ enum engine_input_handler_result northd_sb_port_binding_handler(struct engine_node *, void *data); enum engine_input_handler_result northd_lb_data_handler(struct engine_node *, void *data); -enum engine_input_handler_result -northd_sb_fdb_change_handler(struct engine_node *node, void *data); void *en_routes_init(struct engine_node *node OVS_UNUSED, struct engine_arg *arg OVS_UNUSED); void en_route_policies_cleanup(void *data); diff --git a/northd/en-port-binding-chassisredirect.c b/northd/en-port-binding-chassisredirect.c new file mode 100644 index 000000000..5b2e7a9e4 --- /dev/null +++ b/northd/en-port-binding-chassisredirect.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include "inc-proc-eng.h" +#include "ovn-nb-idl.h" +#include "en-datapath-logical-router.h" +#include "en-datapath-logical-switch.h" +#include "en-port-binding-chassisredirect.h" +#include "port_binding_pair.h" +#include "ovn-util.h" + +#include "openvswitch/vlog.h" + +#include "hmapx.h" + +#define CR_SWITCH_PORT_TYPE "chassisredirect-logical-switch-port" +#define CR_ROUTER_PORT_TYPE "chassisredirect-logical-router-port" + +VLOG_DEFINE_THIS_MODULE(en_port_binding_chassisredirect_port); + +struct router_dgps { + const struct ovn_synced_logical_router *lr; + const struct nbrec_logical_router_port **dgps; + size_t n_dgps; +}; + +struct switch_localnets { + const struct ovn_synced_logical_switch *ls; + size_t n_localnet_ports; +}; + +struct port_router_dgps { + const struct nbrec_logical_router_port *nbrp; + struct router_dgps *r; +}; + +static struct port_router_dgps * +port_router_dgps_alloc(const struct nbrec_logical_router_port *nbrp, + struct router_dgps *r) +{ + struct port_router_dgps *p_dgps = xmalloc(sizeof *p_dgps); + p_dgps->nbrp = nbrp; + p_dgps->r = r; + + return p_dgps; +} + +void * +en_port_binding_chassisredirect_port_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *args OVS_UNUSED) +{ + struct ovn_unpaired_port_binding_map *map = xzalloc(sizeof *map); + ovn_unpaired_port_binding_map_init(map, NULL); + return map; +} + +struct chassisredirect_router_port { + char *name; + const struct nbrec_logical_router_port *nbrp; +}; + +static struct chassisredirect_router_port * +chassisredirect_router_port_alloc(const struct nbrec_logical_router_port *nbrp) +{ + struct chassisredirect_router_port *crp = xmalloc(sizeof *crp); + crp->name = ovn_chassis_redirect_name(nbrp->name); + crp->nbrp = nbrp; + + return crp; +} + +static void +chassisredirect_router_port_free(struct chassisredirect_router_port *crp) +{ + free(crp->name); + free(crp); +} + +struct chassisredirect_switch_port { + char *name; + const struct nbrec_logical_switch_port *nbsp; +}; + +static struct chassisredirect_switch_port * +chassisredirect_switch_port_alloc(const struct nbrec_logical_switch_port *nbsp) +{ + struct chassisredirect_switch_port *crp = xmalloc(sizeof *crp); + crp->name = ovn_chassis_redirect_name(nbsp->name); + crp->nbsp = nbsp; + + return crp; +} + +static void +chassisredirect_switch_port_free(struct chassisredirect_switch_port *csp) +{ + free(csp->name); + free(csp); +} + +static void +chassisredirect_port_binding_map_destroy( + struct ovn_unpaired_port_binding_map *map) +{ + struct shash_node *node; + SHASH_FOR_EACH (node, &map->ports) { + struct ovn_unpaired_port_binding *upb = node->data; + if (!strcmp(upb->type, CR_ROUTER_PORT_TYPE)) { + chassisredirect_router_port_free(upb->cookie); + } else { + chassisredirect_switch_port_free(upb->cookie); + } + } + ovn_unpaired_port_binding_map_destroy(map); +} + +enum engine_node_state +en_port_binding_chassisredirect_port_run(struct engine_node *node, void *data) +{ + const struct ovn_synced_logical_router_map *lr_map = + engine_get_input_data("datapath_synced_logical_router", node); + const struct ovn_synced_logical_switch_map *ls_map = + engine_get_input_data("datapath_synced_logical_switch", node); + + struct ovn_unpaired_port_binding_map *map = data; + + chassisredirect_port_binding_map_destroy(map); + ovn_unpaired_port_binding_map_init(map, NULL); + + struct shash ports = SHASH_INITIALIZER(&ports); + const struct ovn_synced_logical_router *lr; + struct hmapx all_rdgps = HMAPX_INITIALIZER(&all_rdgps); + HMAP_FOR_EACH (lr, hmap_node, &lr_map->synced_routers) { + if (smap_get(&lr->nb->options, "chassis")) { + /* If the logical router has the chassis option set, + * then we ignore any ports that have gateway_chassis + * or ha_chassis_group options set. + */ + continue; + } + struct router_dgps *rdgps = xzalloc(sizeof *rdgps); + rdgps->lr = lr; + rdgps->dgps = xzalloc(sizeof(*rdgps->dgps) * lr->nb->n_ports); + hmapx_add(&all_rdgps, rdgps); + const struct nbrec_logical_router_port *nbrp; + for (size_t i = 0; i < lr->nb->n_ports; i++) { + nbrp = lr->nb->ports[i]; + if (nbrp->ha_chassis_group || nbrp->n_gateway_chassis) { + rdgps->dgps[rdgps->n_dgps++] = nbrp; + shash_add(&ports, nbrp->name, + port_router_dgps_alloc(nbrp, rdgps)); + } + } + } + + struct hmapx all_localnets = HMAPX_INITIALIZER(&all_localnets); + const struct ovn_synced_logical_switch *ls; + HMAP_FOR_EACH (ls, hmap_node, &ls_map->synced_switches) { + struct switch_localnets *localnets = xzalloc(sizeof *localnets); + localnets->ls = ls; + hmapx_add(&all_localnets, localnets); + for (size_t i = 0; i < ls->nb->n_ports; i++) { + const struct nbrec_logical_switch_port *nbsp = ls->nb->ports[i]; + if (!strcmp(nbsp->type, "localnet")) { + localnets->n_localnet_ports++; + } + } + } + + /* All logical router DGPs need corresponding chassisredirect ports + * made + */ + struct hmapx_node *hmapx_node; + HMAPX_FOR_EACH (hmapx_node, &all_rdgps) { + struct router_dgps *rdgps = hmapx_node->data; + struct ovn_unpaired_port_binding *upb; + for (size_t i = 0; i < rdgps->n_dgps; i++) { + const struct nbrec_logical_router_port *nbrp = rdgps->dgps[i]; + struct chassisredirect_router_port *crp = + chassisredirect_router_port_alloc(nbrp); + upb = ovn_unpaired_port_binding_alloc(0, crp->name, + CR_ROUTER_PORT_TYPE, + crp, rdgps->lr->sb); + shash_add(&map->ports, crp->name, upb); + } + } + + /* Logical switch ports that are peered with DGPs need chassisredirect + * ports created if + * 1. The DGP it is paired with is the only one on its router, and + * 2. There are no localnet ports on the switch + */ + HMAPX_FOR_EACH (hmapx_node, &all_localnets) { + struct switch_localnets *localnets = hmapx_node->data; + if (localnets->n_localnet_ports > 0) { + continue; + } + for (size_t i = 0; i < localnets->ls->nb->n_ports; i++) { + const struct nbrec_logical_switch_port *nbsp = + localnets->ls->nb->ports[i]; + if (strcmp(nbsp->type, "router")) { + continue; + } + const char *peer_name = smap_get( ->options, "router-port"); + if (!peer_name) { + continue; + } + struct port_router_dgps *prdgps = shash_find_data(&ports, + peer_name); + if (!prdgps) { + continue; + } + if (prdgps->r->n_dgps > 1) { + continue; + } + struct ovn_unpaired_port_binding *upb; + struct chassisredirect_switch_port *crp = + chassisredirect_switch_port_alloc(nbsp); + upb = ovn_unpaired_port_binding_alloc(0, crp->name, + CR_SWITCH_PORT_TYPE, + crp, localnets->ls->sb); + shash_add(&map->ports, crp->name, upb); + } + } + + return EN_UPDATED; +} + +void +en_port_binding_chassisredirect_port_cleanup(void *data) +{ + struct ovn_unpaired_port_binding_map *map = data; + chassisredirect_port_binding_map_destroy(map); +} + + +static void +ovn_paired_chassisredirect_port_map_init( + struct ovn_paired_chassisredirect_port_map *map) +{ + shash_init(&map->paired_chassisredirect_router_ports); + shash_init(&map->paired_chassisredirect_switch_ports); +} + +static void +ovn_paired_chassisredirect_port_map_destroy( + struct ovn_paired_chassisredirect_port_map *map) +{ + shash_destroy_free_data(&map->paired_chassisredirect_switch_ports); + shash_destroy_free_data(&map->paired_chassisredirect_router_ports); +} + +void * +en_port_binding_paired_chassisredirect_port_init( + struct engine_node *node OVS_UNUSED, struct engine_arg *args OVS_UNUSED) +{ + struct ovn_paired_chassisredirect_port_map *map = xzalloc(sizeof *map); + ovn_paired_chassisredirect_port_map_init(map); + return map; +} + +enum engine_node_state +en_port_binding_paired_chassisredirect_port_run(struct engine_node *node, + void *data) +{ + const struct ovn_paired_port_bindings *pbs = + engine_get_input_data("port_binding_pair", node); + struct ovn_paired_chassisredirect_port_map *map = data; + + ovn_paired_chassisredirect_port_map_destroy(map); + ovn_paired_chassisredirect_port_map_init(map); + + struct ovn_paired_port_binding *port; + LIST_FOR_EACH (port, list_node, &pbs->paired_pbs) { + if (!strcmp(port->type, CR_SWITCH_PORT_TYPE)) { + const struct chassisredirect_switch_port *cr_port = port->cookie; + struct ovn_paired_chassisredirect_switch_port *paired_cr_port; + paired_cr_port = xmalloc(sizeof *cr_port); + paired_cr_port->name = cr_port->name; + paired_cr_port->sb = port->sb_pb; + paired_cr_port->primary_port = cr_port->nbsp; + shash_add(&map->paired_chassisredirect_switch_ports, cr_port->name, + paired_cr_port); + } else if (!strcmp(port->type, CR_ROUTER_PORT_TYPE)) { + const struct chassisredirect_router_port *cr_port = port->cookie; + struct ovn_paired_chassisredirect_router_port *paired_cr_port; + paired_cr_port = xmalloc(sizeof *cr_port); + paired_cr_port->name = cr_port->name; + paired_cr_port->sb = port->sb_pb; + paired_cr_port->primary_port = cr_port->nbrp; + shash_add(&map->paired_chassisredirect_router_ports, cr_port->name, + paired_cr_port); + } + } + + return EN_UPDATED; +} + +void +en_port_binding_paired_chassisredirect_port_cleanup(void *data) +{ + struct ovn_paired_chassisredirect_port_map *map = data; + + ovn_paired_chassisredirect_port_map_destroy(map); +} diff --git a/northd/en-port-binding-chassisredirect.h b/northd/en-port-binding-chassisredirect.h new file mode 100644 index 000000000..bbea31993 --- /dev/null +++ b/northd/en-port-binding-chassisredirect.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EN_PORT_BINDING_CHASSISREDIRECT_PORT_H +#define EN_PORT_BINDING_CHASSISREDIRECT_PORT_H + +#include "lib/inc-proc-eng.h" +#include "openvswitch/shash.h" + + +void *en_port_binding_chassisredirect_port_init(struct engine_node *, + struct engine_arg *); + +enum engine_node_state en_port_binding_chassisredirect_port_run( + struct engine_node *, void *data); +void en_port_binding_chassisredirect_port_cleanup(void *data); + +struct ovn_paired_chassisredirect_switch_port { + const char *name; + const struct nbrec_logical_switch_port *primary_port; + const struct sbrec_port_binding *sb; +}; + +struct ovn_paired_chassisredirect_router_port { + const char *name; + const struct nbrec_logical_router_port *primary_port; + const struct sbrec_port_binding *sb; +}; + +struct ovn_paired_chassisredirect_port_map { + struct shash paired_chassisredirect_router_ports; + struct shash paired_chassisredirect_switch_ports; +}; + +void *en_port_binding_paired_chassisredirect_port_init(struct engine_node *, + struct engine_arg *); +enum engine_node_state en_port_binding_paired_chassisredirect_port_run( + struct engine_node *, void *data); +void en_port_binding_paired_chassisredirect_port_cleanup(void *data); +#endif /* EN_PORT_BINDING_CHASSISREDIRECT_PORT_H */ diff --git a/northd/en-port-binding-logical-router-port.c b/northd/en-port-binding-logical-router-port.c new file mode 100644 index 000000000..07cd9ac26 --- /dev/null +++ b/northd/en-port-binding-logical-router-port.c @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "openvswitch/hmap.h" +#include "openvswitch/vlog.h" +#include "util.h" + +#include "inc-proc-eng.h" +#include "ovn-nb-idl.h" +#include "port_binding_pair.h" +#include "en-datapath-logical-router.h" +#include "en-port-binding-logical-router-port.h" + +#define LRP_TYPE_NAME "logical-router-port" + +VLOG_DEFINE_THIS_MODULE(en_port_binding_logical_router_port); + +struct logical_router_port_cookie { + const struct nbrec_logical_router_port *nbrp; + const struct ovn_synced_logical_router *router; +}; + +static struct logical_router_port_cookie * +logical_router_port_cookie_alloc(const struct nbrec_logical_router_port *nbrp, + const struct ovn_synced_logical_router *lr) +{ + struct logical_router_port_cookie *cookie = xmalloc(sizeof *cookie); + cookie->nbrp = nbrp; + cookie->router = lr; + + return cookie; +} + +static void +logical_router_port_cookie_free(struct logical_router_port_cookie *cookie) +{ + free(cookie); +} + +static void +unpaired_logical_router_port_map_destroy( + struct ovn_unpaired_port_binding_map *map) +{ + struct shash_node *node; + SHASH_FOR_EACH (node, &map->ports) { + struct ovn_unpaired_port_binding *upb = node->data; + logical_router_port_cookie_free(upb->cookie); + } + ovn_unpaired_port_binding_map_destroy(map); +} + +void * +en_port_binding_logical_router_port_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *args OVS_UNUSED) +{ + struct ovn_unpaired_port_binding_map *map = xzalloc(sizeof *map); + ovn_unpaired_port_binding_map_init(map, NULL); + return map; +} + +enum engine_node_state +en_port_binding_logical_router_port_run(struct engine_node *node, void *data) +{ + const struct ovn_synced_logical_router_map *lr_map = + engine_get_input_data("datapath_synced_logical_router", node); + + struct ovn_unpaired_port_binding_map *map = data; + + unpaired_logical_router_port_map_destroy(map); + ovn_unpaired_port_binding_map_init(map, NULL); + + const struct ovn_synced_logical_router *paired_lr; + HMAP_FOR_EACH (paired_lr, hmap_node, &lr_map->synced_routers) { + const struct nbrec_logical_router_port *nbrp; + for (size_t i = 0; i < paired_lr->nb->n_ports; i++) { + nbrp = paired_lr->nb->ports[i]; + uint32_t requested_tunnel_key = smap_get_int(&nbrp->options, + "requested-tnl-key", + 0); + struct logical_router_port_cookie *cookie = + logical_router_port_cookie_alloc(nbrp, paired_lr); + struct ovn_unpaired_port_binding *upb; + upb = ovn_unpaired_port_binding_alloc(requested_tunnel_key, + nbrp->name, + LRP_TYPE_NAME, + cookie, + paired_lr->sb); + smap_clone(&upb->external_ids, &nbrp->external_ids); + if (!shash_add_once(&map->ports, nbrp->name, upb)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "duplicate logical router port %s", + nbrp->name); + } + } + } + + return EN_UPDATED; +} + +void +en_port_binding_logical_router_port_cleanup(void *data) +{ + struct ovn_unpaired_port_binding_map *map = data; + unpaired_logical_router_port_map_destroy(map); +} + +static void +paired_logical_router_port_map_init( + struct ovn_paired_logical_router_port_map *router_port_map) +{ + shash_init(&router_port_map->paired_router_ports); +} + +static void +paired_logical_router_port_map_destroy( + struct ovn_paired_logical_router_port_map *router_port_map) +{ + shash_destroy_free_data(&router_port_map->paired_router_ports); +} + +void * +en_port_binding_paired_logical_router_port_init( + struct engine_node *node OVS_UNUSED, struct engine_arg *args OVS_UNUSED) +{ + struct ovn_paired_logical_router_port_map *router_port_map; + router_port_map = xzalloc(sizeof *router_port_map); + paired_logical_router_port_map_init(router_port_map); + + return router_port_map; +} + +enum engine_node_state +en_port_binding_paired_logical_router_port_run(struct engine_node *node, + void *data) +{ + const struct ovn_paired_port_bindings *pbs = + engine_get_input_data("port_binding_pair", node); + struct ovn_paired_logical_router_port_map *router_port_map = data; + + paired_logical_router_port_map_destroy(router_port_map); + paired_logical_router_port_map_init(router_port_map); + + struct ovn_paired_port_binding *spb; + LIST_FOR_EACH (spb, list_node, &pbs->paired_pbs) { + if (strcmp(spb->type, LRP_TYPE_NAME)) { + continue; + } + const struct logical_router_port_cookie *cookie = spb->cookie; + struct ovn_paired_logical_router_port *lrp = xzalloc(sizeof *lrp); + lrp->nb = cookie->nbrp; + lrp->router = cookie->router; + lrp->sb = spb->sb_pb; + shash_add(&router_port_map->paired_router_ports, lrp->nb->name, lrp); + } + + return EN_UPDATED; +} + +void en_port_binding_paired_logical_router_port_cleanup(void *data) +{ + struct ovn_paired_logical_router_port_map *map = data; + paired_logical_router_port_map_destroy(map); +} diff --git a/northd/en-port-binding-logical-router-port.h b/northd/en-port-binding-logical-router-port.h new file mode 100644 index 000000000..156a25da6 --- /dev/null +++ b/northd/en-port-binding-logical-router-port.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EN_PORT_BINDING_LOGICAL_ROUTER_PORT_H +#define EN_PORT_BINDING_LOGICAL_ROUTER_PORT_H + +#include "lib/inc-proc-eng.h" +#include "openvswitch/shash.h" + +void *en_port_binding_logical_router_port_init(struct engine_node *, + struct engine_arg *); + +enum engine_node_state en_port_binding_logical_router_port_run( + struct engine_node *, void *data); +void en_port_binding_logical_router_port_cleanup(void *data); + +struct ovn_paired_logical_router_port { + const struct nbrec_logical_router_port *nb; + const struct sbrec_port_binding *sb; + const struct ovn_synced_logical_router *router; +}; + +struct ovn_paired_logical_router_port_map { + struct shash paired_router_ports; +}; + +void *en_port_binding_paired_logical_router_port_init(struct engine_node *, + struct engine_arg *); + +enum engine_node_state en_port_binding_paired_logical_router_port_run( + struct engine_node *, void *data); +void en_port_binding_paired_logical_router_port_cleanup(void *data); + +#endif /* EN_PORT_BINDING_LOGICAL_ROUTER_PORT_H */ diff --git a/northd/en-port-binding-logical-switch-port.c b/northd/en-port-binding-logical-switch-port.c new file mode 100644 index 000000000..d4fcece5f --- /dev/null +++ b/northd/en-port-binding-logical-switch-port.c @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "openvswitch/hmap.h" +#include "openvswitch/vlog.h" +#include "util.h" + +#include "inc-proc-eng.h" +#include "ovn-nb-idl.h" +#include "ovn-sb-idl.h" +#include "port_binding_pair.h" +#include "en-datapath-logical-switch.h" +#include "en-port-binding-logical-switch-port.h" +#include "northd.h" + +#define LSP_TYPE_NAME "logical-switch-port" + +VLOG_DEFINE_THIS_MODULE(en_port_binding_logical_switch_port); + +struct logical_switch_port_cookie { + const struct nbrec_logical_switch_port *nbsp; + const struct ovn_synced_logical_switch *sw; +}; + +static struct logical_switch_port_cookie * +logical_switch_port_cookie_alloc(const struct nbrec_logical_switch_port *nbsp, + const struct ovn_synced_logical_switch *sw) +{ + struct logical_switch_port_cookie *cookie = xmalloc(sizeof *cookie); + cookie->nbsp = nbsp; + cookie->sw = sw; + return cookie; +} + +static void +logical_switch_port_cookie_free(struct logical_switch_port_cookie *cookie) +{ + free(cookie); +} + +static bool +switch_port_sb_is_valid(const struct sbrec_port_binding *sb_pb, + const struct ovn_unpaired_port_binding *upb) +{ + const struct logical_switch_port_cookie *cookie = upb->cookie; + + bool update_sbrec = false; + if (lsp_is_type_changed(sb_pb, cookie->nbsp, &update_sbrec) + && update_sbrec) { + return false; + } + + return true; +} + +struct ovn_unpaired_port_binding_map_callbacks switch_port_callbacks = { + .sb_is_valid = switch_port_sb_is_valid, +}; + +static void +unpaired_logical_switch_port_map_destroy( + struct ovn_unpaired_port_binding_map *map) +{ + struct shash_node *node; + SHASH_FOR_EACH (node, &map->ports) { + struct ovn_unpaired_port_binding *upb = node->data; + logical_switch_port_cookie_free(upb->cookie); + } + ovn_unpaired_port_binding_map_destroy(map); +} + +void * +en_port_binding_logical_switch_port_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *args OVS_UNUSED) +{ + struct ovn_unpaired_port_binding_map *map = xzalloc(sizeof *map); + ovn_unpaired_port_binding_map_init(map, &switch_port_callbacks); + return map; +} + +enum engine_node_state +en_port_binding_logical_switch_port_run(struct engine_node *node, void *data) +{ + const struct ovn_synced_logical_switch_map *ls_map = + engine_get_input_data("datapath_synced_logical_switch", node); + + struct ovn_unpaired_port_binding_map *map = data; + + unpaired_logical_switch_port_map_destroy(map); + ovn_unpaired_port_binding_map_init(map, &switch_port_callbacks); + + const struct ovn_synced_logical_switch *paired_ls; + HMAP_FOR_EACH (paired_ls, hmap_node, &ls_map->synced_switches) { + const struct nbrec_logical_switch_port *nbsp; + for (size_t i = 0; i < paired_ls->nb->n_ports; i++) { + nbsp = paired_ls->nb->ports[i]; + uint32_t requested_tunnel_key = smap_get_int( ->options, + "requested-tnl-key", + 0); + struct logical_switch_port_cookie *cookie = + logical_switch_port_cookie_alloc(nbsp, paired_ls); + struct ovn_unpaired_port_binding *upb; + upb = ovn_unpaired_port_binding_alloc(requested_tunnel_key, + nbsp->name, + LSP_TYPE_NAME, + cookie, + paired_ls->sb); + smap_clone(&upb->external_ids,  ->external_ids); + const char *name = smap_get( ->external_ids, + "neutron:port_name"); + if (name && name[0]) { + smap_add(&upb->external_ids, "name", name); + } + if (!shash_add_once(&map->ports, nbsp->name, upb)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "duplicate logical port %s", nbsp->name); + } + } + } + return EN_UPDATED; +} + +void +en_port_binding_logical_switch_port_cleanup(void *data) +{ + struct ovn_unpaired_port_binding_map *map = data; + unpaired_logical_switch_port_map_destroy(map); +} + +static void +paired_logical_switch_port_map_init( + struct ovn_paired_logical_switch_port_map *switch_port_map) +{ + shash_init(&switch_port_map->paired_switch_ports); +} + +static void +paired_logical_switch_port_map_destroy( + struct ovn_paired_logical_switch_port_map *switch_port_map) +{ + shash_destroy_free_data(&switch_port_map->paired_switch_ports); +} + +void * +en_port_binding_paired_logical_switch_port_init( + struct engine_node *node OVS_UNUSED, struct engine_arg *args OVS_UNUSED) +{ + struct ovn_paired_logical_switch_port_map *switch_port_map; + switch_port_map = xzalloc(sizeof *switch_port_map); + paired_logical_switch_port_map_init(switch_port_map); + + return switch_port_map; +} + +enum engine_node_state +en_port_binding_paired_logical_switch_port_run(struct engine_node *node, + void *data) +{ + const struct ovn_paired_port_bindings *pbs = + engine_get_input_data("port_binding_pair", node); + struct ovn_paired_logical_switch_port_map *switch_port_map = data; + + paired_logical_switch_port_map_destroy(switch_port_map); + paired_logical_switch_port_map_init(switch_port_map); + + struct ovn_paired_port_binding *spb; + LIST_FOR_EACH (spb, list_node, &pbs->paired_pbs) { + if (strcmp(spb->type, LSP_TYPE_NAME)) { + continue; + } + const struct logical_switch_port_cookie *cookie = spb->cookie; + struct ovn_paired_logical_switch_port *lsw = xzalloc(sizeof *lsw); + lsw->nb = cookie->nbsp; + lsw->sw = cookie->sw; + lsw->sb = spb->sb_pb; + shash_add(&switch_port_map->paired_switch_ports, lsw->nb->name, lsw); + + /* This deals with a special case where a logical switch port is + * removed and added back very quickly. The sequence of events is as + * follows: + * 1) NB Logical_Switch_Port "lsp" is added to the NB DB. + * 2) en-port-binding-pair creates a corresponding SB Port_Binding. + * 3) The user binds the port to a hypervisor. + * 4) ovn-controller on the hypervisor sets the SB Port_Binding "up" + * column to "true". + * 5) ovn-northd sets the Logical_Switch_Port "up" column to "true". + * 6) A user deletes and then re-adds "lsp" back to the NB + * Logical_Switch_Port column very quickly, so quickly that we + * do not detect the deletion at all. + * 7) The new northbound Logical_Switch_Port has its "up" column + * empty (i.e. not "true") since it is new. + * 8) The pairing code matches the new Logical_Switch_Port with the + * existing Port_Binding for "lsp" since the pairing code matches + * using the name of the Logical_Switch_Port. + * + * At this point, the SB Port_Binding's "up" column is set "true", + * but the NB Logical_Switch_Port's "up" column is not. We need to + * ensure the NB Logical_Switch_Port's "up" column is set to "true" + * as well. + * + * In most cases, setting the NB Logical_Switch_Port "up" column to + * true is accomplished when changes on the SB Port_Binding are + * detected. But in this rare case, there is no SB Port_Binding + * change, so the "up" column is unserviced. + */ + lsp_set_up(lsw->sb, lsw->nb); + } + + return EN_UPDATED; +} + +void en_port_binding_paired_logical_switch_port_cleanup(void *data) +{ + struct ovn_paired_logical_switch_port_map *map = data; + paired_logical_switch_port_map_destroy(map); +} diff --git a/northd/en-port-binding-logical-switch-port.h b/northd/en-port-binding-logical-switch-port.h new file mode 100644 index 000000000..9ef32ce88 --- /dev/null +++ b/northd/en-port-binding-logical-switch-port.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EN_PORT_BINDING_LOGICAL_SWITCH_PORT_H +#define EN_PORT_BINDING_LOGICAL_SWITCH_PORT_H + +#include "lib/inc-proc-eng.h" +#include "openvswitch/shash.h" + + +void *en_port_binding_logical_switch_port_init(struct engine_node *, + struct engine_arg *); + +enum engine_node_state en_port_binding_logical_switch_port_run( + struct engine_node *, void *data); +void en_port_binding_logical_switch_port_cleanup(void *data); + +struct ovn_paired_logical_switch_port { + const struct nbrec_logical_switch_port *nb; + const struct sbrec_port_binding *sb; + const struct ovn_synced_logical_switch *sw; +}; + +struct ovn_paired_logical_switch_port_map { + struct shash paired_switch_ports; +}; + +void *en_port_binding_paired_logical_switch_port_init(struct engine_node *, + struct engine_arg *); + +enum engine_node_state en_port_binding_paired_logical_switch_port_run( + struct engine_node *, void *data); +void en_port_binding_paired_logical_switch_port_cleanup(void *data); + +#endif /* EN_PORT_BINDING_LOGICAL_SWITCH_PORT_H */ diff --git a/northd/en-port-binding-mirror.c b/northd/en-port-binding-mirror.c new file mode 100644 index 000000000..6e8647dba --- /dev/null +++ b/northd/en-port-binding-mirror.c @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include "ovn-util.h" +#include "lib/inc-proc-eng.h" +#include "ovn-nb-idl.h" +#include "en-datapath-logical-switch.h" +#include "en-port-binding-mirror.h" +#include "port_binding_pair.h" +#include "northd.h" +#include "openvswitch/vlog.h" + +#define MIRROR_PORT_TYPE "mirror" + +VLOG_DEFINE_THIS_MODULE(en_port_binding_mirror); + +struct mirror_port { + char *name; + const char *sink; + const struct nbrec_logical_switch_port *nbsp; +}; + +static struct mirror_port * +mirror_port_alloc(const struct sbrec_datapath_binding *sb, const char *sink, + const struct nbrec_logical_switch_port *nbsp) +{ + struct mirror_port *mp = xzalloc(sizeof *mp); + mp->name = ovn_mirror_port_name(ovn_datapath_name(sb), sink); + mp->sink = sink; + mp->nbsp = nbsp; + + return mp; +} + +static void +mirror_port_free(struct mirror_port *mp) +{ + free(mp->name); + free(mp); +} + +static void +unpaired_mirror_map_destroy(struct ovn_unpaired_port_binding_map *map) +{ + struct shash_node *node; + SHASH_FOR_EACH (node, &map->ports) { + struct ovn_unpaired_port_binding *upb = node->data; + mirror_port_free(upb->cookie); + } + ovn_unpaired_port_binding_map_destroy(map); +} + +void * +en_port_binding_mirror_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + struct ovn_unpaired_port_binding_map *map = xzalloc(sizeof *map); + ovn_unpaired_port_binding_map_init(map, NULL); + return map; +} + +enum engine_node_state +en_port_binding_mirror_run(struct engine_node *node, void *data) +{ + const struct ovn_synced_logical_switch_map *ls_map = + engine_get_input_data("datapath_synced_logical_switch", node); + struct ovn_unpaired_port_binding_map *map = data; + + unpaired_mirror_map_destroy(map); + ovn_unpaired_port_binding_map_init(map, NULL); + + /* Typically, we'd use an ovsdb_idl_index to search for a specific record + * based on a column value. However, we currently are not monitoring + * the Logical_Switch_Port table at all in ovn-northd. Introducing + * this monitoring is likely more computationally intensive than + * making an on-the-fly sset of logical switch port names. + */ + struct sset all_switch_ports = SSET_INITIALIZER(&all_switch_ports); + const struct ovn_synced_logical_switch *ls; + HMAP_FOR_EACH (ls, hmap_node, &ls_map->synced_switches) { + for (size_t i = 0; i < ls->nb->n_ports; i++) { + sset_add(&all_switch_ports, ls->nb->ports[i]->name); + } + } + + HMAP_FOR_EACH (ls, hmap_node, &ls_map->synced_switches) { + for (size_t i = 0; i < ls->nb->n_ports; i++) { + const struct nbrec_logical_switch_port *nbsp = ls->nb->ports[i]; + for (size_t j = 0; j < nbsp->n_mirror_rules; j++) { + struct nbrec_mirror *nb_mirror = nbsp->mirror_rules[j]; + if (strcmp(nb_mirror->type, "lport")) { + continue; + } + if (!sset_find(&all_switch_ports, nb_mirror->sink)) { + continue; + } + struct mirror_port *mp = mirror_port_alloc(ls->sb, + nb_mirror->sink, + nbsp); + struct ovn_unpaired_port_binding *upb; + upb = ovn_unpaired_port_binding_alloc(0, mp->name, + MIRROR_PORT_TYPE, mp, + ls->sb); + shash_add(&map->ports, mp->name, upb); + } + } + } + sset_destroy(&all_switch_ports); + + return EN_UPDATED; +} + +void +en_port_binding_mirror_cleanup(void *data) +{ + struct ovn_unpaired_port_binding_map *map = data; + unpaired_mirror_map_destroy(map); +} + +static void +ovn_paired_mirror_map_init( + struct ovn_paired_mirror_map *map) +{ + shash_init(&map->paired_mirror_ports); +} + +static void +ovn_paired_mirror_map_destroy( + struct ovn_paired_mirror_map *map) +{ + shash_destroy_free_data(&map->paired_mirror_ports); +} + +void * +en_port_binding_paired_mirror_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + struct ovn_paired_mirror_map *map = xzalloc(sizeof *map); + ovn_paired_mirror_map_init(map); + return map; +} + +enum engine_node_state +en_port_binding_paired_mirror_run(struct engine_node *node, + void *data) +{ + const struct ovn_paired_port_bindings *pbs = + engine_get_input_data("port_binding_pair", node); + struct ovn_paired_mirror_map *map = data; + + ovn_paired_mirror_map_destroy(map); + ovn_paired_mirror_map_init(map); + + struct ovn_paired_port_binding *port; + LIST_FOR_EACH (port, list_node, &pbs->paired_pbs) { + if (strcmp(port->type, MIRROR_PORT_TYPE)) { + continue; + } + const struct mirror_port *mp = port->cookie; + struct ovn_paired_mirror *opm = xmalloc(sizeof *opm); + opm->name = mp->name; + opm->sink = mp->sink; + opm->sb = port->sb_pb; + opm->nbsp = mp->nbsp; + shash_add(&map->paired_mirror_ports, opm->name, opm); + } + + return EN_UPDATED; +} + +void +en_port_binding_paired_mirror_cleanup(void *data) +{ + struct ovn_paired_mirror_map *map = data; + + ovn_paired_mirror_map_destroy(map); +} + diff --git a/northd/en-port-binding-mirror.h b/northd/en-port-binding-mirror.h new file mode 100644 index 000000000..a4bf2645a --- /dev/null +++ b/northd/en-port-binding-mirror.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EN_PORT_BINDING_MIRROR_H +#define EN_PORT_BINDING_MIRROR_H + +#include "lib/inc-proc-eng.h" +#include "openvswitch/shash.h" + +void *en_port_binding_mirror_init(struct engine_node *, + struct engine_arg *); + +enum engine_node_state en_port_binding_mirror_run(struct engine_node *, + void *data); +void en_port_binding_mirror_cleanup(void *data); + +struct ovn_paired_mirror { + const char *name; + const char *sink; + const struct nbrec_logical_switch_port *nbsp; + const struct sbrec_port_binding *sb; +}; + +struct ovn_paired_mirror_map { + struct shash paired_mirror_ports; +}; + +void *en_port_binding_paired_mirror_init(struct engine_node *, + struct engine_arg *); + +enum engine_node_state en_port_binding_paired_mirror_run(struct engine_node *, + void *data); +void en_port_binding_paired_mirror_cleanup(void *data); + +#endif /* EN_PORT_BINDING_MIRROR_H */ diff --git a/northd/en-port-binding-pair.c b/northd/en-port-binding-pair.c new file mode 100644 index 000000000..21f7fefc8 --- /dev/null +++ b/northd/en-port-binding-pair.c @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "en-port-binding-pair.h" +#include "en-global-config.h" +#include "port_binding_pair.h" +#include "ovn-sb-idl.h" +#include "mcast-group-index.h" + +#include "openvswitch/vlog.h" + +VLOG_DEFINE_THIS_MODULE(port_binding_pair); + +void * +en_port_binding_pair_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *args OVS_UNUSED) +{ + struct ovn_paired_port_bindings *paired_port_bindings + = xzalloc(sizeof *paired_port_bindings); + ovs_list_init(&paired_port_bindings->paired_pbs); + hmap_init(&paired_port_bindings->tunnel_key_maps); + + return paired_port_bindings; +} + +static struct ovn_unpaired_port_binding * +find_unpaired_port_binding(const struct ovn_unpaired_port_binding_map **maps, + size_t n_maps, + const struct sbrec_port_binding *sb_pb) +{ + const struct ovn_unpaired_port_binding_map *map; + + for (size_t i = 0; i < n_maps; i++) { + map = maps[i]; + struct ovn_unpaired_port_binding *upb; + upb = shash_find_data(&map->ports, sb_pb->logical_port); + if (upb && map->cb->sb_is_valid(sb_pb, upb)) { + return upb; + } + } + + return NULL; +} + +struct tunnel_key_map { + struct hmap_node hmap_node; + uint32_t datapath_tunnel_key; + struct hmap port_tunnel_keys; +}; + +static struct tunnel_key_map * +find_tunnel_key_map(uint32_t datapath_tunnel_key, + const struct hmap *tunnel_key_maps) +{ + uint32_t hash = hash_int(datapath_tunnel_key, 0); + struct tunnel_key_map *key_map; + HMAP_FOR_EACH_WITH_HASH (key_map, hmap_node, hash, tunnel_key_maps) { + if (key_map->datapath_tunnel_key == datapath_tunnel_key) { + return key_map; + } + } + return NULL; +} + +static struct tunnel_key_map * +alloc_tunnel_key_map(uint32_t datapath_tunnel_key, + struct hmap *tunnel_key_maps) +{ + uint32_t hash = hash_int(datapath_tunnel_key, 0); + struct tunnel_key_map *key_map; + + key_map = xzalloc(sizeof *key_map); + key_map->datapath_tunnel_key = datapath_tunnel_key; + hmap_init(&key_map->port_tunnel_keys); + hmap_insert(tunnel_key_maps, &key_map->hmap_node, hash); + + return key_map; + +} + +static struct tunnel_key_map * +find_or_alloc_tunnel_key_map(const struct sbrec_datapath_binding *sb_dp, + struct hmap *tunnel_key_maps) +{ + struct tunnel_key_map *key_map = find_tunnel_key_map(sb_dp->tunnel_key, + tunnel_key_maps); + if (!key_map) { + key_map = alloc_tunnel_key_map(sb_dp->tunnel_key, tunnel_key_maps); + } + return key_map; +} + +static void +tunnel_key_maps_destroy(struct hmap *tunnel_key_maps) +{ + struct tunnel_key_map *key_map; + HMAP_FOR_EACH_POP (key_map, hmap_node, tunnel_key_maps) { + hmap_destroy(&key_map->port_tunnel_keys); + free(key_map); + } + hmap_destroy(tunnel_key_maps); +} + +struct candidate_spb { + struct ovs_list list_node; + struct ovn_paired_port_binding *spb; + uint32_t requested_tunnel_key; + uint32_t existing_tunnel_key; + struct tunnel_key_map *tunnel_key_map; +}; + +static void +reset_port_binding_pair_data( + struct ovn_paired_port_bindings *paired_port_bindings) +{ + /* Free the old paired port_bindings */ + struct ovn_paired_port_binding *spb; + LIST_FOR_EACH_POP (spb, list_node, &paired_port_bindings->paired_pbs) { + free(spb); + } + tunnel_key_maps_destroy(&paired_port_bindings->tunnel_key_maps); + + hmap_init(&paired_port_bindings->tunnel_key_maps); + ovs_list_init(&paired_port_bindings->paired_pbs); +} + +static struct candidate_spb * +candidate_spb_alloc(const struct ovn_unpaired_port_binding *upb, + const struct sbrec_port_binding *sb_pb, + struct hmap *tunnel_key_maps) +{ + struct ovn_paired_port_binding *spb; + spb = xzalloc(sizeof *spb); + spb->sb_pb = sb_pb; + spb->cookie = upb->cookie; + spb->type = upb->type; + sbrec_port_binding_set_external_ids(sb_pb, &upb->external_ids); + sbrec_port_binding_set_logical_port(sb_pb, upb->name); + + struct candidate_spb *candidate; + candidate = xzalloc(sizeof *candidate); + candidate->spb = spb; + candidate->requested_tunnel_key = upb->requested_tunnel_key; + candidate->existing_tunnel_key = spb->sb_pb->tunnel_key; + candidate->tunnel_key_map = find_or_alloc_tunnel_key_map(upb->sb_dp, + tunnel_key_maps); + + return candidate; +} + +static void +get_candidate_pbs_from_sb( + const struct sbrec_port_binding_table *sb_pb_table, + const struct ovn_unpaired_port_binding_map **input_maps, + size_t n_input_maps, struct hmap *tunnel_key_maps, + struct ovs_list *candidate_spbs, struct smap *visited) +{ + const struct sbrec_port_binding *sb_pb; + const struct ovn_unpaired_port_binding *upb; + SBREC_PORT_BINDING_TABLE_FOR_EACH_SAFE (sb_pb, sb_pb_table) { + upb = find_unpaired_port_binding(input_maps, n_input_maps, sb_pb); + if (!upb) { + sbrec_port_binding_delete(sb_pb); + continue; + } + + if (!uuid_equals(&upb->sb_dp->header_.uuid, + &sb_pb->datapath->header_.uuid)) { + /* A matching unpaired port was found for this port binding, but it + * has moved to a different datapath. Delete the old SB port + * binding so that a new one will be created later when we traverse + * unpaired port bindings later. + */ + sbrec_port_binding_delete(sb_pb); + continue; + } + + if (!smap_add_once(visited, sb_pb->logical_port, upb->type)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_INFO_RL( + &rl, "deleting port_binding "UUID_FMT" with " + "duplicate name %s", + UUID_ARGS(&sb_pb->header_.uuid), sb_pb->logical_port); + sbrec_port_binding_delete(sb_pb); + continue; + } + struct candidate_spb *candidate; + candidate = candidate_spb_alloc(upb, sb_pb, tunnel_key_maps); + ovs_list_push_back(candidate_spbs, &candidate->list_node); + } +} + +static void +get_candidate_pbs_from_nb( + struct ovsdb_idl_txn *ovnsb_idl_txn, + const struct ovn_unpaired_port_binding_map **input_maps, + uint32_t n_input_maps, + struct hmap *tunnel_key_maps, + struct ovs_list *candidate_spbs, + struct smap *visited) +{ + for (size_t i = 0; i < n_input_maps; i++) { + const struct ovn_unpaired_port_binding_map *map = input_maps[i]; + struct shash_node *shash_node; + SHASH_FOR_EACH (shash_node, &map->ports) { + const struct ovn_unpaired_port_binding *upb = shash_node->data; + const char *visited_type = smap_get(visited, upb->name); + if (visited_type) { + if (strcmp(upb->type, visited_type)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, + 1); + VLOG_WARN_RL(&rl, "duplicate logical port %s", upb->name); + } + continue; + } else { + /* Add the port to "visited" to help with detection of + * duplicated port names across different types of ports. + */ + smap_add_once(visited, upb->name, upb->type); + } + const struct sbrec_port_binding *sb_pb; + sb_pb = sbrec_port_binding_insert(ovnsb_idl_txn); + + struct candidate_spb *candidate; + candidate = candidate_spb_alloc(upb, sb_pb, tunnel_key_maps); + ovs_list_push_back(candidate_spbs, &candidate->list_node); + } + } +} + +static void +pair_requested_tunnel_keys(struct ovs_list *candidate_spbs, + struct ovs_list *paired_pbs) +{ + struct candidate_spb *candidate; + LIST_FOR_EACH_SAFE (candidate, list_node, candidate_spbs) { + if (!candidate->requested_tunnel_key) { + continue; + } + if (candidate->requested_tunnel_key >= OVN_VXLAN_MIN_MULTICAST) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "Tunnel key %"PRIu32" for port %s" + " is incompatible with VXLAN", + candidate->requested_tunnel_key, + candidate->spb->sb_pb->logical_port); + continue; + } + + if (ovn_add_tnlid(&candidate->tunnel_key_map->port_tunnel_keys, + candidate->requested_tunnel_key)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "Logical port_binding %s requests same " + "tunnel key %"PRIu32" as another logical " + "port_binding on the same datapath", + candidate->spb->sb_pb->logical_port, + candidate->requested_tunnel_key); + } + sbrec_port_binding_set_tunnel_key(candidate->spb->sb_pb, + candidate->requested_tunnel_key); + ovs_list_remove(&candidate->list_node); + ovs_list_push_back(paired_pbs, &candidate->spb->list_node); + free(candidate); + } +} + +static void +pair_existing_tunnel_keys(struct ovs_list *candidate_spbs, + struct ovs_list *paired_pbs) +{ + struct candidate_spb *candidate; + LIST_FOR_EACH_SAFE (candidate, list_node, candidate_spbs) { + if (!candidate->existing_tunnel_key) { + continue; + } + /* Existing southbound pb. If this key is available, + * reuse it. + */ + if (ovn_add_tnlid(&candidate->tunnel_key_map->port_tunnel_keys, + candidate->existing_tunnel_key)) { + ovs_list_remove(&candidate->list_node); + ovs_list_push_back(paired_pbs, &candidate->spb->list_node); + free(candidate); + } + } +} + +static void +pair_new_tunnel_keys(struct ovs_list *candidate_spbs, + struct ovs_list *paired_pbs, + uint32_t max_pb_tunnel_id) +{ + uint32_t hint = 0; + struct candidate_spb *candidate; + LIST_FOR_EACH_SAFE (candidate, list_node, candidate_spbs) { + uint32_t tunnel_key = + ovn_allocate_tnlid(&candidate->tunnel_key_map->port_tunnel_keys, + "port", 1, max_pb_tunnel_id, + &hint); + if (!tunnel_key) { + continue; + } + sbrec_port_binding_set_tunnel_key(candidate->spb->sb_pb, + tunnel_key); + ovs_list_remove(&candidate->list_node); + ovs_list_push_back(paired_pbs, &candidate->spb->list_node); + free(candidate); + } + +} + +static void +free_unpaired_candidates(struct ovs_list *candidate_spbs) +{ + struct candidate_spb *candidate; + /* Anything from this list represents a port_binding where a tunnel ID + * could not be allocated. Delete the SB port_binding binding for these. + */ + LIST_FOR_EACH_POP (candidate, list_node, candidate_spbs) { + sbrec_port_binding_delete(candidate->spb->sb_pb); + free(candidate->spb); + free(candidate); + } +} + +static void +cleanup_stale_fdb_entries(const struct sbrec_fdb_table *sbrec_fdb_table, + struct hmap *tunnel_key_maps) +{ + const struct sbrec_fdb *fdb_e; + SBREC_FDB_TABLE_FOR_EACH_SAFE (fdb_e, sbrec_fdb_table) { + bool delete = true; + struct tunnel_key_map *map = find_tunnel_key_map(fdb_e->dp_key, + tunnel_key_maps); + if (map) { + if (ovn_tnlid_present(&map->port_tunnel_keys, fdb_e->port_key)) { + delete = false; + } + } + + if (delete) { + sbrec_fdb_delete(fdb_e); + } + } +} + +enum engine_node_state +en_port_binding_pair_run(struct engine_node *node , void *data) +{ + const struct sbrec_port_binding_table *sb_pb_table = + EN_OVSDB_GET(engine_get_input("SB_port_binding", node)); + const struct sbrec_fdb_table *sb_fdb_table = + EN_OVSDB_GET(engine_get_input("SB_fdb", node)); + const struct ed_type_global_config *global_config = + engine_get_input_data("global_config", node); + /* The inputs are: + * * Some number of input maps. + * * Southbound Port Binding table. + * * Global config data. + * * FDB Table. + * + * Therefore, the number of inputs - 3 is the number of input + * maps from the port_binding-specific nodes. + */ + size_t n_input_maps = node->n_inputs - 3; + const struct ovn_unpaired_port_binding_map **input_maps = + xmalloc(n_input_maps *sizeof *input_maps); + struct ovn_paired_port_bindings *paired_port_bindings = data; + + for (size_t i = 0; i < n_input_maps; i++) { + input_maps[i] = engine_get_data(node->inputs[i].node); + } + + reset_port_binding_pair_data(paired_port_bindings); + + struct smap visited = SMAP_INITIALIZER(&visited); + struct ovs_list candidate_spbs = OVS_LIST_INITIALIZER(&candidate_spbs); + get_candidate_pbs_from_sb(sb_pb_table, input_maps, n_input_maps, + &paired_port_bindings->tunnel_key_maps, + &candidate_spbs, &visited); + + const struct engine_context *eng_ctx = engine_get_context(); + get_candidate_pbs_from_nb(eng_ctx->ovnsb_idl_txn, input_maps, + n_input_maps, + &paired_port_bindings->tunnel_key_maps, + &candidate_spbs, &visited); + + smap_destroy(&visited); + + pair_requested_tunnel_keys(&candidate_spbs, + &paired_port_bindings->paired_pbs); + pair_existing_tunnel_keys(&candidate_spbs, + &paired_port_bindings->paired_pbs); + pair_new_tunnel_keys(&candidate_spbs, &paired_port_bindings->paired_pbs, + global_config->max_pb_tunnel_id); + + cleanup_stale_fdb_entries(sb_fdb_table, + &paired_port_bindings->tunnel_key_maps); + + free_unpaired_candidates(&candidate_spbs); + free(input_maps); + + return EN_UPDATED; +} + +void +en_port_binding_pair_cleanup(void *data) +{ + struct ovn_paired_port_bindings *paired_port_bindings = data; + struct ovn_paired_port_binding *spb; + + LIST_FOR_EACH_POP (spb, list_node, &paired_port_bindings->paired_pbs) { + free(spb); + } + tunnel_key_maps_destroy(&paired_port_bindings->tunnel_key_maps); +} + +enum engine_input_handler_result +port_binding_fdb_change_handler(struct engine_node *node, void *data) +{ + struct ovn_paired_port_bindings *paired_port_bindings = data; + const struct sbrec_fdb_table *sbrec_fdb_table = + EN_OVSDB_GET(engine_get_input("SB_fdb", node)); + + /* check if changed rows are stale and delete them */ + const struct sbrec_fdb *fdb_e, *fdb_prev_del = NULL; + SBREC_FDB_TABLE_FOR_EACH_TRACKED (fdb_e, sbrec_fdb_table) { + if (sbrec_fdb_is_deleted(fdb_e)) { + continue; + } + + if (fdb_prev_del) { + sbrec_fdb_delete(fdb_prev_del); + } + + fdb_prev_del = fdb_e; + struct tunnel_key_map *tunnel_key_map = + find_tunnel_key_map(fdb_e->dp_key, + &paired_port_bindings->tunnel_key_maps); + if (tunnel_key_map) { + if (ovn_tnlid_present(&tunnel_key_map->port_tunnel_keys, + fdb_e->port_key)) { + fdb_prev_del = NULL; + } + } + } + + if (fdb_prev_del) { + sbrec_fdb_delete(fdb_prev_del); + } + + return EN_HANDLED_UNCHANGED; +} diff --git a/northd/en-port-binding-pair.h b/northd/en-port-binding-pair.h new file mode 100644 index 000000000..9b9417487 --- /dev/null +++ b/northd/en-port-binding-pair.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EN_PORT_BINDING_PAIR_H +#define EN_PORT_BINDING_PAIR_H + +#include "inc-proc-eng.h" + +void *en_port_binding_pair_init(struct engine_node *node, + struct engine_arg *args); + + +enum engine_node_state en_port_binding_pair_run(struct engine_node *node, + void *data); + +void en_port_binding_pair_cleanup(void *data); + +enum engine_input_handler_result +port_binding_fdb_change_handler(struct engine_node *, void *data); + +#endif /* EN_PORT_BINDING_PAIR_H */ diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c index bdc6c48df..752f1e9dc 100644 --- a/northd/inc-proc-northd.c +++ b/northd/inc-proc-northd.c @@ -50,6 +50,11 @@ #include "en-datapath-logical-router.h" #include "en-datapath-logical-switch.h" #include "en-datapath-sync.h" +#include "en-port-binding-logical-router-port.h" +#include "en-port-binding-logical-switch-port.h" +#include "en-port-binding-chassisredirect.h" +#include "en-port-binding-mirror.h" +#include "en-port-binding-pair.h" #include "unixctl.h" #include "util.h" @@ -187,6 +192,15 @@ static ENGINE_NODE(datapath_logical_switch); static ENGINE_NODE(datapath_synced_logical_router); static ENGINE_NODE(datapath_synced_logical_switch); static ENGINE_NODE(datapath_sync); +static ENGINE_NODE(port_binding_logical_router_port); +static ENGINE_NODE(port_binding_logical_switch_port); +static ENGINE_NODE(port_binding_chassisredirect_port); +static ENGINE_NODE(port_binding_mirror); +static ENGINE_NODE(port_binding_paired_logical_router_port); +static ENGINE_NODE(port_binding_paired_logical_switch_port); +static ENGINE_NODE(port_binding_paired_chassisredirect_port); +static ENGINE_NODE(port_binding_paired_mirror); +static ENGINE_NODE(port_binding_pair); void inc_proc_northd_init(struct ovsdb_idl_loop *nb, struct ovsdb_idl_loop *sb) @@ -232,6 +246,36 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_datapath_synced_logical_switch, &en_datapath_sync, NULL); + engine_add_input(&en_port_binding_logical_switch_port, + &en_datapath_synced_logical_switch, NULL); + engine_add_input(&en_port_binding_logical_router_port, + &en_datapath_synced_logical_router, NULL); + engine_add_input(&en_port_binding_chassisredirect_port, + &en_datapath_synced_logical_switch, NULL); + engine_add_input(&en_port_binding_chassisredirect_port, + &en_datapath_synced_logical_router, NULL); + engine_add_input(&en_port_binding_mirror, + &en_datapath_synced_logical_switch, NULL); + engine_add_input(&en_port_binding_pair, + &en_port_binding_logical_switch_port, NULL); + engine_add_input(&en_port_binding_pair, + &en_port_binding_logical_router_port, NULL); + engine_add_input(&en_port_binding_pair, + &en_port_binding_chassisredirect_port, NULL); + engine_add_input(&en_port_binding_pair, &en_port_binding_mirror, NULL); + engine_add_input(&en_port_binding_pair, &en_sb_port_binding, NULL); + engine_add_input(&en_port_binding_pair, &en_global_config, NULL); + engine_add_input(&en_port_binding_pair, &en_sb_fdb, + port_binding_fdb_change_handler); + engine_add_input(&en_port_binding_paired_logical_router_port, + &en_port_binding_pair, NULL); + engine_add_input(&en_port_binding_paired_logical_switch_port, + &en_port_binding_pair, NULL); + engine_add_input(&en_port_binding_paired_chassisredirect_port, + &en_port_binding_pair, NULL); + engine_add_input(&en_port_binding_paired_mirror, &en_port_binding_pair, + NULL); + engine_add_input(&en_northd, &en_nb_mirror, NULL); engine_add_input(&en_northd, &en_nb_mirror_rule, NULL); engine_add_input(&en_northd, &en_nb_static_mac_binding, NULL); @@ -247,7 +291,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_northd, &en_sb_service_monitor, NULL); engine_add_input(&en_northd, &en_sb_static_mac_binding, NULL); engine_add_input(&en_northd, &en_sb_chassis_template_var, NULL); - engine_add_input(&en_northd, &en_sb_fdb, northd_sb_fdb_change_handler); + engine_add_input(&en_northd, &en_sb_fdb, engine_noop_handler); engine_add_input(&en_northd, &en_global_config, northd_global_config_handler); @@ -286,6 +330,14 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, engine_noop_handler); engine_add_input(&en_northd, &en_datapath_synced_logical_switch, engine_noop_handler); + engine_add_input(&en_northd, &en_port_binding_paired_logical_router_port, + engine_noop_handler); + engine_add_input(&en_northd, &en_port_binding_paired_logical_switch_port, + engine_noop_handler); + engine_add_input(&en_northd, &en_port_binding_paired_chassisredirect_port, + engine_noop_handler); + engine_add_input(&en_northd, &en_port_binding_paired_mirror, + engine_noop_handler); engine_add_input(&en_lr_nat, &en_northd, lr_nat_northd_handler); diff --git a/northd/northd.c b/northd/northd.c index 47b54dbd9..51f1c3eb6 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -54,6 +54,10 @@ #include "en-sampling-app.h" #include "en-datapath-logical-switch.h" #include "en-datapath-logical-router.h" +#include "en-port-binding-logical-switch-port.h" +#include "en-port-binding-logical-router-port.h" +#include "en-port-binding-chassisredirect.h" +#include "en-port-binding-mirror.h" #include "lib/ovn-parallel-hmap.h" #include "ovn/actions.h" #include "ovn/features.h" @@ -464,7 +468,7 @@ od_has_lb_vip(const struct ovn_datapath *od) } } -static const char * +const char * ovn_datapath_name(const struct sbrec_datapath_binding *sb) { return smap_get_def(&sb->external_ids, "name", ""); @@ -495,8 +499,6 @@ ovn_datapath_create(struct hmap *datapaths, const struct uuid *key, od->sb = sb; od->nbs = nbs; od->nbr = nbr; - hmap_init(&od->port_tnlids); - od->port_key_hint = 0; hmap_insert(datapaths, &od->key_node, uuid_hash(&od->key)); od->lr_group = NULL; hmap_init(&od->ports); @@ -527,7 +529,6 @@ ovn_datapath_destroy(struct hmap *datapaths, struct ovn_datapath *od) * private list and once we've exited that function it is not safe to * use it. */ hmap_remove(datapaths, &od->key_node); - ovn_destroy_tnlids(&od->port_tnlids); destroy_ipam_info(&od->ipam_info); vector_destroy(&od->router_ports); vector_destroy(&od->ls_peers); @@ -986,6 +987,7 @@ ovn_port_create(struct hmap *ports, const char *key, op->sb = sb; ovn_port_set_nb(op, nbsp, nbrp); op->primary_port = op->cr_port = NULL; + op->tunnel_key = sb->tunnel_key; hmap_insert(ports, &op->key_node, hash_string(op->key, 0)); op->lflow_ref = lflow_ref_create(); @@ -997,10 +999,6 @@ ovn_port_create(struct hmap *ports, const char *key, static void ovn_port_cleanup(struct ovn_port *port) { - if (port->tunnel_key) { - ovs_assert(port->od); - ovn_free_tnlid(&port->od->port_tnlids, port->tunnel_key); - } for (int i = 0; i < port->n_lsp_addrs; i++) { destroy_lport_addresses(&port->lsp_addrs[i]); } @@ -1077,12 +1075,6 @@ ovn_port_find(const struct hmap *ports, const char *name) return ovn_port_find__(ports, name, false); } -static struct ovn_port * -ovn_port_find_bound(const struct hmap *ports, const char *name) -{ - return ovn_port_find__(ports, name, true); -} - static bool lsp_is_clone_to_unknown(const struct nbrec_logical_switch_port *nbsp) { @@ -1147,7 +1139,7 @@ lsp_disable_arp_nd_rsp(const struct nbrec_logical_switch_port *nbsp) return smap_get_bool( ->options, "disable_arp_nd_rsp", false); } -static bool +bool lsp_is_type_changed(const struct sbrec_port_binding *sb, const struct nbrec_logical_switch_port *nbsp, bool *update_sbrec) @@ -1883,104 +1875,35 @@ parse_lsp_addrs(struct ovn_port *op) } } -static void -create_mirror_port(struct ovn_port *op, struct hmap *ports, - struct ovs_list *both_dbs, struct ovs_list *nb_only, - const struct nbrec_mirror *nb_mirror) -{ - char *mp_name = ovn_mirror_port_name(ovn_datapath_name(op->od->sb), - nb_mirror->sink); - struct ovn_port *mp = ovn_port_find(ports, mp_name); - struct ovn_port *target_port = ovn_port_find(ports, nb_mirror->sink); - - if (!target_port) { - goto clear; - } - - if (!mp) { - mp = ovn_port_create(ports, mp_name, op->nbsp, NULL, NULL); - ovs_list_push_back(nb_only, &mp->list); - } else if (mp->sb) { - ovn_port_set_nb(mp, op->nbsp, NULL); - ovs_list_remove(&mp->list); - ovs_list_push_back(both_dbs, &mp->list); - } else { - goto clear; - } - - mp->mirror_target_port = target_port; - mp->od = op->od; +static struct ovn_port * +create_mirror_port(const struct ovn_port *source, + struct ovn_port *sink, const char *mirror_port_name, + struct hmap *ports, + const struct sbrec_port_binding *sb_pb) +{ + struct ovn_port *mp = ovn_port_create(ports, mirror_port_name, + source->nbsp, NULL, sb_pb); + ovn_port_set_nb(mp, source->nbsp, NULL); + mp->mirror_target_port = sink; + mp->od = source->od; -clear: - free(mp_name); + return mp; } static struct ovn_port * join_logical_ports_lsp(struct hmap *ports, - struct ovs_list *nb_only, struct ovs_list *both, struct ovn_datapath *od, const struct nbrec_logical_switch_port *nbsp, + const struct sbrec_port_binding *sb_pb, const char *name, unsigned long *queue_id_bitmap, - struct hmap *tag_alloc_table, - struct hmapx *mirror_attached_ports) -{ - struct ovn_port *op = ovn_port_find_bound(ports, name); - if (op && (op->od || op->nbsp || op->nbrp)) { - static struct vlog_rate_limit rl - = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "duplicate logical port %s", name); - return NULL; - } else if (op && (!op->sb || op->sb->datapath == od->sb)) { - /* - * Handle cases where lport type was explicitly changed - * in the NBDB, in such cases: - * 1. remove the current sbrec of the affected lport from - * the port_binding table. - * - * 2. create a new sbrec with the same logical_port as the - * deleted lport and add it to the nb_only list which - * will make the northd handle this lport as a new - * created one and recompute everything that is needed - * for this lport. - * - * This change will affect container/virtual lport type - * changes only for now, this change is needed in - * contaier/virtual lport cases to avoid port type - * conflicts in the ovn-controller when the user clears - * the parent_port field in the container lport or updated - * the lport type. - * - */ - bool update_sbrec = false; - if (op->sb && lsp_is_type_changed(op->sb, nbsp, - &update_sbrec) - && update_sbrec) { - ovs_list_remove(&op->list); - sbrec_port_binding_delete(op->sb); - ovn_port_destroy(ports, op); - op = ovn_port_create(ports, name, nbsp, - NULL, NULL); - ovs_list_push_back(nb_only, &op->list); - } else { - ovn_port_set_nb(op, nbsp, NULL); - ovs_list_remove(&op->list); - - uint32_t queue_id = smap_get_int(&op->sb->options, - "qdisc_queue_id", 0); - if (queue_id) { - bitmap_set1(queue_id_bitmap, queue_id); - } - - ovs_list_push_back(both, &op->list); - - /* This port exists due to a SB binding, but should - * not have been initialized fully. */ - ovs_assert(!op->n_lsp_addrs && !op->n_ps_addrs); - } - } else { - op = ovn_port_create(ports, name, nbsp, NULL, NULL); - ovs_list_push_back(nb_only, &op->list); + struct hmap *tag_alloc_table) +{ + struct ovn_port *op = ovn_port_create(ports, name, nbsp, NULL, sb_pb); + uint32_t queue_id = smap_get_int(&op->sb->options, + "qdisc_queue_id", 0); + if (queue_id) { + bitmap_set1(queue_id_bitmap, queue_id); } if (lsp_is_localnet(nbsp)) { @@ -2000,47 +1923,23 @@ join_logical_ports_lsp(struct hmap *ports, hmap_insert(&od->ports, &op->dp_node, hmap_node_hash(&op->key_node)); - if (nbsp->n_mirror_rules) { - hmapx_add(mirror_attached_ports, op); - } - tag_alloc_add_existing_tags(tag_alloc_table, nbsp); return op; } static struct ovn_port* join_logical_ports_lrp(struct hmap *ports, - struct ovs_list *nb_only, struct ovs_list *both, struct hmapx *dgps, struct ovn_datapath *od, const struct nbrec_logical_router_port *nbrp, + const struct sbrec_port_binding *sb_pb, const char *name, struct lport_addresses *lrp_networks) { if (!lrp_networks->n_ipv4_addrs && !lrp_networks->n_ipv6_addrs) { return NULL; } - struct ovn_port *op = ovn_port_find_bound(ports, name); - if (op && (op->od || op->nbsp || op->nbrp)) { - static struct vlog_rate_limit rl - = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "duplicate logical router port %s", - name); - destroy_lport_addresses(lrp_networks); - return NULL; - } else if (op && (!op->sb || op->sb->datapath == od->sb)) { - ovn_port_set_nb(op, NULL, nbrp); - ovs_list_remove(&op->list); - ovs_list_push_back(both, &op->list); - - /* This port exists but should not have been - * initialized fully. */ - ovs_assert(!op->lrp_networks.n_ipv4_addrs - && !op->lrp_networks.n_ipv6_addrs); - } else { - op = ovn_port_create(ports, name, NULL, nbrp, NULL); - ovs_list_push_back(nb_only, &op->list); - } + struct ovn_port *op = ovn_port_create(ports, name, NULL, nbrp, sb_pb); op->lrp_networks = *lrp_networks; op->od = od; @@ -2094,128 +1993,126 @@ join_logical_ports_lrp(struct hmap *ports, static struct ovn_port * -create_cr_port(struct ovn_port *op, struct hmap *ports, - struct ovs_list *both_dbs, struct ovs_list *nb_only) +create_cr_port(struct ovn_port *op, const char *name, struct hmap *ports, + const struct sbrec_port_binding *sb_pb) { - char *redirect_name = ovn_chassis_redirect_name( - op->nbsp ? op->nbsp->name : op->nbrp->name); - - struct ovn_port *crp = ovn_port_find(ports, redirect_name); - if (crp && crp->sb && crp->sb->datapath == op->od->sb) { - ovn_port_set_nb(crp, op->nbsp, op->nbrp); - ovs_list_remove(&crp->list); - ovs_list_push_back(both_dbs, &crp->list); - } else { - crp = ovn_port_create(ports, redirect_name, - op->nbsp, op->nbrp, NULL); - ovs_list_push_back(nb_only, &crp->list); - } + struct ovn_port *crp = ovn_port_create(ports, name, op->nbsp, op->nbrp, + sb_pb); crp->primary_port = op; op->cr_port = crp; crp->od = op->od; - free(redirect_name); return crp; } -/* Returns true if chassis resident port needs to be created for - * op's peer logical switch. False otherwise. - * - * Chassis resident port needs to be created if the following - * conditionsd are met: - * - op is a distributed gateway port - * - op is the only distributed gateway port attached to its - * router - * - op's peer logical switch has no localnet ports. - */ -static bool -peer_needs_cr_port_creation(struct ovn_port *op) -{ - if ((op->nbrp->n_gateway_chassis || op->nbrp->ha_chassis_group) - && vector_len(&op->od->l3dgw_ports) == 1 && op->peer && op->peer->nbsp - && vector_is_empty(&op->peer->od->localnet_ports)) { - return true; - } - - return false; -} - static void -join_mirror_ports(struct ovn_port *op, - const struct nbrec_logical_switch_port *nbsp, - struct hmap *ports, struct ovs_list *both, - struct ovs_list *nb_only) +join_logical_ports( + struct hmap *ls_datapaths, struct hmap *lr_datapaths, + const struct ovn_paired_logical_switch_port_map *paired_lsps, + const struct ovn_paired_logical_router_port_map *paired_lrps, + const struct ovn_paired_chassisredirect_port_map *paired_crps, + const struct ovn_paired_mirror_map *paired_mirrors, + struct hmap *ls_ports, struct hmap *lr_ports, + unsigned long *queue_id_bitmap, + struct hmap *tag_alloc_table) { - /* Create mirror targets port bindings if there any mirror - * with lport type attached to this port. */ - for (size_t j = 0; j < op->nbsp->n_mirror_rules; j++) { - struct nbrec_mirror *mirror = nbsp->mirror_rules[j]; - if (!strcmp(mirror->type, "lport")) { - create_mirror_port(op, ports, both, nb_only, mirror); + struct ovn_datapath *od; + struct hmapx dgps = HMAPX_INITIALIZER(&dgps); + + struct shash_node *node; + SHASH_FOR_EACH (node, &paired_lrps->paired_router_ports) { + struct ovn_paired_logical_router_port *slrp = node->data; + od = ovn_datapath_from_sbrec(ls_datapaths, lr_datapaths, + slrp->router->sb); + if (!od) { + /* This can happen if the router is not enabled */ + continue; } + struct lport_addresses lrp_networks; + if (!extract_lrp_networks(slrp->nb, &lrp_networks)) { + static struct vlog_rate_limit rl + = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad 'mac' %s", slrp->nb->mac); + continue; + } + + join_logical_ports_lrp(lr_ports, &dgps, od, slrp->nb, slrp->sb, + slrp->nb->name, &lrp_networks); } -} -static void -join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table, - struct hmap *ls_datapaths, struct hmap *lr_datapaths, - struct hmap *ports, unsigned long *queue_id_bitmap, - struct hmap *tag_alloc_table, struct ovs_list *sb_only, - struct ovs_list *nb_only, struct ovs_list *both) -{ - ovs_list_init(sb_only); - ovs_list_init(nb_only); - ovs_list_init(both); + SHASH_FOR_EACH (node, &paired_lsps->paired_switch_ports) { + struct ovn_paired_logical_switch_port *slsp = node->data; + od = ovn_datapath_from_sbrec(ls_datapaths, lr_datapaths, + slsp->sw->sb); + if (!od) { + /* This should not happen, but we'll be defensive just in case */ + continue; + } + join_logical_ports_lsp(ls_ports, od, slsp->nb, slsp->sb, + slsp->nb->name, queue_id_bitmap, + tag_alloc_table); + } - const struct sbrec_port_binding *sb; - SBREC_PORT_BINDING_TABLE_FOR_EACH (sb, sbrec_pb_table) { - struct ovn_port *op = ovn_port_create(ports, sb->logical_port, - NULL, NULL, sb); - ovs_list_push_back(sb_only, &op->list); + SHASH_FOR_EACH (node, &paired_crps->paired_chassisredirect_router_ports) { + struct ovn_paired_chassisredirect_router_port *crp = node->data; + struct ovn_port *primary_port = + ovn_port_find(lr_ports, crp->primary_port->name); + create_cr_port(primary_port, crp->name, lr_ports, crp->sb); } - struct ovn_datapath *od; - struct hmapx dgps = HMAPX_INITIALIZER(&dgps); - struct hmapx mirror_attached_ports = - HMAPX_INITIALIZER(&mirror_attached_ports); - HMAP_FOR_EACH (od, key_node, lr_datapaths) { - ovs_assert(od->nbr); - for (size_t i = 0; i < od->nbr->n_ports; i++) { - const struct nbrec_logical_router_port *nbrp - = od->nbr->ports[i]; - - struct lport_addresses lrp_networks; - if (!extract_lrp_networks(nbrp, &lrp_networks)) { - static struct vlog_rate_limit rl - = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad 'mac' %s", nbrp->mac); - continue; - } - join_logical_ports_lrp(ports, nb_only, both, &dgps, - od, nbrp, - nbrp->name, &lrp_networks); - } + SHASH_FOR_EACH (node, &paired_crps->paired_chassisredirect_switch_ports) { + struct ovn_paired_chassisredirect_switch_port *crp = node->data; + struct ovn_port *primary_port = + ovn_port_find(ls_ports, crp->primary_port->name); + create_cr_port(primary_port, crp->name, ls_ports, crp->sb); } - HMAP_FOR_EACH (od, key_node, ls_datapaths) { - ovs_assert(od->nbs); - for (size_t i = 0; i < od->nbs->n_ports; i++) { - const struct nbrec_logical_switch_port *nbsp - = od->nbs->ports[i]; - join_logical_ports_lsp(ports, nb_only, both, od, nbsp, - nbsp->name, queue_id_bitmap, - tag_alloc_table, &mirror_attached_ports); + SHASH_FOR_EACH (node, &paired_mirrors->paired_mirror_ports) { + struct ovn_paired_mirror *mirror = node->data; + struct ovn_port *source_port = + ovn_port_find(ls_ports, mirror->nbsp->name); + struct ovn_port *sink_port = + ovn_port_find(ls_ports, mirror->sink); + if (!sink_port) { + continue; } + create_mirror_port(source_port, sink_port, mirror->name, ls_ports, + mirror->sb); } /* Connect logical router ports, and logical switch ports of type "router", * to their peers. As well as logical switch ports of type "switch" to * theirs. */ + struct ovn_port *op; - HMAP_FOR_EACH (op, key_node, ports) { - if (op->nbsp && lsp_is_router(op->nbsp) && !op->primary_port) { - struct ovn_port *peer = ovn_port_get_peer(ports, op); + HMAP_FOR_EACH (op, key_node, lr_ports) { + if (op->nbrp->peer && !is_cr_port(op)) { + struct ovn_port *peer = ovn_port_find(lr_ports, op->nbrp->peer); + if (peer) { + if (peer->nbrp && peer->nbrp->peer && + !strcmp(op->nbrp->name, peer->nbrp->peer)) { + /* We only configure LRP peers if each LRP has the other as + * its peer. */ + op->peer = peer; + } else if (peer->nbsp) { + /* An ovn_port for a switch port of type "router" does have + * a router port as its peer (see the case above for + * "router" ports), but this is set via options:router-port + * in Logical_Switch_Port and does not involve the + * Logical_Router_Port's 'peer' column. */ + static struct vlog_rate_limit rl = + VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Bad configuration: The peer of router " + "port %s is a switch port", op->key); + } + } + } + } + + HMAP_FOR_EACH (op, key_node, ls_ports) { + if (lsp_is_router(op->nbsp) && !op->primary_port) { + struct ovn_port *peer = ovn_port_get_peer(lr_ports, op); if (!peer || !peer->nbrp) { continue; } @@ -2271,21 +2168,21 @@ join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table, arp_proxy, op->nbsp->name); } } - } else if (op->nbsp && op->nbsp->peer && lsp_is_switch(op->nbsp)) { + } else if (op->nbsp->peer && lsp_is_switch(op->nbsp)) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - struct ovn_port *peer = ovn_port_find(ports, op->nbsp->peer); + struct ovn_port *peer = ovn_port_find(ls_ports, op->nbsp->peer); if (!peer) { continue; } - if (peer->nbrp || (peer->nbsp && lsp_is_router(peer->nbsp))) { + if (lsp_is_router(peer->nbsp)) { VLOG_WARN_RL(&rl, "Bad configuration: The peer of switch " "port %s is a router port", op->key); continue; } - if (!peer->nbsp || !lsp_is_switch(peer->nbsp)) { + if (!lsp_is_switch(peer->nbsp)) { /* Common case. Likely the manual configuration is not * finished yet. */ continue; @@ -2300,26 +2197,6 @@ join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table, } op->peer = peer; - } else if (op->nbrp && op->nbrp->peer && !is_cr_port(op)) { - struct ovn_port *peer = ovn_port_find(ports, op->nbrp->peer); - if (peer) { - if (peer->nbrp && peer->nbrp->peer && - !strcmp(op->nbrp->name, peer->nbrp->peer)) { - /* We only configure LRP peers if each LRP has the other as - * its peer. */ - op->peer = peer; - } else if (peer->nbsp) { - /* An ovn_port for a switch port of type "router" does have - * a router port as its peer (see the case above for - * "router" ports), but this is set via options:router-port - * in Logical_Switch_Port and does not involve the - * Logical_Router_Port's 'peer' column. */ - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Bad configuration: The peer of router " - "port %s is a switch port", op->key); - } - } } } @@ -2330,11 +2207,6 @@ join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table, ovs_assert(op->nbrp); ovs_assert(op->nbrp->ha_chassis_group || op->nbrp->n_gateway_chassis); - /* Additional "derived" ovn_port crp represents the instance of op on - * the gateway chassis. */ - struct ovn_port *crp = create_cr_port(op, ports, both, nb_only); - ovs_assert(crp); - /* Add to l3dgw_ports in od, for later use during flow creation. */ vector_push(&od->l3dgw_ports, &op); @@ -2345,41 +2217,16 @@ join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table, } } - - /* Create chassisredirect port for the distributed gateway port's (DGP) - * peer if - * - DGP's router has only one DGP and - * - Its peer is a logical switch port and - * - Its peer's logical switch has no localnet ports - * - * This is required to support - * - NAT via geneve (for the overlay provider networks) and - * - to centralize routing on the gateway chassis for the traffic - * destined to the DGP's networks. - * - * Future enhancement: Support 'centralizerouting' for all the DGP's - * of a logical router. - * */ - HMAPX_FOR_EACH (hmapx_node, &dgps) { - op = hmapx_node->data; - if (peer_needs_cr_port_creation(op)) { - create_cr_port(op->peer, ports, both, nb_only); - } - } hmapx_destroy(&dgps); - HMAPX_FOR_EACH (hmapx_node, &mirror_attached_ports) { - op = hmapx_node->data; - if (op && op->nbsp) { - join_mirror_ports(op, op->nbsp, ports, both, nb_only); - } - } - hmapx_destroy(&mirror_attached_ports); - /* Wait until all ports have been connected to add to IPAM since * it relies on proper peers to be set */ - HMAP_FOR_EACH (op, key_node, ports) { + HMAP_FOR_EACH (op, key_node, ls_ports) { + ipam_add_port_addresses(op->od, op); + } + + HMAP_FOR_EACH (op, key_node, lr_ports) { ipam_add_port_addresses(op->od, op); } } @@ -2783,15 +2630,6 @@ copy_gw_chassis_from_nbrp_to_sbpb( free(sb_ha_chassis); } -static const char* -op_get_name(const struct ovn_port *op) -{ - ovs_assert(op->nbsp || op->nbrp); - const char *name = op->nbsp ? op->nbsp->name - : op->nbrp->name; - return name; -} - static void ovn_update_ipv6_prefix(struct hmap *lr_ports) { @@ -3052,8 +2890,6 @@ ovn_port_update_sbrec(struct ovsdb_idl_txn *ovnsb_txn, const char *addresses = ds_cstr(&s); sbrec_port_binding_set_mac(op->sb, &addresses, 1); ds_destroy(&s); - - sbrec_port_binding_set_external_ids(op->sb, &op->nbrp->external_ids); } else { if (op->mirror_target_port) { /* In case of using a lport mirror, we establish a port binding @@ -3262,15 +3098,6 @@ ovn_port_update_sbrec(struct ovsdb_idl_txn *ovnsb_txn, op->sb, (const char **) op->nbsp->port_security, op->nbsp->n_port_security); - struct smap ids = SMAP_INITIALIZER(&ids); - smap_clone(&ids, &op->nbsp->external_ids); - const char *name = smap_get(&ids, "neutron:port_name"); - if (name && name[0]) { - smap_add(&ids, "name", name); - } - sbrec_port_binding_set_external_ids(op->sb, &ids); - smap_destroy(&ids); - if (!op->nbsp->n_mirror_rules) { /* Nothing is set. Clear mirror_rules from pb. */ sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0); @@ -3328,27 +3155,6 @@ cleanup_sb_ha_chassis_groups( } } -static void -cleanup_stale_fdb_entries(const struct sbrec_fdb_table *sbrec_fdb_table, - struct hmap *ls_datapaths) -{ - const struct sbrec_fdb *fdb_e; - SBREC_FDB_TABLE_FOR_EACH_SAFE (fdb_e, sbrec_fdb_table) { - bool delete = true; - struct ovn_datapath *od - = ovn_datapath_find_by_key(ls_datapaths, fdb_e->dp_key); - if (od) { - if (ovn_tnlid_present(&od->port_tnlids, fdb_e->port_key)) { - delete = false; - } - } - - if (delete) { - sbrec_fdb_delete(fdb_e); - } - } -} - static void delete_fdb_entries(struct ovsdb_idl_index *sbrec_fdb_by_dp_and_port, uint32_t dp_key, uint32_t port_key) @@ -4122,64 +3928,6 @@ sync_pbs_for_northd_changed_ovn_ports( return true; } -static bool -ovn_port_add_tnlid(struct ovn_port *op, uint32_t tunnel_key) -{ - bool added = ovn_add_tnlid(&op->od->port_tnlids, tunnel_key); - if (added) { - op->tunnel_key = tunnel_key; - if (tunnel_key > op->od->port_key_hint) { - op->od->port_key_hint = tunnel_key; - } - } - return added; -} - -/* Returns false if the requested key is confict with another allocated key, so - * that the I-P engine can fallback to recompute if needed; otherwise return - * true (even if the key is not allocated). */ -static bool -ovn_port_assign_requested_tnl_id(struct ovn_port *op) -{ - const struct smap *options = (op->nbsp - ? &op->nbsp->options - : &op->nbrp->options); - uint32_t tunnel_key = smap_get_int(options, "requested-tnl-key", 0); - if (tunnel_key) { - if (vxlan_mode && tunnel_key >= OVN_VXLAN_MIN_MULTICAST) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Tunnel key %"PRIu32" for port %s " - "is incompatible with VXLAN", - tunnel_key, op_get_name(op)); - return true; - } - if (!ovn_port_add_tnlid(op, tunnel_key)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - VLOG_WARN_RL(&rl, "Logical %s port %s requests same tunnel key " - "%"PRIu32" as another LSP or LRP", - op->nbsp ? "switch" : "router", - op_get_name(op), tunnel_key); - return false; - } - } - return true; -} - -static bool -ovn_port_allocate_key(struct ovn_port *op) -{ - if (!op->tunnel_key) { - uint8_t key_bits = vxlan_mode ? 12 : 16; - op->tunnel_key = ovn_allocate_tnlid(&op->od->port_tnlids, "port", - 1, (1u << (key_bits - 1)) - 1, - &op->od->port_key_hint); - if (!op->tunnel_key) { - return false; - } - } - return true; -} - /* Updates the southbound Port_Binding table so that it contains the logical * switch ports specified by the northbound database. * @@ -4188,17 +3936,19 @@ ovn_port_allocate_key(struct ovn_port *op) * datapaths. */ static void build_ports(struct ovsdb_idl_txn *ovnsb_txn, - const struct sbrec_port_binding_table *sbrec_port_binding_table, const struct sbrec_mirror_table *sbrec_mirror_table, const struct sbrec_mac_binding_table *sbrec_mac_binding_table, const struct sbrec_ha_chassis_group_table *sbrec_ha_chassis_group_table, struct ovsdb_idl_index *sbrec_chassis_by_name, struct ovsdb_idl_index *sbrec_chassis_by_hostname, struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name, + const struct ovn_paired_logical_switch_port_map *paired_lsps, + const struct ovn_paired_logical_router_port_map *paired_lrps, + const struct ovn_paired_chassisredirect_port_map *paired_crps, + const struct ovn_paired_mirror_map *paired_mirrors, struct hmap *ls_datapaths, struct hmap *lr_datapaths, struct hmap *ls_ports, struct hmap *lr_ports) { - struct ovs_list sb_only, nb_only, both; /* XXX: Add tag_alloc_table and queue_id_bitmap as part of northd_data * to improve I-P. */ struct hmap tag_alloc_table = HMAP_INITIALIZER(&tag_alloc_table); @@ -4209,107 +3959,37 @@ build_ports(struct ovsdb_idl_txn *ovnsb_txn, struct sset active_ha_chassis_grps = SSET_INITIALIZER(&active_ha_chassis_grps); - /* Borrow ls_ports for joining NB and SB for both LSPs and LRPs. - * We will split them later. */ - struct hmap *ports = ls_ports; - join_logical_ports(sbrec_port_binding_table, ls_datapaths, lr_datapaths, - ports, queue_id_bitmap, - &tag_alloc_table, &sb_only, &nb_only, &both); + join_logical_ports(ls_datapaths, lr_datapaths, + paired_lsps, paired_lrps, paired_crps, + paired_mirrors, ls_ports, lr_ports, queue_id_bitmap, + &tag_alloc_table); - /* Purge stale Mac_Bindings if ports are deleted. */ - bool remove_mac_bindings = !ovs_list_is_empty(&sb_only); - - /* Assign explicitly requested tunnel ids first. */ struct ovn_port *op; - LIST_FOR_EACH (op, list, &both) { - ovn_port_assign_requested_tnl_id(op); - } - LIST_FOR_EACH (op, list, &nb_only) { - ovn_port_assign_requested_tnl_id(op); - } - - /* Keep nonconflicting tunnel IDs that are already assigned. */ - LIST_FOR_EACH (op, list, &both) { - if (!op->tunnel_key) { - ovn_port_add_tnlid(op, op->sb->tunnel_key); - } - } - - /* Assign new tunnel ids where needed. */ - LIST_FOR_EACH_SAFE (op, list, &both) { - if (!ovn_port_allocate_key(op)) { - sbrec_port_binding_delete(op->sb); - ovs_list_remove(&op->list); - ovn_port_destroy(ports, op); - } - } - LIST_FOR_EACH_SAFE (op, list, &nb_only) { - if (!ovn_port_allocate_key(op)) { - ovs_list_remove(&op->list); - ovn_port_destroy(ports, op); - } - } - - /* For logical ports that are in both databases, update the southbound - * record based on northbound data. - * For logical ports that are in NB database, do any tag allocation - * needed. */ - LIST_FOR_EACH_SAFE (op, list, &both) { - /* When reusing stale Port_Bindings, make sure that stale - * Mac_Bindings are purged. - */ - if (op->od->sb != op->sb->datapath) { - remove_mac_bindings = true; - } - if (op->nbsp) { - tag_alloc_create_new_tag(&tag_alloc_table, op->nbsp); - } + /* For logical ports, update the southbound record based on northbound + * data. + * For logical switch ports, do any tag allocation needed. + */ + HMAP_FOR_EACH (op, key_node, ls_ports) { + tag_alloc_create_new_tag(&tag_alloc_table, op->nbsp); ovn_port_update_sbrec(ovnsb_txn, sbrec_chassis_by_name, sbrec_chassis_by_hostname, sbrec_ha_chassis_grp_by_name, sbrec_mirror_table, op, queue_id_bitmap, &active_ha_chassis_grps); - op->od->is_transit_router |= is_transit_router_port(op); - ovs_list_remove(&op->list); } - /* Add southbound record for each unmatched northbound record. */ - LIST_FOR_EACH_SAFE (op, list, &nb_only) { - op->sb = sbrec_port_binding_insert(ovnsb_txn); + HMAP_FOR_EACH (op, key_node, lr_ports) { ovn_port_update_sbrec(ovnsb_txn, sbrec_chassis_by_name, sbrec_chassis_by_hostname, sbrec_ha_chassis_grp_by_name, sbrec_mirror_table, op, queue_id_bitmap, &active_ha_chassis_grps); - sbrec_port_binding_set_logical_port(op->sb, op->key); op->od->is_transit_router |= is_transit_router_port(op); - ovs_list_remove(&op->list); - } - - /* Delete southbound records without northbound matches. */ - if (!ovs_list_is_empty(&sb_only)) { - LIST_FOR_EACH_SAFE (op, list, &sb_only) { - ovs_list_remove(&op->list); - sbrec_port_binding_delete(op->sb); - ovn_port_destroy(ports, op); - } } - /* Move logical router ports to lr_ports, and logical switch ports will - * remain in ports/ls_ports. */ - HMAP_FOR_EACH_SAFE (op, key_node, ports) { - if (!op->nbrp) { - continue; - } - hmap_remove(ports, &op->key_node); - hmap_insert(lr_ports, &op->key_node, op->key_node.hash); - } - - if (remove_mac_bindings) { - cleanup_mac_bindings(sbrec_mac_binding_table, lr_datapaths, lr_ports); - } + cleanup_mac_bindings(sbrec_mac_binding_table, lr_datapaths, lr_ports); tag_alloc_destroy(&tag_alloc_table); bitmap_free(queue_id_bitmap); @@ -4453,67 +4133,39 @@ ovn_port_find_in_datapath(struct ovn_datapath *od, return NULL; } -static bool +static void ls_port_init(struct ovn_port *op, struct ovsdb_idl_txn *ovnsb_txn, struct ovn_datapath *od, - const struct sbrec_port_binding *sb, const struct sbrec_mirror_table *sbrec_mirror_table, struct ovsdb_idl_index *sbrec_chassis_by_name, struct ovsdb_idl_index *sbrec_chassis_by_hostname) { op->od = od; parse_lsp_addrs(op); - /* Assign explicitly requested tunnel ids first. */ - if (!ovn_port_assign_requested_tnl_id(op)) { - return false; - } - /* Keep nonconflicting tunnel IDs that are already assigned. */ - if (sb) { - if (!op->tunnel_key) { - ovn_port_add_tnlid(op, sb->tunnel_key); - } - } - /* Assign new tunnel ids where needed. */ - if (!ovn_port_allocate_key(op)) { - return false; - } - /* Create new binding, if needed. */ - if (sb) { - op->sb = sb; - } else { - /* XXX: the new SB port_binding will change in IDL, so need to handle - * SB port_binding updates incrementally to achieve end-to-end - * incremental processing. */ - op->sb = sbrec_port_binding_insert(ovnsb_txn); - sbrec_port_binding_set_logical_port(op->sb, op->key); - } ovn_port_update_sbrec(ovnsb_txn, sbrec_chassis_by_name, sbrec_chassis_by_hostname, NULL, sbrec_mirror_table, op, NULL, NULL); - return true; } static struct ovn_port * ls_port_create(struct ovsdb_idl_txn *ovnsb_txn, struct hmap *ls_ports, const char *key, const struct nbrec_logical_switch_port *nbsp, struct ovn_datapath *od, + const struct sbrec_port_binding *sb, const struct sbrec_mirror_table *sbrec_mirror_table, struct ovsdb_idl_index *sbrec_chassis_by_name, struct ovsdb_idl_index *sbrec_chassis_by_hostname) { struct ovn_port *op = ovn_port_create(ls_ports, key, nbsp, NULL, - NULL); + sb); hmap_insert(&od->ports, &op->dp_node, hmap_node_hash(&op->key_node)); - if (!ls_port_init(op, ovnsb_txn, od, NULL, sbrec_mirror_table, - sbrec_chassis_by_name, sbrec_chassis_by_hostname)) { - ovn_port_destroy(ls_ports, op); - return NULL; - } + ls_port_init(op, ovnsb_txn, od, sbrec_mirror_table, + sbrec_chassis_by_name, sbrec_chassis_by_hostname); return op; } -static bool +static void ls_port_reinit(struct ovn_port *op, struct ovsdb_idl_txn *ovnsb_txn, const struct nbrec_logical_switch_port *nbsp, struct ovn_datapath *od, @@ -4524,10 +4176,11 @@ ls_port_reinit(struct ovn_port *op, struct ovsdb_idl_txn *ovnsb_txn, { ovn_port_cleanup(op); op->sb = sb; + op->tunnel_key = sb->tunnel_key; ovn_port_set_nb(op, nbsp, NULL); op->primary_port = op->cr_port = NULL; - return ls_port_init(op, ovnsb_txn, od, sb, sbrec_mirror_table, - sbrec_chassis_by_name, sbrec_chassis_by_hostname); + ls_port_init(op, ovnsb_txn, od, sbrec_mirror_table, + sbrec_chassis_by_name, sbrec_chassis_by_hostname); } /* Returns true if the logical switch has changes which can be @@ -4544,6 +4197,7 @@ ls_changes_can_be_handled( { /* Check if the columns are changed in this row. */ enum nbrec_logical_switch_column_id col; + for (col = 0; col < NBREC_LOGICAL_SWITCH_N_COLUMNS; col++) { if (nbrec_logical_switch_is_updated(ls, col)) { if (col == NBREC_LOGICAL_SWITCH_COL_ACLS || @@ -4693,15 +4347,18 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn, /* Compare the individual ports in the old and new Logical Switches */ for (size_t j = 0; j < changed_ls->n_ports; ++j) { - struct nbrec_logical_switch_port *new_nbsp = changed_ls->ports[j]; - op = ovn_port_find_in_datapath(od, new_nbsp); + const struct ovn_paired_logical_switch_port *paired_lsp = + shash_find_data(&ni->paired_lsps->paired_switch_ports, + changed_ls->ports[j]->name); + op = ovn_port_find_in_datapath(od, paired_lsp->nb); if (!op) { - if (!lsp_can_be_inc_processed(new_nbsp)) { + if (!lsp_can_be_inc_processed(paired_lsp->nb)) { goto fail; } op = ls_port_create(ovnsb_idl_txn, &nd->ls_ports, - new_nbsp->name, new_nbsp, od, + paired_lsp->nb->name, paired_lsp->nb, od, + paired_lsp->sb, ni->sbrec_mirror_table, ni->sbrec_chassis_by_name, ni->sbrec_chassis_by_hostname); @@ -4709,28 +4366,27 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn, goto fail; } add_op_to_northd_tracked_ports(&trk_lsps->created, op); - } else if (ls_port_has_changed(new_nbsp)) { + } else if (ls_port_has_changed(paired_lsp->nb)) { /* Existing port updated */ bool temp = false; - if (lsp_is_type_changed(op->sb, new_nbsp, &temp) || + if (lsp_is_type_changed(op->sb, paired_lsp->nb, &temp) || !op->lsp_can_be_inc_processed || - !lsp_can_be_inc_processed(new_nbsp)) { + !lsp_can_be_inc_processed(paired_lsp->nb)) { goto fail; } - const struct sbrec_port_binding *sb = op->sb; - if (sset_contains(&nd->svc_monitor_lsps, new_nbsp->name)) { + if (sset_contains(&nd->svc_monitor_lsps, paired_lsp->nb->name)) { /* This port is used for svc monitor, which may be impacted * by this change. Fallback to recompute. */ goto fail; } - if (!lsp_handle_mirror_rules_changes(new_nbsp) || + if (!lsp_handle_mirror_rules_changes(paired_lsp->nb) || is_lsp_mirror_target_port(ni->nbrec_mirror_by_type_and_sink, op)) { /* Fallback to recompute. */ goto fail; } if (!check_lsp_is_up && - !check_lsp_changes_other_than_up(new_nbsp)) { + !check_lsp_changes_other_than_up(paired_lsp->nb)) { /* If the only change is the "up" column while the * "ignore_lsp_down" is set to true, just ignore this * change. */ @@ -4739,17 +4395,11 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn, } uint32_t old_tunnel_key = op->tunnel_key; - if (!ls_port_reinit(op, ovnsb_idl_txn, - new_nbsp, - od, sb, ni->sbrec_mirror_table, - ni->sbrec_chassis_by_name, - ni->sbrec_chassis_by_hostname)) { - if (sb) { - sbrec_port_binding_delete(sb); - } - ovn_port_destroy(&nd->ls_ports, op); - goto fail; - } + ls_port_reinit(op, ovnsb_idl_txn, + paired_lsp->nb, + od, paired_lsp->sb, ni->sbrec_mirror_table, + ni->sbrec_chassis_by_name, + ni->sbrec_chassis_by_hostname); add_op_to_northd_tracked_ports(&trk_lsps->updated, op); if (old_tunnel_key != op->tunnel_key) { @@ -4774,7 +4424,6 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn, add_op_to_northd_tracked_ports(&trk_lsps->deleted, op); hmap_remove(&nd->ls_ports, &op->key_node); hmap_remove(&od->ports, &op->dp_node); - sbrec_port_binding_delete(op->sb); delete_fdb_entries(ni->sbrec_fdb_by_dp_and_port, od->tunnel_key, op->tunnel_key); if (is_lsp_mirror_target_port(ni->nbrec_mirror_by_type_and_sink, @@ -19032,13 +18681,16 @@ ovnnb_db_run(struct northd_input *input_data, &data->ls_datapaths, &data->lr_datapaths, &data->lb_datapaths_map, &data->lb_group_datapaths_map); build_ports(ovnsb_txn, - input_data->sbrec_port_binding_table, input_data->sbrec_mirror_table, input_data->sbrec_mac_binding_table, input_data->sbrec_ha_chassis_group_table, input_data->sbrec_chassis_by_name, input_data->sbrec_chassis_by_hostname, input_data->sbrec_ha_chassis_grp_by_name, + input_data->paired_lsps, + input_data->paired_lrps, + input_data->paired_crps, + input_data->paired_mirrors, &data->ls_datapaths.datapaths, &data->lr_datapaths.datapaths, &data->ls_ports, &data->lr_ports); build_lb_port_related_data(ovnsb_txn, @@ -19072,10 +18724,7 @@ ovnnb_db_run(struct northd_input *input_data, sync_template_vars(ovnsb_txn, input_data->nbrec_chassis_template_var_table, input_data->sbrec_chassis_template_var_table); - cleanup_stale_fdb_entries(input_data->sbrec_fdb_table, - &data->ls_datapaths.datapaths); stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec()); - } /* Stores the set of chassis which references an ha_chassis_group. @@ -19267,6 +18916,25 @@ handle_cr_port_binding_changes(const struct sbrec_port_binding *sb, } } +void +lsp_set_up(const struct sbrec_port_binding *pb, + const struct nbrec_logical_switch_port *lsp) +{ + bool up = false; + + if (lsp_is_router(lsp) || lsp_is_switch(lsp)) { + up = true; + } else if (pb->chassis) { + up = !smap_get_bool(&pb->chassis->other_config, "is-remote", false) + ? pb->n_up && pb->up[0] + : true; + } + + if (!lsp->up || *lsp->up != up) { + nbrec_logical_switch_port_set_up(lsp, &up, 1); + } +} + /* Handle changes to the 'chassis' column of the 'Port_Binding' table. When * this column is not empty, it means we need to set the corresponding logical * port as 'up' in the northbound DB. */ @@ -19315,25 +18983,13 @@ handle_port_binding_changes(struct ovsdb_idl_txn *ovnsb_txn, continue; } - bool up = false; - - if (lsp_is_router(op->nbsp) || lsp_is_switch(op->nbsp)) { - up = true; - } else if (sb->chassis) { - up = !smap_get_bool(&sb->chassis->other_config, "is-remote", false) - ? sb->n_up && sb->up[0] - : true; - } - - if (!op->nbsp->up || *op->nbsp->up != up) { - nbrec_logical_switch_port_set_up(op->nbsp, &up, 1); - } + lsp_set_up(sb, op->nbsp); /* ovn-controller will update 'Port_Binding.up' only if it was * explicitly set to 'false'. */ if (!op->sb->n_up) { - up = false; + bool up = false; sbrec_port_binding_set_up(op->sb, &up, 1); } diff --git a/northd/northd.h b/northd/northd.h index e5a9cc775..ee34c28c0 100644 --- a/northd/northd.h +++ b/northd/northd.h @@ -74,6 +74,12 @@ struct northd_input { const struct ovn_synced_logical_switch_map *synced_lses; const struct ovn_synced_logical_router_map *synced_lrs; + /* Paired port binding inputs. */ + const struct ovn_paired_logical_switch_port_map *paired_lsps; + const struct ovn_paired_logical_router_port_map *paired_lrps; + const struct ovn_paired_chassisredirect_port_map *paired_crps; + const struct ovn_paired_mirror_map *paired_mirrors; + /* Indexes */ struct ovsdb_idl_index *sbrec_chassis_by_name; struct ovsdb_idl_index *sbrec_chassis_by_hostname; @@ -376,9 +382,6 @@ struct ovn_datapath { /* Logical switch data. */ struct vector router_ports; /* Vector of struct ovn_port *. */ - struct hmap port_tnlids; - uint32_t port_key_hint; - bool has_unknown; bool has_vtep_lports; bool has_arp_proxy_port; @@ -827,6 +830,8 @@ void ovnsb_db_run(struct ovsdb_idl_txn *ovnnb_txn, const struct sbrec_ha_chassis_group_table *, struct hmap *ls_ports, struct hmap *lr_ports); +void lsp_set_up(const struct sbrec_port_binding *pb, + const struct nbrec_logical_switch_port *lsp); bool northd_handle_ls_changes(struct ovsdb_idl_txn *, const struct northd_input *, struct northd_data *); @@ -1037,4 +1042,10 @@ struct ovn_port_routable_addresses get_op_addresses( void destroy_routable_addresses(struct ovn_port_routable_addresses *ra); +bool lsp_is_type_changed(const struct sbrec_port_binding *sb, + const struct nbrec_logical_switch_port *nbsp, + bool *update_sbrec); + +const char * +ovn_datapath_name(const struct sbrec_datapath_binding *sb); #endif /* NORTHD_H */ diff --git a/northd/port_binding_pair.c b/northd/port_binding_pair.c new file mode 100644 index 000000000..bfd3d0b42 --- /dev/null +++ b/northd/port_binding_pair.c @@ -0,0 +1,81 @@ +/* Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "port_binding_pair.h" + +struct ovn_unpaired_port_binding * +ovn_unpaired_port_binding_alloc(uint32_t requested_tunnel_key, + const char *name, + const char *type, + void *cookie, + const struct sbrec_datapath_binding *sb_dp) +{ + struct ovn_unpaired_port_binding *pb = xzalloc(sizeof *pb); + pb->requested_tunnel_key = requested_tunnel_key; + pb->name = name; + pb->type = type; + pb->cookie = cookie; + pb->sb_dp = sb_dp; + smap_init(&pb->external_ids); + + return pb; +} + +void +ovn_unpaired_port_binding_destroy(struct ovn_unpaired_port_binding *pb) +{ + smap_destroy(&pb->external_ids); +} + +static bool +default_sb_is_valid(const struct sbrec_port_binding *sb_pb OVS_UNUSED, + const struct ovn_unpaired_port_binding *upb OVS_UNUSED) +{ + return true; +} + +static struct ovn_unpaired_port_binding_map_callbacks default_callbacks = { + .sb_is_valid = default_sb_is_valid, +}; + +void +ovn_unpaired_port_binding_map_init( + struct ovn_unpaired_port_binding_map *map, + const struct ovn_unpaired_port_binding_map_callbacks *cb) +{ + shash_init(&map->ports); + if (cb) { + map->cb = cb; + } else { + map->cb = &default_callbacks; + } +} + +void +ovn_unpaired_port_binding_map_destroy( + struct ovn_unpaired_port_binding_map *map) +{ + struct ovn_unpaired_port_binding *pb; + struct shash_node *node; + SHASH_FOR_EACH_SAFE (node, &map->ports) { + pb = node->data; + shash_delete(&map->ports, node); + ovn_unpaired_port_binding_destroy(pb); + free(pb); + } + shash_destroy(&map->ports); +} diff --git a/northd/port_binding_pair.h b/northd/port_binding_pair.h new file mode 100644 index 000000000..c76d30ca1 --- /dev/null +++ b/northd/port_binding_pair.h @@ -0,0 +1,117 @@ +/* Copyright (c) 2025, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PORT_BINDING_PAIR_H +#define PORT_BINDING_PAIR_H 1 + +#include "openvswitch/hmap.h" +#include "openvswitch/list.h" +#include "openvswitch/shash.h" +#include "smap.h" + +/* Port Binding pairing API. This file consists of utility functions + * that can be used when pairing northbound port types (e.g. + * Logical_Router_Port and Logical_Switch_Port) to southbound Port_Bindings. + * + * The basic flow of data is as such. + * 1. A northbound type is converted into an ovn_unpaired_port_binding. + * All ovn_unpaired_port_bindings are placed into an ovn_unpaired_datapath_map. + * 2. The en_port_binding_pair node takes all of the maps in as input and + * pairs them with southbound port bindings. This includes allocating + * tunnel keys across all ports. The output of this node is + * ovn_paired_port_bindings, which contains a list of all paired port bindings. + * 3. A northbound type-aware node then takes the ovn_paired_port_bindings, + * and decodes the generic paired port bindings back into a type-specific + * version (e.g. ovn_paired_logical_router_port). Later nodes can then consume + * these type-specific paired port binding types in order to perform + * further processing. + * + * It is important to note that this code pairs northbound ports to southbound + * port bindings, but it does not 100% sync them. The following fields are + * synced between the northbound port and the southbound Port_Binding: + * - logical_port + * - tunnel_key + * - external_ids + * + * Two later incremental engine nodes sync the rest of the fields on the Port + * Binding. en_northd syncs the vast majority of the data. Then finally, + * en_sync_to_sb syncs the nat_addresses of the Port_Binding. + */ + +struct ovn_unpaired_port_binding { + uint32_t requested_tunnel_key; + struct smap external_ids; + void *cookie; + const char *name; + const char *type; + const struct sbrec_datapath_binding *sb_dp; +}; + +struct sbrec_port_binding; +struct ovn_unpaired_port_binding_map_callbacks { + bool (*sb_is_valid)(const struct sbrec_port_binding *sp_pb, + const struct ovn_unpaired_port_binding *upb); +}; + +struct ovn_unpaired_port_binding_map { + struct shash ports; + const struct ovn_unpaired_port_binding_map_callbacks *cb; +}; + +struct sbrec_port_binding; +struct unpaired_port_data; + +struct unpaired_port_data_callbacks { + bool (*is_valid)(const struct unpaired_port_data *unpaired, + const struct sbrec_port_binding *sp_pb); + struct ovn_unpaired_port_binding * + (*find)(const struct unpaired_port_data *unpaired, + const struct sbrec_port_binding *sb_pb); + void (*get_ports)(const struct unpaired_port_data *unpaired, + struct shash *returned_ports); +}; + +struct unpaired_port_data { + void *private_data; + struct unpaired_port_data_callbacks *cb; +}; + +struct ovn_paired_port_binding { + struct ovs_list list_node; + const void *cookie; + const char *type; + const struct sbrec_port_binding *sb_pb; +}; + +struct ovn_paired_port_bindings { + struct ovs_list paired_pbs; + struct hmap tunnel_key_maps; +}; + +struct ovn_unpaired_port_binding *ovn_unpaired_port_binding_alloc( + uint32_t requested_tunnel_key, const char *name, + const char *type, + void *cookie, + const struct sbrec_datapath_binding *sb_dp); + +void ovn_unpaired_port_binding_destroy(struct ovn_unpaired_port_binding *pb); + +void ovn_unpaired_port_binding_map_init( + struct ovn_unpaired_port_binding_map *map, + const struct ovn_unpaired_port_binding_map_callbacks *cb); +void ovn_unpaired_port_binding_map_destroy( + struct ovn_unpaired_port_binding_map *map); + +#endif /* PORT_BINDING_PAIR_H */