From patchwork Wed Sep 2 21:17:31 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297031 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=HJZlc3U5; 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 4hZwZT0P4Vz1y03 for ; Thu, 03 Sep 2026 07:18:40 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 0FBF1608A8; Wed, 2 Sep 2026 21:18:39 +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 zyQ50dkdm0Iw; Wed, 2 Sep 2026 21:18:38 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp3.osuosl.org E7EE860834 Authentication-Results: smtp3.osuosl.org; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383918; b=aPDx9TS+Sp5kWc56BKGrUpacANRM8ITSkIcvzQNO/4Gi6IXy/WxZIef+QUbjHvKtb0m/ j334SsPgXJSAPZJUe/pCk3KeF9DJ4vt+wYph2vFCA0A8GE7TOUrxMxZNCzL0dNHZGhPiO 7YfaysHbSiEBz77kmYIasbiLVH8UPAtVzYkRrGV8zDo0463slulrX1p+0Zk96ojlT9YzY WyfJIqfKXwVrwD+d7M0HiJvKMafYZxBEt5VGS6QeIlhJEQO1NK2XlYB6/UBuW/lfgyndj ut4AqvMyovi1BOb//vuXQYCxruBvQdH45XLYwSXcqdC1GQhCmb3EdhLbFNuDa2yVK/Q== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383918; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=VNezbuu9b2FkfVpEqfw/zURSewM6c5PIhCmKtQINv3U=; b=L5m4OjRS2hzrCHAaFaQIiPvDbB/NvclcsOrSyXNS5A/35qWDgNPaFD4sE1Y6+jN83Vo2 FXF5NyqZzymyZMI5+FlcqXBPgU6O5Q9F117VorF5tiXIk3caWvYCrhkQuapwoN8+fGGWP TI1iJ0U3FmybHmZiNYOIJQrRiCEPbcZprr4GM4jtu8AJ2jPOEaYu/Nfm++nDOb+X/YTNk yHHJInD/J5BDKt97IoXNh2uBelBnJ9/3U7JnkfQk+36OKXBxTPdGdE4NHcqWCHs6xuPuX e/ds/Ed/WRfs9LKtooSpvUo2z3mZ9I/0K3HM7YsHSvGmsW3IO86AE+2kDNnGmgkaXFg== ARC-Authentication-Results: i=2; smtp3.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=HJZlc3U5; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" 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= Authentication-Results: smtp3.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=HJZlc3U5 Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp3.osuosl.org (Postfix) with ESMTPS id E7EE860834; Wed, 2 Sep 2026 21:18:37 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id CEE20C0418; Wed, 2 Sep 2026 21:18:37 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138]) by lists.linuxfoundation.org (Postfix) with ESMTP id EE632C0417 for ; Wed, 2 Sep 2026 21:18:36 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id E0DBE80FBA for ; Wed, 2 Sep 2026 21:18:36 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id fAfzzZ5y6fCK for ; Wed, 2 Sep 2026 21:18:36 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp1.osuosl.org 36ED580FDE Authentication-Results: smtp1.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::b12a" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383916; b=oIGDNGkaQUzvVKpz4vdWgY/FBK52iOtq7RlRwFbQy+3uotvjUECsJUM7YWAoww1Y7y7L qFp0dy4dg71b3gMcallfWE6QEmo4r6H/FoMi3gVZFqGTMI4NmPPClbkFftHs62r7XBmNo DWuKQT1awiNUIYoH5m6wIu7SD4XZiZUw4dg2obgzEV24EYcQNYoO6lxJxnXN73NcEaLD/ AFtU04H+LOuoMXUFKoVoYOrV64cPpKRBZNF1+6iwK+icdhLiCHqXw04V0Hdu/KzuxDKS6 HItj9Q+ieyuMtrlgegwa3nRGkJKJ64PFvr2iptAxX8KYsZ//6OLZs0DXLXZIIu0AaTA== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383916; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=mf/WdyJuzbgOoReewcY44ERr7neawQcXBeO0pjyxEfU=; b=ZzUiwvuFRk59W7SHPoCfZT5gCKYozMs6/yMq2o3tyYq7gxL0T9pieCT5PoLY+1LX/kN7 8ojcaM+sgBOuEsEREYstWE5xH8MkkVjKIVZm//AYuo+PJybOVYQvMoETDZUZ6cM4lkZhA B/1JTxxd0mnWlfmiOwrZFhs0Im3htIfjEiHvDTzAjqWDCIzFKdDHdOiwFNgA+xz2p4NJN WH5udxu+gsljkhUTRlmSJvHw6Ytz98dX1bB8co5FwIxW0j3x1V50UyPT94DBTCf2Rm+zd aBci1dhaUgjYTjtMzqVjmEi5++Jbo7412eovQGYv+gF2fKaVi1eeg5vcRVA+Xyn5cGw== ARC-Authentication-Results: i=1; smtp1.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=HJZlc3U5; arc=none smtp.remote-ip="2607:f8b0:4864:20::b12a" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::b12a; helo=mail-yx1-xb12a.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp1.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Authentication-Results: smtp1.osuosl.org; dkim=pass (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=HJZlc3U5 Received: from mail-yx1-xb12a.google.com (mail-yx1-xb12a.google.com [IPv6:2607:f8b0:4864:20::b12a]) by smtp1.osuosl.org (Postfix) with ESMTPS id 36ED580FDE for ; Wed, 2 Sep 2026 21:18:35 +0000 (UTC) Received: by mail-yx1-xb12a.google.com with SMTP id 956f58d0204a3-66e63c2c9e1so380524d50.0 for ; Wed, 02 Sep 2026 14:18:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383914; x=1788988714; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=mf/WdyJuzbgOoReewcY44ERr7neawQcXBeO0pjyxEfU=; b=HJZlc3U5mTvBM0kRr+D4xoa/Ocd6wIM/OirNwaSfbmciqUyDWWrxIPdZ13jV7pcxM8 tdgXyVnGBaL3DTF6zX4DBsvMFPA8svkoBQ+28w7ikxyfH7Fk/KE669a0Wnp4OPFOIC8j HnNY72d9R5qTCeMmJAm8JvKtjQiBsZ8H0GPLcKBjFPmLHVYpOpkK1vSQRxHiVAPYdOBL tLnLLdTq8LYcqSxhfsY8TV+COTMT9R7kfhw0avuvCn+sSyhW67IfkwsLgDKsb3kqZGSz VFt38LnkrLaqqirtNjzT+dFvaw7211P9a4fHzC/4CBBZBtanSXajk08+PKLv2Ah5s4ma qx5w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383914; x=1788988714; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=mf/WdyJuzbgOoReewcY44ERr7neawQcXBeO0pjyxEfU=; b=SpmjK/kvL67GzbEFnVJhfxGG/3H2UqyS2WcANhncCIM/ctmb0gZYnj7goyzzq6WlUO SHBpxai/DexDGDxT5MtSX/yx30WS1OUvohE8K4kPX5G+667zl4+CKugLDTcrKZBKCnqW KFp4BdEAyUkMtLsOy59uU6+dfYuoiTgTKTw43D1plX2tupqTZYAWeiByUGL589B/dwLJ zVGdLsLj+NkbpCREuTAcGfjnVWmaGTF4K8/tXB54F1ktsz9nZ0Unkdoax0mREuQVGOu6 MxPUWhA7RTJ47APPM61Dk7Zb6MyRk4j2JnZ9QVAqz7fOh3TVHPRCDD05k0MgHqzkU+OC ER6A== X-Gm-Message-State: AFuF++lUli8Bbx2Y6PvZeTBShlV/OvmPek1YEU4Nvt9RdC6WuxAm8f+M resk+kqolZLwZ+ImilQHq0C7T1VlN7YIvSIH5OW5tAG9NimyA4nqE33G8AVVGZqgcQ0z/K+SdLN mAgIuUaXfSp3q5jaugu8xV/9yWrWwibDJY6Loe4Wko0ahyBUgj+QOY6LHmr78M/Y= X-Gm-Gg: AYBFou2/oAY6ldY/2Y8VTaMXFMCM/lElVDYMU/IbHgUGzBhRh1xnaFzrLLpLrz5AUpO QxvA7UgohE2Rio4VLbegeRGbaAMf1DkOssTvplejQKs4RxKxQs1CL0Pl9YTcgXaESzHL6ljhkvS X48myVO+DIVGin0jtZfeY9b8+7PoCDk4J44DWC4N1jfMtseYbGhTvZ2EP7E1NAm/TVGMOvt+89h QDnIx4Epyc1Cv8rQiaKLs7IpF4SqpO6GFfIzG5WLifcQnEVuj+A3OnWUnTQ0lf1xth/liBE44Il 0eVFVyyXB0wZfVzSiGGe4Oqj5kOmGvMMZArDlu5ZZStKhJzoO+iDVIKybZtGjOyeIfezVd/YHpi JeiIp4DI3zJj1eAZny6WqldJTFp7DeFImgc+5RJD7/kayvmH4z4e0xo1/f5J/Xd6WNeOfuazznx HOzAgA3ntoTC+EIKskLCn7iu4LQwF8ENXyw7Zm0LJAT635IQbuNq1hYrGg2sqiTo9b/FnM4MVx5 pwoDstrBcoJWqdq9NfBwk2Ke/XzfJ6d X-Received: by 2002:a05:690e:dc5:b0:66e:6323:48f0 with SMTP id 956f58d0204a3-66fa9190a90mr717071d50.18.1788383914119; Wed, 02 Sep 2026 14:18:34 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.18.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:18:33 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:31 -0300 Message-Id: <20260902211744.2159261-2-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 01/14] ic: Do not include config.h from headers. 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: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" config.h is generated by autoconf and is only meant to be included by source files, as the very first include so that the feature macros it defines apply to every header that follows. ic/en-ic.h and ic/inc-proc-ic.h, added by commit 782b8b535438 ("ovn-ic: Introduce incremental processing."), include it themselves. Drop it from both: every .c file that includes these headers already includes first. Suggested-by: Mark Michelson Assisted-by: Claude Opus 5, Claude Code Signed-off-by: Paulo Guilherme Silva Acked-by: Lucas Vargas Dias --- ic/en-ic.h | 2 -- ic/inc-proc-ic.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/ic/en-ic.h b/ic/en-ic.h index a4b75bb0e..48150b163 100644 --- a/ic/en-ic.h +++ b/ic/en-ic.h @@ -1,8 +1,6 @@ #ifndef EN_IC_H #define EN_IC_H 1 -#include - #include #include #include diff --git a/ic/inc-proc-ic.h b/ic/inc-proc-ic.h index 36464564d..30e3071ae 100644 --- a/ic/inc-proc-ic.h +++ b/ic/inc-proc-ic.h @@ -1,8 +1,6 @@ #ifndef INC_PROC_IC_H #define INC_PROC_IC_H 1 -#include - #include "ovn-ic.h" #include "ovsdb-idl.h" #include "lib/inc-proc-eng.h" From patchwork Wed Sep 2 21:17:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297033 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=RZaoWgdo; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138]) (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 4hZwZZ2j8Hz1xwZ for ; Thu, 03 Sep 2026 07:18:46 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 8720F8105C; Wed, 2 Sep 2026 21:18:44 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id tHrj53HVAHg7; Wed, 2 Sep 2026 21:18:43 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp1.osuosl.org 10C0381000 Authentication-Results: smtp1.osuosl.org; arc=fail smtp.remote-ip=140.211.9.56 ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383923; b=STeCXjm6pMlY/zkt3P0c0sqk7slIcd16gzaeoZUfsFObJkKXNFey7uQUn/naxfaDaWal wFmQJCgUJINgTyPHQ0ajP4aBVa8twpfhvtFcOAumLm2TbuC/C+A4RzLej5S5fFHjCoE/O S6gmEXJaaD41uzm1DQVVDAsSIXhKQoWCy0IR/MoQNh4vrbgk4lz0DqJKCgLRG3NG4blf1 VKf/JUCQrhLkF65i+pC7Dew36VLp0j1TzMJNJKp9VL8CdSYPjetRGqHkfuOcut6cJ1IvJ voybTzVhIc3LrajkKmBBVllCqtU2OgpxeSxroEIheJr3BCWIhD4qtRfoyNqVI0uyUsA== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383923; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=cAcxT563q03lU3/e2pleWsQtvjuY+IJgi+Y2sTKQDEk=; b=JQJixd+FALS1oEPZdklcBUOVxZ/HpvLNeIn+OiY4bD87UIEpgTQf49Z+sv8tgqA068j2 Y3ghtrKi5Mi5gYIbgo82W/iMCrxYmizKwKBMENPZe/fi4msvfJkEJ81R4VtQtZEqz8Cyx OWwKAWDWAm6EqMvWQXMnIeh12xIegK6j8d/IzmFrzyySTriV8bc3uWPjPkCoR/LGWLgEb gARJ4R7Q65y7/2LPl5/Wnhz6DqwkEJC8O/+pxmdStNHxfFNeeYMDRzxxHGH9vVfv3gxXe Ju00i3H0pCSr7K3uXHs78kcu5fkqkwGGR9y+4kZ3rr5Kycm5ZRsCUZAXmAjGMsQrZHw== ARC-Authentication-Results: i=2; smtp1.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=RZaoWgdo; dmarc=pass header.from=magalu.cloud; arc=fail smtp.remote-ip=140.211.9.56 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= Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=RZaoWgdo Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp1.osuosl.org (Postfix) with ESMTPS id 10C0381000; Wed, 2 Sep 2026 21:18:43 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id AAA84C041A; Wed, 2 Sep 2026 21:18:42 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138]) by lists.linuxfoundation.org (Postfix) with ESMTP id A4925C041A for ; Wed, 2 Sep 2026 21:18:41 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 8607480FDD for ; Wed, 2 Sep 2026 21:18:41 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id uC6UZkn8UwFn for ; Wed, 2 Sep 2026 21:18:40 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp1.osuosl.org 1A75280FFF Authentication-Results: smtp1.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::112c" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383919; b=lgHO8LsR8YctCEeOhlUewn8rlE/G6fsynijV5suhUHnGnwcXx3kjoydqRdfMhoeM7l8J ct4i7UHZV/MxMnKGgc8yD5lz3TAvcHD+q8D7csqisAuLDyngwVxZ6YAupGqPg15Mmn5D8 lZVVpx1IDKoSGS4xYIbZpeC5v/Z9RPMt6283uZj6QhJeF+So7tDlXP9UBpvR3Z1/07t0+ UyqBjAq2MF+7zXhK9cZ7FNUqhNujaGQTpJgY7tdxvpArxnMb0a0nsJVpo7/FbOO0Qq4LH 29YqnUhCPo5T+jj/NEOWsGfqCq7NA4CffMD6jRbl4bhw7ZtWjL5RAta9Qyw6INMO4cQ== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383919; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=vW7AM4pmzxxahjiGHJpP7iCVwtTU36OcTExFJPIC+Vw=; b=CaqkckDtgob7XaYNownnryQXWR2wwcQO8P6h4/Z1WHl8d5Vj6d/MaacaebPjMZJL8Ss1 twqPaYEFlfU9IanGx0D0mG8l3otClafkpBkMY2EnONFzU/aKzEJH2InBbC9+hFyQz6+0w YF9wT2BiiZ5++vUIAoJcG2uK+ZTLvW1oQDFu7RgIHLECDtDb+/ycyxdSUU8YZz7le+P3i cgHFbqpdjTkGC44eeUUWQLmumu6CIGrYggMcihRW1Wf0+deQ3Lcrl/YUg4niSpzKtR1xT HqXJ8zJQRf+E9fFVh8AslJbrL8gzVKRepevDO4M/kmOOmxXsaR42vXwVCVJOXNBUfmw== ARC-Authentication-Results: i=1; smtp1.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=RZaoWgdo; arc=none smtp.remote-ip="2607:f8b0:4864:20::112c" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::112c; helo=mail-yw1-x112c.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp1.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Received: from mail-yw1-x112c.google.com (mail-yw1-x112c.google.com [IPv6:2607:f8b0:4864:20::112c]) by smtp1.osuosl.org (Postfix) with ESMTPS id 1A75280FFF for ; Wed, 2 Sep 2026 21:18:38 +0000 (UTC) Received: by mail-yw1-x112c.google.com with SMTP id 00721157ae682-86e7f44b773so4698087b3.2 for ; Wed, 02 Sep 2026 14:18:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383917; x=1788988717; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=vW7AM4pmzxxahjiGHJpP7iCVwtTU36OcTExFJPIC+Vw=; b=RZaoWgdo/15MrJvFv79oqAtTqs4xdvX5McUtNO/1RKbcLK/hSfrzjJ6lfW/IbFbt/G rcKDoybHFy9UW4wl9j9YQ47gxSb0DNe3+4O6Y6pmJGhTQIyJ/X6jXxsE/dmRvx/GepH8 7yh0hfEmQ65ralAkbSD0d83hMI6ADSPfviUeOqb3BthP+onbMO8etKFH97uJ5KUTt0Nq xHpB7wcadDUluCctAra620f6biajWIg49Vldb2bd+t13VciyXiutJe6lZkW7pL5Xz44P nZICCETWR8KsOJJmrKNtVbfSnl7oT8Ostjg0uvT62i2d/bQhrMIi/asPAIqAPPLpORVO 5+Tw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383917; x=1788988717; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=vW7AM4pmzxxahjiGHJpP7iCVwtTU36OcTExFJPIC+Vw=; b=faFlHclKkzBnZABT4yVlhMwMWFRlWNhhEdbhsL6mDHRRZHBr7X3ZJAWtHSNETm/cv+ nd2HlNPHxqF+w19f3fYCZKk+wmzmOC0+6P6rXVh3Nw6v6sbTPp41qf9SZhcXaCZZzBTI TbFlQqGqWa8omTMhZw4ODPFFdrhhWRAqmxXGHBHtrE8+HJ6gqyGijbKaB1txcqlsyT9r 6D289+4jbRbh+gLfli4DqfwvwUY8G/lP33SvVgnwnV+anUH9kH+X8svgGSFOHQXdSF66 +uSxNv5rgnzobs03Gh6Jxp4qarwF8TjxOj6BIjW/mKN7rxUrZhDG086KlvJPvjkbvd3o FJLg== X-Gm-Message-State: AFuF++lAVoz+787Ytr44/Bk8LFv12ywyEBTnuHzHJPJz4RVm8nUaM15p 9ZrQM9kEDFaAPwntFbMlc/WTEROu1n1YP9jFlKX+ckNdBEt9LMBFqttERVqaffmDXmpKeso/lKT skMnGQKxPp8EtApTBT8QBnwwGSAjKHjTjEEbFUcfxvBm+dazn/TcXK1Ov4S9G6nY= X-Gm-Gg: AYBFou2/d2xW3Xol//VSKu3HJPpMou9L1mJrYm15Hgkdz3c4Epd/Z5+az9FVDzIQQ6b JjfQGz3OomUoFw2FCoADtoCcxfX41nWt4CfCNq7ueotNVZMgGtRPDYDMExdxLZ6A1bRyD9QUeVj mhhHrmyRdZwK8OSBu+1D+5NKEcaPqnXbeqq5t8NO8sV84Q5wiGpupM9M7apFUA4PmAFjV4LotoV NS+Zh+c7JFBbQb1r8KM+bPG/XRl4h9yZTiONNSvzpRoKzucCSZyvcSz2bIquCk2bK/4i9daaGKx e/fDsuG9H3cChtlfgW5uvVt9B09OjX7tUAJPi2sYMTc6ekd9clQWhlNAIPoaWMlVFDJbimRj3ey Q2voQ2Vh/kAmOjhi/BgXTtWIPgffNcBRkMPRdP9QubwnRpNRnM7aaf4u9l22DfAcsK8j5K6vZVa tId9Dsf0KSCfBScSO+Xb/BeFypLwUtO800pciB4nwsGY5Lmpv6Kc0bTuvG/pYeB4pmyPOlR1cH5 hxZnxrofOqr5G2ZTYzC0wIpSH5Yuq55q0EPlQSxNbI= X-Received: by 2002:a05:690c:c384:b0:853:c23e:9077 with SMTP id 00721157ae682-86c53995626mr30788517b3.26.1788383917345; Wed, 02 Sep 2026 14:18:37 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.18.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:18:36 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:32 -0300 Message-Id: <20260902211744.2159261-3-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 02/14] ic: Add missing license headers. 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: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" ic/en-ic.h and ic/inc-proc-ic.h have carried no license boilerplate since they were added by 782b8b535 ("ovn-ic: Introduce incremental processing."), unlike every other header in the directory. Add the Apache 2.0 header the rest of ic/ uses, and separate it from the include guard with a blank line, which is what the .c files in the directory and most of the tree already do. Give ic/ovn-ic.h the same blank line so every header under ic/ is formatted alike. This is kept in its own patch so that the patches that follow stay limited to the incremental-processing work. The same boilerplate is missing from a handful of engine-node headers under northd/ and br-controller/. Those are left alone here. Assisted-by: Claude Opus 5, Claude Code Signed-off-by: Paulo Guilherme Silva --- ic/en-ic.h | 14 ++++++++++++++ ic/inc-proc-ic.h | 14 ++++++++++++++ ic/ovn-ic.h | 1 + 3 files changed, 29 insertions(+) diff --git a/ic/en-ic.h b/ic/en-ic.h index 48150b163..bbba92dd0 100644 --- a/ic/en-ic.h +++ b/ic/en-ic.h @@ -1,3 +1,17 @@ +/* + * 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_IC_H #define EN_IC_H 1 diff --git a/ic/inc-proc-ic.h b/ic/inc-proc-ic.h index 30e3071ae..993050f8a 100644 --- a/ic/inc-proc-ic.h +++ b/ic/inc-proc-ic.h @@ -1,3 +1,17 @@ +/* + * 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 INC_PROC_IC_H #define INC_PROC_IC_H 1 diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h index 1d3f4e4de..4db493e08 100644 --- a/ic/ovn-ic.h +++ b/ic/ovn-ic.h @@ -11,6 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #ifndef OVN_IC_H #define OVN_IC_H 1 From patchwork Wed Sep 2 21:17:33 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297034 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=BoWDC6m0; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.137; helo=smtp4.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.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 4hZwZd5LfFz1xwZ for ; Thu, 03 Sep 2026 07:18:49 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 05E3A40B15; Wed, 2 Sep 2026 21:18:48 +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 kx1n75QTHQuZ; Wed, 2 Sep 2026 21:18:46 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp4.osuosl.org 5EF9A40B0A Authentication-Results: smtp4.osuosl.org; arc=fail smtp.remote-ip=140.211.9.56 ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383926; b=meQPQDJySwr0C6XVX1zbHSBMJLWo0frqSlkxL7FmcPgRGKlPeXEbIqdRS4w1kKCt3QSo VVrkE/D3Lv17fU6h+Kw7+OBHweg/0gMoulESCKTS3drxidcLAGosNvRQTwNhnLNF4k2px 359IRyQ5NCB8HsukBLAG0WYUQEeSaNDSAUSr6REyqFGfdNC9xI1yzAhFIelnIZ1YiS7xs W4gepd2ldXDE1psnXJaRWU/YRzhkWLLx4VGFdEvsEusaVzOaYT8F1Z8YQgqjoKxkJe3iF t/G58kFB6CtrXK9Qfk2xShuFc6BYHaGfc8vAHCyQrUPhfvQn2DlOo9ccDrMuls4K0UQ== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383926; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=yJIm59WTeP2IwBWsvM/njFMkh3+MkzZ9wRxOJW5rwZI=; b=BYv2DgypAfpScmqVHQrcaX90qm/7gIBrHB+1V+6iWTj6u6iJJIuSxS6/SOIKDoTvdkWn U2bjVNLEJX0T5wtRYQfnyn2VLT0R72grUpE18HUJ3ndbZOKfv9x4pMD9tUAqyweHYoGIz Cg5RP0Co6ihDglIch55yQ4dbB/naNaXgJzCLCHc5643TMsSP1AnEpc/b4wx9MB0fyzs+B swqoJG/VotDFFHiGVFev3bbKTAPcxK3Wwni4cO82DiSMcAzB0KBcNE6/wkSfSBIhwG9Ic UYR1KnZ7URRi+b+woixlkSYLJptorBCCSPR+G1MXHIOZocLH8ZsrF9rw/ZkTjXB+NcQ== ARC-Authentication-Results: i=2; smtp4.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=BoWDC6m0; dmarc=pass header.from=magalu.cloud; arc=fail smtp.remote-ip=140.211.9.56 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= Authentication-Results: smtp4.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=BoWDC6m0 Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp4.osuosl.org (Postfix) with ESMTPS id 5EF9A40B0A; Wed, 2 Sep 2026 21:18:46 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 3C476C090A; Wed, 2 Sep 2026 21:18:46 +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 32019C0911 for ; Wed, 2 Sep 2026 21:18:44 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 18A4440B18 for ; Wed, 2 Sep 2026 21:18:44 +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 E37gMVrnYkOP for ; Wed, 2 Sep 2026 21:18:42 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp4.osuosl.org 91C4A40B46 Authentication-Results: smtp4.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::1136" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383922; b=hNbzlnlcS3x2Oul8FDCSn8vtu5pCQGWDSl47CRmYgQWzUcDF31GIn+BQgn7ON6I/neT6 QjXUoudkaoLyuPACTEVz6yY+SKsSGvReWa8mPumXEDO7l05ezYeVL+ZLMyPdAjhz9hec6 mjqYsTKvCXNVOqxGsaLMAm0X2z2AFq2kYIgofIQdCd0N0dsQtMajypYlu9JQEhutHwUp+ xl87RdOdwVUQ070ilnyCIgFjzJdnMenrdTjAh7zOnPm76epob2+CX/APgtAPIck6ZJCuA Uh0M4DLSm4mf/HKHX5MOY+CK1OkC3P8D80KQrNG1VObClrp+Zk/r+dPDLRE/89uYcpw== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383922; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=dGYlHiTmpWbsC54jwViXnR8ZXRKEwAeKsWqEyVeqrJs=; b=OAC01aklZ2zdRLDtSNZK6h/6Ig3xdmkwSBPd+OTAzRzVz6rMh4hSsXlYTLepPZCarA91 BuV3wkNyjnZ828ueS5S4phUJv/McoM5jOmseVcK0sZxmM/5T/dtXkSZlt4Kd7t8ca55eD hEFa7aMbYeAVKZKM+kHa0vt6/fMiziP3wSw+awMqDZrOxPKX2NGANqeneErvuxDJHL31j qNDbpZPzkoXfViZmxlGQxwgQbN4ajvaDHQescsYSbU1x0EzlD06/1mls5Hs6mn6b57Aj0 MwzOcd24SjQqjWwikh6FU3TZ0tNZ5V5Lc6SuB5qgyWjpXkRtaRUlmh2NdXKk0s3Nxqw== ARC-Authentication-Results: i=1; smtp4.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=BoWDC6m0; arc=none smtp.remote-ip="2607:f8b0:4864:20::1136" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::1136; helo=mail-yw1-x1136.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp4.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Received: from mail-yw1-x1136.google.com (mail-yw1-x1136.google.com [IPv6:2607:f8b0:4864:20::1136]) by smtp4.osuosl.org (Postfix) with ESMTPS id 91C4A40B46 for ; Wed, 2 Sep 2026 21:18:42 +0000 (UTC) Received: by mail-yw1-x1136.google.com with SMTP id 00721157ae682-836c8bde2dcso20172257b3.0 for ; Wed, 02 Sep 2026 14:18:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383921; x=1788988721; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=dGYlHiTmpWbsC54jwViXnR8ZXRKEwAeKsWqEyVeqrJs=; b=BoWDC6m0RI7rmU4027HcJ1gs0IzHNsUP16UFOTDDzmdUj7cezqdOD4Zrizp1tm3rtd IuKosW1P0vu7AyhrwbxHqx/7UNOnwST8VlTEwMc7UFrGe4ssj201l2dnkpHmfExa3aP9 9fQ6/xmceh7XBASqVgp9qszkpuim8esDjIFXXmUoGvfrN9ouPspNU3hzYSh0Vfu2ZufJ ARMHy54Gd9TTBXVizuLK7MidxqM9BBnZuyFRPzHXEPvGEhfMyU1xatj0TebEDlleupxm mrN+qJOIKtSNRaCgzUGzran4r5DyUq74oDSi76hzu0o5gC7ngRXuGzMP52kihuqrt5dr 40bg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383921; x=1788988721; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=dGYlHiTmpWbsC54jwViXnR8ZXRKEwAeKsWqEyVeqrJs=; b=tCgd4eP69ftr3pGmZd33dOPmRugoDKtCBKaarAJgzsOXdxcyBg4AoEjEMfV+thspKA 7dYTaU28OinDgO1qKcPbg8q2Yl72/f8TMkTX1rgiOALD3LLScsH00rBotOGNIziLoOlg n6wlLAFgLS9acp25L5mVSiE6pF6zoXIbQX4Q9JtXft1rC3T23MD3UKA4JEXfTmYpT8Ay NnOgy8Vu1FayWy3p0t8kCIycUCWHpG9MJ/IDmQ8NSD+3bMmtjE5iqCRz36/ozmDuZgep xiuAC1I8uMD1Z61eKvowr9MhRqMI93wfgA2eNH8Mb3Hoh1iMZwPuEQkQ9l3XfU0SkR/0 j7NA== X-Gm-Message-State: AFuF++mLX3PWDhBpxKX/qOKM0B2ii0SaTlUc+2QWNJKHSP529ODBb36z j96QBgBhjmCtgLmldES9F9Y4FWMD/vQbQqVuENBpfRAW943POISPooKWWto+DGRvnVIikAjO45+ Q/WqHEgU4mIV3JSC2h4cI8qdOjBIHobYyZXZ3iGXEAryTaXae2p2JtC1RH7vE1tg= X-Gm-Gg: AYBFou1c4HdhDMd4fplR+zBSp12FVhCJrUzE19zA0UefmjHVbuk3BpjzI/C+m+jq+Ca 9/TSCc8/nQJEiMIzIRKLYCMFBdx8iwpulPQ8Bh5vvWpZCEkoFXtYwYg8jhjZbEn67jFR8RZ/2hX vdVVYwj/P78YPfA53VHe5owBK66HOdYDPHEIJj/Hc9WPaqsBMpQLLtd1N5lU1d5RSUpUj4GgmQN nY1DHNARmpQh266YWSsraRoLwjTul20T5oom75sGk/2Z0MfpjfqI69UmIPXa8wYGV6a2NzqlRFw ErFXuHeJeCQRL8TpcaQ9WCfHBskoF4+bBRc7ILd6xgttZQ3tMkmrXlzUakUwYI7UktizH/ReNdl uPnIsqYWQqBdvjaYLwMNC3eP75Zm1NQ4lG10hw/r9BtU1UqVDC24CtlKq1UgN0VkbyD4ufortsW y1PSg2yjvsK73HBbWw8ndGQ0X7dyfxbWPSyT7CWHwb1YtffYXp7bxvXheDMAWpnSfa1QhJGlLu6 H2/b1p1lDqYWkuK0+jMIF7glgUbC4lz X-Received: by 2002:a05:690c:d:b0:857:97ce:dfa5 with SMTP id 00721157ae682-86c4ecab28dmr38099997b3.10.1788383920722; Wed, 02 Sep 2026 14:18:40 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.18.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:18:40 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:33 -0300 Message-Id: <20260902211744.2159261-4-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 03/14] northd: Read ic-vxlan_mode as a boolean value, not key presence. 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: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" en-global-config.c decided IC VXLAN mode (and therefore the datapath tunnel-id range) using smap_get(), which is true whenever the key EXISTS. ovn-ic stamps other_config:ic-vxlan_mode on every transit switch, including the literal value "false". As a result northd treated plain Geneve deployments as VXLAN-IC and capped max_dp_tunnel_id at OVN_MAX_DP_VXLAN_KEY_LOCAL (1023); past ~1023 local datapaths northd fails datapath tunnel-id allocation and the SB never converges. Read the boolean value with smap_get_bool() instead. Fixes: 059763e8683d ("ic: Fix vxlan encap mode.") Assisted-by: Claude Opus 4.8, Claude Code Signed-off-by: Paulo Guilherme Silva Acked-by: Lucas Vargas Dias --- northd/en-global-config.c | 4 ++-- tests/ovn-northd.at | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/northd/en-global-config.c b/northd/en-global-config.c index 4e6b07ebe..f31e7d3f2 100644 --- a/northd/en-global-config.c +++ b/northd/en-global-config.c @@ -187,7 +187,7 @@ en_global_config_run(struct engine_node *node , void *data) bool ic_vxlan_mode = false; const struct nbrec_logical_switch *nbs; NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH (nbs, nbrec_ls_table) { - if (smap_get(&nbs->other_config, "ic-vxlan_mode")) { + if (smap_get_bool(&nbs->other_config, "ic-vxlan_mode", false)) { ic_vxlan_mode = true; break; } @@ -478,7 +478,7 @@ global_config_nb_logical_switch_handler(struct engine_node *node, bool ic_vxlan_mode = false; const struct nbrec_logical_switch *nbs; NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH (nbs, nbrec_ls_table) { - if (smap_get(&nbs->other_config, "ic-vxlan_mode")) { + if (smap_get_bool(&nbs->other_config, "ic-vxlan_mode", false)) { ic_vxlan_mode = true; break; } diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at index 1e567704d..48dac8512 100644 --- a/tests/ovn-northd.at +++ b/tests/ovn-northd.at @@ -3392,6 +3392,24 @@ check ovn-nbctl --wait=sb set logical-switch LS other-config:interconn-ts=LS check ovn-nbctl --wait=sb set logical-switch LS other-config:ic-vxlan_mode=true AT_CHECK([test "$(get_max_tunid)" -eq 1023]) +dnl ovn-ic stamps ic-vxlan_mode on every transit switch, including with the +dnl value "false". Only a true value must restrict the tunnel key range. +check ovn-nbctl --wait=sb set logical-switch LS other-config:ic-vxlan_mode=false +AT_CHECK([test "$(get_max_tunid)" -eq 4095]) + +dnl Anything that is not a boolean true is treated as false. +check ovn-nbctl --wait=sb set logical-switch LS other-config:ic-vxlan_mode=foo +AT_CHECK([test "$(get_max_tunid)" -eq 4095]) + +dnl The same must hold on a full recompute, not only on the incremental path. +check ovn-appctl -t northd/ovn-northd inc-engine/recompute +check ovn-nbctl --wait=sb sync +AT_CHECK([test "$(get_max_tunid)" -eq 4095]) + +dnl Setting it back to true restricts the range again. +check ovn-nbctl --wait=sb set logical-switch LS other-config:ic-vxlan_mode=true +AT_CHECK([test "$(get_max_tunid)" -eq 1023]) + check ovn-nbctl --wait=sb clear logical-switch LS other-config AT_CHECK([test "$(get_max_tunid)" -eq 4095]) From patchwork Wed Sep 2 21:17:34 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297035 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=AgftY8ZX; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138]) (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 4hZwZj1S7nz1xwZ for ; Thu, 03 Sep 2026 07:18:53 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id CC1DA81006; Wed, 2 Sep 2026 21:18:51 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id K8HcRJY0ykQw; Wed, 2 Sep 2026 21:18:50 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp1.osuosl.org C971F8108B Authentication-Results: smtp1.osuosl.org; arc=fail smtp.remote-ip=140.211.9.56 ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383928; b=VgpDky6NjmPfKMEBDIlTMr5dSnvKHqOzFomo48db93+j0s9usc6RsAySULeWNu+CnTM6 4MzDUEeawG7T+LqUTVQ4l4Om66bdPVBDji3eKbJt6JIiPgCZ2C9HzxLMGpHiyfGKd1l8N MzhxuOHebJzL2WLPbkqKOV7b01dOoiZ5YKfPcFNoe85rN4lugekyYT4Jr5D8x7pKLIqFv kWcShg4/9iMTl2i5Pzf61SGEclM6C2mYWfHOYwZMbpamZbeCpwZq+9wGe/3t2Yu4ff1jx DbZCMfaQ+pZV0MI3T4Rd09rNwQidGRNibi5zew20UNp7uLQHMnnBWEG5HtvTNXAcoCg== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383928; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=j75h7VxqEsMlwLxsuttsGZu4lz7a0cxQ0pfJ8VFU6uA=; b=D4HKjedIFnE81ehkFzYZ0pkwKdntg98IYFxQO03ps/WVnlrsZ8HKn6ombsJ5XYHUGVXv GYkPWE6HuiTpekstrqpbm8I02k1aIHq01+32Rr2DB/aizePOlp9LOY506081ZaobLN021 pEO29smdY73s2tlCNKL2cE+EoWhSwkgc/BE2B0sN4Ld5IoXO319JcfYVzutuWkbwdtq0b Xs/DxCwTyP/qYo9XSsMB0RQJDd1xSuviKt/wU/97EY2BWEeujbmdsnEn87lJJL6Z6RNwp ZcQzDo0Jc/gRuCDgAlOci4pQhui6Aax93EuFYGtAyJvrHjinRnA7vvgUrFFRIYJRj1w== ARC-Authentication-Results: i=2; smtp1.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=AgftY8ZX; arc=fail smtp.remote-ip=140.211.9.56 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= Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=AgftY8ZX Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp1.osuosl.org (Postfix) with ESMTPS id C971F8108B; Wed, 2 Sep 2026 21:18:48 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 969AEC091B; Wed, 2 Sep 2026 21:18:48 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp2.osuosl.org (smtp2.osuosl.org [140.211.166.133]) by lists.linuxfoundation.org (Postfix) with ESMTP id 6536BC0D00 for ; Wed, 2 Sep 2026 21:18:47 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 5DD32401B0 for ; Wed, 2 Sep 2026 21:18:47 +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 ObxQVuJSmMYa for ; Wed, 2 Sep 2026 21:18:46 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp2.osuosl.org 93824403BB Authentication-Results: smtp2.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::1134" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383926; b=LBZc+v5bbgmaZcy/GGE9ey411sI2J1KTQ01yoqISBbT7RWXOSQvX76gKGeCB1Le6SgVf xyMeHWrMVl2w0hnzK1T5m41tcwadYodhmhaXBWlTZVgn76AfsCZQvUMsz+UrOCOimaaC+ IKQdP5tA2v+0jHh6PvZg90EGuZqzWWkEQh53wyCj/j00bOZwPJw7bKm3atjkRjpQGKx2K 1RMepuZigXHDLMmmb9nJYqg/JomlQ5l2pfAoblsqXgdem2znXEPCdgVafOSEA0MbOWm+V BEYH53UjIBjneYDpEL0f+0CkZoWHncrVjSEScakAiJS6nBLSu8vyoKcFfhxt+IRPNBg== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383926; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=w+yZyhxqm+LAbgGJBBSLK5hwWXW75fS7/dZEXA3+oYo=; b=iRJ5gR3KNBnuinDUt2jbuTEzbaf+aOqQB91mOU8FstPyrtVz3fYDE/sGoAa7MyiOrcZ9 hnnhFtO96hH6Di624j5g4W5RuGVVO4jl1sU0+x2uDgodrblqfHyHvayLqC1fPArepCcXp gGPmowkqtiNtWyFCxscoEgxx/5tZwAz1wEaqFm93UrCtSaPcw8swiQtsjo3xTGlEaDzQz k+RE+JocCV6YVBu2gLFE6IMOq7b1+Yd9uNUVAOXTs0Y/FMaEUtX+EoA+alT8QoOrvzClS JbRGSFXP52IGG4yQd3SJpPMl4buGJWr9WaxeeFFLda9pYnZs2xkjWeHMnOhlXZ7q95Q== ARC-Authentication-Results: i=1; smtp2.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=AgftY8ZX; arc=none smtp.remote-ip="2607:f8b0:4864:20::1134" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::1134; helo=mail-yw1-x1134.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp2.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Authentication-Results: smtp2.osuosl.org; dkim=pass (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=AgftY8ZX Received: from mail-yw1-x1134.google.com (mail-yw1-x1134.google.com [IPv6:2607:f8b0:4864:20::1134]) by smtp2.osuosl.org (Postfix) with ESMTPS id 93824403BB for ; Wed, 2 Sep 2026 21:18:46 +0000 (UTC) Received: by mail-yw1-x1134.google.com with SMTP id 00721157ae682-836c8bde2dcso20172707b3.0 for ; Wed, 02 Sep 2026 14:18:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383925; x=1788988725; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=w+yZyhxqm+LAbgGJBBSLK5hwWXW75fS7/dZEXA3+oYo=; b=AgftY8ZX3sratEo/ll28Cl5A/dIzz8c/iYoo6QhqfxbLG1oReQA6mXGE635nx9kuBi 4KsLzayyNh3nVt0ZeL2F9tPAFMjHpZ9rHfa4sOqs7QRqWV+4zF1TbuNhzl7knhVHhvA+ 0vqRgGrvZHhhwMJMa9EXG0I32wAei9EJRmulMNqXUswbHH2uEmaysTfe8M+S0C4GfaWH K6OTwKtoVZMJFo1Y3P2t4sjFIcrPpascCZmD8wBllHidECtdi4XWUSYqIBUX2JybSEgU ir6OMXg2DcTdmDT2vyXILexFmvJMZWQknqnvgf1PZVLk+m25lOmhzquNoWbtcEAyT3PK 0Exg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383925; x=1788988725; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=w+yZyhxqm+LAbgGJBBSLK5hwWXW75fS7/dZEXA3+oYo=; b=c/0aq9IK6jeSuSqyQ9DUGmK9BqdahucNY+5WevXD7hRBTnCbdmBkmXnsr8fCTOVptG YAl9Qjw8lIR+mr6njNQZGjswH8b3HnoVK30qRhfRmLAlaNMVh/TSaNN+Qf4fWKAVGS2y /7lhVHHPJmi6NXuSt4ZYrWDoTM7KOkUKzUQl4OPGp7rQ8krlbE5XC43SWTXtkiEI83f1 SCbPvBX9Ys+oWN1k2yO5YY1mtcWJAMtM8qdUnlDYt5lO3HmySNhCrhcU/I4IMknhujOq xsZ/8bS5y3FcuFgXkb8pH2dRQ296dcgO6vQIrhM8Z571C6fs4OY1EcYYFlkFzGTgIQj4 NINw== X-Gm-Message-State: AFuF++nWBsfwiIZZhGdcQLA+FZyWm/KfO5VX5Lu6kvi+FaT2I8yMJbXp /rATTkn89Wz2BH/JdQliKD9a9nQySdITuWooxPgOQJBWi3XiRqAGoR0NpJQH/44OfL3gNjpVv4K M3L/+1tcPvxYR/HuVeha5K9YJJYSkTdcSQQawUrchGeyGx/EbGenie1Y4MeSVTDU= X-Gm-Gg: AYBFou016l/xdyaEXGbhQWu5XSDprtMXFfGk254QnZQD2yplgI1nWInW9475vsObOKT f8HGioqd+iN25Y7Bnq+fkZj4gLw1Mz6gYRjPYYa84FJhqE0ue9T3b5Q4f+a9oGVWqArKBUXW5eG U4cpPS9Guz+K2YXQCi/x8dcwEmot98twTCj/t9BN5rOWnnPw9aNCQs25CUZkpUYktzfvdLtJ7B4 UMU9/XTkQCDbr77CwXErAgZJ1zU7M2fFwfiN79jR7+jt3DX/Juyjy9gU9n3nzND84/IBEBaE44a 8wfFLk5q5tKSXxcb9VK0nvoTACEruxvv9hq7SHmYMdPowfVV8HZZQ05A9B/EskBU76QmTojDpOU qmyJ5INZ1cv7uyChzyOgkGBRbEVvbM5V7aDKC8YNuQx0MsLOvJ/a5TZdChPzmgJFr6pKGt281+N sKNdTcN1MfI1atyvZQIsYeJiL3HQ7lecFSOKpngSmW7nX6qwtMTgLClziW0ZeF0fHiDbge2OpJc y8awUcikKEoIrrPecP86NMx/swMSXbf X-Received: by 2002:a05:690c:a799:b0:80d:f376:5ba9 with SMTP id 00721157ae682-86c5339871emr27510907b3.24.1788383924618; Wed, 02 Sep 2026 14:18:44 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.18.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:18:43 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:34 -0300 Message-Id: <20260902211744.2159261-5-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 04/14] ic: Prepare *_run functions for engine nodes. 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: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Make gateway_run, address_set_run, ts_run, tr_run, port_binding_run, route_run, sync_service_monitor and enumerate_datapaths non-static and declare them in ovn-ic.h so the upcoming per-subsystem incremental-processing engine nodes (ic/en-*.c) can invoke them. No functional change. Assisted-by: Claude Opus 4.8, Claude Code Signed-off-by: Paulo Guilherme Silva Acked-by: Lucas Vargas Dias --- ic/ovn-ic.c | 16 ++++++++-------- ic/ovn-ic.h | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c index be03d1781..738685eaa 100644 --- a/ic/ovn-ic.c +++ b/ic/ovn-ic.c @@ -191,7 +191,7 @@ ic_pb_get_type(const struct icsbrec_port_binding *isb_pb) return IC_SWITCH_PORT; } -static void +void enumerate_datapaths(struct ic_context *ctx, struct hmap *dp_tnlids, struct shash *isb_ts_dps, struct shash *isb_tr_dps) { @@ -220,7 +220,7 @@ is_az_leader(struct ovsdb_idl_txn *txn) return idl && ovsdb_idl_has_lock(idl); } -static void +void ts_run(struct ic_context *ctx, struct hmap *dp_tnlids, struct shash *isb_ts_dps) { @@ -353,7 +353,7 @@ ts_run(struct ic_context *ctx, struct hmap *dp_tnlids, } } -static void +void tr_run(struct ic_context *ctx, struct hmap *dp_tnlids, struct shash *isb_tr_dps) { @@ -610,7 +610,7 @@ sync_addr_set_from_icsb(struct ovsdb_idl_txn *ovnnb_txn, sorted_array_destroy(&sorted_addrs); } -static void +void address_set_run(struct ic_context *ctx) { if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { @@ -710,7 +710,7 @@ address_set_run(struct ic_context *ctx) shash_destroy(&ic_remote_as); } -static void +void gateway_run(struct ic_context *ctx) { if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) { @@ -1399,7 +1399,7 @@ find_lsp_in_sb(struct ic_context *ctx, return find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, lsp->name); } -static void +void port_binding_run(struct ic_context *ctx) { if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { @@ -3134,7 +3134,7 @@ delete_orphan_ic_routes(struct ic_context *ctx, icsbrec_route_index_destroy_row(isb_route_key); } -static void +void route_run(struct ic_context *ctx) { if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { @@ -3580,7 +3580,7 @@ destroy_service_monitor_data(struct sync_service_monitor_data *sync_data) free(sync_data->prpg_svc_monitor_mac); } -static void +void sync_service_monitor(struct ic_context *ctx) { if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) { diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h index 4db493e08..5a1cf060a 100644 --- a/ic/ovn-ic.h +++ b/ic/ovn-ic.h @@ -62,6 +62,24 @@ struct ic_state { enum ic_datapath_type { IC_SWITCH, IC_ROUTER, IC_DATAPATH_MAX }; enum ic_port_binding_type { IC_SWITCH_PORT, IC_ROUTER_PORT, IC_PORT_MAX }; +struct hmap; +struct shash; + void ovn_db_run(struct ic_context *ctx); +/* Per-subsystem entry points, invoked by the incremental-processing engine + * nodes (see ic/en-*.c). Each performs a full recompute of its subsystem and + * may be invoked independently when its engine inputs change. */ +void enumerate_datapaths(struct ic_context *ctx, struct hmap *dp_tnlids, + struct shash *isb_ts_dps, struct shash *isb_tr_dps); +void gateway_run(struct ic_context *ctx); +void address_set_run(struct ic_context *ctx); +void ts_run(struct ic_context *ctx, struct hmap *dp_tnlids, + struct shash *isb_ts_dps); +void tr_run(struct ic_context *ctx, struct hmap *dp_tnlids, + struct shash *isb_tr_dps); +void port_binding_run(struct ic_context *ctx); +void route_run(struct ic_context *ctx); +void sync_service_monitor(struct ic_context *ctx); + #endif /* OVN_IC_H */ From patchwork Wed Sep 2 21:17:35 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297036 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=UMuOvEst; 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 4hZwb32PbDz1xwZ for ; Thu, 03 Sep 2026 07:19:11 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id E111B4044E; Wed, 2 Sep 2026 21:19:09 +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 coCZAmx4YYQp; Wed, 2 Sep 2026 21:19:05 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp2.osuosl.org 9B4E04037D Authentication-Results: smtp2.osuosl.org; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383945; b=QdtpPzBpWR/lvVjkZepHQFMFAWh394aas9SFZ0VTO+G+CoICupTvCDuI3Y2VIuZFYEFg JY3Nl3mRAlRv0t4Yc5wqbHd6KOFr8wEQcbfnHtyOcLLiCr9cAAc92Cbr1sKuaYJ0Rx0Jd oEpUfq5F9CJH3mLYdi4kC3I93zJLpaQpfOgGSZXp7Z59AlX1qiwsoSB7beQB76ub15Ohr 1l80JY2H5vAIJUyAu7xVivy4D7uio/JQOaIskXDNbw0jhAezsONeiMCaS9KePCiq0f+MJ fjQMDwsx1dAXzXXyC4VLwLZmlz9j46eHbHp2QG8IeqtJlAo2UpBzDkYwCAJt/UviNAA== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383945; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=jccvXdk4xWudUxBJ1MN7FQnxFkmC68FSkdPoj4iW9ZU=; b=OPX37ASdAJoCE5gpSZAGLsiy3OdjrCZV75Ab/k5B9p6SAy/qkaEWmz1AsxCascOHpVm7 lnx5yaITNsHkJAvF8AI4x5ZfhSINQR2YPA8HJU6Ms5/vTnWj6tv4wOWVIW70JRYJWl69N kV1KqV+oBa0CAAEN/CgymJ4ymN7K1dXs+oASBTaIOk3Cene+NepuO2cHeQN9P4iFJvJcB jCba5FHjH7Vz4VEJdIKjzTt41Z0uJkHRqE60BfpPO8875yy5HQfgmoa0bLIJTfsZDgAQO aMljiW5N3m7HCjm0XZy49SmBt7g621r5AQ3eyHyQvA2/PQi+Z7sUaG4d/jfx91dBE+Q== ARC-Authentication-Results: i=2; smtp2.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=UMuOvEst; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" 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= Authentication-Results: smtp2.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=UMuOvEst Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp2.osuosl.org (Postfix) with ESMTPS id 9B4E04037D; Wed, 2 Sep 2026 21:19:05 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 81261C0D0C; Wed, 2 Sep 2026 21:19:05 +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 64DE1C0D0C for ; Wed, 2 Sep 2026 21:19:03 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 564F84083E for ; Wed, 2 Sep 2026 21:19:03 +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 YAsxJL6NvfTU for ; Wed, 2 Sep 2026 21:18:57 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp4.osuosl.org 2211740B75 Authentication-Results: smtp4.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::1134" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383935; b=o2IyVgn80U999cYXiT6sKy9BALUx4oufkaQQnm9xaeV2UJyMEdtgZrThA7QsDreaR82p IbN0ScVF6ts5tb8vopayoG5YO0QAGXbDPlFpSOGik24x92yZixLgxCEIwaYtYGBq/R72Z pU5OXNXnXym8sukTaT0MtsmgpAcDpDG/P6eQmTTsqCeiUSxX0/nj+XLjhVWPMSPLlPvmc LKWBQYc42orfZBzSuhuEVO6/4ofB+dpxeDCm8/4CQUUe8nakwKYud/yLAWq+OUnHvXrbI ozGPhbj1C6s64FzNh1IJsYIMCB0nQtwor/azzcKDWuA5i3W4zEY7cIH4RAw3s2KKlPw== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383935; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=YLgozxaPjSwxUclLcKCoA8L/wgSio+6b5QpYsWzMMD8=; b=ecltRuK3YwyT0hNTS7CSFSBfU+3/1t9S8E2jPL/Ka4GftaB+Hx8ViF5YskrYnoOzXnU9 5/PLkL+U3GN+/IoaOKVUIN0DDObmK2AQPPx0SQDGY/vil8Xsb44CH5EDbNxwy/WwqJSJJ W7iEvJyqZ2F5BXO//u8IF4bNCTc+uyaT2YP3Fkg3pgSiBBc5g7RsnOuUQbpG8gJYzTXRs LJtQjraGL3Y03PIO+imLLrmduCNeQQ8OCcxTFNNdQ+Il5qMOE2HtyGnsKwgsKup15NEbG Sy2z9W4tSmT7ktnui5PambDoq6SUgOXJeeWUHGP1pFzlA0aNUNhMmqROA1X6r/MY/Ug== ARC-Authentication-Results: i=1; smtp4.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=UMuOvEst; arc=none smtp.remote-ip="2607:f8b0:4864:20::1134" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::1134; helo=mail-yw1-x1134.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp4.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Authentication-Results: smtp4.osuosl.org; dkim=pass (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=UMuOvEst Received: from mail-yw1-x1134.google.com (mail-yw1-x1134.google.com [IPv6:2607:f8b0:4864:20::1134]) by smtp4.osuosl.org (Postfix) with ESMTPS id 2211740B75 for ; Wed, 2 Sep 2026 21:18:54 +0000 (UTC) Received: by mail-yw1-x1134.google.com with SMTP id 00721157ae682-7dbcb505578so21264477b3.3 for ; Wed, 02 Sep 2026 14:18:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383933; x=1788988733; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=YLgozxaPjSwxUclLcKCoA8L/wgSio+6b5QpYsWzMMD8=; b=UMuOvEstzY62ckpKODftoz+Oub446SNUwippQdUc/2KFKGf1CqZ3g0rvq58XxsqUtm TfULPnqOnrr98b0fC5AnqbTvi1GFqDqtKyw4d6e0uBSEI/f3M42qmdBOQPIa54oDkEKB GC++zdRjjrs7blXMzkc3XFH5rAYxLpHyzePehVpgEkfwD0bMyTmjwzISOt1k17Ul5gwB W5CRQBzeVTakodTReWQneHHovCSzBk+u7NOm2ZaWr4gXftu5IvTH0dXCJGh3CFbvFbVf oiwmkMjeuYAFwJ3ckXmHKRvRtO2lJkL1RANMGwtIxfgX4IfRQ6sMe8aHFZMy87WCQEmD EvTQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383933; x=1788988733; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=YLgozxaPjSwxUclLcKCoA8L/wgSio+6b5QpYsWzMMD8=; b=B6Isz6aCJDXWo2Gj866JYy59djoVhrFsr7mFxj9I+dvn8iTHmcA1UHqYjCosxhSi+Z 78isXlPtl/tC89J3JImluvPM7v+J4mUP/qrdkb6Vcp2tVGeJWJwGD0vHU3fXgVL9MBm/ UcFnM201jb+TkiRgyG9RppAo2qgBEXfo/yTyAhxltwVFA9IEqpFgqhSS2pIcgiVuNqkX 93oqjNz9rJTfSDCb1FrSurWqz9Y+iY3GKdTz8OUxzNGfdtBRBY22RyZjKEUqjudkNecf nF7mHLFffDMGkl9pjyX+vOoWMa7bd/8bNKNH8Kqg8Mxr7xROZ3O83Wy9y9ZS88I4L2c/ bVqA== X-Gm-Message-State: AFuF++lBlBFGgJlcGRPz22yPYAjOn8pq4s7AJZRjNKUZA8SpTJEX6lsy yIBTwYIuJAc3xd4DIWsfB5wDZnTMqpKl2ZNmNAUWKlqNxrSthHrCoE2DuVtuCLUB4h+z5yR8ZIG Df/a3nMfRuqnO/T0BHi2g0OaoQU+4c+nt8jdfN/3jjDKrrIJgCjbw91ohBYlCnpM= X-Gm-Gg: AYBFou0muQ2djm9XMEbIy1bDv2lbW83geXfwtFcBfmgob8qqwqVeSaHl+f1d+kKXxZP iXK5K8ne0dc3cV0RAZo7sq2Pz3TK4N5mEEB7VBbVQz3vsXIMqvd89hmKn67PYA0QJwj/F7oPO0+ I/STbZxW+dWI2BqxesQclvuLU9Jtw3gFjWjQiynIUBF2LRsJTF90yiEPA6xcwUKsXwt5E1449G2 DefnsBIuzsW+5veJ0x2Y9P3DSHkSjt+AfLFFjLdn+IW/IAX+ejRd6BtPVx/iEBDr1ey0gnzPk6x X12sdcjVrk2gW7fSHk5hmolGEedJvA4ZPZWGDR7dxAVJuiKrq/NOe8vrTSvl95X5y8jUkHzlid8 J18qvyOid0MvxHQwA9Wo4tAF8mgYcayp6AEb3djUay2szPhtO16jebNd+S/+fsxZkBJMHmwpvFj lgMjI1DdnaKtndPPIi6P6bDNQ6pt3NUnXx6b4fwFJSMl2T577efKB/TCzCsDDr3GtCcRiB+hUdA nhOFHhC2No85Mji9qIhr8Bk4HqeN1udBfPn3Owp5no= X-Received: by 2002:a05:690c:698b:b0:80d:15a3:7b1c with SMTP id 00721157ae682-86c4df2328cmr33765107b3.4.1788383928509; Wed, 02 Sep 2026 14:18:48 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.18.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:18:47 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:35 -0300 Message-Id: <20260902211744.2159261-6-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 05/14] ic: Split full recompute into per-subsystem engine nodes. 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: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Replace the single monolithic en_ic engine node (which ran the whole ovn_db_run() full recompute on any tracked change) with one engine node per subsystem: en_dp_enum, en_gateway, en_ts, en_tr, en_port_binding, en_route, en_service_monitor and en_address_set. en_ic becomes a pure output node that only aggregates them. Each subsystem node still performs a full recompute in its run() method (no change handlers yet), but is now gated on only the table inputs it actually reads, so an unrelated change no longer forces every subsystem to recompute. Change handlers are added incrementally in later commits. The transit switch/router datapath tunnel-key allocator and the transit switch/router datapath maps (previously built on the stack by enumerate_datapaths() and shared by ts_run()/tr_run()) are now owned by the en_dp_enum node. en_dp_enum depends on every input that can trigger a tunnel-key allocation (IC-SB datapath bindings, transit switches, transit routers and vxlan_mode) so the allocator is always rebuilt from scratch in the same iteration as any allocation, preserving correctness. The address-set synchronization added by the "ovn-ic: Address set synchronization across AZs" upstream commit becomes the en_address_set node, gated on the NB/SB/IC-SB Address_Set tables and NB_Global. The ic_sb_global table is no longer modeled as an engine input: it only carries IC-SB sequence numbers written by update_sequence_numbers() in the main loop, outside the engine, and is not read by any subsystem. No functional change: all existing ovn-ic system tests pass. Assisted-by: Claude Opus 4.8, Claude Code Signed-off-by: Paulo Guilherme Silva Acked-by: Lucas Vargas Dias --- ic/automake.mk | 16 +++++ ic/en-address-set.c | 45 ++++++++++++ ic/en-address-set.h | 25 +++++++ ic/en-dp-enum.c | 68 ++++++++++++++++++ ic/en-dp-enum.h | 44 ++++++++++++ ic/en-gateway.c | 45 ++++++++++++ ic/en-gateway.h | 24 +++++++ ic/en-ic.c | 9 ++- ic/en-port-binding.c | 45 ++++++++++++ ic/en-port-binding.h | 25 +++++++ ic/en-route.c | 45 ++++++++++++ ic/en-route.h | 24 +++++++ ic/en-service-monitor.c | 46 ++++++++++++ ic/en-service-monitor.h | 26 +++++++ ic/en-tr.c | 47 ++++++++++++ ic/en-tr.h | 24 +++++++ ic/en-ts.c | 47 ++++++++++++ ic/en-ts.h | 24 +++++++ ic/inc-proc-ic.c | 154 ++++++++++++++++++++++++++++++---------- ic/ovn-ic.c | 21 ------ ic/ovn-ic.h | 2 - 21 files changed, 742 insertions(+), 64 deletions(-) create mode 100644 ic/en-address-set.c create mode 100644 ic/en-address-set.h create mode 100644 ic/en-dp-enum.c create mode 100644 ic/en-dp-enum.h create mode 100644 ic/en-gateway.c create mode 100644 ic/en-gateway.h create mode 100644 ic/en-port-binding.c create mode 100644 ic/en-port-binding.h create mode 100644 ic/en-route.c create mode 100644 ic/en-route.h create mode 100644 ic/en-service-monitor.c create mode 100644 ic/en-service-monitor.h create mode 100644 ic/en-tr.c create mode 100644 ic/en-tr.h create mode 100644 ic/en-ts.c create mode 100644 ic/en-ts.h diff --git a/ic/automake.mk b/ic/automake.mk index a69b1030d..fd888d60c 100644 --- a/ic/automake.mk +++ b/ic/automake.mk @@ -4,6 +4,22 @@ ic_ovn_ic_SOURCES = ic/ovn-ic.c \ ic/ovn-ic.h \ ic/en-ic.c \ ic/en-ic.h \ + ic/en-dp-enum.c \ + ic/en-dp-enum.h \ + ic/en-gateway.c \ + ic/en-gateway.h \ + ic/en-ts.c \ + ic/en-ts.h \ + ic/en-tr.c \ + ic/en-tr.h \ + ic/en-port-binding.c \ + ic/en-port-binding.h \ + ic/en-route.c \ + ic/en-route.h \ + ic/en-service-monitor.c \ + ic/en-service-monitor.h \ + ic/en-address-set.c \ + ic/en-address-set.h \ ic/inc-proc-ic.c \ ic/inc-proc-ic.h ic_ovn_ic_LDADD = \ diff --git a/ic/en-address-set.c b/ic/en-address-set.c new file mode 100644 index 000000000..764dd71d2 --- /dev/null +++ b/ic/en-address-set.c @@ -0,0 +1,45 @@ +/* + * 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-address-set.h" +#include "lib/inc-proc-eng.h" +#include "openvswitch/vlog.h" +#include "ovn-ic.h" + +VLOG_DEFINE_THIS_MODULE(en_ic_address_set); + +enum engine_node_state +en_address_set_run(struct engine_node *node OVS_UNUSED, void *data OVS_UNUSED) +{ + const struct engine_context *eng_ctx = engine_get_context(); + struct ic_context *ctx = eng_ctx->client_ctx; + + address_set_run(ctx); + + return EN_UPDATED; +} + +void * +en_address_set_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + return NULL; +} + +void +en_address_set_cleanup(void *data OVS_UNUSED) +{ +} diff --git a/ic/en-address-set.h b/ic/en-address-set.h new file mode 100644 index 000000000..490d4189c --- /dev/null +++ b/ic/en-address-set.h @@ -0,0 +1,25 @@ +/* + * 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_IC_ADDRESS_SET_H +#define EN_IC_ADDRESS_SET_H 1 + +#include "lib/inc-proc-eng.h" + +enum engine_node_state en_address_set_run(struct engine_node *node, + void *data); +void *en_address_set_init(struct engine_node *node, struct engine_arg *arg); +void en_address_set_cleanup(void *data); + +#endif /* EN_IC_ADDRESS_SET_H */ diff --git a/ic/en-dp-enum.c b/ic/en-dp-enum.c new file mode 100644 index 000000000..fd447ab52 --- /dev/null +++ b/ic/en-dp-enum.c @@ -0,0 +1,68 @@ +/* + * 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-dp-enum.h" +#include "lib/inc-proc-eng.h" +#include "lib/ovn-util.h" +#include "openvswitch/hmap.h" +#include "openvswitch/shash.h" +#include "openvswitch/vlog.h" +#include "ovn-ic.h" + +VLOG_DEFINE_THIS_MODULE(en_ic_dp_enum); + +enum engine_node_state +en_dp_enum_run(struct engine_node *node OVS_UNUSED, void *data_) +{ + const struct engine_context *eng_ctx = engine_get_context(); + struct ic_context *ctx = eng_ctx->client_ctx; + struct ed_type_dp_enum *data = data_; + + /* Clear the previous iteration's state and rebuild from the current IC-SB + * datapath bindings. */ + ovn_destroy_tnlids(&data->dp_tnlids); + hmap_init(&data->dp_tnlids); + shash_clear(&data->isb_ts_dps); + shash_clear(&data->isb_tr_dps); + + enumerate_datapaths(ctx, &data->dp_tnlids, &data->isb_ts_dps, + &data->isb_tr_dps); + + return EN_UPDATED; +} + +void * +en_dp_enum_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + struct ed_type_dp_enum *data = xzalloc(sizeof *data); + + hmap_init(&data->dp_tnlids); + shash_init(&data->isb_ts_dps); + shash_init(&data->isb_tr_dps); + + return data; +} + +void +en_dp_enum_cleanup(void *data_) +{ + struct ed_type_dp_enum *data = data_; + + ovn_destroy_tnlids(&data->dp_tnlids); + shash_destroy(&data->isb_ts_dps); + shash_destroy(&data->isb_tr_dps); +} diff --git a/ic/en-dp-enum.h b/ic/en-dp-enum.h new file mode 100644 index 000000000..ef925ea68 --- /dev/null +++ b/ic/en-dp-enum.h @@ -0,0 +1,44 @@ +/* + * 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_IC_DP_ENUM_H +#define EN_IC_DP_ENUM_H 1 + +#include "lib/inc-proc-eng.h" +#include "openvswitch/hmap.h" +#include "openvswitch/shash.h" + +/* Data maintained by the "dp_enum" engine node: the result of enumerating the + * IC-SB datapath bindings. It is the explicit, engine-visible form of the + * state that used to be built on the stack by enumerate_datapaths() inside + * ovn_db_run(). The transit-switch (en_ts) and transit-router (en_tr) nodes + * consume this data instead of rebuilding it themselves, which also keeps the + * shared 'dp_tnlids' tunnel-key allocator consistent between them. */ +struct ed_type_dp_enum { + /* Set of tunnel keys already in use by IC-SB datapath bindings. Holds + * 'struct tnlid_node *' entries owned by this node. */ + struct hmap dp_tnlids; + /* Transit-switch datapath bindings, keyed by transit switch name. Values + * are 'const struct icsbrec_datapath_binding *' owned by the IDL. */ + struct shash isb_ts_dps; + /* Transit-router datapath bindings, keyed by NB IC UUID string. Values + * are 'const struct icsbrec_datapath_binding *' owned by the IDL. */ + struct shash isb_tr_dps; +}; + +enum engine_node_state en_dp_enum_run(struct engine_node *node, void *data); +void *en_dp_enum_init(struct engine_node *node, struct engine_arg *arg); +void en_dp_enum_cleanup(void *data); + +#endif /* EN_IC_DP_ENUM_H */ diff --git a/ic/en-gateway.c b/ic/en-gateway.c new file mode 100644 index 000000000..f41166018 --- /dev/null +++ b/ic/en-gateway.c @@ -0,0 +1,45 @@ +/* + * 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-gateway.h" +#include "lib/inc-proc-eng.h" +#include "openvswitch/vlog.h" +#include "ovn-ic.h" + +VLOG_DEFINE_THIS_MODULE(en_ic_gateway); + +enum engine_node_state +en_gateway_run(struct engine_node *node OVS_UNUSED, void *data OVS_UNUSED) +{ + const struct engine_context *eng_ctx = engine_get_context(); + struct ic_context *ctx = eng_ctx->client_ctx; + + gateway_run(ctx); + + return EN_UPDATED; +} + +void * +en_gateway_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + return NULL; +} + +void +en_gateway_cleanup(void *data OVS_UNUSED) +{ +} diff --git a/ic/en-gateway.h b/ic/en-gateway.h new file mode 100644 index 000000000..af0355a45 --- /dev/null +++ b/ic/en-gateway.h @@ -0,0 +1,24 @@ +/* + * 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_IC_GATEWAY_H +#define EN_IC_GATEWAY_H 1 + +#include "lib/inc-proc-eng.h" + +enum engine_node_state en_gateway_run(struct engine_node *node, void *data); +void *en_gateway_init(struct engine_node *node, struct engine_arg *arg); +void en_gateway_cleanup(void *data); + +#endif /* EN_IC_GATEWAY_H */ diff --git a/ic/en-ic.c b/ic/en-ic.c index 2db9d3b84..d671e2cb0 100644 --- a/ic/en-ic.c +++ b/ic/en-ic.c @@ -29,11 +29,10 @@ VLOG_DEFINE_THIS_MODULE(en_ic); enum engine_node_state en_ic_run(struct engine_node *node OVS_UNUSED, void *data OVS_UNUSED) { - const struct engine_context *eng_ctx = engine_get_context(); - struct ic_context *ctx = eng_ctx->client_ctx; - - ovn_db_run(ctx); - + /* 'en_ic' is the engine's output node and only aggregates the per + * subsystem nodes (gateway, ts, tr, port_binding, route, service_monitor + * and address_set). Each of those nodes performs (and gates) its own + * work, so this node has nothing to compute itself. */ return EN_UPDATED; } diff --git a/ic/en-port-binding.c b/ic/en-port-binding.c new file mode 100644 index 000000000..8843d6cef --- /dev/null +++ b/ic/en-port-binding.c @@ -0,0 +1,45 @@ +/* + * 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.h" +#include "lib/inc-proc-eng.h" +#include "openvswitch/vlog.h" +#include "ovn-ic.h" + +VLOG_DEFINE_THIS_MODULE(en_ic_port_binding); + +enum engine_node_state +en_port_binding_run(struct engine_node *node OVS_UNUSED, void *data OVS_UNUSED) +{ + const struct engine_context *eng_ctx = engine_get_context(); + struct ic_context *ctx = eng_ctx->client_ctx; + + port_binding_run(ctx); + + return EN_UPDATED; +} + +void * +en_port_binding_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + return NULL; +} + +void +en_port_binding_cleanup(void *data OVS_UNUSED) +{ +} diff --git a/ic/en-port-binding.h b/ic/en-port-binding.h new file mode 100644 index 000000000..d5fc2552c --- /dev/null +++ b/ic/en-port-binding.h @@ -0,0 +1,25 @@ +/* + * 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_IC_PORT_BINDING_H +#define EN_IC_PORT_BINDING_H 1 + +#include "lib/inc-proc-eng.h" + +enum engine_node_state en_port_binding_run(struct engine_node *node, + void *data); +void *en_port_binding_init(struct engine_node *node, struct engine_arg *arg); +void en_port_binding_cleanup(void *data); + +#endif /* EN_IC_PORT_BINDING_H */ diff --git a/ic/en-route.c b/ic/en-route.c new file mode 100644 index 000000000..ec39d89d9 --- /dev/null +++ b/ic/en-route.c @@ -0,0 +1,45 @@ +/* + * 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-route.h" +#include "lib/inc-proc-eng.h" +#include "openvswitch/vlog.h" +#include "ovn-ic.h" + +VLOG_DEFINE_THIS_MODULE(en_ic_route); + +enum engine_node_state +en_route_run(struct engine_node *node OVS_UNUSED, void *data OVS_UNUSED) +{ + const struct engine_context *eng_ctx = engine_get_context(); + struct ic_context *ctx = eng_ctx->client_ctx; + + route_run(ctx); + + return EN_UPDATED; +} + +void * +en_route_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + return NULL; +} + +void +en_route_cleanup(void *data OVS_UNUSED) +{ +} diff --git a/ic/en-route.h b/ic/en-route.h new file mode 100644 index 000000000..9e9921850 --- /dev/null +++ b/ic/en-route.h @@ -0,0 +1,24 @@ +/* + * 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_IC_ROUTE_H +#define EN_IC_ROUTE_H 1 + +#include "lib/inc-proc-eng.h" + +enum engine_node_state en_route_run(struct engine_node *node, void *data); +void *en_route_init(struct engine_node *node, struct engine_arg *arg); +void en_route_cleanup(void *data); + +#endif /* EN_IC_ROUTE_H */ diff --git a/ic/en-service-monitor.c b/ic/en-service-monitor.c new file mode 100644 index 000000000..b85a36dc7 --- /dev/null +++ b/ic/en-service-monitor.c @@ -0,0 +1,46 @@ +/* + * 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-service-monitor.h" +#include "lib/inc-proc-eng.h" +#include "openvswitch/vlog.h" +#include "ovn-ic.h" + +VLOG_DEFINE_THIS_MODULE(en_ic_service_monitor); + +enum engine_node_state +en_service_monitor_run(struct engine_node *node OVS_UNUSED, + void *data OVS_UNUSED) +{ + const struct engine_context *eng_ctx = engine_get_context(); + struct ic_context *ctx = eng_ctx->client_ctx; + + sync_service_monitor(ctx); + + return EN_UPDATED; +} + +void * +en_service_monitor_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + return NULL; +} + +void +en_service_monitor_cleanup(void *data OVS_UNUSED) +{ +} diff --git a/ic/en-service-monitor.h b/ic/en-service-monitor.h new file mode 100644 index 000000000..c0d6f0f0b --- /dev/null +++ b/ic/en-service-monitor.h @@ -0,0 +1,26 @@ +/* + * 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_IC_SERVICE_MONITOR_H +#define EN_IC_SERVICE_MONITOR_H 1 + +#include "lib/inc-proc-eng.h" + +enum engine_node_state en_service_monitor_run(struct engine_node *node, + void *data); +void *en_service_monitor_init(struct engine_node *node, + struct engine_arg *arg); +void en_service_monitor_cleanup(void *data); + +#endif /* EN_IC_SERVICE_MONITOR_H */ diff --git a/ic/en-tr.c b/ic/en-tr.c new file mode 100644 index 000000000..4f05a4325 --- /dev/null +++ b/ic/en-tr.c @@ -0,0 +1,47 @@ +/* + * 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-dp-enum.h" +#include "en-tr.h" +#include "lib/inc-proc-eng.h" +#include "openvswitch/vlog.h" +#include "ovn-ic.h" + +VLOG_DEFINE_THIS_MODULE(en_ic_tr); + +enum engine_node_state +en_tr_run(struct engine_node *node, void *data OVS_UNUSED) +{ + const struct engine_context *eng_ctx = engine_get_context(); + struct ic_context *ctx = eng_ctx->client_ctx; + struct ed_type_dp_enum *dp = engine_get_input_data("dp_enum", node); + + tr_run(ctx, &dp->dp_tnlids, &dp->isb_tr_dps); + + return EN_UPDATED; +} + +void * +en_tr_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + return NULL; +} + +void +en_tr_cleanup(void *data OVS_UNUSED) +{ +} diff --git a/ic/en-tr.h b/ic/en-tr.h new file mode 100644 index 000000000..c558c0913 --- /dev/null +++ b/ic/en-tr.h @@ -0,0 +1,24 @@ +/* + * 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_IC_TR_H +#define EN_IC_TR_H 1 + +#include "lib/inc-proc-eng.h" + +enum engine_node_state en_tr_run(struct engine_node *node, void *data); +void *en_tr_init(struct engine_node *node, struct engine_arg *arg); +void en_tr_cleanup(void *data); + +#endif /* EN_IC_TR_H */ diff --git a/ic/en-ts.c b/ic/en-ts.c new file mode 100644 index 000000000..e8df1888a --- /dev/null +++ b/ic/en-ts.c @@ -0,0 +1,47 @@ +/* + * 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-dp-enum.h" +#include "en-ts.h" +#include "lib/inc-proc-eng.h" +#include "openvswitch/vlog.h" +#include "ovn-ic.h" + +VLOG_DEFINE_THIS_MODULE(en_ic_ts); + +enum engine_node_state +en_ts_run(struct engine_node *node, void *data OVS_UNUSED) +{ + const struct engine_context *eng_ctx = engine_get_context(); + struct ic_context *ctx = eng_ctx->client_ctx; + struct ed_type_dp_enum *dp = engine_get_input_data("dp_enum", node); + + ts_run(ctx, &dp->dp_tnlids, &dp->isb_ts_dps); + + return EN_UPDATED; +} + +void * +en_ts_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + return NULL; +} + +void +en_ts_cleanup(void *data OVS_UNUSED) +{ +} diff --git a/ic/en-ts.h b/ic/en-ts.h new file mode 100644 index 000000000..cf33d9ab6 --- /dev/null +++ b/ic/en-ts.h @@ -0,0 +1,24 @@ +/* + * 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_IC_TS_H +#define EN_IC_TS_H 1 + +#include "lib/inc-proc-eng.h" + +enum engine_node_state en_ts_run(struct engine_node *node, void *data); +void *en_ts_init(struct engine_node *node, struct engine_arg *arg); +void en_ts_cleanup(void *data); + +#endif /* EN_IC_TS_H */ diff --git a/ic/inc-proc-ic.c b/ic/inc-proc-ic.c index bbcbcdd17..a6f029e37 100644 --- a/ic/inc-proc-ic.c +++ b/ic/inc-proc-ic.c @@ -27,6 +27,14 @@ #include "openvswitch/vlog.h" #include "inc-proc-ic.h" #include "en-ic.h" +#include "en-dp-enum.h" +#include "en-gateway.h" +#include "en-ts.h" +#include "en-tr.h" +#include "en-port-binding.h" +#include "en-route.h" +#include "en-service-monitor.h" +#include "en-address-set.h" #include "ovn-util.h" #include "unixctl.h" #include "util.h" @@ -108,8 +116,11 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_ic); ICNB_NODES #undef ICNB_NODE +/* Note: the ic_sb_global table is intentionally not modeled as an engine input + * node. It only carries IC-SB sequence numbers, which are written by + * update_sequence_numbers() in the main loop (outside the engine) and are not + * read by any subsystem node. */ #define ICSB_NODES \ - ICSB_NODE(ic_sb_global, "ic_sb_global") \ ICSB_NODE(availability_zone, "availability_zone") \ ICSB_NODE(service_monitor, "service_monitor") \ ICSB_NODE(route, "route") \ @@ -162,6 +173,14 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_ic); /* Define engine nodes for other nodes. They should be defined as static to * avoid sparse errors. */ +static ENGINE_NODE(dp_enum); +static ENGINE_NODE(gateway); +static ENGINE_NODE(ts); +static ENGINE_NODE(tr); +static ENGINE_NODE(port_binding); +static ENGINE_NODE(route); +static ENGINE_NODE(service_monitor); +static ENGINE_NODE(address_set); static ENGINE_NODE(ic); void inc_proc_ic_init(struct ovsdb_idl_loop *nb, @@ -169,41 +188,104 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, struct ovsdb_idl_loop *icnb, struct ovsdb_idl_loop *icsb) { - /* Define relationships between nodes where first argument is dependent - * on the second argument */ - engine_add_input(&en_ic, &en_nb_nb_global, NULL); - engine_add_input(&en_ic, &en_nb_logical_router_static_route, NULL); - engine_add_input(&en_ic, &en_nb_logical_router, NULL); - engine_add_input(&en_ic, &en_nb_logical_router_port, NULL); - engine_add_input(&en_ic, &en_nb_logical_switch, NULL); - engine_add_input(&en_ic, &en_nb_logical_switch_port, NULL); - engine_add_input(&en_ic, &en_nb_load_balancer, NULL); - engine_add_input(&en_ic, &en_nb_load_balancer_group, NULL); - engine_add_input(&en_ic, &en_nb_address_set, NULL); - - engine_add_input(&en_ic, &en_sb_sb_global, NULL); - engine_add_input(&en_ic, &en_sb_chassis, NULL); - engine_add_input(&en_ic, &en_sb_encap, NULL); - engine_add_input(&en_ic, &en_sb_datapath_binding, NULL); - engine_add_input(&en_ic, &en_sb_port_binding, NULL); - engine_add_input(&en_ic, &en_sb_service_monitor, NULL); - engine_add_input(&en_ic, &en_sb_learned_route, NULL); - engine_add_input(&en_ic, &en_sb_address_set, NULL); - - engine_add_input(&en_ic, &en_icnb_ic_nb_global, NULL); - engine_add_input(&en_ic, &en_icnb_transit_switch, NULL); - engine_add_input(&en_ic, &en_icnb_transit_router, NULL); - engine_add_input(&en_ic, &en_icnb_transit_router_port, NULL); - - engine_add_input(&en_ic, &en_icsb_encap, NULL); - engine_add_input(&en_ic, &en_icsb_service_monitor, NULL); - engine_add_input(&en_ic, &en_icsb_ic_sb_global, NULL); - engine_add_input(&en_ic, &en_icsb_port_binding, NULL); - engine_add_input(&en_ic, &en_icsb_availability_zone, NULL); - engine_add_input(&en_ic, &en_icsb_gateway, NULL); - engine_add_input(&en_ic, &en_icsb_route, NULL); - engine_add_input(&en_ic, &en_icsb_datapath_binding, NULL); - engine_add_input(&en_ic, &en_icsb_address_set, NULL); + /* Define relationships between nodes where the first argument is dependent + * on the second argument. + * + * Each subsystem node below depends on the table input nodes it reads, so + * the engine only re-runs a subsystem when one of its inputs changed. No + * change handlers are wired yet: every dependency uses a NULL handler, so + * any tracked change to an input triggers a full recompute of just that + * subsystem (run() method). This preserves the previous behavior while + * splitting the monolithic ovn_db_run() into independently-gated nodes. + * Change handlers are added incrementally in a later step. */ + + /* en_dp_enum: enumerate IC-SB datapath bindings (tunnel-key allocator and + * transit switch/router datapath maps shared by en_ts and en_tr). + * + * en_ts and en_tr allocate datapath tunnel keys from the shared + * 'dp_tnlids' set owned by this node, mutating it during their run. To + * keep that allocator correct, en_dp_enum must rebuild it from scratch in + * the same iteration as any allocation. It therefore depends not only on + * the IC-SB datapath bindings themselves, but also on every input that can + * cause en_ts/en_tr to allocate a key: a new transit switch or router, or + * a change of vxlan_mode (which forces a tunnel-key refresh). */ + engine_add_input(&en_dp_enum, &en_icsb_datapath_binding, NULL); + engine_add_input(&en_dp_enum, &en_icnb_transit_switch, NULL); + engine_add_input(&en_dp_enum, &en_icnb_transit_router, NULL); + engine_add_input(&en_dp_enum, &en_icnb_ic_nb_global, NULL); + + /* en_gateway: sync gateways/chassis between SB and IC-SB. */ + engine_add_input(&en_gateway, &en_icsb_availability_zone, NULL); + engine_add_input(&en_gateway, &en_icsb_gateway, NULL); + engine_add_input(&en_gateway, &en_icsb_encap, NULL); + engine_add_input(&en_gateway, &en_sb_chassis, NULL); + engine_add_input(&en_gateway, &en_sb_encap, NULL); + + /* en_ts: sync transit switches to NB and IC-SB datapath bindings. */ + engine_add_input(&en_ts, &en_dp_enum, NULL); + engine_add_input(&en_ts, &en_icnb_ic_nb_global, NULL); + engine_add_input(&en_ts, &en_icnb_transit_switch, NULL); + engine_add_input(&en_ts, &en_nb_logical_switch, NULL); + engine_add_input(&en_ts, &en_icsb_encap, NULL); + + /* en_tr: sync transit routers to NB and IC-SB datapath bindings. */ + engine_add_input(&en_tr, &en_dp_enum, NULL); + engine_add_input(&en_tr, &en_icnb_transit_router, NULL); + engine_add_input(&en_tr, &en_nb_logical_router, NULL); + + /* en_port_binding: sync cross-AZ port bindings. */ + engine_add_input(&en_port_binding, &en_icsb_availability_zone, NULL); + engine_add_input(&en_port_binding, &en_icsb_port_binding, NULL); + engine_add_input(&en_port_binding, &en_icnb_transit_switch, NULL); + engine_add_input(&en_port_binding, &en_icnb_transit_router, NULL); + engine_add_input(&en_port_binding, &en_icnb_transit_router_port, NULL); + engine_add_input(&en_port_binding, &en_nb_logical_switch, NULL); + engine_add_input(&en_port_binding, &en_nb_logical_switch_port, NULL); + engine_add_input(&en_port_binding, &en_nb_logical_router, NULL); + engine_add_input(&en_port_binding, &en_nb_logical_router_port, NULL); + engine_add_input(&en_port_binding, &en_sb_port_binding, NULL); + engine_add_input(&en_port_binding, &en_sb_chassis, NULL); + + /* en_route: advertise/learn cross-AZ routes. */ + engine_add_input(&en_route, &en_icsb_availability_zone, NULL); + engine_add_input(&en_route, &en_icsb_port_binding, NULL); + engine_add_input(&en_route, &en_icsb_route, NULL); + engine_add_input(&en_route, &en_icnb_transit_switch, NULL); + engine_add_input(&en_route, &en_nb_nb_global, NULL); + engine_add_input(&en_route, &en_nb_logical_router, NULL); + engine_add_input(&en_route, &en_nb_logical_router_port, NULL); + engine_add_input(&en_route, &en_nb_logical_router_static_route, NULL); + engine_add_input(&en_route, &en_nb_logical_switch_port, NULL); + engine_add_input(&en_route, &en_nb_load_balancer, NULL); + engine_add_input(&en_route, &en_nb_load_balancer_group, NULL); + engine_add_input(&en_route, &en_sb_datapath_binding, NULL); + engine_add_input(&en_route, &en_sb_learned_route, NULL); + + /* en_service_monitor: sync load-balancer health checks across AZs. */ + engine_add_input(&en_service_monitor, &en_icsb_availability_zone, NULL); + engine_add_input(&en_service_monitor, &en_icsb_service_monitor, NULL); + engine_add_input(&en_service_monitor, &en_sb_sb_global, NULL); + engine_add_input(&en_service_monitor, &en_sb_service_monitor, NULL); + engine_add_input(&en_service_monitor, &en_sb_port_binding, NULL); + + /* en_address_set: advertise/learn address sets across AZs. */ + engine_add_input(&en_address_set, &en_icsb_availability_zone, NULL); + engine_add_input(&en_address_set, &en_nb_nb_global, NULL); + engine_add_input(&en_address_set, &en_nb_address_set, NULL); + engine_add_input(&en_address_set, &en_sb_address_set, NULL); + engine_add_input(&en_address_set, &en_icsb_address_set, NULL); + + /* en_ic: output node aggregating all subsystems. Order matches the + * previous ovn_db_run() call order; in particular en_ts is added before + * en_tr so they allocate datapath tunnel keys from the shared en_dp_enum + * allocator in the same order as before. */ + engine_add_input(&en_ic, &en_gateway, NULL); + engine_add_input(&en_ic, &en_ts, NULL); + engine_add_input(&en_ic, &en_tr, NULL); + engine_add_input(&en_ic, &en_port_binding, NULL); + engine_add_input(&en_ic, &en_route, NULL); + engine_add_input(&en_ic, &en_service_monitor, NULL); + engine_add_input(&en_ic, &en_address_set, NULL); struct engine_arg engine_arg = { .nb_idl = nb->idl, diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c index 738685eaa..6e0d1b4e1 100644 --- a/ic/ovn-ic.c +++ b/ic/ovn-ic.c @@ -3782,27 +3782,6 @@ inc_proc_graph_dump(const char *end_node) ovsdb_idl_loop_destroy(&ovnisb_idl_loop); } -void -ovn_db_run(struct ic_context *ctx) -{ - struct hmap dp_tnlids = HMAP_INITIALIZER(&dp_tnlids); - struct shash isb_ts_dps = SHASH_INITIALIZER(&isb_ts_dps); - struct shash isb_tr_dps = SHASH_INITIALIZER(&isb_tr_dps); - - gateway_run(ctx); - enumerate_datapaths(ctx, &dp_tnlids, &isb_ts_dps, &isb_tr_dps); - ts_run(ctx, &dp_tnlids, &isb_ts_dps); - tr_run(ctx, &dp_tnlids, &isb_tr_dps); - port_binding_run(ctx); - route_run(ctx); - sync_service_monitor(ctx); - address_set_run(ctx); - - ovn_destroy_tnlids(&dp_tnlids); - shash_destroy(&isb_ts_dps); - shash_destroy(&isb_tr_dps); -} - static void parse_options(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) { diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h index 5a1cf060a..44dd4c597 100644 --- a/ic/ovn-ic.h +++ b/ic/ovn-ic.h @@ -65,8 +65,6 @@ enum ic_port_binding_type { IC_SWITCH_PORT, IC_ROUTER_PORT, IC_PORT_MAX }; struct hmap; struct shash; -void ovn_db_run(struct ic_context *ctx); - /* Per-subsystem entry points, invoked by the incremental-processing engine * nodes (see ic/en-*.c). Each performs a full recompute of its subsystem and * may be invoked independently when its engine inputs change. */ From patchwork Wed Sep 2 21:17:36 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297037 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=yGQ//BnO; 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 4hZwb81fGnz1xwZ for ; Thu, 03 Sep 2026 07:19:16 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id D183960B5D; Wed, 2 Sep 2026 21:19:14 +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 mneOy0aCoA67; Wed, 2 Sep 2026 21:19:08 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp3.osuosl.org B922760897 Authentication-Results: smtp3.osuosl.org; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383947; b=Sjc53tzwbg/VpFGLu9Wy654HgqNemJ/s5J18fseZeHM+qwqtxRoAZ2VVAs5J3QnWeAHV a3ugL3NuYRz6W22a1Q8dC5AD0LP+UYxWmfe8oVmVcMfLhNnqt2E2KmWw1j91RYWt77xkU K80OVGKJyIIjsqK+cRu1noNf0eTnuwT2vQSU30J+jZ2JCbmJwclru+275ekPjeudw+JBD 3C4uZF0QrhjURhbHgcst9N5GoKBts1a0gMlo0jzP4N2jOLfzrKj1jzHZU2ZV8YmkYibGX e8x/NuBxlgtlO8uD5NV06N8cIkWO895tsbiacdhJFAhMlkOoys4NlmKULt6TRcAgTDw== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383947; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=ww2L+mVQ1tjB4WDx9eYJXPe0WIa4mKKIPaVdrqrU18c=; b=pVecZae5TAK6JltO6RMka/sTiqGsznlrrZmjXDEK2E9UJ/sxfjawGzG+4S3RH7hC2I8w rkpnBGVyfByLtwAsnQF5IFbILTPXGfIodL97mJ0RTpYaU80Qyf9JPm/mjIR9K1i7GTrkm gVuvZZ8jeyh0XYLidmBBq/eZCEAp3PzY6iNTlsHlZb96ptZq2Y20bI4ukFBE6IuISfRIK L6icQkTEAOsZyZpKdT3QE771UnhMp3C99TNJuTA+W9iIRiAmkYgYgB4Xwp3LsWTtuIG3s xyTeqzZChZBoMmzDFYo56jkkNYbeN4uDXJcmmt1o3v5gPifPL5eJI+ubWZG8P4fgkJw== ARC-Authentication-Results: i=2; smtp3.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b="yGQ//BnO"; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" 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= Authentication-Results: smtp3.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=yGQ//BnO Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp3.osuosl.org (Postfix) with ESMTPS id B922760897; Wed, 2 Sep 2026 21:19:07 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 838C1C090F; Wed, 2 Sep 2026 21:19:07 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) by lists.linuxfoundation.org (Postfix) with ESMTP id 7B8BFC0D06 for ; Wed, 2 Sep 2026 21:19:05 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 5436C80FF5 for ; Wed, 2 Sep 2026 21:19:05 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 5ng3FmfoW9v4 for ; Wed, 2 Sep 2026 21:19:00 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp1.osuosl.org 77446810C4 Authentication-Results: smtp1.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::1135" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383936; b=Qtgg/0G11TqMHe1eprsiEkQXkR5O8yMZtZHQPzYQWnrzzCYST4oydqOoFXWNeAL7N3ku 4+Ry4ViT4TXjmHO4wcam0UMfiN2375atuzbXhLUIGx7+C3k0ykiMdE+dnqIbnTeg8beAh +MegpnWJDkv9ydFrKz3SL43frYvcLeWRwQbqTMBSr4MBurOHapwnkm+Jnxtb9o2U4LGLY Usuq03syyiBi+EWGrn7AWKSUyRLgiAZP3US/G5th0p+k2A2DHBsKGjZbAusJ91rN16kKD 3EkgdX0EcYX83NYU7q0osU4TUW6lz+X7/P8fpCjmQzFLNRr/2W4yIVGvdlnE7N9QvvA== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383936; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=0FTP7HppqraHtcAR+IIxBHKlxSK17MeZhi4PS80gUl4=; b=f62Be6NDkA5o/5wldKksAMld6afqHofdXr+zqTa0MPWHs2YDlw1fyEy91EcQ6TLYiYjh wIfaavci7ya5lT9p4+Xu6Ghy5lQGNX0BQGbbEews45Eb/Oy8mi1lMgNeWdjZzvX+VBglh xfgbmwhP4fcAqg+KpWB8grDlm1+Uqyzw+djespMJFHEHpeywLeL7cht8/3cqNHS/iEDuk y/CEeoBjywhzziCLURfjFzpXbcfC9T4O6vdq3G0AS+GY11M22VSH1dCycfYpRhIdM4pOE cgcTCLpRQK1Ojqe2bYo8dPO1yEEs1HCxrCL1cJfBelYSRPU4fNbupoiNm/1x5StPMnA== ARC-Authentication-Results: i=1; smtp1.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b="yGQ//BnO"; arc=none smtp.remote-ip="2607:f8b0:4864:20::1135" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::1135; helo=mail-yw1-x1135.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp1.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Authentication-Results: smtp1.osuosl.org; dkim=pass (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=yGQ//BnO Received: from mail-yw1-x1135.google.com (mail-yw1-x1135.google.com [IPv6:2607:f8b0:4864:20::1135]) by smtp1.osuosl.org (Postfix) with ESMTPS id 77446810C4 for ; Wed, 2 Sep 2026 21:18:55 +0000 (UTC) Received: by mail-yw1-x1135.google.com with SMTP id 00721157ae682-86162c086f8so5862027b3.1 for ; Wed, 02 Sep 2026 14:18:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383935; x=1788988735; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=0FTP7HppqraHtcAR+IIxBHKlxSK17MeZhi4PS80gUl4=; b=yGQ//BnOl2uZ6oaXxlMK/jlXo1/IHq8mOuL8cmh23hhfBLJ+a3GdQmCDNaQWeo0Z1X 2BQPR8u6/ohJwPceFJQ7zm1jw+LyRWZLLTZb2mKcGe0dHy6ZQhnzHlp6uwGcHtPUDypQ YqhBJFDCf+4lqjTlrum9/CLK6ru+yBAtHnePQQcbsZA17AKSMj9l29FOSReuagrWJD++ 3C/3JD0RYQWlac+JhI/wspewLaMlZtD7UD7uqqPu2+l+W3O79V00LvvrTOYurYa5GiEG ciNmusS/B/J5C7eRkKJlyPZa5SbtdYy2kD0V+qm04j64lMU8bai2WNIO+q6uSj7GzM/B GsdA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383935; x=1788988735; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=0FTP7HppqraHtcAR+IIxBHKlxSK17MeZhi4PS80gUl4=; b=X2qBFflF97gv/khKc0J3Yki4Lke88qmrgTyiDfXvcVf0m7r8kkRImCpRaytD5ZPrIU u6psU8iYnh/J14W/csKm5vGG5dvEX4LxJueQKk2D9WxmAYOhJ/uk7WEy4npr57y+ZW/M lNxgFZ8HCs36xlMY/ZbIkwk4YRcCBB15tSCk0cjg3yeYBG7c+mOcliNwxssv1kQ9017d 1NZG04eQZSPrYvEAUqlvLPR/uHE9WoBS3K73Re9PasZTkIP5wXf2puvX2YR6CivUUTwR Anpdfafb89WZbVVqNMAcsV7mevpA0QsN1evk0DE3HwlFPZFVoSWaqIVOSeplJjUn7+uq juhw== X-Gm-Message-State: AFuF++mXBlHVWsJzD5SNeQJFRh3VXxkR/6CyNSVEsIGsUBwFEMnM3Z5p 2LN+/vEouf8te1cjgXyJPu3fAxEHotZRs3y0Ai6Ztpjzsfc5GSovs1L0wbdQTdv76sFLu2XpbmP uUwBifgYDk+zNG4+MGz6+SjgaCa8AJKrLnHsQ31UIJZZz9+0x8sp8hbZbGLXBZgw= X-Gm-Gg: AYBFou3LBJ1r3Efe9FV+WAIA3jaGOxzxv6or9uMzTTOUjeDC1A+uu0rXf1O6xzvNolu KENvr5ZDz7h0K+0BuAB4klk0tGp6alSAx5dBToUGPkN7mkYq/pySlVKqJHsGpCUsYVGmpqKG/x6 w7cwWGpEdiJhnkEbtt/HsiCDJ1X/BkiNtLfwGkPIQ+YIDzNpgzanFEyfn642o/tHfumjGyUNhS7 r4Ijz2PUrizr4zvi2/tTiLmBgqsy9WoIgQOjjX6Vfu+Ng/DarzNi9KJfqsSzkbU6QI4G6WiZP/1 zOR+ru2GulXgTdrAYVRVJkRZFVfYvhiCOIBO4RoM89SwuZ/Y+Ic6Ypv/VkzwjBEGWNFIajPb1DC 6I0JO0Op66kA7nz8FFk7hcJHrnEtOXNTyhJWJUcssgw41aVZbWH6jh5ni4NSI8TQPlSvgUftIkJ 1FMQfOY9Zq/2Iw3FJzHt5Ns5D0V5iAPhWVoDRw7ZMe2AuP2VfQHtnFwpO/dk73Nby0hP/Mw26O+ wpvffYtHEs+ADHJkRq35cfKIwVVbtxI X-Received: by 2002:a05:690c:6d11:b0:858:bf8e:d0a4 with SMTP id 00721157ae682-86e72d18d88mr8919667b3.18.1788383932239; Wed, 02 Sep 2026 14:18:52 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.18.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:18:51 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:36 -0300 Message-Id: <20260902211744.2159261-7-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 06/14] ic: Add incremental handler for the en_az node. 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: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Introduce the en_az engine node: move the availability-zone computation (previously done inline in ovn_db_run) into a dedicated node that resolves ctx->runned_az, which the downstream subsystem nodes gate on. Each subsystem node NULL-checks the resolved AZ before running, so the AZ is handed to the subsystem entry points (gateway_run(), port_binding_run(), route_run(), sync_service_monitor() and address_set_run()) as an explicit parameter instead of being re-read from engine state deep inside them. inc_proc_ic_get_runned_az() remains for the main loop, which runs outside the engine. Also wire the ic-backoff-interval-ms option to throttle the engine loop (IC_NB_Global:options:ic-backoff-interval-ms), so a busy IC-SB does not spin ovn-ic at full speed. Assisted-by: Claude Opus 4.8, Claude Code Signed-off-by: Paulo Guilherme Silva Acked-by: Lucas Vargas Dias --- NEWS | 6 ++ ic/automake.mk | 2 + ic/en-address-set.c | 17 +++- ic/en-az.c | 127 ++++++++++++++++++++++++++ ic/en-az.h | 38 ++++++++ ic/en-gateway.c | 12 ++- ic/en-port-binding.c | 12 ++- ic/en-route.c | 12 ++- ic/en-service-monitor.c | 12 ++- ic/en-tr.c | 8 ++ ic/en-ts.c | 8 ++ ic/inc-proc-ic.c | 26 +++++- ic/inc-proc-ic.h | 7 ++ ic/ovn-ic.c | 194 +++++++++++++++++----------------------- ic/ovn-ic.h | 17 ++-- ovn-ic-nb.xml | 23 +++++ 16 files changed, 390 insertions(+), 131 deletions(-) create mode 100644 ic/en-az.c create mode 100644 ic/en-az.h diff --git a/NEWS b/NEWS index aadf6416d..819e81a79 100644 --- a/NEWS +++ b/NEWS @@ -127,6 +127,12 @@ OVN v26.09.0 - xxx xx xxxx represent a logical router port (e.g. ovn-ic transit switch LSPs). Such ports are treated like type=router, including omission from _MC_flood_l2. + - Added a new "options:ic-backoff-interval-ms" key to the IC_NB_Global + table. It caps how long ovn-ic postpones the next run of its + incremental-processing engine after the previous one finished, + coalescing bursts of change-driven runs into fewer, larger runs. It + defaults to 0, which keeps running as soon as there is work to do. A + forced full recompute is never delayed. OVN v26.03.0 - xxx xx xxxx -------------------------- diff --git a/ic/automake.mk b/ic/automake.mk index fd888d60c..b730fc578 100644 --- a/ic/automake.mk +++ b/ic/automake.mk @@ -4,6 +4,8 @@ ic_ovn_ic_SOURCES = ic/ovn-ic.c \ ic/ovn-ic.h \ ic/en-ic.c \ ic/en-ic.h \ + ic/en-az.c \ + ic/en-az.h \ ic/en-dp-enum.c \ ic/en-dp-enum.h \ ic/en-gateway.c \ diff --git a/ic/en-address-set.c b/ic/en-address-set.c index 764dd71d2..5a29f8201 100644 --- a/ic/en-address-set.c +++ b/ic/en-address-set.c @@ -15,6 +15,7 @@ #include #include "en-address-set.h" +#include "en-az.h" #include "lib/inc-proc-eng.h" #include "openvswitch/vlog.h" #include "ovn-ic.h" @@ -22,12 +23,22 @@ VLOG_DEFINE_THIS_MODULE(en_ic_address_set); enum engine_node_state -en_address_set_run(struct engine_node *node OVS_UNUSED, void *data OVS_UNUSED) +en_address_set_run(struct engine_node *node, void *data OVS_UNUSED) { const struct engine_context *eng_ctx = engine_get_context(); struct ic_context *ctx = eng_ctx->client_ctx; - - address_set_run(ctx); + const struct ed_type_az *az = engine_get_input_data("az", node); + + /* runned_az is resolved by the upstream en_az node. Without an AZ there + * is nothing to sync; this also mirrors the previous main-loop gating and + * avoids running address_set_run() before the NB Global row exists (en_az + * leaves runned_az NULL until then, and address_set_run() asserts on a + * NULL NB Global). */ + if (!az->runned_az) { + return EN_UNCHANGED; + } + + address_set_run(ctx, az->runned_az); return EN_UPDATED; } diff --git a/ic/en-az.c b/ic/en-az.c new file mode 100644 index 000000000..4d3c9bc71 --- /dev/null +++ b/ic/en-az.c @@ -0,0 +1,127 @@ +/* + * 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-az.h" +#include "lib/inc-proc-eng.h" +#include "lib/ovn-ic-sb-idl.h" +#include "lib/ovn-nb-idl.h" +#include "openvswitch/vlog.h" +#include "ovn-ic.h" +#include "ovsdb-idl.h" +#include "util.h" + +VLOG_DEFINE_THIS_MODULE(en_ic_az); + +/* Resolves (and, if needed, registers) the availability zone for this ovn-ic + * instance. This node has no engine inputs: like the former az_run() helper + * it runs on every engine iteration and reads the NB Global and IC-SB + * Availability_Zone tables directly. The resolved AZ is stored in this node's + * own data (struct ed_type_az): dependent nodes declare en_az as an input and + * read it with engine_get_input_data("az", node), and the main loop reads it + * with inc_proc_ic_get_runned_az() after inc_proc_ic_run(). It reports + * EN_UPDATED only when the resolved AZ identity changes so dependent nodes do + * not recompute on every iteration. */ +enum engine_node_state +en_az_run(struct engine_node *node OVS_UNUSED, void *data_) +{ + const struct engine_context *eng_ctx = engine_get_context(); + struct ic_context *ctx = eng_ctx->client_ctx; + struct ed_type_az *data = data_; + const struct icsbrec_availability_zone *old_az = data->runned_az; + const struct icsbrec_availability_zone *az; + + data->runned_az = NULL; + + const struct nbrec_nb_global *nb_global = + nbrec_nb_global_first(ctx->ovnnb_idl); + if (!nb_global) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "NB Global does not exist."); + goto out; + } + + /* Update old AZ if name changes. Note: if name changed when ovn-ic + * is not running, one has to manually delete/update the old AZ with: + * "ovn-ic-sbctl destroy avail ". */ + if (ctx->ovnisb_unlocked_txn && data->az_name + && strcmp(data->az_name, nb_global->name)) { + ICSBREC_AVAILABILITY_ZONE_FOR_EACH (az, ctx->ovnisb_unlocked_idl) { + /* AZ name update locally need to update az in ISB. */ + if (nb_global->name[0] && !strcmp(az->name, data->az_name)) { + icsbrec_availability_zone_set_name(az, nb_global->name); + break; + } else if (!nb_global->name[0] + && !strcmp(az->name, data->az_name)) { + icsbrec_availability_zone_delete(az); + break; + } + } + free(data->az_name); + data->az_name = NULL; + } + + if (!nb_global->name[0]) { + goto out; + } + + if (!data->az_name) { + data->az_name = xstrdup(nb_global->name); + } + + if (ctx->ovnisb_unlocked_txn) { + ovsdb_idl_txn_add_comment(ctx->ovnisb_unlocked_txn, "AZ %s", + data->az_name); + } + + ICSBREC_AVAILABILITY_ZONE_FOR_EACH (az, ctx->ovnisb_unlocked_idl) { + if (!strcmp(az->name, data->az_name)) { + data->runned_az = az; + goto out; + } + } + + /* Create AZ in ISB */ + if (ctx->ovnisb_unlocked_txn) { + VLOG_INFO("Register AZ %s to interconnection DB.", data->az_name); + az = icsbrec_availability_zone_insert(ctx->ovnisb_unlocked_txn); + icsbrec_availability_zone_set_name(az, data->az_name); + data->runned_az = az; + } + +out: { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_DBG_RL(&rl, "Availability zone: %s", + data->runned_az ? data->runned_az->name : "not created yet."); + + return data->runned_az == old_az ? EN_UNCHANGED : EN_UPDATED; +} +} + +void * +en_az_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + return xzalloc(sizeof(struct ed_type_az)); +} + +void +en_az_cleanup(void *data_) +{ + struct ed_type_az *data = data_; + + free(data->az_name); + data->az_name = NULL; +} diff --git a/ic/en-az.h b/ic/en-az.h new file mode 100644 index 000000000..64cb23548 --- /dev/null +++ b/ic/en-az.h @@ -0,0 +1,38 @@ +/* + * 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_IC_AZ_H +#define EN_IC_AZ_H 1 + +#include "lib/inc-proc-eng.h" + +struct icsbrec_availability_zone; + +/* Data maintained by the "az" engine node: the availability zone this ovn-ic + * instance runs for. This replaces the former az_run() helper and its file + * scope 'az_name' static variable. */ +struct ed_type_az { + /* The availability zone record for this instance, or NULL if it is not + * known/registered yet. Owned by the IDL. */ + const struct icsbrec_availability_zone *runned_az; + /* The name this instance registered as, persisted across iterations so a + * later rename can update the existing IC-SB record. */ + char *az_name; +}; + +enum engine_node_state en_az_run(struct engine_node *node, void *data); +void *en_az_init(struct engine_node *node, struct engine_arg *arg); +void en_az_cleanup(void *data); + +#endif /* EN_IC_AZ_H */ diff --git a/ic/en-gateway.c b/ic/en-gateway.c index f41166018..3fb00ced9 100644 --- a/ic/en-gateway.c +++ b/ic/en-gateway.c @@ -15,6 +15,7 @@ #include #include "en-gateway.h" +#include "en-az.h" #include "lib/inc-proc-eng.h" #include "openvswitch/vlog.h" #include "ovn-ic.h" @@ -22,12 +23,19 @@ VLOG_DEFINE_THIS_MODULE(en_ic_gateway); enum engine_node_state -en_gateway_run(struct engine_node *node OVS_UNUSED, void *data OVS_UNUSED) +en_gateway_run(struct engine_node *node, void *data OVS_UNUSED) { const struct engine_context *eng_ctx = engine_get_context(); struct ic_context *ctx = eng_ctx->client_ctx; + const struct ed_type_az *az = engine_get_input_data("az", node); - gateway_run(ctx); + /* runned_az is resolved by the upstream en_az node. Without an AZ there + * is nothing to sync (mirrors the previous main-loop gating). */ + if (!az->runned_az) { + return EN_UNCHANGED; + } + + gateway_run(ctx, az->runned_az); return EN_UPDATED; } diff --git a/ic/en-port-binding.c b/ic/en-port-binding.c index 8843d6cef..838b86f69 100644 --- a/ic/en-port-binding.c +++ b/ic/en-port-binding.c @@ -15,6 +15,7 @@ #include #include "en-port-binding.h" +#include "en-az.h" #include "lib/inc-proc-eng.h" #include "openvswitch/vlog.h" #include "ovn-ic.h" @@ -22,12 +23,19 @@ VLOG_DEFINE_THIS_MODULE(en_ic_port_binding); enum engine_node_state -en_port_binding_run(struct engine_node *node OVS_UNUSED, void *data OVS_UNUSED) +en_port_binding_run(struct engine_node *node, void *data OVS_UNUSED) { const struct engine_context *eng_ctx = engine_get_context(); struct ic_context *ctx = eng_ctx->client_ctx; + const struct ed_type_az *az = engine_get_input_data("az", node); - port_binding_run(ctx); + /* runned_az is resolved by the upstream en_az node. Without an AZ there + * is nothing to sync (mirrors the previous main-loop gating). */ + if (!az->runned_az) { + return EN_UNCHANGED; + } + + port_binding_run(ctx, az->runned_az); return EN_UPDATED; } diff --git a/ic/en-route.c b/ic/en-route.c index ec39d89d9..a6d8ccf6e 100644 --- a/ic/en-route.c +++ b/ic/en-route.c @@ -15,6 +15,7 @@ #include #include "en-route.h" +#include "en-az.h" #include "lib/inc-proc-eng.h" #include "openvswitch/vlog.h" #include "ovn-ic.h" @@ -22,12 +23,19 @@ VLOG_DEFINE_THIS_MODULE(en_ic_route); enum engine_node_state -en_route_run(struct engine_node *node OVS_UNUSED, void *data OVS_UNUSED) +en_route_run(struct engine_node *node, void *data OVS_UNUSED) { const struct engine_context *eng_ctx = engine_get_context(); struct ic_context *ctx = eng_ctx->client_ctx; + const struct ed_type_az *az = engine_get_input_data("az", node); - route_run(ctx); + /* runned_az is resolved by the upstream en_az node. Without an AZ there + * is nothing to sync (mirrors the previous main-loop gating). */ + if (!az->runned_az) { + return EN_UNCHANGED; + } + + route_run(ctx, az->runned_az); return EN_UPDATED; } diff --git a/ic/en-service-monitor.c b/ic/en-service-monitor.c index b85a36dc7..d0aab60bb 100644 --- a/ic/en-service-monitor.c +++ b/ic/en-service-monitor.c @@ -15,6 +15,7 @@ #include #include "en-service-monitor.h" +#include "en-az.h" #include "lib/inc-proc-eng.h" #include "openvswitch/vlog.h" #include "ovn-ic.h" @@ -22,13 +23,20 @@ VLOG_DEFINE_THIS_MODULE(en_ic_service_monitor); enum engine_node_state -en_service_monitor_run(struct engine_node *node OVS_UNUSED, +en_service_monitor_run(struct engine_node *node, void *data OVS_UNUSED) { const struct engine_context *eng_ctx = engine_get_context(); struct ic_context *ctx = eng_ctx->client_ctx; + const struct ed_type_az *az = engine_get_input_data("az", node); - sync_service_monitor(ctx); + /* runned_az is resolved by the upstream en_az node. Without an AZ there + * is nothing to sync (mirrors the previous main-loop gating). */ + if (!az->runned_az) { + return EN_UNCHANGED; + } + + sync_service_monitor(ctx, az->runned_az); return EN_UPDATED; } diff --git a/ic/en-tr.c b/ic/en-tr.c index 4f05a4325..c3b1b60c6 100644 --- a/ic/en-tr.c +++ b/ic/en-tr.c @@ -16,6 +16,7 @@ #include "en-dp-enum.h" #include "en-tr.h" +#include "en-az.h" #include "lib/inc-proc-eng.h" #include "openvswitch/vlog.h" #include "ovn-ic.h" @@ -28,6 +29,13 @@ en_tr_run(struct engine_node *node, void *data OVS_UNUSED) const struct engine_context *eng_ctx = engine_get_context(); struct ic_context *ctx = eng_ctx->client_ctx; struct ed_type_dp_enum *dp = engine_get_input_data("dp_enum", node); + const struct ed_type_az *az = engine_get_input_data("az", node); + + /* runned_az is resolved by the upstream en_az node. Without an AZ there + * is nothing to sync (mirrors the previous main-loop gating). */ + if (!az->runned_az) { + return EN_UNCHANGED; + } tr_run(ctx, &dp->dp_tnlids, &dp->isb_tr_dps); diff --git a/ic/en-ts.c b/ic/en-ts.c index e8df1888a..e4ec2a0b7 100644 --- a/ic/en-ts.c +++ b/ic/en-ts.c @@ -16,6 +16,7 @@ #include "en-dp-enum.h" #include "en-ts.h" +#include "en-az.h" #include "lib/inc-proc-eng.h" #include "openvswitch/vlog.h" #include "ovn-ic.h" @@ -28,6 +29,13 @@ en_ts_run(struct engine_node *node, void *data OVS_UNUSED) const struct engine_context *eng_ctx = engine_get_context(); struct ic_context *ctx = eng_ctx->client_ctx; struct ed_type_dp_enum *dp = engine_get_input_data("dp_enum", node); + const struct ed_type_az *az = engine_get_input_data("az", node); + + /* runned_az is resolved by the upstream en_az node. Without an AZ there + * is nothing to sync (mirrors the previous main-loop gating). */ + if (!az->runned_az) { + return EN_UNCHANGED; + } ts_run(ctx, &dp->dp_tnlids, &dp->isb_ts_dps); diff --git a/ic/inc-proc-ic.c b/ic/inc-proc-ic.c index a6f029e37..66958dbf0 100644 --- a/ic/inc-proc-ic.c +++ b/ic/inc-proc-ic.c @@ -27,6 +27,7 @@ #include "openvswitch/vlog.h" #include "inc-proc-ic.h" #include "en-ic.h" +#include "en-az.h" #include "en-dp-enum.h" #include "en-gateway.h" #include "en-ts.h" @@ -173,6 +174,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_ic); /* Define engine nodes for other nodes. They should be defined as static to * avoid sparse errors. */ +static ENGINE_NODE(az); static ENGINE_NODE(dp_enum); static ENGINE_NODE(gateway); static ENGINE_NODE(ts); @@ -215,6 +217,7 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_dp_enum, &en_icnb_ic_nb_global, NULL); /* en_gateway: sync gateways/chassis between SB and IC-SB. */ + engine_add_input(&en_gateway, &en_az, NULL); engine_add_input(&en_gateway, &en_icsb_availability_zone, NULL); engine_add_input(&en_gateway, &en_icsb_gateway, NULL); engine_add_input(&en_gateway, &en_icsb_encap, NULL); @@ -222,6 +225,7 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_gateway, &en_sb_encap, NULL); /* en_ts: sync transit switches to NB and IC-SB datapath bindings. */ + engine_add_input(&en_ts, &en_az, NULL); engine_add_input(&en_ts, &en_dp_enum, NULL); engine_add_input(&en_ts, &en_icnb_ic_nb_global, NULL); engine_add_input(&en_ts, &en_icnb_transit_switch, NULL); @@ -229,11 +233,13 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_ts, &en_icsb_encap, NULL); /* en_tr: sync transit routers to NB and IC-SB datapath bindings. */ + engine_add_input(&en_tr, &en_az, NULL); engine_add_input(&en_tr, &en_dp_enum, NULL); engine_add_input(&en_tr, &en_icnb_transit_router, NULL); engine_add_input(&en_tr, &en_nb_logical_router, NULL); /* en_port_binding: sync cross-AZ port bindings. */ + engine_add_input(&en_port_binding, &en_az, NULL); engine_add_input(&en_port_binding, &en_icsb_availability_zone, NULL); engine_add_input(&en_port_binding, &en_icsb_port_binding, NULL); engine_add_input(&en_port_binding, &en_icnb_transit_switch, NULL); @@ -247,6 +253,7 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_port_binding, &en_sb_chassis, NULL); /* en_route: advertise/learn cross-AZ routes. */ + engine_add_input(&en_route, &en_az, NULL); engine_add_input(&en_route, &en_icsb_availability_zone, NULL); engine_add_input(&en_route, &en_icsb_port_binding, NULL); engine_add_input(&en_route, &en_icsb_route, NULL); @@ -262,13 +269,21 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_route, &en_sb_learned_route, NULL); /* en_service_monitor: sync load-balancer health checks across AZs. */ + engine_add_input(&en_service_monitor, &en_az, NULL); engine_add_input(&en_service_monitor, &en_icsb_availability_zone, NULL); engine_add_input(&en_service_monitor, &en_icsb_service_monitor, NULL); engine_add_input(&en_service_monitor, &en_sb_sb_global, NULL); engine_add_input(&en_service_monitor, &en_sb_service_monitor, NULL); engine_add_input(&en_service_monitor, &en_sb_port_binding, NULL); - /* en_address_set: advertise/learn address sets across AZs. */ + /* en_address_set: advertise/learn address sets across AZs. + * + * Like the other AZ-scoped nodes, address_set_run() partitions IC-SB + * address sets into local/remote by comparing their availability_zone + * against this instance's AZ, so it depends on en_az (which reports + * EN_UPDATED only when the AZ identity changes) to be re-run when the + * resolved AZ changes. */ + engine_add_input(&en_address_set, &en_az, NULL); engine_add_input(&en_address_set, &en_icsb_availability_zone, NULL); engine_add_input(&en_address_set, &en_nb_nb_global, NULL); engine_add_input(&en_address_set, &en_nb_address_set, NULL); @@ -331,7 +346,7 @@ inc_proc_ic_run(struct ic_context *ctx, int64_t now = time_msec(); /* Postpone the next run by length of current run with maximum capped - * by "northd-backoff-interval-ms" interval. */ + * by "ic-backoff-interval-ms" interval. */ ic_eng_ctx->next_run_ms = now + MIN(now - start, ic_eng_ctx->backoff_ms); return engine_has_updated(); @@ -344,6 +359,13 @@ inc_proc_ic_cleanup(void) engine_set_context(NULL); } +const struct icsbrec_availability_zone * +inc_proc_ic_get_runned_az(void) +{ + const struct ed_type_az *az = engine_get_data(&en_az); + return az ? az->runned_az : NULL; +} + bool inc_proc_ic_can_run(struct ic_engine_context *ctx) { diff --git a/ic/inc-proc-ic.h b/ic/inc-proc-ic.h index 993050f8a..67565a014 100644 --- a/ic/inc-proc-ic.h +++ b/ic/inc-proc-ic.h @@ -40,6 +40,13 @@ bool inc_proc_ic_run(struct ic_context *ctx, void inc_proc_ic_cleanup(void); bool inc_proc_ic_can_run(struct ic_engine_context *ctx); +struct icsbrec_availability_zone; + +/* Returns the availability zone resolved by the en_az engine node during the + * last inc_proc_ic_run(), or NULL if none is resolved yet. Used by the main + * loop, which runs the AZ sequence-number bookkeeping outside the engine. */ +const struct icsbrec_availability_zone *inc_proc_ic_get_runned_az(void); + static inline void inc_proc_ic_force_recompute(void) { diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c index 6e0d1b4e1..773762ae4 100644 --- a/ic/ovn-ic.c +++ b/ic/ovn-ic.c @@ -99,68 +99,6 @@ Options:\n\ stream_usage("database", true, true, false); } -static const struct icsbrec_availability_zone * -az_run(struct ic_context *ctx) -{ - const struct nbrec_nb_global *nb_global = - nbrec_nb_global_first(ctx->ovnnb_idl); - - if (!nb_global) { - VLOG_INFO("NB Global not exist."); - return NULL; - } - - /* Update old AZ if name changes. Note: if name changed when ovn-ic - * is not running, one has to manually delete/update the old AZ with: - * "ovn-ic-sbctl destroy avail ". */ - static char *az_name; - const struct icsbrec_availability_zone *az; - if (ctx->ovnisb_unlocked_txn && az_name - && strcmp(az_name, nb_global->name)) { - ICSBREC_AVAILABILITY_ZONE_FOR_EACH (az, ctx->ovnisb_unlocked_idl) { - /* AZ name update locally need to update az in ISB. */ - if (nb_global->name[0] && !strcmp(az->name, az_name)) { - icsbrec_availability_zone_set_name(az, nb_global->name); - break; - } else if (!nb_global->name[0] && !strcmp(az->name, az_name)) { - icsbrec_availability_zone_delete(az); - break; - } - } - free(az_name); - az_name = NULL; - } - - if (!nb_global->name[0]) { - return NULL; - } - - if (!az_name) { - az_name = xstrdup(nb_global->name); - } - - if (ctx->ovnisb_unlocked_txn) { - ovsdb_idl_txn_add_comment(ctx->ovnisb_unlocked_txn, "AZ %s", az_name); - } - - ICSBREC_AVAILABILITY_ZONE_FOR_EACH (az, ctx->ovnisb_unlocked_idl) { - if (!strcmp(az->name, az_name)) { - ctx->runned_az = az; - return az; - } - } - - /* Create AZ in ISB */ - if (ctx->ovnisb_unlocked_txn) { - VLOG_INFO("Register AZ %s to interconnection DB.", az_name); - az = icsbrec_availability_zone_insert(ctx->ovnisb_unlocked_txn); - icsbrec_availability_zone_set_name(az, az_name); - ctx->runned_az = az; - return az; - } - return NULL; -} - static uint32_t allocate_dp_key(struct hmap *dp_tnlids, bool vxlan_mode, const char *name) { @@ -611,7 +549,8 @@ sync_addr_set_from_icsb(struct ovsdb_idl_txn *ovnnb_txn, } void -address_set_run(struct ic_context *ctx) +address_set_run(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az) { if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { return; @@ -621,7 +560,7 @@ address_set_run(struct ic_context *ctx) struct shash ic_remote_as = SHASH_INITIALIZER(&ic_remote_as); const struct icsbrec_address_set *ic_as; ICSBREC_ADDRESS_SET_FOR_EACH (ic_as, ctx->ovnisb_unlocked_idl) { - if (ic_as->availability_zone == ctx->runned_az) { + if (ic_as->availability_zone == runned_az) { shash_add(&ic_local_as, ic_as->name, ic_as); } else { /* Merge addresses from all remote AZs that share the same @@ -659,7 +598,7 @@ address_set_run(struct ic_context *ctx) const struct icsbrec_address_set *icsb_as; icsb_as = shash_find_and_delete(&ic_local_as, sb_as->name); sync_addr_set_to_icsb(ctx->ovnisb_unlocked_txn, sb_as, icsb_as, - ctx->runned_az); + runned_az); } } } @@ -711,7 +650,8 @@ address_set_run(struct ic_context *ctx) } void -gateway_run(struct ic_context *ctx) +gateway_run(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az) { if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) { return; @@ -721,7 +661,7 @@ gateway_run(struct ic_context *ctx) struct shash remote_gws = SHASH_INITIALIZER(&remote_gws); const struct icsbrec_gateway *gw; ICSBREC_GATEWAY_FOR_EACH (gw, ctx->ovnisb_unlocked_idl) { - if (gw->availability_zone == ctx->runned_az) { + if (gw->availability_zone == runned_az) { shash_add(&local_gws, gw->name, gw); } else { shash_add(&remote_gws, gw->name, gw); @@ -734,7 +674,8 @@ gateway_run(struct ic_context *ctx) gw = shash_find_and_delete(&local_gws, chassis->name); if (!gw) { gw = icsbrec_gateway_insert(ctx->ovnisb_unlocked_txn); - icsbrec_gateway_set_availability_zone(gw, ctx->runned_az); + icsbrec_gateway_set_availability_zone(gw, + runned_az); icsbrec_gateway_set_name(gw, chassis->name); sync_sb_gw_to_isb(ctx, chassis, gw); } else if (is_gateway_data_changed(gw, chassis)) { @@ -1400,7 +1341,8 @@ find_lsp_in_sb(struct ic_context *ctx, } void -port_binding_run(struct ic_context *ctx) +port_binding_run(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az) { if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { return; @@ -1417,7 +1359,7 @@ port_binding_run(struct ic_context *ctx) const struct icsbrec_port_binding *isb_pb_key = icsbrec_port_binding_index_init_row(ctx->icsbrec_port_binding_by_az); icsbrec_port_binding_index_set_availability_zone(isb_pb_key, - ctx->runned_az); + runned_az); ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, ctx->icsbrec_port_binding_by_az) { @@ -1456,7 +1398,7 @@ port_binding_run(struct ic_context *ctx) ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, ctx->icsbrec_port_binding_by_ts) { - if (isb_pb->availability_zone == ctx->runned_az) { + if (isb_pb->availability_zone == runned_az) { shash_add(&local_pbs, isb_pb->logical_port, isb_pb); shash_find_and_delete(&switch_all_local_pbs, isb_pb->logical_port); @@ -1478,8 +1420,9 @@ port_binding_run(struct ic_context *ctx) if (ctx->ovnisb_txn && is_az_leader(ctx->ovnisb_txn)) { if (!isb_pb) { isb_pb = create_isb_pb( - ctx->ovnisb_txn, tsp->name, ctx->runned_az, - ts->name, &ts->header_.uuid, "transit-switch-port", + ctx->ovnisb_txn, tsp->name, + runned_az, ts->name, + &ts->header_.uuid, "transit-switch-port", &pb_tnlids); } sync_tsp_pb(tsp, isb_pb); @@ -1489,7 +1432,7 @@ port_binding_run(struct ic_context *ctx) isb_pb = shash_find_and_delete(&local_pbs, tsp->name); if (!isb_pb) { isb_pb = create_isb_pb(ctx->ovnisb_unlocked_txn, tsp->name, - ctx->runned_az, + runned_az, ts->name, &ts->header_.uuid, "transit-switch-port", &pb_tnlids); } @@ -1532,8 +1475,8 @@ port_binding_run(struct ic_context *ctx) if (!isb_pb) { isb_pb = create_isb_pb( ctx->ovnisb_unlocked_txn, sb_pb->logical_port, - ctx->runned_az, ts->name, &ts->header_.uuid, - "transit-switch-port", &pb_tnlids); + runned_az, ts->name, + &ts->header_.uuid, "transit-switch-port", &pb_tnlids); sync_ts_isb_pb(ctx, sb_pb, isb_pb); } else { sync_local_port(ctx, isb_pb, sb_pb, lsp); @@ -1617,7 +1560,7 @@ port_binding_run(struct ic_context *ctx) ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, ctx->icsbrec_port_binding_by_ts) { - if (isb_pb->availability_zone == ctx->runned_az) { + if (isb_pb->availability_zone == runned_az) { shash_add(&local_pbs, isb_pb->logical_port, isb_pb); shash_find_and_delete(&router_all_local_pbs, isb_pb->logical_port); @@ -1636,7 +1579,7 @@ port_binding_run(struct ic_context *ctx) isb_pb = shash_find_and_delete(&local_pbs, trp->name); if (!isb_pb) { isb_pb = create_isb_pb(ctx->ovnisb_unlocked_txn, trp->name, - ctx->runned_az, + runned_az, tr->name, &tr->header_.uuid, "transit-router-port", &pb_tnlids); icsbrec_port_binding_set_address(isb_pb, trp->mac); @@ -2611,6 +2554,7 @@ route_learn_tag_rules_allow(const struct route_learn_tag_rules *rules, static void sync_learned_routes(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, struct ic_router_info *ic_lr) { ovs_assert(ctx->ovnnb_txn); @@ -2673,7 +2617,8 @@ sync_learned_routes(struct ic_context *ctx, || uuid_equals(&ic_lr->lr->header_.uuid, &lr_uuid)) { continue; } - } else if (isb_route->availability_zone == ctx->runned_az) { + } else if (isb_route->availability_zone == + runned_az) { continue; } @@ -3135,20 +3080,21 @@ delete_orphan_ic_routes(struct ic_context *ctx, } void -route_run(struct ic_context *ctx) +route_run(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az) { if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { return; } - delete_orphan_ic_routes(ctx, ctx->runned_az); + delete_orphan_ic_routes(ctx, runned_az); struct hmap ic_lrs = HMAP_INITIALIZER(&ic_lrs); const struct icsbrec_port_binding *isb_pb; const struct icsbrec_port_binding *isb_pb_key = icsbrec_port_binding_index_init_row(ctx->icsbrec_port_binding_by_az); icsbrec_port_binding_index_set_availability_zone(isb_pb_key, - ctx->runned_az); + runned_az); /* Each port on TS maps to a logical router, which is stored in the * external_ids:router-id of the IC SB port_binding record. @@ -3225,7 +3171,7 @@ route_run(struct ic_context *ctx) struct shash routes_ad_by_ts = SHASH_INITIALIZER(&routes_ad_by_ts); HMAP_FOR_EACH_SAFE (ic_lr, node, &ic_lrs) { collect_lr_routes(ctx, ic_lr, &routes_ad_by_ts); - sync_learned_routes(ctx, ic_lr); + sync_learned_routes(ctx, runned_az, ic_lr); vector_destroy(&ic_lr->isb_pbs); hmap_destroy(&ic_lr->routes_learned); hmap_remove(&ic_lrs, &ic_lr->node); @@ -3233,7 +3179,8 @@ route_run(struct ic_context *ctx) } struct shash_node *node; SHASH_FOR_EACH (node, &routes_ad_by_ts) { - advertise_routes(ctx, ctx->runned_az, node->name, node->data); + advertise_routes(ctx, runned_az, node->name, + node->data); hmap_destroy(node->data); } shash_destroy_free_data(&routes_ad_by_ts); @@ -3368,6 +3315,7 @@ remove_unused_sb_records(struct hmap *local_sb_svcs_map) static void create_pushed_svcs_mon(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, struct hmap *pushed_svcs_map) { struct sbrec_service_monitor *key = @@ -3386,8 +3334,8 @@ create_pushed_svcs_mon(struct ic_context *ctx, } create_service_monitor_info(pushed_svcs_map, sb_rec, &sb_rec->header_.uuid, - ctx->runned_az->name, target_az_name, - NULL, false); + runned_az->name, + target_az_name, NULL, false); } sbrec_service_monitor_index_destroy_row(key); @@ -3395,6 +3343,7 @@ create_pushed_svcs_mon(struct ic_context *ctx, static void create_synced_svcs_mon(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, struct hmap *synced_svcs_map) { struct icsbrec_service_monitor *key = @@ -3402,7 +3351,7 @@ create_synced_svcs_mon(struct ic_context *ctx, ctx->icsbrec_service_monitor_by_target_az); icsbrec_service_monitor_index_set_target_availability_zone( - key, ctx->runned_az->name); + key, runned_az->name); const struct icsbrec_service_monitor *ic_rec; ICSBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (ic_rec, key, @@ -3419,7 +3368,7 @@ create_synced_svcs_mon(struct ic_context *ctx, const char *chassis_name = pb->chassis ? pb->chassis->name : NULL; create_service_monitor_info(synced_svcs_map, ic_rec, &ic_rec->header_.uuid, - ctx->runned_az->name, + runned_az->name, NULL, chassis_name, true); } @@ -3428,6 +3377,7 @@ create_synced_svcs_mon(struct ic_context *ctx, static void create_local_ic_svcs_map(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, struct hmap *owned_svc_map) { struct icsbrec_service_monitor *key = @@ -3435,14 +3385,14 @@ create_local_ic_svcs_map(struct ic_context *ctx, ctx->icsbrec_service_monitor_by_source_az); icsbrec_service_monitor_index_set_source_availability_zone( - key, ctx->runned_az->name); + key, runned_az->name); const struct icsbrec_service_monitor *ic_rec; ICSBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (ic_rec, key, ctx->icsbrec_service_monitor_by_source_az) { create_service_monitor_info(owned_svc_map, ic_rec, &ic_rec->header_.uuid, - ctx->runned_az->name, NULL, + runned_az->name, NULL, NULL, true); } @@ -3451,6 +3401,7 @@ create_local_ic_svcs_map(struct ic_context *ctx, static void create_local_sb_svcs_map(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, struct hmap *owned_svc_map) { struct sbrec_service_monitor *key = @@ -3465,7 +3416,7 @@ create_local_sb_svcs_map(struct ic_context *ctx, ctx->sbrec_service_monitor_by_ic_learned) { create_service_monitor_info(owned_svc_map, sb_rec, &sb_rec->header_.uuid, - ctx->runned_az->name, NULL, + runned_az->name, NULL, NULL, false); } @@ -3545,6 +3496,7 @@ lookup_icsb_svc_rec(struct ic_context *ctx, static void create_service_monitor_data(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, struct sync_service_monitor_data *sync_data) { const struct sbrec_sb_global *ic_sb = sbrec_sb_global_first( @@ -3557,10 +3509,11 @@ create_service_monitor_data(struct ic_context *ctx, } sync_data->prpg_svc_monitor_mac = xstrdup(svc_monitor_mac); - create_pushed_svcs_mon(ctx, &sync_data->pushed_svcs_map); - create_synced_svcs_mon(ctx, &sync_data->synced_svcs_map); - create_local_ic_svcs_map(ctx, &sync_data->local_ic_svcs_map); - create_local_sb_svcs_map(ctx, &sync_data->local_sb_svcs_map); + create_pushed_svcs_mon(ctx, runned_az, &sync_data->pushed_svcs_map); + create_synced_svcs_mon(ctx, runned_az, &sync_data->synced_svcs_map); + create_local_ic_svcs_map(ctx, runned_az, &sync_data->local_ic_svcs_map); + create_local_sb_svcs_map(ctx, runned_az, + &sync_data->local_sb_svcs_map); } static void @@ -3581,7 +3534,8 @@ destroy_service_monitor_data(struct sync_service_monitor_data *sync_data) } void -sync_service_monitor(struct ic_context *ctx) +sync_service_monitor(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az) { if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) { return; @@ -3594,7 +3548,7 @@ sync_service_monitor(struct ic_context *ctx) hmap_init(&sync_data.local_ic_svcs_map); hmap_init(&sync_data.local_sb_svcs_map); - create_service_monitor_data(ctx, &sync_data); + create_service_monitor_data(ctx, runned_az, &sync_data); struct service_monitor_info *svc_mon; HMAP_FOR_EACH_SAFE (svc_mon, hmap_node, &sync_data.pushed_svcs_map) { @@ -3708,6 +3662,7 @@ sync_service_monitor(struct ic_context *ctx) */ static void update_sequence_numbers(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, struct ovsdb_idl_loop *ic_sb_loop) { if (!ctx->ovnisb_unlocked_txn || !ctx->ovninb_txn) { @@ -3726,14 +3681,14 @@ update_sequence_numbers(struct ic_context *ctx, } if ((ic_nb->nb_ic_cfg != ic_sb->nb_ic_cfg) && - (ic_nb->nb_ic_cfg != ctx->runned_az->nb_ic_cfg)) { + (ic_nb->nb_ic_cfg != runned_az->nb_ic_cfg)) { /* Deal with potential overflows. */ - if (ctx->runned_az->nb_ic_cfg == INT64_MAX) { - icsbrec_availability_zone_set_nb_ic_cfg(ctx->runned_az, 0); + if (runned_az->nb_ic_cfg == INT64_MAX) { + icsbrec_availability_zone_set_nb_ic_cfg(runned_az, 0); } ic_sb_loop->next_cfg = ic_nb->nb_ic_cfg; ovsdb_idl_txn_increment(ctx->ovnisb_unlocked_txn, - &ctx->runned_az->header_, + &runned_az->header_, &icsbrec_availability_zone_col_nb_ic_cfg, true); return; } @@ -3741,22 +3696,22 @@ update_sequence_numbers(struct ic_context *ctx, /* handle cases where accidentally AZ:ic_nb_cfg exceeds * the INB:ic_nb_cfg. */ - if (ctx->runned_az->nb_ic_cfg != ic_sb_loop->cur_cfg) { - icsbrec_availability_zone_set_nb_ic_cfg(ctx->runned_az, + if (runned_az->nb_ic_cfg != ic_sb_loop->cur_cfg) { + icsbrec_availability_zone_set_nb_ic_cfg(runned_az, ic_sb_loop->cur_cfg); return; } const struct icsbrec_availability_zone *other_az; ICSBREC_AVAILABILITY_ZONE_FOR_EACH (other_az, ctx->ovnisb_unlocked_idl) { - if (other_az->nb_ic_cfg != ctx->runned_az->nb_ic_cfg) { + if (other_az->nb_ic_cfg != runned_az->nb_ic_cfg) { return; } } /* All the AZs are updated successfully, update SB/NB counter. */ if (ic_nb->nb_ic_cfg != ic_sb->nb_ic_cfg) { - icsbrec_ic_sb_global_set_nb_ic_cfg(ic_sb, ctx->runned_az->nb_ic_cfg); - icnbrec_ic_nb_global_set_sb_ic_cfg(ic_nb, ctx->runned_az->nb_ic_cfg); + icsbrec_ic_sb_global_set_nb_ic_cfg(ic_sb, runned_az->nb_ic_cfg); + icnbrec_ic_nb_global_set_sb_ic_cfg(ic_nb, runned_az->nb_ic_cfg); } } @@ -4333,6 +4288,17 @@ main(int argc, char *argv[]) ovnisb_idl_loop.idl, ovnisb_unlocked_idl_loop.idl, ovninb_idl_loop.idl); + + /* Postpone the next engine run by the length of the previous one, up + * to this interval, to coalesce bursts of change-driven runs. A + * forced recompute (set_force_recompute) still runs immediately, so + * this only throttles the natural, incremental path. */ + const struct icnbrec_ic_nb_global *ic_nb_global = + icnbrec_ic_nb_global_first(ovninb_idl_loop.idl); + eng_ctx.backoff_ms = ic_nb_global + ? smap_get_uint(&ic_nb_global->options, + "ic-backoff-interval-ms", 0) : 0; + memory_run(); if (memory_should_report()) { struct simap usage = SIMAP_INITIALIZER(&usage); @@ -4503,12 +4469,16 @@ main(int argc, char *argv[]) ovsdb_idl_has_ever_connected(ctx.ovnisb_unlocked_idl)) { if (ctx.ovnnb_txn && ctx.ovnsb_txn && ctx.ovninb_txn && ctx.ovnisb_unlocked_txn && inc_proc_ic_can_run(&eng_ctx)) { - ctx.runned_az = az_run(&ctx); - VLOG_DBG("Availability zone: %s", ctx.runned_az ? - ctx.runned_az->name : "not created yet."); - if (ctx.runned_az) { - (void) inc_proc_ic_run(&ctx, &eng_ctx); - update_sequence_numbers(&ctx, + /* The availability zone is now resolved by the en_az + * engine node. The subsystem nodes skip their work when + * there is no AZ; the sequence-number bookkeeping below + * runs outside the engine, so it reads the resolved AZ via + * inc_proc_ic_get_runned_az(). */ + (void) inc_proc_ic_run(&ctx, &eng_ctx); + const struct icsbrec_availability_zone *runned_az = + inc_proc_ic_get_runned_az(); + if (runned_az) { + update_sequence_numbers(&ctx, runned_az, &ovnisb_unlocked_idl_loop); } } else if (!inc_proc_ic_get_force_recompute()) { diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h index 44dd4c597..3d2e20483 100644 --- a/ic/ovn-ic.h +++ b/ic/ovn-ic.h @@ -29,7 +29,6 @@ struct ic_context { struct ovsdb_idl_txn *ovninb_txn; struct ovsdb_idl_txn *ovnisb_txn; struct ovsdb_idl_txn *ovnisb_unlocked_txn; - const struct icsbrec_availability_zone *runned_az; struct ovsdb_idl_index *nbrec_ls_by_name; struct ovsdb_idl_index *nbrec_lr_by_name; struct ovsdb_idl_index *nbrec_lrp_by_name; @@ -64,20 +63,26 @@ enum ic_port_binding_type { IC_SWITCH_PORT, IC_ROUTER_PORT, IC_PORT_MAX }; struct hmap; struct shash; +struct icsbrec_availability_zone; /* Per-subsystem entry points, invoked by the incremental-processing engine * nodes (see ic/en-*.c). Each performs a full recompute of its subsystem and * may be invoked independently when its engine inputs change. */ void enumerate_datapaths(struct ic_context *ctx, struct hmap *dp_tnlids, struct shash *isb_ts_dps, struct shash *isb_tr_dps); -void gateway_run(struct ic_context *ctx); -void address_set_run(struct ic_context *ctx); +void gateway_run(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az); +void address_set_run(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az); void ts_run(struct ic_context *ctx, struct hmap *dp_tnlids, struct shash *isb_ts_dps); void tr_run(struct ic_context *ctx, struct hmap *dp_tnlids, struct shash *isb_tr_dps); -void port_binding_run(struct ic_context *ctx); -void route_run(struct ic_context *ctx); -void sync_service_monitor(struct ic_context *ctx); +void port_binding_run(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az); +void route_run(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az); +void sync_service_monitor(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az); #endif /* OVN_IC_H */ diff --git a/ovn-ic-nb.xml b/ovn-ic-nb.xml index f6110324c..408b44558 100644 --- a/ovn-ic-nb.xml +++ b/ovn-ic-nb.xml @@ -65,6 +65,29 @@ options are described individually below. + +

+ Upper bound, in milliseconds, on how long ovn-ic + postpones the next run of its incremental-processing engine. + After each engine run, the next run is delayed by the duration of + the run that just finished, capped by this value. This coalesces + bursts of change-driven runs into fewer, larger runs, trading a + small amount of latency for reduced CPU usage when the databases + are changing rapidly. +

+ +

+ This throttling only applies to the natural, change-driven + incremental path. A forced full recompute is never delayed and + always runs immediately. +

+ +

+ The default value is 0, which disables the backoff so + each run is scheduled as soon as there is work to do. +

+
+

The inactivity probe interval of the connection to the OVN IC From patchwork Wed Sep 2 21:17:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297040 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=NPRqDNde; 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 4hZwbM4SdSz1y03 for ; Thu, 03 Sep 2026 07:19:27 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 3862E403B9; Wed, 2 Sep 2026 21:19:26 +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 Bq9WJgFmLQ2J; Wed, 2 Sep 2026 21:19:16 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp2.osuosl.org 92B8940455 Authentication-Results: smtp2.osuosl.org; arc=fail smtp.remote-ip=140.211.9.56 ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383950; b=NHc6/pgTANsKecYVyZrA/sXpSJk5T0HbueAoZknFWLJlg/XVeMLt0NUKh67ZQjIllR3I /JhksGHFIPbBjLCETjEE9SvOwZgC14vlxN47o2+LRVoB/HYiwzJniChqH7RaGmNvYJuWM vjBYJ+3q3YE/MXSVRrvPFbEN7z0A1gb4/tUrmi8mRN2EwlQuV4sd1Vg9MTHFLCBJSiUv9 bUN83bEMTFDI2QqynRe/JHTZjwzf3czZ0PAt/A2zGbPPBfi//arA0GTUYVqiI12oUCDiz dZohvtcQPU19gvS2+WI9rQt1spFzoPxNmp39XokFZRZqBE6hi+FKZ76GEnyUPTxVehg== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383950; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=D2aGQJmdYQ1CgHJpFWDwhjLOSyyH3PIbLBfYdWf1vnQ=; b=ZMqdRb/sggVphZL/qkG5DmqK3VtHiufQAOnlXtLLMO6HR+pqn6ygCiZ8sckP+3SyrJN8 1K5okb77RtoEQ3BfAoEPPDCljU7cgvY9oSNSLbbT8wAUtq8oNHkVXndy50Qr4sZacbYhI A8k6RniwC4TgXuB308zOQvz+lC/rLIb4yE4ZCr/iKTg/1Q9GsUr9cCLBPEDy91Tz9tALq KePtai2hLmBnH5BDjigh3RSnZGVuhot3xxztjGG8xnUqi81a/ZdTm1eX1XR83P35lqo1P fotzDLXEevQ9KFeRED3ZYJPK1tlY5atZbdRl9ddlVbKTszf1BvcXl7e45q0pgq8spYA== ARC-Authentication-Results: i=2; smtp2.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=NPRqDNde; dmarc=pass header.from=magalu.cloud; arc=fail smtp.remote-ip=140.211.9.56 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= Authentication-Results: smtp2.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=NPRqDNde Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp2.osuosl.org (Postfix) with ESMTPS id 92B8940455; Wed, 2 Sep 2026 21:19:10 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 67D72C041A; Wed, 2 Sep 2026 21:19:10 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133]) by lists.linuxfoundation.org (Postfix) with ESMTP id BCF14C0D12 for ; Wed, 2 Sep 2026 21:19:05 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 8799C4037E for ; Wed, 2 Sep 2026 21:19:05 +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 QdGh45E0isk1 for ; Wed, 2 Sep 2026 21:19:00 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp2.osuosl.org 776D94037D Authentication-Results: smtp2.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::1135" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383938; b=Nf56ndMdRf1ggZtUnbNGAzZcvKU1RpCwE0EzX5bPXGPB/1RvkfrbWeEFce9ro6r0PHuM o6KAbr/pa2GkKn5mh0ul86Lc27wOWh8vRWgylHu8TcL0jUMBeuR1AGAtSEob9HU+NtdtJ eyP9fYqgZoc9X2Zw77mzu5TGYAkDSr2rlcH+yWwIAy/Yijtgz8slPboxvMs2qLq6H/2lj VxOCIVjQw50ifhA2zn5lxw7lz8zO7jjiW5McBduv52KmKyw4Y53TJfU5QgbNN26kWcUFB +O/K7yTjlQZTrP2UGiaJRvkdEdpISin/96M5dT590Xerhe02MHAxXP3W9NQIzVABtXQ== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383938; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=O3F1sQaiG9adK7Da0GBAlp0GW9ThwDYIth5D8/GjZBg=; b=YU0pug7YPE3vU3xKGquO0u0sfrjWcwetxCldEE3k9O79fcJYxnc8hfHNDEaIjtetVlR5 nM0XJ2iA66JUckwSiYnEHEB5ySlsYk6O33w5E0ZXA8VzARsz+Ba+tSfXYi8ZR8A7YN9FX PHRcxb7twHsrb6oeY6XjTK4JjERtZHWtPg/Wulkksm+kySfURbTeRNaGACytk/eLV9COy s3sIKLYWa/TfzAN3LG7DjTlU8KmmPTiE7psbvGQMvfbtbaqH4oY0QTnWzXCJxpZSDXs3N uUn2dsl15So2v4AdSLQy/kbt7B5I1/WwU/y8MXq71cRvkl2Dmp0TRKAAbsf1oIXgTHg== ARC-Authentication-Results: i=1; smtp2.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=NPRqDNde; arc=none smtp.remote-ip="2607:f8b0:4864:20::1135" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::1135; helo=mail-yw1-x1135.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp2.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Received: from mail-yw1-x1135.google.com (mail-yw1-x1135.google.com [IPv6:2607:f8b0:4864:20::1135]) by smtp2.osuosl.org (Postfix) with ESMTPS id 776D94037D for ; Wed, 2 Sep 2026 21:18:57 +0000 (UTC) Received: by mail-yw1-x1135.google.com with SMTP id 00721157ae682-7dbcb505578so21265077b3.3 for ; Wed, 02 Sep 2026 14:18:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383936; x=1788988736; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=O3F1sQaiG9adK7Da0GBAlp0GW9ThwDYIth5D8/GjZBg=; b=NPRqDNde6Nt6Yh5rOmyC6kcTB3fC4ME8vB3ygU9Z2WI2j7YXP3bBImQy7IkIZD2EOP b3ow9PG1wxOwFKaPBIIXmFXZoBbJGFjqX/URd30O7OJFB3XtOMH5McizqXhM1zUWg9cp A8aMxTXsoTetT/F7JiC1VcKVCh6+LbWfVgYsa8jw4TdpN95mjOJ63jFr5Clea9CMXzJ8 ZidrTQ1ebqWyeSjet/a+22Up1BHPeMWwSrNp4xsmg7TCpaLFs0CvnS6mNdXv+PbMEqc6 uH6Nlah7mDp2GlX82jrmNO6rLEoFUf/0JV8JL2BsoaKaAtFUqMF7Oxq/5Dlbb9SKFLQ+ rhCA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383936; x=1788988736; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=O3F1sQaiG9adK7Da0GBAlp0GW9ThwDYIth5D8/GjZBg=; b=Bmb6EmNZroh1ufXu+cv1piTmhswrofNJLjnhRMMDUNt9KxsIaWENbuZDk1HSAPBGGV ExMU8POMpZvjGUIB4IywK7EiNW8QK5tAiMaknW3g7HXiBWdwZDLCFfyw0ci9YFmFJ7fw FoV1y2km5rAm930547bhGcIxKFJI66Cf9/E6gZypbx+reFVnM3/rA66xW15WGlopi2nA ZNtZGMh4WYIKrnBhDy92pi33ERVnKMvH9uTi4/1P05Wd6dO64uEq43gvz3BAB3HR0X4/ rUBO+OsS/GBBATs0XZe6N9mGbcEOo0WEoli5hn86eHsnIOQ6+IOb3z2CswbAR9x0hVRu Uc6Q== X-Gm-Message-State: AFuF++l/KD1j8dFJ+BeEXi84EvQO+5/6IQ5b3JQqM/G3Nw2TIjE3uZse KUlcMrqxKTM1kuNyz5bXyRx57GR4P/4gLSimWjOVE9JDn5t2uItsed/y/FS1Xey1pG5vLS5qg9Q 184ay5/Y1EUBpUKSvyLoLuCBtrUlAGnIYTzrZ2HkWOMMwnoqqqdhwEuv4UQ+cABc= X-Gm-Gg: AYBFou0XNO65iaDCBglrZQ67c7h+Bn3Ec4j2yP3CwUz8Vdp6rVLn3zgZ0dDd/toAE2X F5PUa7DbbN/XUoeTQW0a9NAd6Oa2Pb47317/DEsYRDGzcTRKTnHEALceQq+lrUVU1m8BKdJrtu7 4oApEEASnmCxoTYWlbK0QYelDQS5Bw2hnK68EMVwpRe62sLr71MUBZt115Zgyb2MqwmG8fTIinc 0TV/R9J4O+5735Pv+BKTFY8MDZ0mhf3GkoVDftDn0Ce1tSMkjTNhGU/CAUZIl8SrmfgGQRg7aRG 9MVa9K/0DdwBBNyjqPOSCvB9A1nyARx/pjzS/90WVTVYA0AoklMh33rHzV8uKgANCr6ypgKhtHB TAFkL8FMVElJ/DMrodRxuBTeV7EnoI5eZ3Hf2DPp/4TiT4vLF429nkhMiBtvyCgJKLtACE9f23m UA6cL8g2rlO8h84PoMjx01Ak0i7VMdOaa/4uESJZsyLIXZqVN32kjtfxhI0DXegJyLfmHvmO2A6 AAQwBazIdlLCSapDNq4tzeZdHGmteIfgIk/MMcF8L8= X-Received: by 2002:a05:690c:399:b0:85b:946f:129e with SMTP id 00721157ae682-86c54b1dc48mr32268377b3.33.1788383935578; Wed, 02 Sep 2026 14:18:55 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.18.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:18:54 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:37 -0300 Message-Id: <20260902211744.2159261-8-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 07/14] ic: Add the en_tunnel_key node. 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: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Replace the en_dp_enum engine node with a new en_tunnel_key node that is the single owner of IC-SB Datapath_Binding creation, tunnel-key allocation, VXLAN-range refresh and garbage collection, for both transit switches and transit routers. en_dp_enum kept a datapath map and tunnel-key allocator that en_ts and en_tr mutated (cloning it first, and still risking dangling pointers when they deleted bindings it referenced). Now en_ts and en_tr build their own transit-switch/router datapath maps each run and only maintain the AZ NB mirrors; concentrating tunnel-key allocation in en_tunnel_key keeps the keys globally unique across both datapath types without any node mutating another node's data. This addresses the review of the en_dp_enum node. Signed-off-by: Paulo Guilherme Silva --- ic/automake.mk | 4 +- ic/en-dp-enum.c | 68 ------- ic/en-dp-enum.h | 44 ----- ic/en-tr.c | 90 ++++++++- ic/en-ts.c | 34 +++- ic/en-tunnel-key.c | 449 +++++++++++++++++++++++++++++++++++++++++++++ ic/en-tunnel-key.h | 60 ++++++ ic/inc-proc-ic.c | 64 ++++--- ic/ovn-ic.c | 341 ++++++++++++++-------------------- ic/ovn-ic.h | 36 +++- 10 files changed, 827 insertions(+), 363 deletions(-) delete mode 100644 ic/en-dp-enum.c delete mode 100644 ic/en-dp-enum.h create mode 100644 ic/en-tunnel-key.c create mode 100644 ic/en-tunnel-key.h diff --git a/ic/automake.mk b/ic/automake.mk index b730fc578..82338d5dd 100644 --- a/ic/automake.mk +++ b/ic/automake.mk @@ -6,14 +6,14 @@ ic_ovn_ic_SOURCES = ic/ovn-ic.c \ ic/en-ic.h \ ic/en-az.c \ ic/en-az.h \ - ic/en-dp-enum.c \ - ic/en-dp-enum.h \ ic/en-gateway.c \ ic/en-gateway.h \ ic/en-ts.c \ ic/en-ts.h \ ic/en-tr.c \ ic/en-tr.h \ + ic/en-tunnel-key.c \ + ic/en-tunnel-key.h \ ic/en-port-binding.c \ ic/en-port-binding.h \ ic/en-route.c \ diff --git a/ic/en-dp-enum.c b/ic/en-dp-enum.c deleted file mode 100644 index fd447ab52..000000000 --- a/ic/en-dp-enum.c +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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-dp-enum.h" -#include "lib/inc-proc-eng.h" -#include "lib/ovn-util.h" -#include "openvswitch/hmap.h" -#include "openvswitch/shash.h" -#include "openvswitch/vlog.h" -#include "ovn-ic.h" - -VLOG_DEFINE_THIS_MODULE(en_ic_dp_enum); - -enum engine_node_state -en_dp_enum_run(struct engine_node *node OVS_UNUSED, void *data_) -{ - const struct engine_context *eng_ctx = engine_get_context(); - struct ic_context *ctx = eng_ctx->client_ctx; - struct ed_type_dp_enum *data = data_; - - /* Clear the previous iteration's state and rebuild from the current IC-SB - * datapath bindings. */ - ovn_destroy_tnlids(&data->dp_tnlids); - hmap_init(&data->dp_tnlids); - shash_clear(&data->isb_ts_dps); - shash_clear(&data->isb_tr_dps); - - enumerate_datapaths(ctx, &data->dp_tnlids, &data->isb_ts_dps, - &data->isb_tr_dps); - - return EN_UPDATED; -} - -void * -en_dp_enum_init(struct engine_node *node OVS_UNUSED, - struct engine_arg *arg OVS_UNUSED) -{ - struct ed_type_dp_enum *data = xzalloc(sizeof *data); - - hmap_init(&data->dp_tnlids); - shash_init(&data->isb_ts_dps); - shash_init(&data->isb_tr_dps); - - return data; -} - -void -en_dp_enum_cleanup(void *data_) -{ - struct ed_type_dp_enum *data = data_; - - ovn_destroy_tnlids(&data->dp_tnlids); - shash_destroy(&data->isb_ts_dps); - shash_destroy(&data->isb_tr_dps); -} diff --git a/ic/en-dp-enum.h b/ic/en-dp-enum.h deleted file mode 100644 index ef925ea68..000000000 --- a/ic/en-dp-enum.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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_IC_DP_ENUM_H -#define EN_IC_DP_ENUM_H 1 - -#include "lib/inc-proc-eng.h" -#include "openvswitch/hmap.h" -#include "openvswitch/shash.h" - -/* Data maintained by the "dp_enum" engine node: the result of enumerating the - * IC-SB datapath bindings. It is the explicit, engine-visible form of the - * state that used to be built on the stack by enumerate_datapaths() inside - * ovn_db_run(). The transit-switch (en_ts) and transit-router (en_tr) nodes - * consume this data instead of rebuilding it themselves, which also keeps the - * shared 'dp_tnlids' tunnel-key allocator consistent between them. */ -struct ed_type_dp_enum { - /* Set of tunnel keys already in use by IC-SB datapath bindings. Holds - * 'struct tnlid_node *' entries owned by this node. */ - struct hmap dp_tnlids; - /* Transit-switch datapath bindings, keyed by transit switch name. Values - * are 'const struct icsbrec_datapath_binding *' owned by the IDL. */ - struct shash isb_ts_dps; - /* Transit-router datapath bindings, keyed by NB IC UUID string. Values - * are 'const struct icsbrec_datapath_binding *' owned by the IDL. */ - struct shash isb_tr_dps; -}; - -enum engine_node_state en_dp_enum_run(struct engine_node *node, void *data); -void *en_dp_enum_init(struct engine_node *node, struct engine_arg *arg); -void en_dp_enum_cleanup(void *data); - -#endif /* EN_IC_DP_ENUM_H */ diff --git a/ic/en-tr.c b/ic/en-tr.c index c3b1b60c6..9c1eed3de 100644 --- a/ic/en-tr.c +++ b/ic/en-tr.c @@ -14,21 +14,100 @@ #include -#include "en-dp-enum.h" -#include "en-tr.h" #include "en-az.h" +#include "en-tr.h" #include "lib/inc-proc-eng.h" +#include "lib/ovn-ic-nb-idl.h" +#include "lib/ovn-ic-sb-idl.h" +#include "lib/ovn-nb-idl.h" +#include "openvswitch/shash.h" #include "openvswitch/vlog.h" #include "ovn-ic.h" +#include "ovsdb-idl.h" +#include "smap.h" +#include "util.h" +#include "uuid.h" VLOG_DEFINE_THIS_MODULE(en_ic_tr); +/* Builds 'isb_tr_dps': the committed IC-SB transit-router Datapath_Bindings + * keyed by NB IC UUID string. Read-only; used to copy each committed tunnel + * key into its NB Logical_Router mirror's options:requested-tnl-key. This is + * en_tr's own local data, rebuilt each run and never mutated by another node. + */ +static void +collect_tr_datapaths(struct ic_context *ctx, struct shash *isb_tr_dps) +{ + shash_init(isb_tr_dps); + + const struct icsbrec_datapath_binding *isb_dp; + ICSBREC_DATAPATH_BINDING_FOR_EACH (isb_dp, ctx->ovnisb_idl) { + if (ic_dp_get_type(isb_dp) == IC_ROUTER) { + if (!isb_dp->nb_ic_uuid) { + /* Not inserted by ovn-ic: nothing to reconcile. */ + continue; + } + char *uuid_str = uuid_to_string(isb_dp->nb_ic_uuid); + shash_add(isb_tr_dps, uuid_str, isb_dp); + free(uuid_str); + } + } +} + +/* Syncs transit routers to their AZ NB Logical_Router mirrors: creates the + * mirror if missing, and copies each committed IC-SB Datapath_Binding tunnel + * key into options:requested-tnl-key. IC-SB Datapath_Binding creation/keying/ + * GC is owned by en_tunnel_key. */ +static void +tr_run(struct ic_context *ctx, struct shash *isb_tr_dps) +{ + if (!ctx->ovnnb_txn) { + return; + } + + const struct nbrec_logical_router *lr; + struct shash nb_tres = SHASH_INITIALIZER(&nb_tres); + NBREC_LOGICAL_ROUTER_FOR_EACH (lr, ctx->ovnnb_idl) { + const char *tr_name = smap_get(&lr->options, "interconn-tr"); + if (tr_name) { + shash_add(&nb_tres, tr_name, lr); + } + } + + const struct icnbrec_transit_router *tr; + ICNBREC_TRANSIT_ROUTER_FOR_EACH (tr, ctx->ovninb_idl) { + lr = shash_find_and_delete(&nb_tres, tr->name); + if (!lr) { + lr = nbrec_logical_router_insert(ctx->ovnnb_txn); + nbrec_logical_router_set_name(lr, tr->name); + nbrec_logical_router_update_options_setkey( + lr, "interconn-tr", tr->name); + } + char *uuid_str = uuid_to_string(&tr->header_.uuid); + const struct icsbrec_datapath_binding *isb_dp = + shash_find_data(isb_tr_dps, uuid_str); + free(uuid_str); + + if (isb_dp) { + char *tnl_key_str = xasprintf("%"PRId64, isb_dp->tunnel_key); + nbrec_logical_router_update_options_setkey( + lr, "requested-tnl-key", tnl_key_str); + free(tnl_key_str); + } + } + + struct shash_node *node; + SHASH_FOR_EACH (node, &nb_tres) { + nbrec_logical_router_delete(node->data); + } + shash_destroy(&nb_tres); +} + enum engine_node_state en_tr_run(struct engine_node *node, void *data OVS_UNUSED) { const struct engine_context *eng_ctx = engine_get_context(); struct ic_context *ctx = eng_ctx->client_ctx; - struct ed_type_dp_enum *dp = engine_get_input_data("dp_enum", node); const struct ed_type_az *az = engine_get_input_data("az", node); /* runned_az is resolved by the upstream en_az node. Without an AZ there @@ -37,7 +116,10 @@ en_tr_run(struct engine_node *node, void *data OVS_UNUSED) return EN_UNCHANGED; } - tr_run(ctx, &dp->dp_tnlids, &dp->isb_tr_dps); + struct shash isb_tr_dps; + collect_tr_datapaths(ctx, &isb_tr_dps); + tr_run(ctx, &isb_tr_dps); + shash_destroy(&isb_tr_dps); return EN_UPDATED; } diff --git a/ic/en-ts.c b/ic/en-ts.c index e4ec2a0b7..d983e6bb0 100644 --- a/ic/en-ts.c +++ b/ic/en-ts.c @@ -14,21 +14,44 @@ #include -#include "en-dp-enum.h" -#include "en-ts.h" #include "en-az.h" +#include "en-ts.h" #include "lib/inc-proc-eng.h" +#include "lib/ovn-ic-nb-idl.h" +#include "lib/ovn-ic-sb-idl.h" +#include "lib/ovn-nb-idl.h" +#include "openvswitch/shash.h" #include "openvswitch/vlog.h" +#include "ovsdb-idl.h" +#include "smap.h" +#include "sset.h" #include "ovn-ic.h" VLOG_DEFINE_THIS_MODULE(en_ic_ts); +/* Builds 'isb_ts_dps': the committed IC-SB transit-switch Datapath_Bindings + * keyed by transit-switch name. Read-only; ts_sync_scope() copies each + * committed tunnel key into its NB mirror's requested-tnl-key. This is + * en_ts's own local data, rebuilt each run and never mutated by another + * node. */ +static void +collect_ts_datapaths(struct ic_context *ctx, struct shash *isb_ts_dps) +{ + shash_init(isb_ts_dps); + + const struct icsbrec_datapath_binding *isb_dp; + ICSBREC_DATAPATH_BINDING_FOR_EACH (isb_dp, ctx->ovnisb_idl) { + if (ic_dp_get_type(isb_dp) == IC_SWITCH) { + shash_add(isb_ts_dps, isb_dp->transit_switch, isb_dp); + } + } +} + enum engine_node_state en_ts_run(struct engine_node *node, void *data OVS_UNUSED) { const struct engine_context *eng_ctx = engine_get_context(); struct ic_context *ctx = eng_ctx->client_ctx; - struct ed_type_dp_enum *dp = engine_get_input_data("dp_enum", node); const struct ed_type_az *az = engine_get_input_data("az", node); /* runned_az is resolved by the upstream en_az node. Without an AZ there @@ -37,7 +60,10 @@ en_ts_run(struct engine_node *node, void *data OVS_UNUSED) return EN_UNCHANGED; } - ts_run(ctx, &dp->dp_tnlids, &dp->isb_ts_dps); + struct shash isb_ts_dps; + collect_ts_datapaths(ctx, &isb_ts_dps); + ts_sync_scope(ctx, &isb_ts_dps, NULL); + shash_destroy(&isb_ts_dps); return EN_UPDATED; } diff --git a/ic/en-tunnel-key.c b/ic/en-tunnel-key.c new file mode 100644 index 000000000..6e149554a --- /dev/null +++ b/ic/en-tunnel-key.c @@ -0,0 +1,449 @@ +/* + * 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-tunnel-key.h" +#include "lib/inc-proc-eng.h" +#include "lib/ovn-ic-nb-idl.h" +#include "lib/ovn-ic-sb-idl.h" +#include "lib/ovn-nb-idl.h" +#include "lib/ovn-util.h" +#include "openvswitch/hmap.h" +#include "openvswitch/shash.h" +#include "openvswitch/vlog.h" +#include "ovn-ic.h" +#include "ovsdb-idl.h" +#include "smap.h" +#include "sset.h" +#include "util.h" +#include "uuid.h" + +VLOG_DEFINE_THIS_MODULE(en_ic_tunnel_key); + +/* Returns true if 'key' falls in the global VXLAN datapath tunnel-key range. +*/ +static bool +dp_key_in_vxlan_range(int64_t key) +{ + return key >= OVN_MIN_DP_VXLAN_KEY_GLOBAL && + key <= OVN_MAX_DP_VXLAN_KEY_GLOBAL; +} + +/* Rebuilds the tunnel-key allocator 'data->dp_tnlids' and, in 'isb_ts_dps' + * (keyed by transit-switch name) / 'isb_tr_dps' (keyed by NB IC UUID string), + * every committed IC-SB Datapath_Binding. Read from the locked IC-SB IDL, + * which is the same IDL this node writes to and reads back from, so no + * locked/unlocked bridging is needed: the tracked changes (from the unlocked + * IDL) are used only to scope which entities to reconcile, never to mutate + * state directly. */ +static void +tunnel_key_build_state(struct ic_context *ctx, struct ed_type_tunnel_key *data, + struct shash *isb_ts_dps, struct shash *isb_tr_dps) +{ + ovn_destroy_tnlids(&data->dp_tnlids); + hmap_init(&data->dp_tnlids); + shash_init(isb_ts_dps); + shash_init(isb_tr_dps); + + const struct icsbrec_datapath_binding *isb_dp; + ICSBREC_DATAPATH_BINDING_FOR_EACH (isb_dp, ctx->ovnisb_idl) { + ovn_add_tnlid(&data->dp_tnlids, isb_dp->tunnel_key); + if (ic_dp_get_type(isb_dp) == IC_ROUTER) { + if (!isb_dp->nb_ic_uuid) { + /* Not inserted by ovn-ic: nothing to reconcile. */ + continue; + } + char *uuid_str = uuid_to_string(isb_dp->nb_ic_uuid); + shash_add(isb_tr_dps, uuid_str, isb_dp); + free(uuid_str); + } else { + shash_add(isb_ts_dps, isb_dp->transit_switch, isb_dp); + } + } +} + +/* Publishes 'dp_key' to the AZ NB Logical_Switch mirror of transit switch + * 'ts_name' ('nb_ts_mirrors', keyed by name), when the mirror exists. */ +static void +tunnel_key_publish_ts_mirror(struct shash *nb_ts_mirrors, const char *ts_name, + int64_t dp_key) +{ + const struct nbrec_logical_switch *ls = + shash_find_data(nb_ts_mirrors, ts_name); + if (!ls) { + return; + } + + char *tnl_key_str = xasprintf("%"PRId64, dp_key); + nbrec_logical_switch_update_other_config_setkey(ls, "requested-tnl-key", + tnl_key_str); + free(tnl_key_str); +} + +/* Reconciles one transit switch 'ts's IC-SB Datapath_Binding: creates it (with + * a fresh globally-unique tunnel key) when 'isb_dp' is NULL, reallocates its + * key if the encap (VXLAN) mode changed its range, and back-fills the type / + * nb_ic_uuid columns. Whenever a key is allocated - on creation and on + * reallocation alike - it is published to the AZ NB Logical_Switch mirror in + * this same iteration. Leaving the mirror to a later run is not safe: the + * committed key only reaches en_ts on a following engine run, and nothing + * guarantees one happens after the IC-SB transaction lands, so the mirror can + * keep a key from the wrong range indefinitely. */ +static void +tunnel_key_reconcile_ts(struct ic_context *ctx, + struct ed_type_tunnel_key *data, + const struct icnbrec_transit_switch *ts, + const struct icsbrec_datapath_binding *isb_dp, + struct shash *nb_ts_mirrors, bool vxlan_mode) +{ + if (!isb_dp) { + int64_t dp_key = allocate_dp_key(&data->dp_tnlids, vxlan_mode, + "transit switch datapath"); + if (!dp_key) { + return; + } + struct icsbrec_datapath_binding *new_dp = + icsbrec_datapath_binding_insert(ctx->ovnisb_txn); + icsbrec_datapath_binding_set_transit_switch(new_dp, ts->name); + icsbrec_datapath_binding_set_tunnel_key(new_dp, dp_key); + icsbrec_datapath_binding_set_type(new_dp, "transit-switch"); + icsbrec_datapath_binding_set_nb_ic_uuid(new_dp, &ts->header_.uuid, 1); + + tunnel_key_publish_ts_mirror(nb_ts_mirrors, ts->name, dp_key); + return; + } + + if (dp_key_in_vxlan_range(isb_dp->tunnel_key) != vxlan_mode) { + int64_t dp_key = allocate_dp_key(&data->dp_tnlids, vxlan_mode, + "transit switch datapath"); + if (dp_key) { + icsbrec_datapath_binding_set_tunnel_key(isb_dp, dp_key); + tunnel_key_publish_ts_mirror(nb_ts_mirrors, ts->name, dp_key); + } + } + if (!isb_dp->type) { + icsbrec_datapath_binding_set_type(isb_dp, "transit-switch"); + } + if (!isb_dp->nb_ic_uuid) { + icsbrec_datapath_binding_set_nb_ic_uuid(isb_dp, &ts->header_.uuid, 1); + } +} + +/* Reconciles one transit router 'tr's IC-SB Datapath_Binding: creates it (with + * a fresh globally-unique tunnel key, always from the non-VXLAN range) when + * 'isb_dp' is NULL. Its key reaches the NB Logical_Router mirror on a + * follow-up iteration (en_tr copies the committed key back). */ +static void +tunnel_key_reconcile_tr(struct ic_context *ctx, + struct ed_type_tunnel_key *data, + const struct icnbrec_transit_router *tr, + const struct icsbrec_datapath_binding *isb_dp) +{ + if (isb_dp) { + return; + } + + int64_t dp_key = allocate_dp_key(&data->dp_tnlids, false, + "transit router datapath"); + if (!dp_key) { + return; + } + struct icsbrec_datapath_binding *new_dp = + icsbrec_datapath_binding_insert(ctx->ovnisb_txn); + icsbrec_datapath_binding_set_tunnel_key(new_dp, dp_key); + icsbrec_datapath_binding_set_nb_ic_uuid(new_dp, &tr->header_.uuid, 1); + icsbrec_datapath_binding_set_type(new_dp, "transit-router"); +} + +/* Builds 'nb_ts_mirrors' (keyed by transit-switch name) from the AZ NB + * Logical_Switch mirrors. A table walk (not the by-name index) is used so it + * sees the mirrors en_ts just inserted in this transaction, which lets a + * brand-new binding publish its key to the mirror in the same iteration. */ +static void +tunnel_key_collect_nb_mirrors(struct ic_context *ctx, + struct shash *nb_ts_mirrors) +{ + shash_init(nb_ts_mirrors); + if (!ctx->ovnnb_txn) { + return; + } + const struct nbrec_logical_switch *ls; + NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->ovnnb_idl) { + const char *ts_name = smap_get(&ls->other_config, "interconn-ts"); + if (ts_name && !shash_find(nb_ts_mirrors, ts_name)) { + shash_add(nb_ts_mirrors, ts_name, + CONST_CAST(struct nbrec_logical_switch *, ls)); + } + } +} + +enum engine_node_state +en_tunnel_key_run(struct engine_node *node OVS_UNUSED, void *data_) +{ + const struct engine_context *eng_ctx = engine_get_context(); + struct ic_context *ctx = eng_ctx->client_ctx; + struct ed_type_tunnel_key *data = data_; + + struct shash isb_ts_dps, isb_tr_dps; + tunnel_key_build_state(ctx, data, &isb_ts_dps, &isb_tr_dps); + + /* The IC-SB Datapath_Binding table is the leader's to write; only touch it + * (create/refresh/delete) while holding the IC-SB lock. */ + if (ctx->ovnisb_txn && is_az_leader(ctx->ovnisb_txn)) { + bool vxlan_mode = ic_ts_compute_vxlan_mode(ctx); + struct shash nb_ts_mirrors; + tunnel_key_collect_nb_mirrors(ctx, &nb_ts_mirrors); + + const struct icnbrec_transit_switch *ts; + ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) { + const struct icsbrec_datapath_binding *isb_dp = + shash_find_and_delete(&isb_ts_dps, ts->name); + tunnel_key_reconcile_ts(ctx, data, ts, isb_dp, &nb_ts_mirrors, + vxlan_mode); + } + + const struct icnbrec_transit_router *tr; + ICNBREC_TRANSIT_ROUTER_FOR_EACH (tr, ctx->ovninb_idl) { + char *uuid_str = uuid_to_string(&tr->header_.uuid); + const struct icsbrec_datapath_binding *isb_dp = + shash_find_and_delete(&isb_tr_dps, uuid_str); + free(uuid_str); + tunnel_key_reconcile_tr(ctx, data, tr, isb_dp); + } + + /* Delete IC-SB Datapath_Bindings whose IC-NB entity is gone. */ + struct shash_node *sn; + SHASH_FOR_EACH (sn, &isb_ts_dps) { + icsbrec_datapath_binding_delete(sn->data); + } + SHASH_FOR_EACH (sn, &isb_tr_dps) { + icsbrec_datapath_binding_delete(sn->data); + } + + shash_destroy(&nb_ts_mirrors); + } + + shash_destroy(&isb_ts_dps); + shash_destroy(&isb_tr_dps); + + return EN_UPDATED; +} + +/* Reconciles only the transit switches named in 'ts_scope' and the transit + * routers whose NB IC UUID string is in 'tr_scope': creates a missing binding, + * refreshes a mis-ranged key, and deletes a binding whose IC-NB entity is + * gone. Returns EN_UNHANDLED (forcing a recompute) when the IC-SB + * lock/transaction needed to write the binding is not available yet. */ +static enum engine_input_handler_result +tunnel_key_sync_scope(struct ic_context *ctx, struct ed_type_tunnel_key *data, + struct sset *ts_scope, struct sset *tr_scope) +{ + if (sset_is_empty(ts_scope) && sset_is_empty(tr_scope)) { + return EN_HANDLED_UNCHANGED; + } + if (!ctx->ovnisb_txn || !is_az_leader(ctx->ovnisb_txn)) { + return EN_UNHANDLED; + } + + bool vxlan_mode = ic_ts_compute_vxlan_mode(ctx); + struct shash isb_ts_dps, isb_tr_dps; + tunnel_key_build_state(ctx, data, &isb_ts_dps, &isb_tr_dps); + struct shash nb_ts_mirrors; + tunnel_key_collect_nb_mirrors(ctx, &nb_ts_mirrors); + + /* IC-NB Transit_Switch has no generated get_for_name(); map name -> row so + * an in-scope transit switch can be looked up. */ + struct shash icnb_ts = SHASH_INITIALIZER(&icnb_ts); + if (!sset_is_empty(ts_scope)) { + const struct icnbrec_transit_switch *ts; + ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) { + if (!shash_find(&icnb_ts, ts->name)) { + shash_add(&icnb_ts, ts->name, + CONST_CAST(struct icnbrec_transit_switch *, ts)); + } + } + } + + const char *name; + SSET_FOR_EACH (name, ts_scope) { + const struct icnbrec_transit_switch *ts = + shash_find_data(&icnb_ts, name); + const struct icsbrec_datapath_binding *isb_dp = + shash_find_data(&isb_ts_dps, name); + if (ts) { + tunnel_key_reconcile_ts(ctx, data, ts, isb_dp, &nb_ts_mirrors, + vxlan_mode); + } else if (isb_dp) { + icsbrec_datapath_binding_delete(isb_dp); + } + } + shash_destroy(&icnb_ts); + + const char *uuid_str; + SSET_FOR_EACH (uuid_str, tr_scope) { + struct uuid tr_uuid; + if (!uuid_from_string(&tr_uuid, uuid_str)) { + continue; + } + const struct icnbrec_transit_router *tr = + icnbrec_transit_router_get_for_uuid(ctx->ovninb_idl, &tr_uuid); + const struct icsbrec_datapath_binding *isb_dp = + shash_find_data(&isb_tr_dps, uuid_str); + if (tr) { + tunnel_key_reconcile_tr(ctx, data, tr, isb_dp); + } else if (isb_dp) { + icsbrec_datapath_binding_delete(isb_dp); + } + } + + shash_destroy(&nb_ts_mirrors); + shash_destroy(&isb_ts_dps); + shash_destroy(&isb_tr_dps); + + return EN_HANDLED_UPDATED; +} + +/* IC-NB Transit_Switch: a new transit switch needs its IC-SB Datapath_Binding + * created; a deleted one needs it garbage-collected. Both are scoped by name + * and reconciled without a full recompute. */ +enum engine_input_handler_result +en_tunnel_key_icnb_transit_switch_handler(struct engine_node *node, + void *data_) +{ + struct ed_type_tunnel_key *data = data_; + struct ic_context *ctx = engine_get_context()->client_ctx; + const struct icnbrec_transit_switch_table *tbl = + EN_OVSDB_GET(engine_get_input("ICNB_transit_switch", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct icnbrec_transit_switch *ts; + ICNBREC_TRANSIT_SWITCH_TABLE_FOR_EACH_TRACKED (ts, tbl) { + sset_add(&ts_scope, ts->name); + } + + enum engine_input_handler_result ret = + tunnel_key_sync_scope(ctx, data, &ts_scope, &tr_scope); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* IC-NB Transit_Router: analogous to the transit-switch handler, scoped by the + * transit router's NB IC UUID (the binding key). */ +enum engine_input_handler_result +en_tunnel_key_icnb_transit_router_handler(struct engine_node *node, + void *data_) +{ + struct ed_type_tunnel_key *data = data_; + struct ic_context *ctx = engine_get_context()->client_ctx; + const struct icnbrec_transit_router_table *tbl = + EN_OVSDB_GET(engine_get_input("ICNB_transit_router", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct icnbrec_transit_router *tr; + ICNBREC_TRANSIT_ROUTER_TABLE_FOR_EACH_TRACKED (tr, tbl) { + char *uuid_str = uuid_to_string(&tr->header_.uuid); + sset_add(&tr_scope, uuid_str); + free(uuid_str); + } + + enum engine_input_handler_result ret = + tunnel_key_sync_scope(ctx, data, &ts_scope, &tr_scope); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* IC-SB Datapath_Binding: react only to a binding *deletion* by re-creating it + * for the still-present IC-NB entity (self-healing after an out-of-band + * delete). Inserts and modifies are this node's own writes (or already + * consistent), so they are a no-op here - the requested-tnl-key follow-up is + * driven by en_ts/en_tr's own handlers on this same table. */ +enum engine_input_handler_result +en_tunnel_key_icsb_datapath_binding_handler(struct engine_node *node, + void *data_) +{ + struct ed_type_tunnel_key *data = data_; + struct ic_context *ctx = engine_get_context()->client_ctx; + const struct icsbrec_datapath_binding_table *tbl = + EN_OVSDB_GET(engine_get_input("ICSB_datapath_binding", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct icsbrec_datapath_binding *isb_dp; + ICSBREC_DATAPATH_BINDING_TABLE_FOR_EACH_TRACKED (isb_dp, tbl) { + if (!icsbrec_datapath_binding_is_deleted(isb_dp)) { + continue; + } + if (ic_dp_get_type(isb_dp) == IC_ROUTER) { + if (!isb_dp->nb_ic_uuid) { + /* Not inserted by ovn-ic: nothing to reconcile. */ + continue; + } + char *uuid_str = uuid_to_string(isb_dp->nb_ic_uuid); + sset_add(&tr_scope, uuid_str); + free(uuid_str); + } else { + sset_add(&ts_scope, isb_dp->transit_switch); + } + } + + enum engine_input_handler_result ret = + tunnel_key_sync_scope(ctx, data, &ts_scope, &tr_scope); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* Shared change handler for the IC-NB Global input: ignores + * nb_ic_cfg/sb_ic_cfg sequence-number-only changes, and forces a recompute + * only when the row is created/deleted or its 'options' (e.g. vxlan_mode) + * change. */ +enum engine_input_handler_result +ic_nb_global_options_handler(struct engine_node *node, void *data OVS_UNUSED) +{ + const struct icnbrec_ic_nb_global_table *tbl = + EN_OVSDB_GET(engine_get_input("ICNB_ic_nb_global", node)); + const struct icnbrec_ic_nb_global *icnb; + + ICNBREC_IC_NB_GLOBAL_TABLE_FOR_EACH_TRACKED (icnb, tbl) { + if (icnbrec_ic_nb_global_is_new(icnb) || + icnbrec_ic_nb_global_is_deleted(icnb) || + ovsdb_idl_track_is_updated(&icnb->header_, + &icnbrec_ic_nb_global_col_options)) { + return EN_UNHANDLED; + } + } + + return EN_HANDLED_UNCHANGED; +} + +void * +en_tunnel_key_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + struct ed_type_tunnel_key *data = xzalloc(sizeof *data); + + hmap_init(&data->dp_tnlids); + + return data; +} + +void +en_tunnel_key_cleanup(void *data_) +{ + struct ed_type_tunnel_key *data = data_; + + ovn_destroy_tnlids(&data->dp_tnlids); +} diff --git a/ic/en-tunnel-key.h b/ic/en-tunnel-key.h new file mode 100644 index 000000000..d8f252cb7 --- /dev/null +++ b/ic/en-tunnel-key.h @@ -0,0 +1,60 @@ +/* + * 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_IC_TUNNEL_KEY_H +#define EN_IC_TUNNEL_KEY_H 1 + +#include "lib/inc-proc-eng.h" +#include "openvswitch/hmap.h" + +/* Data maintained by the "tunnel_key" engine node: the set of datapath tunnel + * keys currently in use by IC-SB Datapath_Bindings. This node is the single + * owner of datapath tunnel-key allocation: it runs after en_ts and en_tr + * (which only maintain the AZ NB Logical_Switch/Router mirrors) and + * reconciles every transit switch/router's IC-SB Datapath_Binding, allocating + * a globally unique tunnel key for each new one. Concentrating allocation in + * one node keeps the keys unique across both datapath types without any node + * mutating another node's data. */ +struct ed_type_tunnel_key { + /* Set of tunnel keys already in use by IC-SB Datapath_Bindings. Holds + * 'struct tnlid_node *' entries owned by this node. */ + struct hmap dp_tnlids; +}; + +enum engine_node_state en_tunnel_key_run(struct engine_node *node, void *data); +void *en_tunnel_key_init(struct engine_node *node, struct engine_arg *arg); +void en_tunnel_key_cleanup(void *data); + +/* Scoped incremental handlers: create/refresh/GC the IC-SB Datapath_Binding of + * only the transit switches/routers whose IC-NB row or IC-SB binding changed, + * without a full recompute. */ +enum engine_input_handler_result +en_tunnel_key_icnb_transit_switch_handler(struct engine_node *node, + void *data); +enum engine_input_handler_result +en_tunnel_key_icnb_transit_router_handler(struct engine_node *node, + void *data); +enum engine_input_handler_result +en_tunnel_key_icsb_datapath_binding_handler(struct engine_node *node, + void *data); + +/* Shared change handler for the IC-NB Global input of nodes that only care + * about its 'options' (e.g. vxlan_mode), not the nb_ic_cfg/sb_ic_cfg + * sequence numbers that the sequence-number protocol bumps on every change. + * Named without a node prefix because it is shared by en_tunnel_key and + * en_ts. */ +enum engine_input_handler_result +ic_nb_global_options_handler(struct engine_node *node, void *data); + +#endif /* EN_IC_TUNNEL_KEY_H */ diff --git a/ic/inc-proc-ic.c b/ic/inc-proc-ic.c index 66958dbf0..64aef2c82 100644 --- a/ic/inc-proc-ic.c +++ b/ic/inc-proc-ic.c @@ -28,10 +28,10 @@ #include "inc-proc-ic.h" #include "en-ic.h" #include "en-az.h" -#include "en-dp-enum.h" #include "en-gateway.h" #include "en-ts.h" #include "en-tr.h" +#include "en-tunnel-key.h" #include "en-port-binding.h" #include "en-route.h" #include "en-service-monitor.h" @@ -175,10 +175,10 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_ic); /* Define engine nodes for other nodes. They should be defined as static to * avoid sparse errors. */ static ENGINE_NODE(az); -static ENGINE_NODE(dp_enum); static ENGINE_NODE(gateway); static ENGINE_NODE(ts); static ENGINE_NODE(tr); +static ENGINE_NODE(tunnel_key); static ENGINE_NODE(port_binding); static ENGINE_NODE(route); static ENGINE_NODE(service_monitor); @@ -201,21 +201,6 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, * splitting the monolithic ovn_db_run() into independently-gated nodes. * Change handlers are added incrementally in a later step. */ - /* en_dp_enum: enumerate IC-SB datapath bindings (tunnel-key allocator and - * transit switch/router datapath maps shared by en_ts and en_tr). - * - * en_ts and en_tr allocate datapath tunnel keys from the shared - * 'dp_tnlids' set owned by this node, mutating it during their run. To - * keep that allocator correct, en_dp_enum must rebuild it from scratch in - * the same iteration as any allocation. It therefore depends not only on - * the IC-SB datapath bindings themselves, but also on every input that can - * cause en_ts/en_tr to allocate a key: a new transit switch or router, or - * a change of vxlan_mode (which forces a tunnel-key refresh). */ - engine_add_input(&en_dp_enum, &en_icsb_datapath_binding, NULL); - engine_add_input(&en_dp_enum, &en_icnb_transit_switch, NULL); - engine_add_input(&en_dp_enum, &en_icnb_transit_router, NULL); - engine_add_input(&en_dp_enum, &en_icnb_ic_nb_global, NULL); - /* en_gateway: sync gateways/chassis between SB and IC-SB. */ engine_add_input(&en_gateway, &en_az, NULL); engine_add_input(&en_gateway, &en_icsb_availability_zone, NULL); @@ -224,20 +209,49 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_gateway, &en_sb_chassis, NULL); engine_add_input(&en_gateway, &en_sb_encap, NULL); - /* en_ts: sync transit switches to NB and IC-SB datapath bindings. */ + /* en_ts: sync transit switches to their AZ NB Logical_Switch mirrors. + * en_ts builds its own transit-switch IC-SB Datapath_Binding map each run + * and only maintains the NB mirror; IC-SB Datapath_Binding creation/keying + * is owned by en_tunnel_key (downstream). */ engine_add_input(&en_ts, &en_az, NULL); - engine_add_input(&en_ts, &en_dp_enum, NULL); + engine_add_input(&en_ts, &en_icsb_datapath_binding, NULL); engine_add_input(&en_ts, &en_icnb_ic_nb_global, NULL); engine_add_input(&en_ts, &en_icnb_transit_switch, NULL); engine_add_input(&en_ts, &en_nb_logical_switch, NULL); engine_add_input(&en_ts, &en_icsb_encap, NULL); - /* en_tr: sync transit routers to NB and IC-SB datapath bindings. */ + /* en_tr: sync transit routers to their AZ NB Logical_Router mirrors. + * Like en_ts, IC-SB Datapath_Binding creation/keying is owned by + * en_tunnel_key. */ engine_add_input(&en_tr, &en_az, NULL); - engine_add_input(&en_tr, &en_dp_enum, NULL); + engine_add_input(&en_tr, &en_icsb_datapath_binding, NULL); engine_add_input(&en_tr, &en_icnb_transit_router, NULL); engine_add_input(&en_tr, &en_nb_logical_router, NULL); + /* en_tunnel_key: the single owner of IC-SB Datapath_Binding creation, + * tunnel-key allocation, VXLAN-range refresh and GC, for both transit + * switches and transit routers. Concentrating allocation in one node + * keeps the keys globally unique across both datapath types without any + * node mutating another's data. + * + * It is ordered after en_ts and en_tr (no-op edges) so the AZ NB mirrors + * exist before it publishes a brand-new binding's key to them (the + * anti-flap early publish in en_tunnel_key_run()). The IC-NB transit + * switch/router and IC-SB Datapath_Binding inputs drive create/GC; the + * IC-NB Global (vxlan_mode) and IC-SB Encap inputs drive the VXLAN-range + * refresh. */ + engine_add_input(&en_tunnel_key, &en_ts, engine_noop_handler); + engine_add_input(&en_tunnel_key, &en_tr, engine_noop_handler); + engine_add_input(&en_tunnel_key, &en_icsb_datapath_binding, + en_tunnel_key_icsb_datapath_binding_handler); + engine_add_input(&en_tunnel_key, &en_icnb_transit_switch, + en_tunnel_key_icnb_transit_switch_handler); + engine_add_input(&en_tunnel_key, &en_icnb_transit_router, + en_tunnel_key_icnb_transit_router_handler); + engine_add_input(&en_tunnel_key, &en_icnb_ic_nb_global, + ic_nb_global_options_handler); + engine_add_input(&en_tunnel_key, &en_icsb_encap, NULL); + /* en_port_binding: sync cross-AZ port bindings. */ engine_add_input(&en_port_binding, &en_az, NULL); engine_add_input(&en_port_binding, &en_icsb_availability_zone, NULL); @@ -290,13 +304,13 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_address_set, &en_sb_address_set, NULL); engine_add_input(&en_address_set, &en_icsb_address_set, NULL); - /* en_ic: output node aggregating all subsystems. Order matches the - * previous ovn_db_run() call order; in particular en_ts is added before - * en_tr so they allocate datapath tunnel keys from the shared en_dp_enum - * allocator in the same order as before. */ + /* en_ic: output node aggregating all subsystems. en_tunnel_key is added + * after en_ts and en_tr, matching its ordering dependency on them (it + * publishes a new binding's key to the NB mirror they create). */ engine_add_input(&en_ic, &en_gateway, NULL); engine_add_input(&en_ic, &en_ts, NULL); engine_add_input(&en_ic, &en_tr, NULL); + engine_add_input(&en_ic, &en_tunnel_key, NULL); engine_add_input(&en_ic, &en_port_binding, NULL); engine_add_input(&en_ic, &en_route, NULL); engine_add_input(&en_ic, &en_service_monitor, NULL); diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c index 773762ae4..57a55eff0 100644 --- a/ic/ovn-ic.c +++ b/ic/ovn-ic.c @@ -99,7 +99,7 @@ Options:\n\ stream_usage("database", true, true, false); } -static uint32_t +uint32_t allocate_dp_key(struct hmap *dp_tnlids, bool vxlan_mode, const char *name) { uint32_t hint = vxlan_mode ? OVN_MIN_DP_VXLAN_KEY_GLOBAL @@ -109,7 +109,7 @@ allocate_dp_key(struct hmap *dp_tnlids, bool vxlan_mode, const char *name) &hint); } -static enum ic_datapath_type +enum ic_datapath_type ic_dp_get_type(const struct icsbrec_datapath_binding *isb_dp) { if (isb_dp->type && !strcmp(isb_dp->type, "transit-router")) { @@ -129,250 +129,177 @@ ic_pb_get_type(const struct icsbrec_port_binding *isb_pb) return IC_SWITCH_PORT; } -void -enumerate_datapaths(struct ic_context *ctx, struct hmap *dp_tnlids, - struct shash *isb_ts_dps, struct shash *isb_tr_dps) -{ - const struct icsbrec_datapath_binding *isb_dp; - ICSBREC_DATAPATH_BINDING_FOR_EACH (isb_dp, ctx->ovnisb_idl) { - ovn_add_tnlid(dp_tnlids, isb_dp->tunnel_key); - - enum ic_datapath_type dp_type = ic_dp_get_type(isb_dp); - if (dp_type == IC_ROUTER) { - char *uuid_str = uuid_to_string(isb_dp->nb_ic_uuid); - shash_add(isb_tr_dps, uuid_str, isb_dp); - free(uuid_str); - } else { - shash_add(isb_ts_dps, isb_dp->transit_switch, isb_dp); - } - } -} - /* * Check if the AZ is the leader by checking the lock. */ -static bool +bool is_az_leader(struct ovsdb_idl_txn *txn) { struct ovsdb_idl *idl = ovsdb_idl_txn_get_idl(txn); return idl && ovsdb_idl_has_lock(idl); } -void -ts_run(struct ic_context *ctx, struct hmap *dp_tnlids, - struct shash *isb_ts_dps) +/* Returns true if transit-switch datapaths must use the VXLAN tunnel-key + * range: IC-NB requests vxlan_mode and the IC-SB actually has a VXLAN encap. + * + * Warning: ovnisb_unlocked should not be used to insert data on IC_SB which + * can cause a constraint violation, as an example, inserting data to IC-SB + * datapath_binding. */ +bool +ic_ts_compute_vxlan_mode(struct ic_context *ctx) { - const struct icnbrec_transit_switch *ts; - bool dp_key_refresh = false; - bool vxlan_mode = false; const struct icnbrec_ic_nb_global *ic_nb = icnbrec_ic_nb_global_first(ctx->ovninb_idl); - /* - * Warning: ovnisb_unlocked should not be used to insert data on IC_SB - * which can cause a constraint violation, as an example, inserting data to - * IC-SB datapath_binding. - */ if (ic_nb && smap_get_bool(&ic_nb->options, "vxlan_mode", false)) { const struct icsbrec_encap *encap; ICSBREC_ENCAP_FOR_EACH (encap, ctx->ovnisb_unlocked_idl) { if (!strcmp(encap->type, "vxlan")) { - vxlan_mode = true; - break; + return true; } } } + return false; +} - /* Sync INB TS to AZ NB */ - if (ctx->ovnnb_txn) { - struct shash nb_tses = SHASH_INITIALIZER(&nb_tses); - const struct nbrec_logical_switch *ls; - - /* Get current NB Logical_Switch with other_config:interconn-ts */ - NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->ovnnb_idl) { - const char *ts_name = smap_get(&ls->other_config, "interconn-ts"); - if (ts_name) { - shash_add(&nb_tses, ts_name, ls); - } - } - - /* Create/update NB Logical_Switch for each TS */ - ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) { - ls = shash_find_and_delete(&nb_tses, ts->name); - if (!ls) { - ls = nbrec_logical_switch_insert(ctx->ovnnb_txn); - nbrec_logical_switch_set_name(ls, ts->name); - nbrec_logical_switch_update_other_config_setkey(ls, - "interconn-ts", - ts->name); - nbrec_logical_switch_update_other_config_setkey( - ls, "ic-vxlan_mode", vxlan_mode ? "true" : "false"); - } else { - bool _vxlan_mode = smap_get_bool(&ls->other_config, - "ic-vxlan_mode", false); - if (_vxlan_mode != vxlan_mode) { - dp_key_refresh = true; - nbrec_logical_switch_update_other_config_setkey( - ls, "ic-vxlan_mode", - vxlan_mode ? "true" : "false"); - } - } - - const struct icsbrec_datapath_binding *isb_dp; - isb_dp = shash_find_data(isb_ts_dps, ts->name); - if (isb_dp) { - int64_t nb_tnl_key = smap_get_int(&ls->other_config, - "requested-tnl-key", - 0); - if (nb_tnl_key != isb_dp->tunnel_key) { - VLOG_DBG("Set other_config:requested-tnl-key %"PRId64 - " for transit switch %s in NB.", - isb_dp->tunnel_key, ts->name); - char *tnl_key_str = xasprintf("%"PRId64, - isb_dp->tunnel_key); - nbrec_logical_switch_update_other_config_setkey( - ls, "requested-tnl-key", tnl_key_str); - free(tnl_key_str); - } - } - } - - /* Delete extra NB Logical_Switch with other_config:interconn-ts */ - struct shash_node *node; - SHASH_FOR_EACH (node, &nb_tses) { - nbrec_logical_switch_delete(node->data); - } - shash_destroy(&nb_tses); - } - - /* Sync TS between INB and ISB. This is performed after syncing with AZ - * SB, to avoid uncommitted ISB datapath tunnel key to be synced back to - * AZ. */ - if (ctx->ovnisb_txn && - is_az_leader(ctx->ovnisb_txn)) { - /* Create ISB Datapath_Binding */ - ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) { - const struct icsbrec_datapath_binding *isb_dp = - shash_find_and_delete(isb_ts_dps, ts->name); - if (!isb_dp) { - /* Allocate tunnel key */ - int64_t dp_key = allocate_dp_key(dp_tnlids, vxlan_mode, - "transit switch datapath"); - if (!dp_key) { - continue; - } - - isb_dp = icsbrec_datapath_binding_insert(ctx->ovnisb_txn); - icsbrec_datapath_binding_set_transit_switch(isb_dp, ts->name); - icsbrec_datapath_binding_set_tunnel_key(isb_dp, dp_key); - } else if (dp_key_refresh) { - /* Refresh tunnel key since encap mode has changed. */ - int64_t dp_key = allocate_dp_key(dp_tnlids, vxlan_mode, - "transit switch datapath"); - if (dp_key) { - icsbrec_datapath_binding_set_tunnel_key(isb_dp, dp_key); - } - } - - if (!isb_dp->type) { - icsbrec_datapath_binding_set_type(isb_dp, "transit-switch"); - } +/* Keep other_config:ic-vxlan_mode on the transit switch's NB Logical_Switch + * mirror in sync with the IC VXLAN mode. northd reads the boolean value with + * smap_get_bool() (northd/en-global-config.c), so writing "false" is correct + * and does not affect the datapath tunnel-id range. Only write when the value + * actually differs to avoid needless transaction churn. */ +static void +ts_set_ic_vxlan_mode(const struct nbrec_logical_switch *ls, bool vxlan_mode) +{ + const char *want = vxlan_mode ? "true" : "false"; + const char *cur = smap_get(&ls->other_config, "ic-vxlan_mode"); + if (!cur || strcmp(cur, want)) { + nbrec_logical_switch_update_other_config_setkey(ls, "ic-vxlan_mode", + want); + } +} - if (!isb_dp->nb_ic_uuid) { - icsbrec_datapath_binding_set_nb_ic_uuid(isb_dp, - &ts->header_.uuid, 1); - } - } +/* Reconciles a single transit switch 'ts's AZ NB Logical_Switch mirror: + * creates it if missing, keeps other_config:ic-vxlan_mode in sync, and copies + * the committed IC-SB Datapath_Binding tunnel key into + * other_config:requested-tnl-key. IC-SB Datapath_Binding creation/keying is + * owned by en_tunnel_key. + * + * 'nb_gc' (keyed by transit-switch name) doubles as a garbage-collection set: + * this function removes the mirror it claims, so whatever remains after every + * in-scope switch has been reconciled is stale and deleted by the caller. + * 'isb_gc' (the IC-SB Datapath_Binding map keyed by transit-switch name) is + * read-only here. */ +static void +ts_sync_one(struct ic_context *ctx, const struct icnbrec_transit_switch *ts, + struct shash *isb_gc, struct shash *nb_gc, bool vxlan_mode) +{ + if (!nb_gc) { + return; + } - struct shash_node *node; - SHASH_FOR_EACH (node, isb_ts_dps) { - icsbrec_datapath_binding_delete(node->data); + const struct nbrec_logical_switch *ls = + shash_find_and_delete(nb_gc, ts->name); + if (!ls) { + ls = nbrec_logical_switch_insert(ctx->ovnnb_txn); + nbrec_logical_switch_set_name(ls, ts->name); + + /* Write other_config as a whole column rather than with per-key + * partial map updates. A partial update is only queued as a map + * operation and is not reflected in 'ls->other_config' until the + * transaction commits, so en_tunnel_key would not recognize this + * brand-new row as a transit switch mirror and could not publish the + * freshly allocated tunnel key to it in this same iteration. northd + * would then pick its own datapath tunnel key and flap it once + * requested-tnl-key finally showed up. */ + struct smap other_config = SMAP_INITIALIZER(&other_config); + smap_add(&other_config, "interconn-ts", ts->name); + smap_add(&other_config, "ic-vxlan_mode", + vxlan_mode ? "true" : "false"); + nbrec_logical_switch_set_other_config(ls, &other_config); + smap_destroy(&other_config); + } else { + ts_set_ic_vxlan_mode(ls, vxlan_mode); + } + + const struct icsbrec_datapath_binding *isb_dp = + shash_find_data(isb_gc, ts->name); + if (isb_dp) { + int64_t nb_tnl_key = smap_get_int(&ls->other_config, + "requested-tnl-key", 0); + if (nb_tnl_key != isb_dp->tunnel_key) { + VLOG_DBG("Set other_config:requested-tnl-key %"PRId64 + " for transit switch %s in NB.", + isb_dp->tunnel_key, ts->name); + char *tnl_key_str = xasprintf("%"PRId64, isb_dp->tunnel_key); + nbrec_logical_switch_update_other_config_setkey( + ls, "requested-tnl-key", tnl_key_str); + free(tnl_key_str); } } } +/* Synchronizes transit switches to their AZ NB Logical_Switch mirrors. When + * 'ts_scope' is NULL every transit switch is reconciled (full recompute); + * otherwise only the switches named in 'ts_scope' are. A name still in scope + * but no longer present in IC-NB (a deleted switch) is honoured: its mirror + * ends up as a garbage-collection leftover and is deleted, matching + * full-recompute behaviour. + * + * 'isb_ts_dps' (the IC-SB Datapath_Binding map keyed by transit-switch name) + * is read only, to copy each committed tunnel key into the mirror's + * requested-tnl-key. IC-SB Datapath_Binding creation/keying/GC is owned by + * en_tunnel_key, so this function neither allocates keys nor mutates + * 'isb_ts_dps'. */ void -tr_run(struct ic_context *ctx, struct hmap *dp_tnlids, - struct shash *isb_tr_dps) +ts_sync_scope(struct ic_context *ctx, struct shash *isb_ts_dps, + const struct sset *ts_scope) { - /* - * Warning: ovnisb_unlocked should not be used to insert data on IC_SB - * which can cause a constraint violation, as an example, inserting data to - * IC-SB datapath_binding. - */ - const struct nbrec_logical_router *lr; + bool full = !ts_scope; + bool vxlan_mode = ic_ts_compute_vxlan_mode(ctx); + /* Build the NB Logical_Switch mirror GC set, keyed by transit-switch + * name. Only needed when an NB transaction is available. */ + struct shash nb_ts_mirrors = SHASH_INITIALIZER(&nb_ts_mirrors); + struct shash *nb_gc = NULL; if (ctx->ovnnb_txn) { - struct shash nb_tres = SHASH_INITIALIZER(&nb_tres); - NBREC_LOGICAL_ROUTER_FOR_EACH (lr, ctx->ovnnb_idl) { - const char *tr_name = smap_get(&lr->options, "interconn-tr"); - if (tr_name) { - shash_add(&nb_tres, tr_name, lr); - } - } - - const struct icnbrec_transit_router *tr; - ICNBREC_TRANSIT_ROUTER_FOR_EACH (tr, ctx->ovninb_idl) { - lr = shash_find_and_delete(&nb_tres, tr->name); - if (!lr) { - lr = nbrec_logical_router_insert(ctx->ovnnb_txn); - nbrec_logical_router_set_name(lr, tr->name); - nbrec_logical_router_update_options_setkey( - lr, "interconn-tr", tr->name); + nb_gc = &nb_ts_mirrors; + if (full) { + const struct nbrec_logical_switch *ls; + NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->ovnnb_idl) { + const char *ts_name = smap_get(&ls->other_config, + "interconn-ts"); + if (ts_name) { + shash_add(nb_gc, ts_name, ls); + } } - char *uuid_str = uuid_to_string(&tr->header_.uuid); - struct icsbrec_datapath_binding *isb_dp = shash_find_data( - isb_tr_dps, uuid_str); - free(uuid_str); - - if (isb_dp) { - char *tnl_key_str = xasprintf("%"PRId64, isb_dp->tunnel_key); - nbrec_logical_router_update_options_setkey( - lr, "requested-tnl-key", tnl_key_str); - free(tnl_key_str); + } else { + const char *name; + SSET_FOR_EACH (name, ts_scope) { + const struct nbrec_logical_switch *ls = + find_ts_in_nb(ctx, CONST_CAST(char *, name)); + if (ls && !shash_find(nb_gc, name)) { + shash_add(nb_gc, name, ls); + } } } + } - struct shash_node *node; - SHASH_FOR_EACH (node, &nb_tres) { - nbrec_logical_router_delete(node->data); - } - shash_destroy(&nb_tres); - } - - /* Sync TR between INB and ISB. This is performed after syncing with AZ - * SB, to avoid uncommitted ISB datapath tunnel key to be synced back to - * AZ. */ - if (ctx->ovnisb_txn && - is_az_leader(ctx->ovnisb_txn)) { - /* Create ISB Datapath_Binding */ - const struct icnbrec_transit_router *tr; - ICNBREC_TRANSIT_ROUTER_FOR_EACH (tr, ctx->ovninb_idl) { - char *uuid_str = uuid_to_string(&tr->header_.uuid); - struct icsbrec_datapath_binding *isb_dp = - shash_find_and_delete(isb_tr_dps, uuid_str); - free(uuid_str); - - if (!isb_dp) { - int dp_key = allocate_dp_key(dp_tnlids, false, - "transit router datapath"); - if (!dp_key) { - continue; - } - - isb_dp = icsbrec_datapath_binding_insert(ctx->ovnisb_txn); - icsbrec_datapath_binding_set_tunnel_key(isb_dp, dp_key); - icsbrec_datapath_binding_set_nb_ic_uuid(isb_dp, - &tr->header_.uuid, 1); - icsbrec_datapath_binding_set_type(isb_dp, "transit-router"); - } + const struct icnbrec_transit_switch *ts; + ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) { + if (full || sset_contains(ts_scope, ts->name)) { + ts_sync_one(ctx, ts, isb_ts_dps, nb_gc, vxlan_mode); } + } + /* Delete extra NB Logical_Switch with other_config:interconn-ts. */ + if (nb_gc) { struct shash_node *node; - SHASH_FOR_EACH (node, isb_tr_dps) { - icsbrec_datapath_binding_delete(node->data); + SHASH_FOR_EACH (node, nb_gc) { + nbrec_logical_switch_delete(node->data); } } + + shash_destroy(&nb_ts_mirrors); } /* Returns true if any information in gw and chassis is different. */ @@ -708,7 +635,7 @@ gateway_run(struct ic_context *ctx, shash_destroy(&remote_gws); } -static const struct nbrec_logical_switch * +const struct nbrec_logical_switch * find_ts_in_nb(struct ic_context *ctx, char *ts_name) { const struct nbrec_logical_switch *key = diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h index 3d2e20483..91be04db6 100644 --- a/ic/ovn-ic.h +++ b/ic/ovn-ic.h @@ -63,21 +63,31 @@ enum ic_port_binding_type { IC_SWITCH_PORT, IC_ROUTER_PORT, IC_PORT_MAX }; struct hmap; struct shash; +struct sset; struct icsbrec_availability_zone; +struct icsbrec_datapath_binding; + +enum ic_datapath_type ic_dp_get_type( + const struct icsbrec_datapath_binding *isb_dp); -/* Per-subsystem entry points, invoked by the incremental-processing engine - * nodes (see ic/en-*.c). Each performs a full recompute of its subsystem and - * may be invoked independently when its engine inputs change. */ -void enumerate_datapaths(struct ic_context *ctx, struct hmap *dp_tnlids, - struct shash *isb_ts_dps, struct shash *isb_tr_dps); void gateway_run(struct ic_context *ctx, const struct icsbrec_availability_zone *runned_az); void address_set_run(struct ic_context *ctx, const struct icsbrec_availability_zone *runned_az); -void ts_run(struct ic_context *ctx, struct hmap *dp_tnlids, - struct shash *isb_ts_dps); -void tr_run(struct ic_context *ctx, struct hmap *dp_tnlids, - struct shash *isb_tr_dps); + +/* Reconciles the AZ NB Logical_Switch mirrors of the transit switches named in + * 'ts_scope' (NULL reconciles every transit switch). 'isb_ts_dps' is the + * transit-switch IC-SB Datapath_Binding map (keyed by transit-switch name), + * read only to copy each committed tunnel key into the mirror's + * requested-tnl-key. IC-SB Datapath_Binding creation/keying/GC is owned by + * the en_tunnel_key node. */ +void ts_sync_scope(struct ic_context *ctx, struct shash *isb_ts_dps, + const struct sset *ts_scope); + +/* True if transit-switch datapaths must use the VXLAN tunnel-key range: IC-NB + * requests vxlan_mode and the IC-SB actually has a VXLAN encap. */ +bool ic_ts_compute_vxlan_mode(struct ic_context *ctx); + void port_binding_run(struct ic_context *ctx, const struct icsbrec_availability_zone *runned_az); void route_run(struct ic_context *ctx, @@ -85,4 +95,12 @@ void route_run(struct ic_context *ctx, void sync_service_monitor(struct ic_context *ctx, const struct icsbrec_availability_zone *runned_az); +/* Shared IC helpers used by more than one engine node. */ +uint32_t +allocate_dp_key(struct hmap *dp_tnlids, bool vxlan_mode, const char *name); +const struct nbrec_logical_switch * +find_ts_in_nb(struct ic_context *ctx, char *ts_name); +bool +is_az_leader(struct ovsdb_idl_txn *txn); + #endif /* OVN_IC_H */ From patchwork Wed Sep 2 21:17:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297038 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=AHYUDut1; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) (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 4hZwbB1gL9z1xwZ for ; Thu, 03 Sep 2026 07:19:18 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id CC4D081000; Wed, 2 Sep 2026 21:19:16 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 1V9xwh3QPy_1; Wed, 2 Sep 2026 21:19:11 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp1.osuosl.org 418DC81082 Authentication-Results: smtp1.osuosl.org; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383949; b=XlWqd6gAGsi5FFrL58XRji5E/z4DIgWcEc66b9RhN1CKs9NcmXND2xQXUU6drfoNFwpQ MzIGpnpKkLtoNy/SlqF7Dcs67nHhpsNc6y3FWwiNSKjq5Y/7bCn8Cc6rbtA0FlO7cq0CF 90TPI1RnruQyl9s6APELGOfsp8iPOJj3ikSR44wzHDfJejRYExabYzVdRP8GQJVpEkm55 NF8HrUEBnon+QjXcFPr2kW5psVhutf71lcBPq80oVREkVG4xprvAO5+HpUPtQBF+W1z/1 FACdD6/ZiVarhVu1A4iVYiD4lGVPOPwYNjKRoKANTKELS9EhlcO8HFUbSSAz30rqnMg== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383949; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=8pIie0zg6XK9uBwTohJ4N39pRAcItInswJBEk2rItio=; b=TthIa9X972w9YqTCDgiD7QkT2BBxkutt9qG8+nqr7vuediCZtTEnO0X0XPM5HHPUkUcf yl7H4bZ6q9q6n/Hnr1iLlG25bDNuxelSATk9hWIYQV7nmr0dpXa6b4SnVpEeUqt8TSpzR 1d3nx6+QLB9PhD0qk/SzcEz41bFQzzC5D8LzKbkGRxoZjM4INFr34WUR3UMPsnX2IC3s5 dIaRPo3PcSLx8+yV3L6lTvdGc7GU/+JM3BAOXXijyQ7yvDWwnNHbXtff5vSTvj56Zn0N/ ttelxf/DJf+axPbuLV88ZuYR+xhixp88qwpchLZaomaJuyamSn6Y02G3ITw+3MmJisQ== ARC-Authentication-Results: i=2; smtp1.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=AHYUDut1; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" 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= Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=AHYUDut1 Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp1.osuosl.org (Postfix) with ESMTPS id 418DC81082; Wed, 2 Sep 2026 21:19:09 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 08824C0913; Wed, 2 Sep 2026 21:19:09 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp2.osuosl.org (smtp2.osuosl.org [140.211.166.133]) by lists.linuxfoundation.org (Postfix) with ESMTP id B6053C090A for ; Wed, 2 Sep 2026 21:19:05 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id A147D40384 for ; Wed, 2 Sep 2026 21:19:05 +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 xfs9l8_yvhQL for ; Wed, 2 Sep 2026 21:19:02 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp2.osuosl.org 6D04A4042B Authentication-Results: smtp2.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::112f" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383941; b=YJuGyYQybd3MFUBaGBkJpY599PpMSSGnqaTgJ4AzIQGt7kgfbWAMyPDDN1+a6sWhUAky mAHYTCrmPc2kkDRUygardLy3xpQALrhnQRYQF7fp8TzEcQ89NVb0n7i//3nGItwihmwWf p8hdTfPR+MdnYQdlGxcjKsimhL5EFGezFC0SqVl+BxXEZm3afMugkEknVkhh7L0NLbTyl bN4qPswMwFAF6AAh2HEOhcFF/BNalQsEmoWVapSSTJSqWca7Jd1d7C+nZOWZzIuNGRDDd gOeHqiQyEq8uzoT7s14geWSnXTcu/n/U5FN6MX+ECgK9uYXrwlwE208lBPGG/pq5yXA== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383941; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=C7oC9g/L/X7nluQHeQ4nMo067qnwBi3BUyxyMCK9Qmc=; b=CnGPm+cQxNWXQR+jXdb3UnffoLO9L3BE0vxfuosWw0xtNZYwIFcVmvqu3i+fCilqFVcG /YpfYACIcwhu/2tEZ72IJxvC6fV1NnB/r9tfpfqfOfucerz6HQwaWg/mbSFhJ9Vd2If9M HIWG8mQDqN088hl6oOzXCRJE1/pSKLSGw+MU4uL0GocQ/XVtfU3d4/x/H9VQ7MUU5am0w HU9ifNlLVbajBSnCk6eGFn+0jtqENf3FPCqyRv6/47UJGkylBir3T2jsknKxWv5j38FG2 vmjRqmiGiwss5LlLy/y2zRFfiKXscwOmFgcJKqOdI1eLiJFescu73jcuLa7rtyS/TcA== ARC-Authentication-Results: i=1; smtp2.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=AHYUDut1; arc=none smtp.remote-ip="2607:f8b0:4864:20::112f" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::112f; helo=mail-yw1-x112f.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp2.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Authentication-Results: smtp2.osuosl.org; dkim=pass (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=AHYUDut1 Received: from mail-yw1-x112f.google.com (mail-yw1-x112f.google.com [IPv6:2607:f8b0:4864:20::112f]) by smtp2.osuosl.org (Postfix) with ESMTPS id 6D04A4042B for ; Wed, 2 Sep 2026 21:19:01 +0000 (UTC) Received: by mail-yw1-x112f.google.com with SMTP id 00721157ae682-7dbcb505578so21265967b3.3 for ; Wed, 02 Sep 2026 14:19:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383940; x=1788988740; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=C7oC9g/L/X7nluQHeQ4nMo067qnwBi3BUyxyMCK9Qmc=; b=AHYUDut10OzulE275MXCJnXJHhIJXw946n2jh1GzRyowuhrDUmVEsGybMY/Vw8FLQq hNG31021hjRPKiWIqHgZFL5ryZru1Dtbgql9s+MWt35Pt7VcCtEvA3HYe8yybsAkQh77 1TchxXsRzYiekvT+19klEAPq6j8uOH9VPnz69xHCb/nm0Wj7am3UoMPQVq0YvRQloZhS PfR1TA2KJzYEqueqRFKBjCGbX9VEQN4bmiAI9f6qUJMIy1lxKtt4zG5GiIWP0s06KUl2 A6gWm5Q1VkbogtB40Zhqmsqe3DYENVF+VfrBIpGsux2I7/AM579LoBUFab4Z6L9s4OaF CEeQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383940; x=1788988740; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=C7oC9g/L/X7nluQHeQ4nMo067qnwBi3BUyxyMCK9Qmc=; b=XdAr2AK0rselnhqyxocJRctLwHrALzVCzptoQ5GoqFFlg5zThsciHR34zqWX8mpajy hsSmgMdRB67nDsOLLTU6BLMHg6GS+lkdsUVQnKi2tg0KS4iMFh9Xxk/tJVGvxSM6cSaz UTwII9PnWR1A/KVWtjylNp0w19YVWIUsW+fPeems5+ZUlfrJSs08Ste/iZeXca57gmOO rSTYuZYQpzoDTOF0Gg+NAinUf+nK4zPJJ16tVhHx/wVWSJ1j5qKczmE+E0/PRK8dM/pW 9ICWqOMxq4H7/bCQ6Zh0SNMtT/RWSrHyjlNppPdzEcftPYhjaeRnopIovQpeaYA0ygng D6JQ== X-Gm-Message-State: AFuF++lixtIsboRhCl6vPSKudzaPUz4tZxqYaGJUXoc/XUL+TpmCP+xq HN5zVJ6opq1RqUKCqjGkzCUOPehDxTDVcl1V/Q5pKdKR5w0vuqV8FJIEFPb87JP99pa3xz6hR4i b+nFhO+bNsKoCkCgPBWVt9LQArRc9XS5739d58hN7RN2qAhbUuFokUu6MCYrUih4= X-Gm-Gg: AYBFou19ZBY263HajepvbDs9nw2+1aGSfmJbCKAFoi55edtIh5EiM4tDSrDfmkjdno2 p+uMqQR8XIj3icL5Aqvz5t/qF7zMM2iIcxvxtJujkDcFMqBjnPadIRHz3N8eCfNtT45sO+BvF1A KA5j/AWJ4qOJ4+jlOQmizYJ+UHYGwUqWl9HxW41lAkoDae9l15JnrZ9SU7JAdanxN0V9F8F30s1 gwqAXG+AXszHaQ7viwSHDsj3jympbfVk4GVPi/egzQ02/2lDWuF7F7Yexx9DAk4D6zR2jZtIR4r Po8+4fB/92nIZXKOOfjpRySc+DogtS2jacsaty+EfQGQsQicYe0CEmQXyvbPOHKd6JP4iXiq1sf kLIbF49Wz19ONUfCZGwXPUFIqjhaTEC9GLeHEvBr9KQSGZn7xqYDgsFliu/tzRR7WvBTSzcW5if LnhXeRo72L/aZFdeBBCxM0RmZ/ggZ6yNLM9U6UX2K32TIEMCfzhVpYJPlXNtPrED06UxcdTykx5 pymCnhezVAnLTCeb9l/5RLczDv+y/RR X-Received: by 2002:a05:690c:a792:b0:844:9f61:92d9 with SMTP id 00721157ae682-86c525fd942mr30128727b3.17.1788383939351; Wed, 02 Sep 2026 14:18:59 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.18.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:18:58 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:38 -0300 Message-Id: <20260902211744.2159261-9-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 08/14] ic: Add incremental handler for the en_gateway node. 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: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Add the incremental change handlers for the en_gateway node so SB/IC-SB gateway and chassis changes are reconciled in place instead of forcing a full recompute. The handlers report EN_HANDLED_UNCHANGED when the tracked rows required no write at all (an unchanged interconn chassis, a gateway update we made ourselves, a deleted chassis with no gateway to clean up), so a tracked change that turns out to be a no-op does not mark the node as updated. Add a tests/ovn-ic.at test exercising the en_dp_enum and en_gateway incremental handlers (the change handler of the node that reads a changed table fires, and a forced recompute produces no diff). Assisted-by: Claude Opus 4.8, Claude Code Signed-off-by: Paulo Guilherme Silva Acked-by: Lucas Vargas Dias --- ic/en-gateway.c | 364 ++++++++++++++++++++++++++++++++++++++++++++++- ic/en-gateway.h | 5 + ic/inc-proc-ic.c | 16 ++- ic/ovn-ic.c | 157 +------------------- ic/ovn-ic.h | 6 +- tests/ovn-ic.at | 64 +++++++++ 6 files changed, 448 insertions(+), 164 deletions(-) diff --git a/ic/en-gateway.c b/ic/en-gateway.c index 3fb00ced9..e3013e6ae 100644 --- a/ic/en-gateway.c +++ b/ic/en-gateway.c @@ -14,14 +14,335 @@ #include -#include "en-gateway.h" #include "en-az.h" +#include "en-gateway.h" #include "lib/inc-proc-eng.h" +#include "lib/ovn-ic-sb-idl.h" +#include "lib/ovn-sb-idl.h" #include "openvswitch/vlog.h" #include "ovn-ic.h" VLOG_DEFINE_THIS_MODULE(en_ic_gateway); +/* Returns true if any information in gw and chassis is different. */ +static bool +is_gateway_data_changed(const struct icsbrec_gateway *gw, + const struct sbrec_chassis *chassis) +{ + if (strcmp(gw->hostname, chassis->hostname)) { + return true; + } + + if (gw->n_encaps != chassis->n_encaps) { + return true; + } + + for (size_t g = 0; g < gw->n_encaps; g++) { + bool found = false; + const struct icsbrec_encap *gw_encap = gw->encaps[g]; + for (size_t s = 0; s < chassis->n_encaps; s++) { + const struct sbrec_encap *chassis_encap = chassis->encaps[s]; + if (!strcmp(gw_encap->type, chassis_encap->type) && + !strcmp(gw_encap->ip, chassis_encap->ip)) { + found = true; + if (!smap_equal(&gw_encap->options, &chassis_encap->options)) { + return true; + } + break; + } + } + if (!found) { + return true; + } + } + + return false; +} + +static void +sync_isb_gw_to_sb(struct ic_context *ctx, + const struct icsbrec_gateway *gw, + const struct sbrec_chassis *chassis) +{ + struct smap temp_map; + sbrec_chassis_set_hostname(chassis, gw->hostname); + smap_clone(&temp_map, &chassis->other_config); + smap_replace(&temp_map, "is-remote", "true"); + /* Use sbrec_chassis_set_other_config instead of + * sbrec_chassis_update_other_config_setkey so the in-memory datum is + * updated for reads in the same loop iteration. */ + sbrec_chassis_set_other_config(chassis, &temp_map); + smap_destroy(&temp_map); + + /* Sync encaps used by this gateway. */ + ovs_assert(gw->n_encaps); + struct sbrec_encap *sb_encap; + struct sbrec_encap **sb_encaps = + xmalloc(gw->n_encaps * sizeof *sb_encaps); + for (size_t i = 0; i < gw->n_encaps; i++) { + sb_encap = sbrec_encap_insert(ctx->ovnsb_txn); + sbrec_encap_set_chassis_name(sb_encap, gw->name); + sbrec_encap_set_ip(sb_encap, gw->encaps[i]->ip); + sbrec_encap_set_type(sb_encap, gw->encaps[i]->type); + sbrec_encap_set_options(sb_encap, &gw->encaps[i]->options); + sb_encaps[i] = sb_encap; + } + sbrec_chassis_set_encaps(chassis, sb_encaps, gw->n_encaps); + free(sb_encaps); +} + +static void +sync_sb_gw_to_isb(struct ic_context *ctx, + const struct sbrec_chassis *chassis, + const struct icsbrec_gateway *gw) +{ + icsbrec_gateway_set_hostname(gw, chassis->hostname); + + /* Sync encaps used by this chassis. */ + ovs_assert(chassis->n_encaps); + struct icsbrec_encap *isb_encap; + struct icsbrec_encap **isb_encaps = + xmalloc(chassis->n_encaps * sizeof *isb_encaps); + for (size_t i = 0; i < chassis->n_encaps; i++) { + isb_encap = icsbrec_encap_insert(ctx->ovnisb_unlocked_txn); + icsbrec_encap_set_gateway_name(isb_encap, + chassis->name); + icsbrec_encap_set_ip(isb_encap, chassis->encaps[i]->ip); + icsbrec_encap_set_type(isb_encap, + chassis->encaps[i]->type); + icsbrec_encap_set_options(isb_encap, + &chassis->encaps[i]->options); + isb_encaps[i] = isb_encap; + } + icsbrec_gateway_set_encaps(gw, isb_encaps, + chassis->n_encaps); + free(isb_encaps); +} + +/* Returns true if it ran to completion, false if the IC-SB/SB transactions + * needed to sync were not available (in which case the caller should report + * that the node is still stale). */ +static bool +gateway_run(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az) +{ + if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) { + return false; + } + + struct shash local_gws = SHASH_INITIALIZER(&local_gws); + struct shash remote_gws = SHASH_INITIALIZER(&remote_gws); + const struct icsbrec_gateway *gw; + ICSBREC_GATEWAY_FOR_EACH (gw, ctx->ovnisb_unlocked_idl) { + if (gw->availability_zone == runned_az) { + shash_add(&local_gws, gw->name, gw); + } else { + shash_add(&remote_gws, gw->name, gw); + } + } + + const struct sbrec_chassis *chassis; + SBREC_CHASSIS_FOR_EACH (chassis, ctx->ovnsb_idl) { + if (smap_get_bool(&chassis->other_config, "is-interconn", false)) { + gw = shash_find_and_delete(&local_gws, chassis->name); + if (!gw) { + gw = icsbrec_gateway_insert(ctx->ovnisb_unlocked_txn); + icsbrec_gateway_set_availability_zone(gw, runned_az); + icsbrec_gateway_set_name(gw, chassis->name); + sync_sb_gw_to_isb(ctx, chassis, gw); + } else if (is_gateway_data_changed(gw, chassis)) { + sync_sb_gw_to_isb(ctx, chassis, gw); + } + } else if (smap_get_bool(&chassis->other_config, "is-remote", false)) { + gw = shash_find_and_delete(&remote_gws, chassis->name); + if (!gw) { + sbrec_chassis_delete(chassis); + } else if (is_gateway_data_changed(gw, chassis)) { + sync_isb_gw_to_sb(ctx, gw, chassis); + } + } + } + + /* Delete extra gateways from ISB for the local AZ */ + struct shash_node *node; + SHASH_FOR_EACH (node, &local_gws) { + icsbrec_gateway_delete(node->data); + } + shash_destroy(&local_gws); + + /* Create SB chassis for remote gateways in ISB */ + SHASH_FOR_EACH (node, &remote_gws) { + gw = node->data; + chassis = sbrec_chassis_insert(ctx->ovnsb_txn); + sbrec_chassis_set_name(chassis, gw->name); + sync_isb_gw_to_sb(ctx, gw, chassis); + } + shash_destroy(&remote_gws); + + return true; +} + +/* Returns the IC-SB gateway named 'name' whose availability zone is (when + * 'local') or is not (otherwise) this instance's AZ, or NULL. There is no + * index on Gateway by name, but the number of gateways is small. */ +static const struct icsbrec_gateway * +find_gw_by_name(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + const char *name, bool local) +{ + const struct icsbrec_gateway *gw; + ICSBREC_GATEWAY_FOR_EACH (gw, ctx->ovnisb_unlocked_idl) { + if ((gw->availability_zone == runned_az) == local + && !strcmp(gw->name, name)) { + return gw; + } + } + return NULL; +} + +/* Incremental handler for en_gateway's SB Chassis input. Mirrors the + * SB-Chassis-driven half of gateway_run() for the tracked chassis only: + * an is-interconn chassis maintains its local IC-SB gateway, while an + * is-remote chassis (one we created from a remote gateway) is re-synced or + * deleted. Returns EN_UNHANDLED to fall back to a full recompute for cases + * that cannot be handled incrementally, and EN_HANDLED_UNCHANGED when the + * tracked chassis required no write at all. */ +static enum engine_input_handler_result +sync_gateway_handle_sb_chassis( + struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + const struct sbrec_chassis_table *tbl) +{ + if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) { + return EN_UNHANDLED; + } + + bool changed = false; + const struct sbrec_chassis *chassis; + SBREC_CHASSIS_TABLE_FOR_EACH_TRACKED (chassis, tbl) { + const struct icsbrec_gateway *local_gw = + find_gw_by_name(ctx, runned_az, chassis->name, true); + + if (sbrec_chassis_is_deleted(chassis)) { + /* The local gateway derived from this chassis (if any) is now + * orphaned. IC-SB Gateway is indexed uniquely by name, so a local + * gateway with this name rules out a remote one: nothing more to + * do for this chassis. */ + if (local_gw) { + icsbrec_gateway_delete(local_gw); + changed = true; + continue; + } + /* If this was a remote chassis we created and the remote gateway + * still exists, it must be recreated: leave that to a full + * recompute. */ + if (find_gw_by_name(ctx, runned_az, chassis->name, false)) { + return EN_UNHANDLED; + } + continue; + } + + if (smap_get_bool(&chassis->other_config, "is-interconn", false)) { + if (!local_gw) { + local_gw = icsbrec_gateway_insert(ctx->ovnisb_unlocked_txn); + icsbrec_gateway_set_availability_zone(local_gw, + runned_az); + icsbrec_gateway_set_name(local_gw, chassis->name); + sync_sb_gw_to_isb(ctx, chassis, local_gw); + changed = true; + } else if (is_gateway_data_changed(local_gw, chassis)) { + sync_sb_gw_to_isb(ctx, chassis, local_gw); + changed = true; + } + } else { + /* No longer an interconn chassis: drop any local gateway derived + * from it. */ + if (local_gw) { + icsbrec_gateway_delete(local_gw); + changed = true; + } + if (smap_get_bool(&chassis->other_config, "is-remote", false)) { + const struct icsbrec_gateway *remote_gw = + find_gw_by_name(ctx, runned_az, chassis->name, false); + if (!remote_gw) { + sbrec_chassis_delete(chassis); + changed = true; + } else if (is_gateway_data_changed(remote_gw, chassis)) { + sync_isb_gw_to_sb(ctx, remote_gw, chassis); + changed = true; + } + } + } + } + + return changed ? EN_HANDLED_UPDATED : EN_HANDLED_UNCHANGED; +} + +/* Incremental handler for en_gateway's IC-SB Gateway input. Mirrors the + * remote-gateway-driven half of gateway_run() for the tracked gateways only: + * a remote gateway maintains its SB chassis mirror. Returns EN_UNHANDLED to + * fall back to a full recompute for cases that cannot be handled + * incrementally, and EN_HANDLED_UNCHANGED when the tracked gateways required + * no write at all. */ +static enum engine_input_handler_result +sync_gateway_handle_icsb_gateway( + struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + const struct icsbrec_gateway_table *tbl) +{ + if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) { + return EN_UNHANDLED; + } + + bool changed = false; + const struct icsbrec_gateway *gw; + ICSBREC_GATEWAY_TABLE_FOR_EACH_TRACKED (gw, tbl) { + bool local = gw->availability_zone == runned_az; + const struct sbrec_chassis *chassis = find_sb_chassis(ctx, gw->name); + + if (icsbrec_gateway_is_deleted(gw)) { + if (!local && chassis && + smap_get_bool(&chassis->other_config, "is-remote", false)) { + sbrec_chassis_delete(chassis); + changed = true; + } + /* A local gateway disappearing while its interconn chassis still + * exists needs the chassis-driven recreate path: + * full recompute. */ + if (local && chassis && + smap_get_bool(&chassis->other_config, "is-interconn", false)) { + return EN_UNHANDLED; + } + continue; + } + + if (local) { + /* Local gateways are produced from SB chassis (handled by the + * SB Chassis handler); a create/update here is our own write. */ + continue; + } + + /* Remote gateway: ensure a matching SB chassis exists and is synced. + * If the only chassis with this name is a local interconn chassis, + * this is an unexpected name collision; fall back. */ + if (chassis && + smap_get_bool(&chassis->other_config, "is-interconn", false)) { + return EN_UNHANDLED; + } + if (!chassis) { + chassis = sbrec_chassis_insert(ctx->ovnsb_txn); + sbrec_chassis_set_name(chassis, gw->name); + sync_isb_gw_to_sb(ctx, gw, chassis); + changed = true; + } else if (is_gateway_data_changed(gw, chassis)) { + sync_isb_gw_to_sb(ctx, gw, chassis); + changed = true; + } + } + + return changed ? EN_HANDLED_UPDATED : EN_HANDLED_UNCHANGED; +} + enum engine_node_state en_gateway_run(struct engine_node *node, void *data OVS_UNUSED) { @@ -35,11 +356,50 @@ en_gateway_run(struct engine_node *node, void *data OVS_UNUSED) return EN_UNCHANGED; } - gateway_run(ctx, az->runned_az); + /* If the transactions needed to sync were not available, the node could + * not be brought up to date; report it as stale so the engine retries. */ + if (!gateway_run(ctx, az->runned_az)) { + return EN_STALE; + } return EN_UPDATED; } +enum engine_input_handler_result +en_gateway_sb_chassis_handler(struct engine_node *node, void *data OVS_UNUSED) +{ + const struct engine_context *eng_ctx = engine_get_context(); + struct ic_context *ctx = eng_ctx->client_ctx; + const struct ed_type_az *az = engine_get_input_data("az", node); + + if (!az->runned_az) { + return EN_HANDLED_UNCHANGED; + } + + const struct sbrec_chassis_table *tbl = + EN_OVSDB_GET(engine_get_input("SB_chassis", node)); + + return sync_gateway_handle_sb_chassis(ctx, az->runned_az, tbl); +} + +enum engine_input_handler_result +en_gateway_icsb_gateway_handler(struct engine_node *node, + void *data OVS_UNUSED) +{ + const struct engine_context *eng_ctx = engine_get_context(); + struct ic_context *ctx = eng_ctx->client_ctx; + const struct ed_type_az *az = engine_get_input_data("az", node); + + if (!az->runned_az) { + return EN_HANDLED_UNCHANGED; + } + + const struct icsbrec_gateway_table *tbl = + EN_OVSDB_GET(engine_get_input("ICSB_gateway", node)); + + return sync_gateway_handle_icsb_gateway(ctx, az->runned_az, tbl); +} + void * en_gateway_init(struct engine_node *node OVS_UNUSED, struct engine_arg *arg OVS_UNUSED) diff --git a/ic/en-gateway.h b/ic/en-gateway.h index af0355a45..14966d91d 100644 --- a/ic/en-gateway.h +++ b/ic/en-gateway.h @@ -21,4 +21,9 @@ enum engine_node_state en_gateway_run(struct engine_node *node, void *data); void *en_gateway_init(struct engine_node *node, struct engine_arg *arg); void en_gateway_cleanup(void *data); +enum engine_input_handler_result +en_gateway_sb_chassis_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +en_gateway_icsb_gateway_handler(struct engine_node *node, void *data); + #endif /* EN_IC_GATEWAY_H */ diff --git a/ic/inc-proc-ic.c b/ic/inc-proc-ic.c index 64aef2c82..a30ddf367 100644 --- a/ic/inc-proc-ic.c +++ b/ic/inc-proc-ic.c @@ -201,12 +201,20 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, * splitting the monolithic ovn_db_run() into independently-gated nodes. * Change handlers are added incrementally in a later step. */ - /* en_gateway: sync gateways/chassis between SB and IC-SB. */ + /* en_gateway: sync gateways/chassis between SB and IC-SB. + * + * The availability zone is provided by en_az (which reports EN_UPDATED + * only when the AZ identity changes). en_gateway does not read the + * Availability_Zone table itself - only gateway rows' availability_zone + * reference and en_az's resolved AZ - so it deliberately does not depend + * on en_icsb_availability_zone, whose rows also carry the + * frequently-bumped nb_ic_cfg sequence number. */ engine_add_input(&en_gateway, &en_az, NULL); - engine_add_input(&en_gateway, &en_icsb_availability_zone, NULL); - engine_add_input(&en_gateway, &en_icsb_gateway, NULL); + engine_add_input(&en_gateway, &en_icsb_gateway, + en_gateway_icsb_gateway_handler); engine_add_input(&en_gateway, &en_icsb_encap, NULL); - engine_add_input(&en_gateway, &en_sb_chassis, NULL); + engine_add_input(&en_gateway, &en_sb_chassis, + en_gateway_sb_chassis_handler); engine_add_input(&en_gateway, &en_sb_encap, NULL); /* en_ts: sync transit switches to their AZ NB Logical_Switch mirrors. diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c index 57a55eff0..9dc6797f0 100644 --- a/ic/ovn-ic.c +++ b/ic/ovn-ic.c @@ -302,102 +302,6 @@ ts_sync_scope(struct ic_context *ctx, struct shash *isb_ts_dps, shash_destroy(&nb_ts_mirrors); } -/* Returns true if any information in gw and chassis is different. */ -static bool -is_gateway_data_changed(const struct icsbrec_gateway *gw, - const struct sbrec_chassis *chassis) -{ - if (strcmp(gw->hostname, chassis->hostname)) { - return true; - } - - if (gw->n_encaps != chassis->n_encaps) { - return true; - } - - for (int g = 0; g < gw->n_encaps; g++) { - - bool found = false; - const struct icsbrec_encap *gw_encap = gw->encaps[g]; - for (int s = 0; s < chassis->n_encaps; s++) { - const struct sbrec_encap *chassis_encap = chassis->encaps[s]; - if (!strcmp(gw_encap->type, chassis_encap->type) && - !strcmp(gw_encap->ip, chassis_encap->ip)) { - found = true; - if (!smap_equal(&gw_encap->options, &chassis_encap->options)) { - return true; - } - break; - } - } - if (!found) { - return true; - } - } - - return false; -} - -static void -sync_isb_gw_to_sb(struct ic_context *ctx, - const struct icsbrec_gateway *gw, - const struct sbrec_chassis *chassis) -{ - struct smap temp_map; - sbrec_chassis_set_hostname(chassis, gw->hostname); - smap_clone(&temp_map, &chassis->other_config); - smap_replace(&temp_map, "is-remote", "true"); - /* Use sbrec_chassis_set_other_config instead of - * sbrec_chassis_update_other_config_setkey so the in-memory datum is - * updated for reads in the same loop iteration. */ - sbrec_chassis_set_other_config(chassis, &temp_map); - smap_destroy(&temp_map); - - /* Sync encaps used by this gateway. */ - ovs_assert(gw->n_encaps); - struct sbrec_encap *sb_encap; - struct sbrec_encap **sb_encaps = - xmalloc(gw->n_encaps * sizeof *sb_encaps); - for (int i = 0; i < gw->n_encaps; i++) { - sb_encap = sbrec_encap_insert(ctx->ovnsb_txn); - sbrec_encap_set_chassis_name(sb_encap, gw->name); - sbrec_encap_set_ip(sb_encap, gw->encaps[i]->ip); - sbrec_encap_set_type(sb_encap, gw->encaps[i]->type); - sbrec_encap_set_options(sb_encap, &gw->encaps[i]->options); - sb_encaps[i] = sb_encap; - } - sbrec_chassis_set_encaps(chassis, sb_encaps, gw->n_encaps); - free(sb_encaps); -} - -static void -sync_sb_gw_to_isb(struct ic_context *ctx, - const struct sbrec_chassis *chassis, - const struct icsbrec_gateway *gw) -{ - icsbrec_gateway_set_hostname(gw, chassis->hostname); - - /* Sync encaps used by this chassis. */ - ovs_assert(chassis->n_encaps); - struct icsbrec_encap *isb_encap; - struct icsbrec_encap **isb_encaps = - xmalloc(chassis->n_encaps * sizeof *isb_encaps); - for (int i = 0; i < chassis->n_encaps; i++) { - isb_encap = icsbrec_encap_insert(ctx->ovnisb_unlocked_txn); - icsbrec_encap_set_gateway_name(isb_encap, - chassis->name); - icsbrec_encap_set_ip(isb_encap, chassis->encaps[i]->ip); - icsbrec_encap_set_type(isb_encap, - chassis->encaps[i]->type); - icsbrec_encap_set_options(isb_encap, - &chassis->encaps[i]->options); - isb_encaps[i] = isb_encap; - } - icsbrec_gateway_set_encaps(gw, isb_encaps, - chassis->n_encaps); - free(isb_encaps); -} - static void nb_addr_set_apply_diff(const void *arg, const char *item, bool add) { @@ -576,65 +480,6 @@ address_set_run(struct ic_context *ctx, shash_destroy(&ic_remote_as); } -void -gateway_run(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az) -{ - if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) { - return; - } - - struct shash local_gws = SHASH_INITIALIZER(&local_gws); - struct shash remote_gws = SHASH_INITIALIZER(&remote_gws); - const struct icsbrec_gateway *gw; - ICSBREC_GATEWAY_FOR_EACH (gw, ctx->ovnisb_unlocked_idl) { - if (gw->availability_zone == runned_az) { - shash_add(&local_gws, gw->name, gw); - } else { - shash_add(&remote_gws, gw->name, gw); - } - } - - const struct sbrec_chassis *chassis; - SBREC_CHASSIS_FOR_EACH (chassis, ctx->ovnsb_idl) { - if (smap_get_bool(&chassis->other_config, "is-interconn", false)) { - gw = shash_find_and_delete(&local_gws, chassis->name); - if (!gw) { - gw = icsbrec_gateway_insert(ctx->ovnisb_unlocked_txn); - icsbrec_gateway_set_availability_zone(gw, - runned_az); - icsbrec_gateway_set_name(gw, chassis->name); - sync_sb_gw_to_isb(ctx, chassis, gw); - } else if (is_gateway_data_changed(gw, chassis)) { - sync_sb_gw_to_isb(ctx, chassis, gw); - } - } else if (smap_get_bool(&chassis->other_config, "is-remote", false)) { - gw = shash_find_and_delete(&remote_gws, chassis->name); - if (!gw) { - sbrec_chassis_delete(chassis); - } else if (is_gateway_data_changed(gw, chassis)) { - sync_isb_gw_to_sb(ctx, gw, chassis); - } - } - } - - /* Delete extra gateways from ISB for the local AZ */ - struct shash_node *node; - SHASH_FOR_EACH (node, &local_gws) { - icsbrec_gateway_delete(node->data); - } - shash_destroy(&local_gws); - - /* Create SB chassis for remote gateways in ISB */ - SHASH_FOR_EACH (node, &remote_gws) { - gw = node->data; - chassis = sbrec_chassis_insert(ctx->ovnsb_txn); - sbrec_chassis_set_name(chassis, gw->name); - sync_isb_gw_to_sb(ctx, gw, chassis); - } - shash_destroy(&remote_gws); -} - const struct nbrec_logical_switch * find_ts_in_nb(struct ic_context *ctx, char *ts_name) { @@ -785,7 +630,7 @@ get_lp_address_for_sb_pb(struct ic_context *ctx, return peer->n_mac ? *peer->mac : NULL; } -static const struct sbrec_chassis * +const struct sbrec_chassis * find_sb_chassis(struct ic_context *ctx, const char *name) { const struct sbrec_chassis *key = diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h index 91be04db6..b18637022 100644 --- a/ic/ovn-ic.h +++ b/ic/ovn-ic.h @@ -66,12 +66,12 @@ struct shash; struct sset; struct icsbrec_availability_zone; struct icsbrec_datapath_binding; +struct sbrec_chassis_table; +struct icsbrec_gateway_table; enum ic_datapath_type ic_dp_get_type( const struct icsbrec_datapath_binding *isb_dp); -void gateway_run(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az); void address_set_run(struct ic_context *ctx, const struct icsbrec_availability_zone *runned_az); @@ -98,6 +98,8 @@ void sync_service_monitor(struct ic_context *ctx, /* Shared IC helpers used by more than one engine node. */ uint32_t allocate_dp_key(struct hmap *dp_tnlids, bool vxlan_mode, const char *name); +const struct sbrec_chassis * +find_sb_chassis(struct ic_context *ctx, const char *name); const struct nbrec_logical_switch * find_ts_in_nb(struct ic_context *ctx, char *ts_name); bool diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at index c9e07250b..bb8365cc9 100644 --- a/tests/ovn-ic.at +++ b/tests/ovn-ic.at @@ -817,6 +817,70 @@ OVN_CLEANUP_IC([az1], [az2]) AT_CLEANUP ]) +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn-ic -- incremental processing - tunnel_key and gateway]) + +# Validates the incremental-processing change handlers of the en_tunnel_key and +# en_gateway engine nodes in two ways: +# - the change handler of the node that reads the affected table fires +# (its compute stat is non-zero), and +# - forcing a full recompute afterwards leaves the databases unchanged, +# i.e. the incremental result matches a full recompute. + +ovn_init_ic_db +net_add n1 +ovn_start az1 +sim_add gw1 +as gw1 +check ovs-vsctl add-br br-phys +ovn_az_attach az1 n1 br-phys 192.168.0.1 +check ovs-vsctl set open . external-ids:ovn-is-interconn=true \ + external-ids:hostname=gw1 +as az1 + +# Wait until the local gateway is registered in IC-SB. +OVS_WAIT_UNTIL([test "$(ovn-ic-sbctl --bare --columns=hostname \ + find gateway name=gw1)" = gw1]) +check ovn-ic-nbctl --wait=sb sync + +# A targeted SB Chassis change is handled by the gateway node's change +# handler: its compute stat becomes non-zero. +check ovn-appctl -t ic/ovn-ic inc-engine/clear-stats +as gw1 +check ovs-vsctl set open . external-ids:hostname=gw1-new +as az1 +OVS_WAIT_UNTIL([test "$(ovn-ic-sbctl --bare --columns=hostname \ + find gateway name=gw1)" = gw1-new]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats gateway compute)" -gt 0]) + +# Exercise the tunnel_key handler too: a transit switch creates an IC-SB +# datapath binding, and adding a port binding drives further IC-SB updates. +check ovn-ic-nbctl --wait=sb ts-add ts1 +OVS_WAIT_UNTIL([test "$(ovn-nbctl --bare --columns=name \ + find logical_switch name=ts1)" = ts1]) +check ovn-ic-nbctl --wait=sb sync + +# After all the incremental updates above, a forced full recompute must not +# change any IC-managed table: incremental processing produced the same result. +m4_define([_IC_DUMP], [ + ovn-ic-sbctl --columns=name,hostname,availability_zone list gateway \ + | sort > $1 + ovn-ic-sbctl --columns=transit_switch,type,tunnel_key \ + list datapath_binding | sort >> $1 + ovn-nbctl --columns=name,other_config list logical_switch | sort >> $1 + ovn-sbctl --columns=name,hostname,other_config list chassis | sort >> $1 +]) +_IC_DUMP([ic_before]) +check ovn-appctl -t ic/ovn-ic inc-engine/recompute +check ovn-ic-nbctl --wait=sb sync +_IC_DUMP([ic_after]) +AT_CHECK([diff ic_before ic_after]) + +OVN_CLEANUP_IC([az1]) +AT_CLEANUP +]) + OVN_FOR_EACH_NORTHD([ AT_SETUP([ovn-ic -- port sync]) From patchwork Wed Sep 2 21:17:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297039 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=uKfz7OHj; 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 4hZwbL5ckpz1xwZ for ; Thu, 03 Sep 2026 07:19:26 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 3F3FC60A41; Wed, 2 Sep 2026 21:19:25 +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 92VRXebOYl_5; Wed, 2 Sep 2026 21:19:21 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp3.osuosl.org 4319960B54 Authentication-Results: smtp3.osuosl.org; arc=fail smtp.remote-ip=140.211.9.56 ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383953; b=Xtp+e3x3UNRr1phAp1F+8Y8eS91sbacGXo3Zyss57PRZmZW9lmSU+8zrpnUWUh/RsoOu 0yjllTXy6F9ZwLROZrgVGWEmmY274gSjMAOygb4Lfc482OccwTcojjaakdp7rI8cUo2Nv H8jEB0h9etJhzKkrcDYWT5FmxCzLBOVa6SyEzS+4ucm8y7t+OfHZfmk3/qiX78eJrkpjp AxCFMCKeI3LHK1so+lIB3Uv9r0XOVq03Ah3xkKccF7wjFJwL2ay5A1c3Sum6Q8Av/C9rK yaNBqrbULhRsAQZh5WcVWYogPJGSvcvOFPRjQVcJCe2UnVZTNM/c0nAxmRneMd/jh/A== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383953; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe:Cc: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=UJAGZZT5sgZOWzd7ZkLMYXAqcsYIXCNOsApeVplPEng=; b=nOiW8hqwjITXLkzFKEukdXD0CDFizf2rKxJEnifyCkNYVg661N76td1c5SLzNTGMYC/k pqs/zRyghdfEJ7MbpF5cZtSgNp1Asg+cbDknKBoUevF6+kPhF9kASLagYjuwiQOoohamx JMLvELYjGm54c1iihZWgKUER9job7O3Pa6P7cRR7uT7swD2vJE9nFrket6biQSa7oRmA6 g+yMv6n5dePeVqLzz6UES87ynKIJHzwzdMaz6jJ1k0sIUeLPTD6QMYIKEzIJM+4KCsAE0 m/0mc3BPWu6oeZRvDipogyK59haLtfUi+T8k3afdXhd+O83UeJV7GFGbfZ4R/vlPTow== ARC-Authentication-Results: i=2; smtp3.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=uKfz7OHj; dmarc=pass header.from=magalu.cloud; arc=fail smtp.remote-ip=140.211.9.56 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= Authentication-Results: smtp3.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=uKfz7OHj Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp3.osuosl.org (Postfix) with ESMTPS id 4319960B54; Wed, 2 Sep 2026 21:19:13 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 1F46FC041B; Wed, 2 Sep 2026 21:19:13 +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 0391BC090F for ; Wed, 2 Sep 2026 21:19:08 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id D68FD60A7F for ; Wed, 2 Sep 2026 21:19:08 +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 MpXFqMWJUhFZ for ; Wed, 2 Sep 2026 21:19:07 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp3.osuosl.org 5ADF1608C1 Authentication-Results: smtp3.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::1129" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383947; b=PSk5yLyH0BWRg/F6YJSF1c60vlWT/YduKmOq+k0edP+5PxDFZUFjA0L/KshvzIGEZhxP PrERkgKMFTNn8WKsbNuF6aQeiErbmzAZSOF750WDpmeeDDCLQYY9scfafmkq1KKlQf7ro 9I5WVkP4D++KbMtungbiqMa06TOhHuFwkPYHOvTpuYMugi/O6lGf5xF+5Po3H1lSDEuic lCseoDJXtArJsTRx6TWO5xQJ0wOqMaCoZjxFVthwtuJuP2Lju85s2lYZ3i2YxSPbM8USq 17OcshLUjghJei/+VE/ieLyaeYBzX+oi6iXcNZl6dN4yeYXzNXjWgzQKvYpywqxwGCQ== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383947; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=6QiGTW6Z9qABym42Vf5OmqX83fOHXaI21NFDy8Cwrrk=; b=ig7D8jE7ejZPgWj7/JOw4qq0oMYPg65xgRfnN3R/zfD2L6ceAX2d35Bhkn3Uec18GEIo nC1LHgkzlALlOEnBzovVoJ8+SxvMtnppbh0xEKC+a28RWTf3tglTu6VR962aUZwnFVoxt 8EiPmifIGhaPjCVf+GS+VwkDe4HDzUdtVaIiJg1V6d4ToJqylDpxQEffOwIAE1fg5evzh b61Mgj+sMU6fEl3h8cIieLrF3Z64pC1xZVsGdiUZF/G+ltVS7rUNKcoQUkFzaKEfpTQ73 SqgeI0MMRfmMt3Av2SQuqmFPCFouBV5Us2G609DonWzpWcpWAtVQXWqps0XVCGjFdXw== ARC-Authentication-Results: i=1; smtp3.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=uKfz7OHj; arc=none smtp.remote-ip="2607:f8b0:4864:20::1129" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::1129; helo=mail-yw1-x1129.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp3.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Received: from mail-yw1-x1129.google.com (mail-yw1-x1129.google.com [IPv6:2607:f8b0:4864:20::1129]) by smtp3.osuosl.org (Postfix) with ESMTPS id 5ADF1608C1 for ; Wed, 2 Sep 2026 21:19:06 +0000 (UTC) Received: by mail-yw1-x1129.google.com with SMTP id 00721157ae682-86b03b75fecso26868577b3.0 for ; Wed, 02 Sep 2026 14:19:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383945; x=1788988745; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=6QiGTW6Z9qABym42Vf5OmqX83fOHXaI21NFDy8Cwrrk=; b=uKfz7OHjXCksx/hWMPa0/DFkctBTVkLFi2rQ7t94vbm5d0lJlUg7KVL2Pl8uicblIE 9s27yQzkRpTo783HAjOv8n0wBKsOOHCSLEh4K6X3h14P5AABkw+byDWVpAgGVTev7Yrg 1R5Cv+6uMqzR0BJvKWdWRS/knMXHsQZGjLFZXDhaDnqDVh45MGVFljKUj0PE+yJo3k3i +tVMoaulsY27UScdlFeCKXWydrkpnVsqN/kqRa+PhDaZUnoxEyUSSUpX2ViOuVHFIaBk yHIOjFrCfEXbWhCFMzM/yEJe0ZjUCsWRH3m72R0MDxxxjpq58XBRGonokZJv0lrPjn7h fNuA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383945; x=1788988745; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=6QiGTW6Z9qABym42Vf5OmqX83fOHXaI21NFDy8Cwrrk=; b=kT8qDmo2sW73AGkPB3+1oBXw9WMUEKjzC+gxxD5mQEK8rJe3FwCvu3m9mweHV9eK5G Hy+LpLcCsUGk8XrHGwe4hu/vnFvTfxftjVjTDBVdpNXFVnAcuAUSnwSkinSh0lM7Pxm1 R4rttY7W5Xqcwl2LBjbD9Y52YcBTtaG7IdXpN8/0ruhPevWVpYeCWSml5dYaYAxWa+gi +hyqD2gSjWeyKSTMKcZG4ovFDf4Qc3DqrULux6gy4NwKWkdZYVpfNlDTnFcRKAtjtICK PkusHtCntQ6wqGmLEUI/Y36L2kZC6qa0vdVe+POA0CQMQlCg6tC0oWNqryi4hrr2sJf8 FXpg== X-Gm-Message-State: AFuF++l99UGwFpy/a5jSZFMs6hGrltIOAV/IMtiwrjVauJJU6U2fKcdl a7fX4SgdKixuzIMiZz25ouS36tRfyQe5FydD4Eg0rWZZ09cqRzvLVX2swLg+IaYCHpI3pgGQmI8 ZNLpXMoyharg1Nw6TPBMqAcZiBCn7xqd+Y24XxxPy8zlbhpWuMADWPT1gky+sUmM= X-Gm-Gg: AYBFou1f64U2RMK/+/1GzK95CPspZqWJOG96+ByWxbBCNrfqaK2DtiD7iOM1ad0DukL JhlaPgo/Vr/f2voY5k/EBzFQDju59eicEF07djrNsNUf9i2kaD8kGaOYB01kpvP47sjuIIAVwtX S4B2MLN2W+mDtoDvI/TbvI7XR9xBuY3ztL5dRnhnC6xB2G+AfBPw6p5kyPBIIKseaoLNWWr/fYC itnUnBGUbZOZ91EhIXuEYVgyl5YJCT22dVaxF9wQO9UBE6JFJYMyQUVTIUa3WrqhbfIoofNZTMb BFF6IrzddbDnVmE67ut0WJgzIt6iJBB4hwCmNatEKLrjF2U+ZFqlsN2QV2tzeMJX5s5GlCPFfNf BqMrdzwxoqR/Pjuhz6EeIQGWNr23RZKTtUbb0dIFefj0T8V77UPL6rAeac6CRcxwtzr25oq+4A/ fDCHR30DodbGcW9BjNTVICd2zSc6b3sUuy4jF73JL5gjLr52XFYLZOzF89LkaN2Lu48VDt0OG41 pM11o5usNZxOBerPkanVZFnMDAL1Zw3 X-Received: by 2002:a05:690c:e257:10b0:820:a55:3ca3 with SMTP id 00721157ae682-86c54b24ae5mr30513927b3.33.1788383943838; Wed, 02 Sep 2026 14:19:03 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.19.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:19:02 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:39 -0300 Message-Id: <20260902211744.2159261-10-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 09/14] ic: Add incremental handler for the en_ts node. 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: , Cc: Tiago Matos Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Add the incremental change handlers for the en_ts node: an NB Logical_Switch change that is not a transit-switch mirror is a no-op, and transit-switch changes are reconciled per transit switch (scoped) instead of forcing a full recompute of every transit switch. Assisted-by: Claude Opus 4.8, Claude Code Co-authored-by: Tiago Matos Signed-off-by: Tiago Matos Signed-off-by: Paulo Guilherme Silva --- ic/en-ts.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++ ic/en-ts.h | 9 ++++ ic/inc-proc-ic.c | 21 +++++--- 3 files changed, 159 insertions(+), 6 deletions(-) diff --git a/ic/en-ts.c b/ic/en-ts.c index d983e6bb0..93a53fe92 100644 --- a/ic/en-ts.c +++ b/ic/en-ts.c @@ -68,6 +68,141 @@ en_ts_run(struct engine_node *node, void *data OVS_UNUSED) return EN_UPDATED; } +/* Returns the ic_context, or NULL when there is no availability zone yet (in + * which case the caller should report EN_HANDLED_UNCHANGED). */ +static struct ic_context * +ts_handler_ctx(struct engine_node *node) +{ + struct ic_context *ctx = engine_get_context()->client_ctx; + const struct ed_type_az *az = engine_get_input_data("az", node); + return az->runned_az ? ctx : NULL; +} + +/* Runs the scoped NB-mirror sync for the collected transit-switch scope and + * maps it to an engine result. ts_sync_scope() gates the NB mirror on the + * ovnnb_txn it needs, exactly as the full recompute does. */ +static enum engine_input_handler_result +ts_scope_finish(struct ic_context *ctx, struct sset *ts_scope) +{ + if (sset_is_empty(ts_scope)) { + return EN_HANDLED_UNCHANGED; + } + + struct shash isb_ts_dps; + collect_ts_datapaths(ctx, &isb_ts_dps); + ts_sync_scope(ctx, &isb_ts_dps, ts_scope); + shash_destroy(&isb_ts_dps); + + return EN_HANDLED_UPDATED; +} + +/* IC-NB Transit_Switch: a new/deleted/renamed transit switch must have its NB + * mirror reconciled. A deletion is honoured via the scoped GC in + * ts_sync_scope() (the deleted row's name stays in scope but is absent from + * IC-NB, so its mirror leftover is removed). */ +enum engine_input_handler_result +en_ts_icnb_transit_switch_handler(struct engine_node *node, + void *data OVS_UNUSED) +{ + struct ic_context *ctx = ts_handler_ctx(node); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icnbrec_transit_switch_table *tbl = + EN_OVSDB_GET(engine_get_input("ICNB_transit_switch", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + const struct icnbrec_transit_switch *ts; + ICNBREC_TRANSIT_SWITCH_TABLE_FOR_EACH_TRACKED (ts, tbl) { + sset_add(&ts_scope, ts->name); + } + + enum engine_input_handler_result ret = ts_scope_finish(ctx, &ts_scope); + sset_destroy(&ts_scope); + return ret; +} + +/* Only transit-switch mirror logical switches (other_config:interconn-ts) + * affect en_ts. A change to such a logical switch reconciles that transit + * switch (re-creating the mirror if it was deleted externally); any other + * logical switch is irrelevant to en_ts, so its change is a no-op, avoiding a + * recompute on unrelated NB Logical_Switch updates. */ +enum engine_input_handler_result +en_ts_nb_logical_switch_handler(struct engine_node *node, + void *data OVS_UNUSED) +{ + struct ic_context *ctx = ts_handler_ctx(node); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_switch_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_switch", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + const struct nbrec_logical_switch *ls; + NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH_TRACKED (ls, tbl) { + const char *ts_name = smap_get(&ls->other_config, "interconn-ts"); + if (ts_name) { + sset_add(&ts_scope, ts_name); + } + } + + enum engine_input_handler_result ret = ts_scope_finish(ctx, &ts_scope); + sset_destroy(&ts_scope); + return ret; +} + +/* IC-SB Datapath_Binding: when a transit switch's datapath tunnel key is + * (re)assigned by en_tunnel_key, the NB Logical_Switch mirror's + * other_config:requested-tnl-key must be updated to the committed value. This + * is what synchronizes the key after a global tunnel-key refresh (an IC-NB + * vxlan_mode change reallocates the datapath key into the VXLAN range in + * IC-SB, but the NB value only catches up on a follow-up iteration). + * + * Only transit-switch bindings have an NB mirror; transit routers (IC_ROUTER) + * are irrelevant. Deletions are ignored: a transit switch removal is + * reconciled through en_ts_icnb_transit_switch_handler and the scoped GC in + * ts_sync_scope(). + * + * Newly *inserted* bindings are also ignored, on purpose. en_tunnel_key + * inserts the binding and already publishes its freshly-allocated key to the + * mirror in the same iteration; reacting to that insert here would re-sync the + * transit switch while its mirror (if just created by + * en_ts_icnb_transit_switch_handler) is still uncommitted - find_ts_in_nb()'s + * index does not see the txn-local insert, so a duplicate NB Logical_Switch + * would be created. Here we only react to a tunnel-key *modify* on an + * already-existing binding (the vxlan refresh), whose mirror already + * exists. */ +enum engine_input_handler_result +en_ts_icsb_datapath_binding_handler(struct engine_node *node, + void *data OVS_UNUSED) +{ + struct ic_context *ctx = ts_handler_ctx(node); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icsbrec_datapath_binding_table *tbl = + EN_OVSDB_GET(engine_get_input("ICSB_datapath_binding", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + const struct icsbrec_datapath_binding *isb_dp; + ICSBREC_DATAPATH_BINDING_TABLE_FOR_EACH_TRACKED (isb_dp, tbl) { + if (icsbrec_datapath_binding_is_deleted(isb_dp) || + icsbrec_datapath_binding_is_new(isb_dp) || + ic_dp_get_type(isb_dp) != IC_SWITCH) { + continue; + } + if (ovsdb_idl_track_is_updated(&isb_dp->header_, + &icsbrec_datapath_binding_col_tunnel_key)) { + sset_add(&ts_scope, isb_dp->transit_switch); + } + } + + enum engine_input_handler_result ret = ts_scope_finish(ctx, &ts_scope); + sset_destroy(&ts_scope); + return ret; +} + void * en_ts_init(struct engine_node *node OVS_UNUSED, struct engine_arg *arg OVS_UNUSED) diff --git a/ic/en-ts.h b/ic/en-ts.h index cf33d9ab6..32bdfedb0 100644 --- a/ic/en-ts.h +++ b/ic/en-ts.h @@ -21,4 +21,13 @@ enum engine_node_state en_ts_run(struct engine_node *node, void *data); void *en_ts_init(struct engine_node *node, struct engine_arg *arg); void en_ts_cleanup(void *data); +enum engine_input_handler_result +en_ts_icnb_transit_switch_handler(struct engine_node *node, void *data); + +enum engine_input_handler_result +en_ts_nb_logical_switch_handler(struct engine_node *node, void *data); + +enum engine_input_handler_result +en_ts_icsb_datapath_binding_handler(struct engine_node *node, void *data); + #endif /* EN_IC_TS_H */ diff --git a/ic/inc-proc-ic.c b/ic/inc-proc-ic.c index a30ddf367..89135fc5d 100644 --- a/ic/inc-proc-ic.c +++ b/ic/inc-proc-ic.c @@ -218,14 +218,23 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_gateway, &en_sb_encap, NULL); /* en_ts: sync transit switches to their AZ NB Logical_Switch mirrors. + * * en_ts builds its own transit-switch IC-SB Datapath_Binding map each run - * and only maintains the NB mirror; IC-SB Datapath_Binding creation/keying - * is owned by en_tunnel_key (downstream). */ + * (local data, never shared) and only maintains the NB mirror. IC-SB + * Datapath_Binding creation/keying is owned by en_tunnel_key (downstream), + * so en_ts no longer allocates tunnel keys. en_icsb_datapath_binding + * drives the follow-up NB requested-tnl-key sync after en_tunnel_key + * (re)assigns a key - notably the global refresh from an IC-NB vxlan_mode + * change (see en_ts_icsb_datapath_binding_handler). */ engine_add_input(&en_ts, &en_az, NULL); - engine_add_input(&en_ts, &en_icsb_datapath_binding, NULL); - engine_add_input(&en_ts, &en_icnb_ic_nb_global, NULL); - engine_add_input(&en_ts, &en_icnb_transit_switch, NULL); - engine_add_input(&en_ts, &en_nb_logical_switch, NULL); + engine_add_input(&en_ts, &en_icsb_datapath_binding, + en_ts_icsb_datapath_binding_handler); + engine_add_input(&en_ts, &en_icnb_ic_nb_global, + ic_nb_global_options_handler); + engine_add_input(&en_ts, &en_icnb_transit_switch, + en_ts_icnb_transit_switch_handler); + engine_add_input(&en_ts, &en_nb_logical_switch, + en_ts_nb_logical_switch_handler); engine_add_input(&en_ts, &en_icsb_encap, NULL); /* en_tr: sync transit routers to their AZ NB Logical_Router mirrors. From patchwork Wed Sep 2 21:17:40 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297041 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=gIHXUGdM; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) (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 4hZwbR3JqYz1xwZ for ; Thu, 03 Sep 2026 07:19:31 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 0789081148; Wed, 2 Sep 2026 21:19:30 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 5R6jfZTqi4QX; Wed, 2 Sep 2026 21:19:27 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp1.osuosl.org 17958810CA Authentication-Results: smtp1.osuosl.org; arc=fail smtp.remote-ip=140.211.9.56 ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383954; b=R694Pnb7zx3iAb0PkltywikjwI8SKghzuda81m75I7AJ6DOV6byyHx2/Rhbs1+qPAfGj TbEU5WpRbIq/oumK5ij2rCSwZahCakesGMulQL/ULBTBmeh0KNV+KlRlU1gqLoB2y/1L3 k91Q/MwKrTVL1fTVhVgLMG1Eqtf3rdgqNNsO6MpwLS583LZybjrW/VBpcNQVn1KSvssXu ul40JBtrHSlL/Hu7Dbt9E92yp6+s3vui5LNmJVvyVFKU/YVNGzog289IrklSD7+gKnpO8 pJfePKUYGFhcwd7gUv/XaZaQNDuBg2ypbPwQjdIxtplWo6S+NAoSNYmgiE1RCmNkVDQ== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383954; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe:Cc: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=aFicePYThGrdfhUL/eXIn7zMjk9MjmnNXGLwif2bBL4=; b=qP2E1O5IuAEhnrbetbpD6iPjhATy5sPmSDzay4ac1jzCks6QgUCij0G/8Yv75COncx1N dJZdFe/UU1Him4aiC3MzHJQnc1bMeIeKoVnc399ZybdJUo4ynYjhkPGc7t08fTVJnRdLm emdizvLM59XH9taN5tbCvH+zsF9Y8Ww4ixG5fyzMN+umpi/w1ZGSloHvX4MNFeMWPz5xF K9rT9GNN0hwV1eS+2zH3lM6+EBkoevawZxn4fsRrutHFIznPKX6wH1Tsp9X3fGnFDGV8K sX5Mvm76M0Mg6K3Js/irBhLnZy0D9PodfGAtPZlsV3uUbgReb5pA6OkYieA0I8737EA== ARC-Authentication-Results: i=2; smtp1.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=gIHXUGdM; dmarc=pass header.from=magalu.cloud; arc=fail smtp.remote-ip=140.211.9.56 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= Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=gIHXUGdM Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp1.osuosl.org (Postfix) with ESMTPS id 17958810CA; Wed, 2 Sep 2026 21:19:14 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 03615C090A; Wed, 2 Sep 2026 21:19:14 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) by lists.linuxfoundation.org (Postfix) with ESMTP id 29CDCC0419 for ; Wed, 2 Sep 2026 21:19:12 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 19E0980FF7 for ; Wed, 2 Sep 2026 21:19:12 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id m5CobmN9UF_y for ; Wed, 2 Sep 2026 21:19:10 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp1.osuosl.org 180D5807D7 Authentication-Results: smtp1.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::1136" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383949; b=WVnFvbrKlBdmjha3/pyTL/XofgrVTmjeAv4flOEogjKP//vseh/cqJl9gomVcD+KiN++ VqLPq3Ivaar6bGP6Y6+6AkqRz2zc6QVneJ3dZmSh859HfQj02vh0fGQfIdsXZ4+8mB6nn 30j3qcH+G7dGWTrvnqIRiIF1v1VZFW6VdN22370WXy0z8c8az3VNqP7ypx+VGHHT8Xg5b 5NvJ2AmEKha6bkMiW7UqxkqNcKfljoeylvUskvFl80zpIm8iQCenyW4QvgwitJmMbwu2G 1Ywy8oR3mRUdvqTslzKzLslO2NCjzeKN4lHKf8HVU7zvXsBO/fwUEcF5dz4WxUtMXUQ== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383949; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=I+bZrXcNs5QIS3ceHvtHNJZI+auaDQy4gIowN9PdN+s=; b=ERjo9gJcfdxvIpuBLiLi2yfWgQwLR2ln2ck8385+FQ/dpN1+QS4WPnQvJuzJn/JYRAr+ W5+9ZOZk8g3yfiRaK6TnQ5GhYlgGuBwWajn3nXR0uxmhWLuAzZoUkO87m/NC+hitsxg8z +qpJonA4q4K8jCDvUAnrgSjoG2bKq5qnmgRlZDNFnQfdZvb8u+7KfJIoQGiM4WkiP2Lu6 S407nvCW+A+vBMNrkq6sD2Tw8yCm3hXAv3oQoEyc1eSh/amEmt706+fEA+Y3RvBiETjjj 4dMp0f4p2ZFy3WdzJ07StSrlVO9FJ/SUaj+dqXxDvKOBHjFfAK03bwkLU+lru697pdQ== ARC-Authentication-Results: i=1; smtp1.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=gIHXUGdM; arc=none smtp.remote-ip="2607:f8b0:4864:20::1136" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::1136; helo=mail-yw1-x1136.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp1.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Received: from mail-yw1-x1136.google.com (mail-yw1-x1136.google.com [IPv6:2607:f8b0:4864:20::1136]) by smtp1.osuosl.org (Postfix) with ESMTPS id 180D5807D7 for ; Wed, 2 Sep 2026 21:19:08 +0000 (UTC) Received: by mail-yw1-x1136.google.com with SMTP id 00721157ae682-861f30636f9so23797597b3.0 for ; Wed, 02 Sep 2026 14:19:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383948; x=1788988748; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=I+bZrXcNs5QIS3ceHvtHNJZI+auaDQy4gIowN9PdN+s=; b=gIHXUGdMiJpsWgh3+87BUitl7qKxEys4/xPxkplN5LIojhbjAFuy6ei55w+HB/JL1w ZYJjgEsmV5N3lw1BCQii16EemU5ypZcZ1I44QwALFvmOaO3R2UZakBylk0TCt12gv6Fi 0ROzsJO7fZwROZB1yQb85LOEVFMyritCCOihwq/xom/y6SvbP20pU11J3w7awihkx8RQ YsfxKE0WMeTnZBNcBtsofLW2w4701LVuYMO5bcJK038E5qMbvsJB/28tp3yh5Dk9zyPt j+XWeiiqcwIqhL1+08QZ8tNMjq81QTVs4XCj4OIEzqCPo6byE19R4PEPyXhmhqLDRhLz zscQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383948; x=1788988748; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=I+bZrXcNs5QIS3ceHvtHNJZI+auaDQy4gIowN9PdN+s=; b=JE453/8PqRJYgLRnrDlsCO8HxaFtrXZdyf/Gr8NPDIE36MlqQfg2Ci9/quq5T/ZAzS 5ZLVHVksjP/avd3DW8m0gmDITGFeqxEX8mOw2BxysNMgOfUUEPi6GLBgtF0fgwJwzmEy 1AzB12lVHY+k8j2KIGLIWbasepEmV01WENd38ZQ7rhO48zQC+c+quNOO8C36Weicaar/ xMBXxOvv9oWQQrfoL/35NRG2w1TAgl1QAwSOvMSy1MJ7lkwvIYjp0qGTpBh0iK9s2yQz V0UR/vFX8ngtI0MPoufRe7ZwyY+haq6LMfZwvBftMmAH4AzSeu+jaENsu3jNRnEn4mmM +6rg== X-Gm-Message-State: AFuF++m9haAQvuj/6HpqD5QYEWXQREAJdu7eutzQPV4QvFnLowxSjXVT bBZ+bI1Unmi1FO4EiI7mcV/2zwW+Tv3M4cVRXnVwWbCr0pCEJexgggyBgupgiYryg8W9HsxLvk6 lMJFhShqho9sQeOnL0RYyD6VIMW8rG/EXtk9xrf858YjnCpCYOJCndWNdnpGDx8Q= X-Gm-Gg: AYBFou2cFzSr492A+Tsf1VfCLclyruNYdcTCr0B0+1VBTDWKq/lVn//DCu3M2DxCItf 3FthTTbhD4OX7zufHUPByDPXDxakYbmI8X43tLGnZbC8BGhB67MyBoP/k3yogKIBVog7r7VmuJ1 iIseSFCLf8pRJ1u7fqlQZn2MfO37ixk+5pWdz2kCTmWm/tEotXagGmt3Bx6OxVzTwtwsWohPL26 p6SRsyRmY9OG2UsWSyqRMQZlG0MgBOCQ0hAzhetV+TLqIPxlVJSsAJHjh76PZfl0eyXmSoBZLaL WiHuXf7AT+Q013DZsRxN3C9jmwC23lc0C0J1bCXWvBu3Xa1oLwERP/FVKpNcs7kdPA0u4JicZsq qnsalugGYUWX814v5+DteoMsTSrs4H1pgWkEdFWjouxfMv4HkY0b5Cm81HVbkgAEV8Vomp4uk/x o+j3Ht08phaSgwRDt+2+7k9Pba1bTQjm359X4K8chS4SYVYLXINeLMxvYQOIV/N4emRLqP7PorY 0/wPNOBINv9iMV2uf9s9Jz8PGGcOY5S X-Received: by 2002:a05:690c:93:b0:80c:5ce9:8f29 with SMTP id 00721157ae682-86c5221a98emr42861187b3.22.1788383947326; Wed, 02 Sep 2026 14:19:07 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.19.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:19:06 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:40 -0300 Message-Id: <20260902211744.2159261-11-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 10/14] ic: Add incremental handler for the en_tr node. 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: , Cc: Tiago Matos Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Add the incremental change handler for the en_tr node and scope its incremental processing to the affected transit-router datapaths, so an IC-NB transit-router (or its port) change reconciles only that router instead of forcing a full recompute. Add a tests/ovn-ic.at test exercising the transit switch and router incremental handlers. Assisted-by: Claude Opus 4.8, Claude Code Co-authored-by: Tiago Matos Signed-off-by: Tiago Matos Signed-off-by: Paulo Guilherme Silva Acked-by: Lucas Vargas Dias --- ic/en-tr.c | 72 ++++++++++++++++++++++++++ ic/en-tr.h | 5 ++ ic/inc-proc-ic.c | 14 +++-- tests/ovn-ic.at | 131 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 4 deletions(-) diff --git a/ic/en-tr.c b/ic/en-tr.c index 9c1eed3de..d62883cc0 100644 --- a/ic/en-tr.c +++ b/ic/en-tr.c @@ -124,6 +124,78 @@ en_tr_run(struct engine_node *node, void *data OVS_UNUSED) return EN_UPDATED; } +/* Only transit-router mirror logical routers (options:interconn-tr) affect + * en_tr. A change to such a logical router is left to a full recompute + * (returns EN_UNHANDLED); any other logical router is irrelevant to en_tr, so + * its change is handled as a no-op, avoiding a recompute on unrelated NB + * Logical_Router updates. */ +enum engine_input_handler_result +en_tr_nb_logical_router_handler(struct engine_node *node, + void *data OVS_UNUSED) +{ + const struct ed_type_az *az = engine_get_input_data("az", node); + + if (!az->runned_az) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_router_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_router", node)); + const struct nbrec_logical_router *lr; + NBREC_LOGICAL_ROUTER_TABLE_FOR_EACH_TRACKED (lr, tbl) { + if (smap_get(&lr->options, "interconn-tr")) { + return EN_UNHANDLED; + } + } + + return EN_HANDLED_UNCHANGED; +} + +/* IC-SB Datapath_Binding: tr_run() reads only transit-router datapath bindings + * (it mirrors their tunnel_key into the NB logical router as + * requested-tnl-key). A new/deleted/modified transit-router binding - created + * by en_tunnel_key - forces a recompute so tr_run() publishes the committed + * key. Transit-switch datapath bindings - the bulk of the churn here - do not + * affect en_tr and are a no-op. A change to the type column cannot be + * classified (the old type is gone), so recompute to be safe. */ +enum engine_input_handler_result +en_tr_icsb_datapath_binding_handler(struct engine_node *node, + void *data OVS_UNUSED) +{ + const struct ed_type_az *az = engine_get_input_data("az", node); + + if (!az->runned_az) { + return EN_HANDLED_UNCHANGED; + } + + const struct icsbrec_datapath_binding_table *tbl = + EN_OVSDB_GET(engine_get_input("ICSB_datapath_binding", node)); + const struct icsbrec_datapath_binding *isb_dp; + ICSBREC_DATAPATH_BINDING_TABLE_FOR_EACH_TRACKED (isb_dp, tbl) { + if (icsbrec_datapath_binding_is_new(isb_dp) || + icsbrec_datapath_binding_is_deleted(isb_dp)) { + /* A new/deleted transit-router binding is the trigger en_tr needs; + * a transit-switch binding (the common churn) is a no-op. Classify + * by the row's own type, which is available on tracked deleted + * rows too - unlike track_is_updated(), which reports every + * column as updated on insert. */ + if (ic_dp_get_type(isb_dp) == IC_ROUTER) { + return EN_UNHANDLED; + } + } else if (ic_dp_get_type(isb_dp) == IC_ROUTER || + ovsdb_idl_track_is_updated( + &isb_dp->header_, + &icsbrec_datapath_binding_col_type)) { + /* A modified transit-router binding (e.g. tunnel_key/nb_ic_uuid), + * or a type change that can no longer be classified, forces a + * recompute. */ + return EN_UNHANDLED; + } + } + + return EN_HANDLED_UNCHANGED; +} + void * en_tr_init(struct engine_node *node OVS_UNUSED, struct engine_arg *arg OVS_UNUSED) diff --git a/ic/en-tr.h b/ic/en-tr.h index c558c0913..cc55a794f 100644 --- a/ic/en-tr.h +++ b/ic/en-tr.h @@ -21,4 +21,9 @@ enum engine_node_state en_tr_run(struct engine_node *node, void *data); void *en_tr_init(struct engine_node *node, struct engine_arg *arg); void en_tr_cleanup(void *data); +enum engine_input_handler_result +en_tr_nb_logical_router_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +en_tr_icsb_datapath_binding_handler(struct engine_node *node, void *data); + #endif /* EN_IC_TR_H */ diff --git a/ic/inc-proc-ic.c b/ic/inc-proc-ic.c index 89135fc5d..88c866f4b 100644 --- a/ic/inc-proc-ic.c +++ b/ic/inc-proc-ic.c @@ -238,12 +238,18 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_ts, &en_icsb_encap, NULL); /* en_tr: sync transit routers to their AZ NB Logical_Router mirrors. - * Like en_ts, IC-SB Datapath_Binding creation/keying is owned by - * en_tunnel_key. */ + * + * Like en_ts, en_tr builds its own transit-router IC-SB Datapath_Binding + * map each run and only maintains the NB mirror; IC-SB Datapath_Binding + * creation/keying is owned by en_tunnel_key. A transit-router binding + * change (created by en_tunnel_key) forces a recompute so en_tr publishes + * the committed key to requested-tnl-key. */ engine_add_input(&en_tr, &en_az, NULL); - engine_add_input(&en_tr, &en_icsb_datapath_binding, NULL); + engine_add_input(&en_tr, &en_icsb_datapath_binding, + en_tr_icsb_datapath_binding_handler); engine_add_input(&en_tr, &en_icnb_transit_router, NULL); - engine_add_input(&en_tr, &en_nb_logical_router, NULL); + engine_add_input(&en_tr, &en_nb_logical_router, + en_tr_nb_logical_router_handler); /* en_tunnel_key: the single owner of IC-SB Datapath_Binding creation, * tunnel-key allocation, VXLAN-range refresh and GC, for both transit diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at index bb8365cc9..793b18163 100644 --- a/tests/ovn-ic.at +++ b/tests/ovn-ic.at @@ -881,6 +881,137 @@ OVN_CLEANUP_IC([az1]) AT_CLEANUP ]) +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn-ic -- incremental processing - transit switch and router]) + +# The en_ts/en_tr NB Logical_Switch/Logical_Router change handlers must not +# trigger a full recompute for logical switches/routers that are not transit +# switch/router mirrors. + +ovn_init_ic_db +net_add n1 +ovn_start az1 +as az1 +check ovn-ic-nbctl --wait=sb ts-add ts1 +OVS_WAIT_UNTIL([test "$(ovn-nbctl --bare --columns=name \ + find logical_switch name=ts1)" = ts1]) + +# Quiesce the engine and reset stats so only the change under test counts. +check ovn-ic-nbctl --wait=sb sync +check ovn-appctl -t ic/ovn-ic inc-engine/recompute +check ovn-ic-nbctl --wait=sb sync +check ovn-appctl -t ic/ovn-ic inc-engine/clear-stats + +# Adding a regular logical switch and a regular logical router must be handled +# by the en_ts/en_tr handlers without a full recompute of those nodes. +check ovn-nbctl --wait=sb ls-add sw0 +check ovn-nbctl --wait=sb lr-add lr0 +OVS_WAIT_UNTIL([test "$(ovn-nbctl --bare --columns=name \ + find logical_router name=lr0)" = lr0]) +check ovn-ic-nbctl --wait=sb sync + +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats ts compute)" -gt 0]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats ts recompute)" = 0]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats tr compute)" -gt 0]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats tr recompute)" = 0]) + +# The transit switch mirror is still intact after the incremental update. +AT_CHECK([test "$(ovn-nbctl --bare --columns=name \ + find logical_switch name=ts1)" = ts1]) + +OVN_CLEANUP_IC([az1]) +AT_CLEANUP +]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn-ic -- incremental processing - transit switch handler scoping]) + +# A transit-switch add/delete in IC-NB must be applied by the en_ts +# en_icnb_transit_switch change handler without a full recompute of en_ts, +# keeping the NB Logical_Switch mirror and the IC-SB Datapath_Binding in sync. + +ovn_init_ic_db +net_add n1 +ovn_start az1 +as az1 +check ovn-ic-nbctl --wait=sb ts-add ts1 +OVS_WAIT_UNTIL([test "$(ovn-nbctl --bare --columns=name \ + find logical_switch name=ts1)" = ts1]) + +# Quiesce the engine and reset stats so only the change under test counts. +check ovn-ic-nbctl --wait=sb sync +check ovn-appctl -t ic/ovn-ic inc-engine/recompute +check ovn-ic-nbctl --wait=sb sync +check ovn-appctl -t ic/ovn-ic inc-engine/clear-stats + +# Adding a second transit switch is handled incrementally (no en_ts recompute) +# and creates its NB mirror and IC-SB datapath binding. +check ovn-ic-nbctl --wait=sb ts-add ts2 +OVS_WAIT_UNTIL([test "$(ovn-nbctl --bare --columns=name \ + find logical_switch name=ts2)" = ts2]) +check ovn-ic-nbctl --wait=sb sync + +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats ts compute)" -gt 0]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats ts recompute)" = 0]) +# The transit-switch IC-SB datapath binding en_tunnel_key creates must not +# churn en_tr: en_tr only consumes transit-router datapath bindings, so its +# en_icsb_datapath_binding handler treats a transit-switch binding as a no-op. +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats tr compute)" -gt 0]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats tr recompute)" = 0]) +# en_tunnel_key owns IC-SB datapath binding creation: a transit-switch add is +# handled by its scoped en_icnb_transit_switch handler, so it must not full +# recompute. +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats tunnel_key compute)" -gt 0]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats tunnel_key recompute)" = 0]) +AT_CHECK([test "$(ovn-ic-sbctl --bare --columns=type \ + find datapath_binding transit_switch=ts2)" = transit-switch]) + +# Deleting a transit switch is also handled incrementally: its NB mirror and +# IC-SB datapath binding are garbage-collected without a full recompute. +check ovn-ic-nbctl --wait=sb ts-del ts1 +OVS_WAIT_UNTIL([test -z "$(ovn-nbctl --bare --columns=name \ + find logical_switch name=ts1)"]) +check ovn-ic-nbctl --wait=sb sync + +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats ts compute)" -gt 0]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats ts recompute)" = 0]) +# A transit-switch delete is likewise reconciled incrementally in en_tunnel_key +# (its scoped en_icnb_transit_switch handler GCs the binding), not by a full +# recompute. +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats tunnel_key compute)" -gt 0]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats tunnel_key recompute)" = 0]) +AT_CHECK([test -z "$(ovn-ic-sbctl --bare --columns=type \ + find datapath_binding transit_switch=ts1)"]) + +# Deleting the NB mirror of a live transit switch out-of-band recreates it via +# the en_ts NB Logical_Switch handler, still without a full recompute. +check ovn-nbctl --wait=sb ls-del ts2 +check ovn-ic-nbctl --wait=sb sync +OVS_WAIT_UNTIL([test "$(ovn-nbctl --bare --columns=name \ + find logical_switch name=ts2)" = ts2]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats ts compute)" -gt 0]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats ts recompute)" = 0]) + +OVN_CLEANUP_IC([az1]) +AT_CLEANUP +]) + OVN_FOR_EACH_NORTHD([ AT_SETUP([ovn-ic -- port sync]) From patchwork Wed Sep 2 21:17:41 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297044 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=s5s5I7np; 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 4hZwc55jNWz1xwZ for ; Thu, 03 Sep 2026 07:20:05 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 65CF7608A6; Wed, 2 Sep 2026 21:20:04 +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 io8VCjG_XNOH; Wed, 2 Sep 2026 21:19:57 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp3.osuosl.org 1926B60B9C Authentication-Results: smtp3.osuosl.org; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383979; b=JbOjw0TEvqTvdF0Y9QwK+8bl7rwT4YIOzA5uXLaJDkbntqprR8KF0RZBEbXkWxq3yQQD qYRjfH+HNY8GyWbg88XjbeGcWu1VvIczpTz4NHG2HN+Y9k3wcWDluYZuHt7fSLOisztNj Ytzc0uoyBb5Xyqc3oJPdiu1uD3YhAWJNBxye8OFZktjcw0w2ij5OTSr3ENomLjgDApoVJ RIWpsMWXWjb8VYiRJADX//ID+KOAhw08WMPCbz8jbVJ2/L0H5t5oqTROb0ssyx7KD3qlN /+uBCt9ySGsgXQGBscbKupEjwtvYhq+nPGDzvEJG7f8j+nCrGaI3CtUUOrWaspA59kA== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383979; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=4B4d6Btc42GrJyZe3RO9mWW0PaNKvctXdwy+DFXhuwU=; b=H5RZqEnh8HrenHuBjX+1SbR8pFpNjdYVWUoo41PvrETjTns661xTV2Wicr6k8d8sfc34 zsBK9dUjIK0p6k0h0wVC4+jA0hVA5vyKaARKNNJUgNKT4LYKmYsbhXxHZHVg0RCHG4siN ciOpM29vejBqH2/QtexwsGZ3jSsq7mFoP5MJdENjReyX59OG3hRDzDTp3VasoDfTPJiLB WtJqNEZKFViJ3Puff9D2owgJ0AkS5efp9vf7taflr1tSWkjWH/GYweoy7g73WHq9YZNmz Sw4TJhNOcl/iZx7bnANXHOVvTlNUq6SxGf/rwGncDMaZbx6lDP9Givc92AxfWx9hsQw== ARC-Authentication-Results: i=2; smtp3.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=s5s5I7np; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" 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= Authentication-Results: smtp3.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=s5s5I7np Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp3.osuosl.org (Postfix) with ESMTPS id 1926B60B9C; Wed, 2 Sep 2026 21:19:39 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 57F29C0D0C; Wed, 2 Sep 2026 21:19:38 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) by lists.linuxfoundation.org (Postfix) with ESMTP id E1268C0418 for ; Wed, 2 Sep 2026 21:19:34 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id CAABA810DC for ; Wed, 2 Sep 2026 21:19:34 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id BostcA1Vc8Tb for ; Wed, 2 Sep 2026 21:19:27 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp1.osuosl.org EF726810C8 Authentication-Results: smtp1.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::1134" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383954; b=dwp6aYzgobOgVFx+irx2Qz2KoLLVvQkadS6XzM3DS8IwQvrDC5VNXw1Mh/rrAwyhylkm qyOxxBNkGZIIn/Zx+Z3FDjgS+gdMAib6VPMeK8s5570NIAViMPlIeWrIvIp/VcegxNucj I+T89NMtZ3j6HGCjbzu+wQK/Unm0TtI7fo2BHmAhKcJbIi0kDVWfs2lC1w7BDxaKNiN6e 6uXUUZADKDSaY0AXViXfmSTbzwc2EHhLNAVUM833orppB2drRQ6aybs4ie+F8UYW7ftdL fjWlFdi9kFXnL1QdhcCP23Yd+DOqDNhExPavoUAxZZM5sJgDFCdVdzX4JyfFRaDlmug== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383954; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=HDAvEwzTM+mvS2cy2gC8m1GLf7B5EHLFTOBaS4GSvqM=; b=BnSVVPVfBPWkPqHFNU0iDzDGl9u1UwVLwltgNcU9PtSny+bsa4g7CZFtbbZR0+48tNUt qM2E8QMcra+OkFuVS/ZP2024Or3OJ1icYfKebA72k4YsSl2Re4Wu4aOfBJvpiv3P1yCe7 ut8VKC+JUCcoPwGnf5diilOqqQWQRFy0OQPLYJb0BdHgg2uCgIQsZUylcwiG5hm5fE8PC oVUdVKTceK6TjbAOx+kHHNXjUqLUJ2O4ubk5KR/zbJhRsHJRlC15/HNCzpHgy9gyJV3K4 005zckS7che4LppcOtO8IlRbBw9qDXTF18QiHW63GvNp88CjDYR0fuIOjwZYDzsIftg== ARC-Authentication-Results: i=1; smtp1.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=s5s5I7np; arc=none smtp.remote-ip="2607:f8b0:4864:20::1134" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::1134; helo=mail-yw1-x1134.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp1.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Authentication-Results: smtp1.osuosl.org; dkim=pass (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=s5s5I7np Received: from mail-yw1-x1134.google.com (mail-yw1-x1134.google.com [IPv6:2607:f8b0:4864:20::1134]) by smtp1.osuosl.org (Postfix) with ESMTPS id EF726810C8 for ; Wed, 2 Sep 2026 21:19:13 +0000 (UTC) Received: by mail-yw1-x1134.google.com with SMTP id 00721157ae682-866e57f63a3so21666177b3.3 for ; Wed, 02 Sep 2026 14:19:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383952; x=1788988752; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=HDAvEwzTM+mvS2cy2gC8m1GLf7B5EHLFTOBaS4GSvqM=; b=s5s5I7np0NRL9/89sk1lvXztt4ANEDYkb9ZuVyVKKXfY+caUyueV/lvijUCKJVQ655 DZc+H1YRIN6GDkwSpG+kuApobkSwMTPWAPhNS0QVG5TqnuJQuGYqMAGLfhjuYOkZjAUo DBRbt6cCZAj3HgBc2CE2QINhPey3+1A669XxORHM7TFFjoyaUOMhUEVb1dZtNAnZQqsm BsixOWM8R4KoW/VxrgxXa/5neA/T7qaRnTWB1rXPaxKbYTyulpHIHtYNtTrENTauS5dd s2V8WNc+whAUKTwdlXrUGveivb4DrjkWzk8cc1nJ6qwIdTdVfDCmoqtJM1P3LVH+d5t/ uiPA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383952; x=1788988752; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=HDAvEwzTM+mvS2cy2gC8m1GLf7B5EHLFTOBaS4GSvqM=; b=LJ3XqnM0LeCyqMBBmOihjNHP/fKYCbQ+OSWANiiEfb+RTpm2GY7ZYEPJY4YsNhwZUr q+gzeF8qOLuNEpm0AaU+r/pNAymH7+Lwkbouo+Taf4tpRNHiet/8LAbdvYJVIJClMSAT xLLodaIu4C4+DmPhRRZWVzI66vSHQzTupzN/xei/2lG2tz5ySjjEYQfGj3/UphbjH1V/ TsDBRQ5jf0lWKpB4mdGkJUswd/swEs//fDlj9GZVyB6BC1hAEk1HQsKLxbEEJORzLrcX Y+FXCdjozrnB2Wlsh2rECq5myeYYA7KEXtrLkcLlJRjNQxhglv/NuEgo0oPD4G0ts/BS oiuQ== X-Gm-Message-State: AFuF++mZxyFM/NuUFl8m2Ykuqzd/JBB123bMEBYcsOcuvZ+zWrJG8Uy0 hNccNmE2oMX4qYC/TI92ZxTUTYhS0yvXgfqyS3JoldGjKzbByPGm5+AIMYwk9a/8MN24xoBFp0N 0qs+ItceoSo4ac3RcydG7GgpigBqT6lOrW1Rf4hkouVrggQcbZgGjHWq3m+UnQlU= X-Gm-Gg: AYBFou14KlLLltPusm+qJ1UNE4I9bkX73llUcj1liEH7m45hXwCMfzSO2+3vC0kHanM cCMvbNdbSIf/lD2JHZ8zgacAH3fY9x9RlVwaYKF9aFh8GkgklBSqGm4mwTEzn1eqvreO+0+A+8l bFFWF5YgVBRhbyJPA4am2RVpoIveeVsJ7NEa1KjFNkxiFLUp+wxB8/OFvjf1c+jkA0LPKUs5n4X +33Ycb1xABG5y1fhKR75VfDwnC1ENcXEg2ZFDYRr1dMxpN9D8c+GcoldBZBnAs9189MQNkM5jpU RViGd5sQKdqSaDjpKRiu/6H8/hOysYRx9mNWecb+++TIkKzszEhebMdh5KgKZ5FYG0HOdDiGw9J Q/yVP5TVtG19cHSfURj5nYM1oilR+AJjE8t6M7TUSMiTAbdy/TWk/7EB+DaMjGxeluqCGd9ZJuS o6dINSf7neekEAC2eTRBjvwtyKzpwVPp0X0+bPBHHI1AoK+9+9VhYko7M5GzSgWbV4bX0RYKVyH DLGCzn1taytgqNuFgmvEHIsGytL6cdl X-Received: by 2002:a05:690c:c505:b0:854:5db3:4ed0 with SMTP id 00721157ae682-86c54d0df49mr43356637b3.31.1788383951035; Wed, 02 Sep 2026 14:19:11 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.19.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:19:10 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:41 -0300 Message-Id: <20260902211744.2159261-12-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 11/14] ic: Add incremental handler for the en_port_binding node. 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: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Add the incremental change handlers for the en_port_binding node and scope its processing per transit switch / transit router: an IC-NB, IC-SB Port_Binding, NB Logical_Switch/Router (or port), SB Port_Binding or SB Chassis change reconciles only the affected entities instead of forcing a full recompute. Supporting changes folded in: - Extract per-entity port-binding sync functions and resolve a transit port -> switch/router via a prebuilt map, so scoped reconciles are O(scope). - Allocate transit-port tunnel keys per transit switch/router, tolerate duplicate IC-SB port bindings, and make lrp_create()/lsp_create() idempotent (reuse an NB port already inserted uncommitted this run). - Handle an IC-NB Transit_Switch_Port column change by reconciling its owning transit switch (a port add/delete is already covered by the Transit_Switch handler through its 'ports' column), mirroring the Transit_Router_Port handler. - Gate the node on the AZ identity and reconcile each transit switch / router at most once per engine run (synced_ts/synced_tr + clear_tracked_data). Add a tests/ovn-ic.at test for the port-binding handler scoping. Assisted-by: Claude Opus 4.8, Claude Code Signed-off-by: Paulo Guilherme Silva Acked-by: Lucas Vargas Dias --- ic/en-port-binding.c | 1603 +++++++++++++++++++++++++++++++++++++++++- ic/en-port-binding.h | 28 + ic/inc-proc-ic.c | 52 +- ic/ovn-ic.c | 846 +--------------------- ic/ovn-ic.h | 22 +- tests/ovn-ic.at | 147 ++++ 6 files changed, 1870 insertions(+), 828 deletions(-) diff --git a/ic/en-port-binding.c b/ic/en-port-binding.c index 838b86f69..7111242d6 100644 --- a/ic/en-port-binding.c +++ b/ic/en-port-binding.c @@ -14,14 +14,1133 @@ #include -#include "en-port-binding.h" #include "en-az.h" +#include "en-port-binding.h" #include "lib/inc-proc-eng.h" +#include "lib/ovn-ic-nb-idl.h" +#include "lib/ovn-ic-sb-idl.h" +#include "lib/ovn-nb-idl.h" +#include "lib/ovn-sb-idl.h" #include "openvswitch/vlog.h" #include "ovn-ic.h" +#include "ovsdb-idl.h" +#include "smap.h" +#include "sset.h" VLOG_DEFINE_THIS_MODULE(en_ic_port_binding); +static const struct nbrec_logical_router * +find_tr_in_nb(struct ic_context *ctx, char *tr_name) +{ + const struct nbrec_logical_router *key = + nbrec_logical_router_index_init_row(ctx->nbrec_lr_by_name); + nbrec_logical_router_index_set_name(key, tr_name); + + const struct nbrec_logical_router *lr; + bool found = false; + NBREC_LOGICAL_ROUTER_FOR_EACH_EQUAL (lr, key, ctx->nbrec_lr_by_name) { + if (smap_get(&lr->options, "interconn-tr")) { + found = true; + break; + } + } + + nbrec_logical_router_index_destroy_row(key); + if (found) { + return lr; + } + + return NULL; +} + +static const struct sbrec_port_binding * +find_peer_port(struct ic_context *ctx, + const struct sbrec_port_binding *sb_pb) +{ + const char *peer_name = smap_get(&sb_pb->options, "peer"); + if (!peer_name) { + return NULL; + } + + return find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, peer_name); +} + +static const struct sbrec_port_binding * +find_crp_from_lrp(struct ic_context *ctx, + const struct sbrec_port_binding *lrp_pb) +{ + char *crp_name = ovn_chassis_redirect_name(lrp_pb->logical_port); + + const struct sbrec_port_binding *pb = + find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, crp_name); + + free(crp_name); + return pb; +} + +static const struct sbrec_port_binding * +find_crp_for_sb_pb(struct ic_context *ctx, + const struct sbrec_port_binding *sb_pb) +{ + const struct sbrec_port_binding *peer = find_peer_port(ctx, sb_pb); + if (!peer) { + return NULL; + } + + return find_crp_from_lrp(ctx, peer); +} + +static const char * +get_lp_address_for_sb_pb(struct ic_context *ctx, + const struct sbrec_port_binding *sb_pb) +{ + const struct nbrec_logical_switch_port *nb_lsp; + + nb_lsp = get_lsp_by_ts_port_name(ctx, sb_pb->logical_port); + if (!nb_lsp) { + /* The SB Port_Binding's logical_port does not correspond to any NB + * logical switch port (e.g. an out-of-band record with a bogus + * logical_port). There is no address to derive. */ + return NULL; + } + if (!strcmp(nb_lsp->type, "switch")) { + /* Switches always have implicit "unknown" address, and IC-SB port + * binding can only have one address specified. */ + return "unknown"; + } + + const struct sbrec_port_binding *peer = find_peer_port(ctx, sb_pb); + if (!peer) { + return NULL; + } + + return peer->n_mac ? *peer->mac : NULL; +} + +static void +sync_lsp_tnl_key(const struct nbrec_logical_switch_port *lsp, + int64_t isb_tnl_key) +{ + int64_t tnl_key = smap_get_int(&lsp->options, "requested-tnl-key", 0); + if (tnl_key != isb_tnl_key) { + VLOG_DBG("Set options:requested-tnl-key %"PRId64 + " for lsp %s in NB.", isb_tnl_key, lsp->name); + char *tnl_key_str = xasprintf("%"PRId64, isb_tnl_key); + nbrec_logical_switch_port_update_options_setkey(lsp, + "requested-tnl-key", + tnl_key_str); + free(tnl_key_str); + } + +} + +static inline void +sync_lrp_tnl_key(const struct nbrec_logical_router_port *lrp, + int64_t isb_tnl_key) +{ + int64_t tnl_key = smap_get_int(&lrp->options, "requested-tnl-key", 0); + if (tnl_key != isb_tnl_key) { + VLOG_DBG("Set options:requested-tnl-key %" PRId64 " for lrp %s in NB.", + isb_tnl_key, lrp->name); + char *tnl_key_str = xasprintf("%"PRId64, isb_tnl_key); + nbrec_logical_router_port_update_options_setkey( + lrp, "requested-tnl-key", tnl_key_str); + free(tnl_key_str); + } +} + +static bool +get_router_uuid_by_sb_pb(struct ic_context *ctx, + const struct sbrec_port_binding *sb_pb, + struct uuid *router_uuid) +{ + const struct sbrec_port_binding *router_pb = find_peer_port(ctx, sb_pb); + if (!router_pb || !router_pb->datapath) { + return false; + } + + return datapath_get_nb_uuid(router_pb->datapath, router_uuid); +} + +static void +update_isb_pb_external_ids(struct ic_context *ctx, + const struct sbrec_port_binding *sb_pb, + const struct icsbrec_port_binding *isb_pb) +{ + struct uuid lr_uuid; + if (!get_router_uuid_by_sb_pb(ctx, sb_pb, &lr_uuid)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Can't get router uuid for transit switch port %s.", + isb_pb->logical_port); + return; + } + + struct uuid current_lr_uuid; + if (smap_get_uuid(&isb_pb->external_ids, "router-id", ¤t_lr_uuid) && + uuid_equals(&lr_uuid, ¤t_lr_uuid)) { + return; + } + + char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&lr_uuid)); + icsbrec_port_binding_update_external_ids_setkey(isb_pb, "router-id", + uuid_s); + free(uuid_s); +} + +/* Sync a local port's fields from TSP INB->ISB. */ +static void +sync_tsp_pb(const struct icnbrec_transit_switch_port *tsp, + const struct icsbrec_port_binding *isb_pb) +{ + if (!isb_pb) { + return; + } + + /* Sync address to ISB. */ + if (!tsp->n_addresses && isb_pb->address[0]) { + icsbrec_port_binding_set_address(isb_pb, ""); + } else if (tsp->n_addresses && + strcmp(tsp->addresses[0], isb_pb->address)) { + icsbrec_port_binding_set_address(isb_pb, tsp->addresses[0]); + } + + /* Sync gateway to ISB. */ + if (strcmp(tsp->chassis, isb_pb->gateway)) { + icsbrec_port_binding_set_gateway(isb_pb, tsp->chassis); + } +} + +/* For each local port: + * - Sync from NB to ISB. + * - Sync gateway from SB to ISB. + * - Sync tunnel key from ISB to NB. + */ +static void +sync_local_port(struct ic_context *ctx, + const struct icsbrec_port_binding *isb_pb, + const struct sbrec_port_binding *sb_pb, + const struct nbrec_logical_switch_port *lsp) +{ + /* Sync address from NB to ISB */ + const char *address = get_lp_address_for_sb_pb(ctx, sb_pb); + if (!address) { + VLOG_DBG("Can't get router/switch port address for logical" + " switch port %s", sb_pb->logical_port); + if (isb_pb->address[0]) { + icsbrec_port_binding_set_address(isb_pb, ""); + } + } else { + if (strcmp(address, isb_pb->address)) { + icsbrec_port_binding_set_address(isb_pb, address); + } + } + + /* Sync gateway from SB to ISB */ + const struct sbrec_port_binding *crp = find_crp_for_sb_pb(ctx, sb_pb); + if (crp && crp->chassis) { + if (strcmp(crp->chassis->name, isb_pb->gateway)) { + icsbrec_port_binding_set_gateway(isb_pb, crp->chassis->name); + } + } else if (!strcmp(lsp->type, "switch") && sb_pb->chassis) { + if (strcmp(sb_pb->chassis->name, isb_pb->gateway)) { + icsbrec_port_binding_set_gateway(isb_pb, sb_pb->chassis->name); + } + } else { + if (isb_pb->gateway[0]) { + icsbrec_port_binding_set_gateway(isb_pb, ""); + } + } + + /* Sync external_ids:router-id to ISB */ + update_isb_pb_external_ids(ctx, sb_pb, isb_pb); + + /* Sync back tunnel key from ISB to NB */ + sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); +} + +static void +sync_tsp_router_peer(const struct icnbrec_transit_switch_port *tsp, + const struct nbrec_logical_switch_port *lsp) +{ + /* Clear the peer for router port type. */ + if (lsp->peer && lsp->peer[0]) { + nbrec_logical_switch_port_set_peer(lsp, NULL); + } + + if (tsp->peer[0]) { + const char *rp = smap_get(&lsp->options, "router-port"); + if (!rp || strcmp(rp, tsp->peer)) { + nbrec_logical_switch_port_update_options_setkey( + lsp, "router-port", tsp->peer); + } + } else if (smap_get(&lsp->options, "router-port")) { + nbrec_logical_switch_port_update_options_delkey(lsp, "router-port"); + } +} + +static void +sync_tsp_non_router_peer(const struct icnbrec_transit_switch_port *tsp, + const struct nbrec_logical_switch_port *lsp) +{ + /* Clear router-port setting for non-routers. */ + if (smap_get(&lsp->options, "router-port")) { + nbrec_logical_switch_port_update_options_delkey(lsp, "router-port"); + } + + if (tsp->peer[0]) { + if (!lsp->peer || strcmp(lsp->peer, tsp->peer)) { + nbrec_logical_switch_port_set_peer(lsp, tsp->peer); + } + } else if (lsp->peer && lsp->peer[0]) { + nbrec_logical_switch_port_set_peer(lsp, NULL); + } +} + +static void +sync_tsp_common(const struct icnbrec_transit_switch_port *tsp, + const struct icsbrec_port_binding *isb_pb, + const struct nbrec_logical_switch_port *lsp) +{ + /* Sync addresses from TSP to NB. */ + nbrec_logical_switch_port_set_addresses( + lsp, (const char **) tsp->addresses, tsp->n_addresses); + + if (tsp->chassis[0]) { + const char *current = smap_get(&lsp->options, "requested-chassis"); + if (!current || strcmp(current, tsp->chassis)) { + nbrec_logical_switch_port_update_options_setkey( + lsp, "requested-chassis", tsp->chassis); + } + } else if (smap_get(&lsp->options, "requested-chassis")) { + nbrec_logical_switch_port_update_options_delkey( + lsp, "requested-chassis"); + } + + /* Sync tunnel key from ISB to NB. */ + sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); + + if (!strcmp(tsp->type, "router")) { + sync_tsp_router_peer(tsp, lsp); + } else { + sync_tsp_non_router_peer(tsp, lsp); + } +} + +static void +sync_tsp_local(const struct icnbrec_transit_switch_port *tsp, + const struct icsbrec_port_binding *isb_pb, + const struct nbrec_logical_switch_port *lsp) +{ + if (strcmp(lsp->type, tsp->type)) { + nbrec_logical_switch_port_set_type(lsp, tsp->type); + } + sync_tsp_common(tsp, isb_pb, lsp); +} + +static void +sync_tsp_remote(const struct icnbrec_transit_switch_port *tsp, + const struct icsbrec_port_binding *isb_pb, + const struct nbrec_logical_switch_port *lsp) +{ + if (strcmp(lsp->type, "remote")) { + nbrec_logical_switch_port_set_type(lsp, "remote"); + } + sync_tsp_common(tsp, isb_pb, lsp); +} + +/* For each remote port: + * - Sync from ISB to NB + * - Sync gateway from ISB to SB + */ +static void +sync_remote_port(struct ic_context *ctx, + const struct icsbrec_port_binding *isb_pb, + const struct nbrec_logical_switch_port *lsp, + const struct sbrec_port_binding *sb_pb) +{ + /* Sync address from ISB to NB */ + if (isb_pb->address[0]) { + if (lsp->n_addresses != 1 || + strcmp(isb_pb->address, lsp->addresses[0])) { + nbrec_logical_switch_port_set_addresses( + lsp, (const char **)&isb_pb->address, 1); + } + } else { + if (lsp->n_addresses != 0) { + nbrec_logical_switch_port_set_addresses(lsp, NULL, 0); + } + } + + /* Sync tunnel key from ISB to NB */ + sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); + + /* Skip port binding if it is already requested by the CMS. */ + if (smap_get(&lsp->options, "requested-chassis")) { + return; + } + + /* Sync gateway from ISB to SB */ + if (isb_pb->gateway[0]) { + if (!sb_pb->chassis || strcmp(sb_pb->chassis->name, isb_pb->gateway)) { + const struct sbrec_chassis *chassis = + find_sb_chassis(ctx, isb_pb->gateway); + if (!chassis) { + VLOG_DBG("Chassis %s is not found in SB, syncing from ISB " + "to SB skipped for logical port %s.", + isb_pb->gateway, lsp->name); + return; + } + sbrec_port_binding_set_chassis(sb_pb, chassis); + } + } else { + if (sb_pb->chassis) { + sbrec_port_binding_set_chassis(sb_pb, NULL); + } + } +} + +/* For each remote port: + * - Sync from ISB to NB + */ +static void +sync_router_port(const struct icsbrec_port_binding *isb_pb, + const struct icnbrec_transit_router_port *trp, + const struct nbrec_logical_router_port *lrp) +{ + /* Sync from ICNB to NB */ + if (trp->chassis[0]) { + const char *chassis_name = + smap_get_def(&lrp->options, "requested-chassis", ""); + if (strcmp(trp->chassis, chassis_name)) { + nbrec_logical_router_port_update_options_setkey( + lrp, "requested-chassis", trp->chassis); + } + } else if (smap_get(&lrp->options, "requested-chassis")) { + nbrec_logical_router_port_update_options_delkey( + lrp, "requested-chassis"); + } + + if (strcmp(trp->mac, lrp->mac)) { + nbrec_logical_router_port_set_mac(lrp, trp->mac); + } + + bool sync_networks = false; + if (trp->n_networks != lrp->n_networks) { + sync_networks = true; + } else { + for (size_t i = 0; i < trp->n_networks; i++) { + if (strcmp(trp->networks[i], lrp->networks[i])) { + sync_networks = true; + break; + } + } + } + + if (sync_networks) { + nbrec_logical_router_port_set_networks( + lrp, (const char **) trp->networks, trp->n_networks); + } + + /* Sync tunnel key from ISB to NB */ + sync_lrp_tnl_key(lrp, isb_pb->tunnel_key); +} + +static void +create_nb_lsp(struct ic_context *ctx, + const struct icsbrec_port_binding *isb_pb, + const struct nbrec_logical_switch *ls) +{ + const struct nbrec_logical_switch_port *lsp = + nbrec_logical_switch_port_insert(ctx->ovnnb_txn); + nbrec_logical_switch_port_set_name(lsp, isb_pb->logical_port); + nbrec_logical_switch_port_set_type(lsp, "remote"); + + bool up = true; + nbrec_logical_switch_port_set_up(lsp, &up, 1); + + if (isb_pb->address[0]) { + nbrec_logical_switch_port_set_addresses( + lsp, (const char **)&isb_pb->address, 1); + } + sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); + nbrec_logical_switch_update_ports_addvalue(ls, lsp); +} + +static uint32_t +allocate_port_key(struct hmap *pb_tnlids) +{ + static uint32_t hint; + return ovn_allocate_tnlid(pb_tnlids, "transit port", + 1, (1u << 15) - 1, &hint); +} + +/* Look up a transit switch / router by name through an index instead of + * scanning every IC-NB row, so a scoped reconcile is O(scope) rather than + * O(all transit switches/routers). */ +static const struct icnbrec_transit_switch * +find_transit_switch_by_name(struct ic_context *ctx, const char *name) +{ + const struct icnbrec_transit_switch *key = + icnbrec_transit_switch_index_init_row( + ctx->icnbrec_transit_switch_by_name); + icnbrec_transit_switch_index_set_name(key, name); + const struct icnbrec_transit_switch *ts = + icnbrec_transit_switch_index_find(ctx->icnbrec_transit_switch_by_name, + key); + icnbrec_transit_switch_index_destroy_row(key); + return ts; +} + +static const struct icnbrec_transit_router * +find_transit_router_by_name(struct ic_context *ctx, const char *name) +{ + const struct icnbrec_transit_router *key = + icnbrec_transit_router_index_init_row( + ctx->icnbrec_transit_router_by_name); + icnbrec_transit_router_index_set_name(key, name); + const struct icnbrec_transit_router *tr = + icnbrec_transit_router_index_find(ctx->icnbrec_transit_router_by_name, + key); + icnbrec_transit_router_index_destroy_row(key); + return tr; +} + +static const struct icsbrec_port_binding * +create_isb_pb(struct ovsdb_idl_txn *isb_txn, const char *logical_port, + const struct icsbrec_availability_zone *az, const char *ts_name, + const struct uuid *nb_ic_uuid, const char *type, + struct hmap *pb_tnlids) +{ + uint32_t pb_tnl_key = allocate_port_key(pb_tnlids); + if (!pb_tnl_key) { + return NULL; + } + + const struct icsbrec_port_binding *isb_pb = + icsbrec_port_binding_insert(isb_txn); + icsbrec_port_binding_set_availability_zone(isb_pb, az); + icsbrec_port_binding_set_transit_switch(isb_pb, ts_name); + icsbrec_port_binding_set_logical_port(isb_pb, logical_port); + icsbrec_port_binding_set_tunnel_key(isb_pb, pb_tnl_key); + icsbrec_port_binding_set_nb_ic_uuid(isb_pb, nb_ic_uuid, 1); + icsbrec_port_binding_set_type(isb_pb, type); + return isb_pb; +} + +static bool +chassis_is_remote(struct ic_context *ctx, const char *chassis_name) +{ + if (chassis_name) { + const struct sbrec_chassis *chassis = + find_sb_chassis(ctx, chassis_name); + if (chassis) { + return smap_get_bool(&chassis->other_config, "is-remote", false); + } else { + return true; + } + } + + return false; +} + +static struct nbrec_logical_router_port * +lrp_create(struct ic_context *ctx, const struct nbrec_logical_router *lr, + const struct icnbrec_transit_router_port *trp) +{ + /* An LRP with this name may already exist as an uncommitted insert from an + * earlier change handler in this same engine iteration: the engine can run + * change handlers and then recompute the node in one iteration (a later + * input handler returning EN_UNHANDLED triggers run() without rolling back + * the earlier handlers' NB writes - see inc-proc-eng engine_recompute()). + * That row is invisible to the caller's lr->ports scan (the pending + * addvalue is not reflected until commit) but is present in the by-name + * index, so reuse it instead of inserting a duplicate, which would violate + * the Logical_Router_Port name uniqueness index and abort the txn. */ + const struct nbrec_logical_router_port *existing = + get_lrp_by_lrp_name(ctx, trp->name); + if (existing) { + return CONST_CAST(struct nbrec_logical_router_port *, existing); + } + + struct nbrec_logical_router_port *lrp = + nbrec_logical_router_port_insert(ctx->ovnnb_txn); + nbrec_logical_router_port_set_name(lrp, trp->name); + + nbrec_logical_router_port_update_options_setkey(lrp, "interconn-tr", + trp->name); + nbrec_logical_router_update_ports_addvalue(lr, lrp); + return lrp; +} + +static void +sync_ts_isb_pb(struct ic_context *ctx, const struct sbrec_port_binding *sb_pb, + const struct icsbrec_port_binding *isb_pb) +{ + const char *address = get_lp_address_for_sb_pb(ctx, sb_pb); + if (address) { + icsbrec_port_binding_set_address(isb_pb, address); + } + + const struct sbrec_port_binding *crp = find_crp_for_sb_pb(ctx, sb_pb); + if (crp && crp->chassis) { + icsbrec_port_binding_set_gateway(isb_pb, crp->chassis->name); + } + + update_isb_pb_external_ids(ctx, sb_pb, isb_pb); + + /* Sync encap so that multiple encaps can be used for the same + * gateway. However, it is not needed for now, since we don't yet + * support specifying encap type/ip for gateway chassis or ha-chassis + * for logical router port in NB DB, and now encap should always be + * empty. The sync can be added if we add such support for gateway + * chassis/ha-chassis in NB DB. */ +} + +static struct nbrec_logical_switch_port * +lsp_create(struct ic_context *ctx, const struct nbrec_logical_switch *ls, + const struct icnbrec_transit_switch_port *tsp) +{ + /* Reuse an LSP already inserted (uncommitted) by an earlier change handler + * in this same engine iteration: the engine can run change handlers and + * then recompute the node in one iteration, and the pending row is + * invisible to the caller's ls->ports scan but present in the by-name + * index. Inserting a duplicate would violate the name uniqueness index. + * See the note in lrp_create(). */ + const struct nbrec_logical_switch_port *existing = + get_lsp_by_ts_port_name(ctx, tsp->name); + if (existing) { + return CONST_CAST(struct nbrec_logical_switch_port *, existing); + } + + struct nbrec_logical_switch_port *lsp = + nbrec_logical_switch_port_insert(ctx->ovnnb_txn); + nbrec_logical_switch_port_set_name(lsp, tsp->name); + + nbrec_logical_switch_port_update_options_setkey(lsp, "interconn-tsp", + tsp->name); + nbrec_logical_switch_update_ports_addvalue(ls, lsp); + return lsp; +} + +static const struct sbrec_port_binding * +find_lsp_in_sb(struct ic_context *ctx, + const struct nbrec_logical_switch_port *lsp) +{ + return find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, lsp->name); +} + +/* Reconcile the IC-SB and NB port bindings of a single transit switch 'ts' + * (against its NB logical-switch mirror). Local IC-SB ports that are matched + * to an existing transit-switch port are removed from 'switch_all_local_pbs' + * so the caller can delete the leftovers as orphans. + * + * The tunnel-key pool is built here, per transit switch, from every + * binding on this switch -- both this AZ's (local) and the other AZs' + * (remote). The IC-SB uniqueness constraint is on (transit_switch, + * tunnel_key) across all AZs, so a per-AZ pool could hand out a key already + * used by a remote AZ on the same switch and the commit would fail. Seeding + * from the per-switch 'by_ts' scan (which this function already performs) + * makes allocations unique per switch as the constraint requires, and lets + * keys be reused across switches so the 15-bit space is not exhausted. */ +static void +port_binding_sync_ts(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + const struct icnbrec_transit_switch *ts, + struct shash *switch_all_local_pbs) +{ + const struct nbrec_logical_switch *ls = find_ts_in_nb(ctx, ts->name); + if (!ls) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "Transit switch %s not found in NB.", ts->name); + return; + } + + struct shash nb_ports = SHASH_INITIALIZER(&nb_ports); + struct shash old_nb_ports = SHASH_INITIALIZER(&old_nb_ports); + struct shash local_pbs = SHASH_INITIALIZER(&local_pbs); + struct shash remote_pbs = SHASH_INITIALIZER(&remote_pbs); + struct hmap pb_tnlids = HMAP_INITIALIZER(&pb_tnlids); + const struct icsbrec_port_binding *isb_pb; + struct shash_node *node; + + for (size_t i = 0; i < ls->n_ports; i++) { + const struct nbrec_logical_switch_port *lsp = ls->ports[i]; + if (smap_get(&lsp->options, "interconn-tsp")) { + shash_add(&nb_ports, lsp->name, lsp); + } else { + shash_add(&old_nb_ports, lsp->name, lsp); + } + } + + const struct icsbrec_port_binding *isb_pb_key = + icsbrec_port_binding_index_init_row(ctx->icsbrec_port_binding_by_ts); + icsbrec_port_binding_index_set_transit_switch(isb_pb_key, ts->name); + + ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, + ctx->icsbrec_port_binding_by_ts) { + if (isb_pb->availability_zone == runned_az) { + shash_add(&local_pbs, isb_pb->logical_port, isb_pb); + shash_find_and_delete(switch_all_local_pbs, isb_pb->logical_port); + } else { + /* One NB Logical_Switch_Port is created per entry below, and + * the NB port name is unique, so deduplicate by logical_port: + * two bindings naming the same port would otherwise become two + * colliding inserts and fail the commit on every run. */ + shash_add_once(&remote_pbs, isb_pb->logical_port, isb_pb); + } + /* Reserve every key in use on this switch (local and remote). */ + ovn_add_tnlid(&pb_tnlids, isb_pb->tunnel_key); + } + icsbrec_port_binding_index_destroy_row(isb_pb_key); + + for (size_t i = 0; i < ts->n_ports; i++) { + struct icnbrec_transit_switch_port *tsp = ts->ports[i]; + + if (!tsp->chassis[0]) { + isb_pb = shash_find_and_delete(&local_pbs, tsp->name); + if (!isb_pb) { + isb_pb = shash_find_and_delete(&remote_pbs, tsp->name); + } + + if (ctx->ovnisb_txn && is_az_leader(ctx->ovnisb_txn)) { + if (!isb_pb) { + isb_pb = create_isb_pb( + ctx->ovnisb_txn, tsp->name, runned_az, + ts->name, &ts->header_.uuid, "transit-switch-port", + &pb_tnlids); + } + sync_tsp_pb(tsp, isb_pb); + } + } else if (!chassis_is_remote(ctx, tsp->chassis)) { + /* Create ISB port_binding as its chassis is local. */ + isb_pb = shash_find_and_delete(&local_pbs, tsp->name); + if (!isb_pb) { + isb_pb = create_isb_pb(ctx->ovnisb_unlocked_txn, tsp->name, + runned_az, + ts->name, &ts->header_.uuid, + "transit-switch-port", &pb_tnlids); + } + sync_tsp_pb(tsp, isb_pb); + } else { + isb_pb = shash_find_and_delete(&remote_pbs, tsp->name); + } + + if (!isb_pb) { + continue; + } + + const struct nbrec_logical_switch_port *lsp = + shash_find_and_delete(&nb_ports, tsp->name); + if (!lsp) { + lsp = lsp_create(ctx, ls, tsp); + } + + if (!tsp->chassis[0] || !chassis_is_remote(ctx, tsp->chassis)) { + sync_tsp_local(tsp, isb_pb, lsp); + } else { + sync_tsp_remote(tsp, isb_pb, lsp); + } + } + + /* Support legacy way of adding transit switch ports. */ + const struct sbrec_port_binding *sb_pb; + const struct nbrec_logical_switch_port *lsp; + SHASH_FOR_EACH (node, &old_nb_ports) { + lsp = node->data; + + if (!strcmp(lsp->type, "router") + || !strcmp(lsp->type, "switch")) { + /* The port is local. */ + sb_pb = find_lsp_in_sb(ctx, lsp); + if (!sb_pb) { + continue; + } + isb_pb = shash_find_and_delete(&local_pbs, lsp->name); + if (!isb_pb) { + isb_pb = create_isb_pb( + ctx->ovnisb_unlocked_txn, sb_pb->logical_port, + runned_az, ts->name, &ts->header_.uuid, + "transit-switch-port", &pb_tnlids); + sync_ts_isb_pb(ctx, sb_pb, isb_pb); + } else { + sync_local_port(ctx, isb_pb, sb_pb, lsp); + } + + if (isb_pb->type) { + icsbrec_port_binding_set_type(isb_pb, "transit-switch-port"); + } + + if (isb_pb->nb_ic_uuid) { + icsbrec_port_binding_set_nb_ic_uuid(isb_pb, + &ts->header_.uuid, 1); + } + } else if (!strcmp(lsp->type, "remote")) { + /* The port is remote. */ + isb_pb = shash_find_and_delete(&remote_pbs, lsp->name); + if (!isb_pb) { + nbrec_logical_switch_update_ports_delvalue(ls, lsp); + } else { + sb_pb = find_lsp_in_sb(ctx, lsp); + if (!sb_pb) { + continue; + } + sync_remote_port(ctx, isb_pb, lsp, sb_pb); + } + } else { + VLOG_DBG("Ignore lsp %s on ts %s with type %s.", + lsp->name, ts->name, lsp->type); + } + } + + SHASH_FOR_EACH (node, &nb_ports) { + nbrec_logical_switch_port_delete(node->data); + nbrec_logical_switch_update_ports_delvalue(ls, node->data); + } + + /* Delete extra port-binding from ISB */ + SHASH_FOR_EACH (node, &local_pbs) { + icsbrec_port_binding_delete(node->data); + } + + /* Create lsp in NB for remote ports */ + SHASH_FOR_EACH (node, &remote_pbs) { + create_nb_lsp(ctx, node->data, ls); + } + + ovn_destroy_tnlids(&pb_tnlids); + shash_destroy(&nb_ports); + shash_destroy(&local_pbs); + shash_destroy(&remote_pbs); + shash_destroy(&old_nb_ports); +} + +/* Reconcile the IC-SB and NB port bindings of a single transit router 'tr' + * (against its NB logical-router mirror). Local IC-SB ports that are matched + * to an existing transit-router port are removed from 'router_all_local_pbs' + * so the caller can delete the leftovers as orphans. New port tunnel keys are + * allocated from a per-router pool seeded from every binding on this router + * (local and remote) -- see the note on port_binding_sync_ts(). */ +static void +port_binding_sync_tr(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + const struct icnbrec_transit_router *tr, + struct shash *router_all_local_pbs) +{ + const struct nbrec_logical_router *lr = find_tr_in_nb(ctx, tr->name); + if (!lr) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "Transit router %s not found in NB.", tr->name); + return; + } + + struct shash nb_ports = SHASH_INITIALIZER(&nb_ports); + struct shash local_pbs = SHASH_INITIALIZER(&local_pbs); + struct shash remote_pbs = SHASH_INITIALIZER(&remote_pbs); + struct hmap pb_tnlids = HMAP_INITIALIZER(&pb_tnlids); + const struct icsbrec_port_binding *isb_pb; + struct shash_node *node; + + for (size_t i = 0; i < lr->n_ports; i++) { + const struct nbrec_logical_router_port *lrp = lr->ports[i]; + if (smap_get(&lrp->options, "interconn-tr")) { + shash_add(&nb_ports, lrp->name, lrp); + } + } + + const struct icsbrec_port_binding *isb_pb_key = + icsbrec_port_binding_index_init_row(ctx->icsbrec_port_binding_by_ts); + icsbrec_port_binding_index_set_transit_switch(isb_pb_key, tr->name); + + ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, + ctx->icsbrec_port_binding_by_ts) { + if (isb_pb->availability_zone == runned_az) { + shash_add(&local_pbs, isb_pb->logical_port, isb_pb); + shash_find_and_delete(router_all_local_pbs, isb_pb->logical_port); + } else { + /* Deduplicate by logical_port, as for a transit switch above: + * one NB Logical_Router_Port is created per entry and the NB + * port name is unique. */ + shash_add_once(&remote_pbs, isb_pb->logical_port, isb_pb); + } + /* Reserve every key in use on this router (local and remote). */ + ovn_add_tnlid(&pb_tnlids, isb_pb->tunnel_key); + } + icsbrec_port_binding_index_destroy_row(isb_pb_key); + + for (size_t i = 0; i < tr->n_ports; i++) { + const struct icnbrec_transit_router_port *trp = tr->ports[i]; + + if (chassis_is_remote(ctx, trp->chassis)) { + isb_pb = shash_find_and_delete(&remote_pbs, trp->name); + } else { + isb_pb = shash_find_and_delete(&local_pbs, trp->name); + if (!isb_pb) { + isb_pb = create_isb_pb(ctx->ovnisb_unlocked_txn, trp->name, + runned_az, + tr->name, &tr->header_.uuid, + "transit-router-port", &pb_tnlids); + icsbrec_port_binding_set_address(isb_pb, trp->mac); + } + } + + /* Don't allow remote ports to create NB LRP until ICSB entry is + * created in the appropriate AZ. */ + if (isb_pb) { + const struct nbrec_logical_router_port *lrp = + shash_find_and_delete(&nb_ports, trp->name); + if (!lrp) { + lrp = lrp_create(ctx, lr, trp); + } + + sync_router_port(isb_pb, trp, lrp); + } + } + + SHASH_FOR_EACH (node, &nb_ports) { + nbrec_logical_router_port_delete(node->data); + nbrec_logical_router_update_ports_delvalue(lr, node->data); + } + + ovn_destroy_tnlids(&pb_tnlids); + shash_destroy(&nb_ports); + shash_destroy(&local_pbs); + shash_destroy(&remote_pbs); +} + +/* Adds to 'gc' (keyed by logical_port) every local IC-SB port binding whose + * transit_switch is one of the names in 'names'. Used to build a transit- + * switch/router-scoped garbage-collection set: the per-entity sync claims the + * still-valid bindings and the caller deletes whatever remains. */ +static void +collect_local_pbs_by_names(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + const struct sset *names, + struct shash *gc) +{ + const char *name; + SSET_FOR_EACH (name, names) { + const struct icsbrec_port_binding *isb_pb; + const struct icsbrec_port_binding *key = + icsbrec_port_binding_index_init_row( + ctx->icsbrec_port_binding_by_ts); + icsbrec_port_binding_index_set_transit_switch(key, name); + ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, key, + ctx->icsbrec_port_binding_by_ts) { + if (isb_pb->availability_zone == runned_az && + !shash_find(gc, isb_pb->logical_port)) { + shash_add(gc, isb_pb->logical_port, isb_pb); + } + } + icsbrec_port_binding_index_destroy_row(key); + } +} + +/* Synchronizes cross-AZ port bindings. When both 'ts_scope' and 'tr_scope' + * are NULL every transit switch and transit router in the AZ is reconciled + * (full recompute). Otherwise only the transit switches named in 'ts_scope' + * and the transit routers named in 'tr_scope' are reconciled (each scope may + * be a non-NULL empty set, meaning "no entity of that kind"). A name no + * longer present in IC-NB (a deleted entity, or one whose NB mirror is gone) + * is still honoured: its local bindings end up as garbage-collection leftovers + * and are deleted, matching the full-recompute behaviour. */ +static void +port_binding_sync_scope(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + const struct sset *ts_scope, + const struct sset *tr_scope) +{ + if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { + return; + } + + bool full = !ts_scope && !tr_scope; + + struct shash switch_all_local_pbs = + SHASH_INITIALIZER(&switch_all_local_pbs); + struct shash router_all_local_pbs = + SHASH_INITIALIZER(&router_all_local_pbs); + struct shash_node *node; + + /* Seed the garbage-collection sets with this AZ's local bindings. Tunnel + * keys are no longer pooled here: port_binding_sync_ts()/_tr() build a + * per-entity key pool from every binding on that switch/router. */ + if (full) { + const struct icsbrec_port_binding *isb_pb; + const struct icsbrec_port_binding *isb_pb_key = + icsbrec_port_binding_index_init_row( + ctx->icsbrec_port_binding_by_az); + icsbrec_port_binding_index_set_availability_zone(isb_pb_key, + runned_az); + ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, + ctx->icsbrec_port_binding_by_az) { + struct shash *gc = ic_pb_get_type(isb_pb) == IC_ROUTER_PORT + ? &router_all_local_pbs : &switch_all_local_pbs; + shash_add(gc, isb_pb->logical_port, isb_pb); + } + icsbrec_port_binding_index_destroy_row(isb_pb_key); + } else { + if (ts_scope) { + collect_local_pbs_by_names(ctx, runned_az, ts_scope, + &switch_all_local_pbs); + } + if (tr_scope) { + collect_local_pbs_by_names(ctx, runned_az, tr_scope, + &router_all_local_pbs); + } + } + + /* Reconcile exactly the in-scope transit switches via an index + * lookup, instead of scanning every transit switch on every call. */ + if (full) { + const struct icnbrec_transit_switch *ts; + ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) { + port_binding_sync_ts(ctx, runned_az, ts, &switch_all_local_pbs); + } + } else if (ts_scope) { + const char *name; + SSET_FOR_EACH (name, ts_scope) { + const struct icnbrec_transit_switch *ts = + find_transit_switch_by_name(ctx, name); + if (ts) { + port_binding_sync_ts(ctx, runned_az, ts, + &switch_all_local_pbs); + } + } + } + + SHASH_FOR_EACH (node, &switch_all_local_pbs) { + icsbrec_port_binding_delete(node->data); + } + shash_destroy(&switch_all_local_pbs); + + /* Same scope-driven reconcile for transit routers. */ + if (full) { + const struct icnbrec_transit_router *tr; + ICNBREC_TRANSIT_ROUTER_FOR_EACH (tr, ctx->ovninb_idl) { + port_binding_sync_tr(ctx, runned_az, tr, &router_all_local_pbs); + } + } else if (tr_scope) { + const char *name; + SSET_FOR_EACH (name, tr_scope) { + const struct icnbrec_transit_router *tr = + find_transit_router_by_name(ctx, name); + if (tr) { + port_binding_sync_tr(ctx, runned_az, tr, + &router_all_local_pbs); + } + } + } + + SHASH_FOR_EACH (node, &router_all_local_pbs) { + icsbrec_port_binding_delete(node->data); + } + + shash_destroy(&router_all_local_pbs); +} + +static void +port_binding_run(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az) +{ + port_binding_sync_scope(ctx, runned_az, NULL, NULL); +} + +/* Build once per handler invocation, a map from every NB transit-router-mirror + * port name to its transit router name (the mirror logical router's name). + * See port_binding_lsp_ts_map_init(). */ +static void +port_binding_lrp_tr_map_init(struct ic_context *ctx, struct shash *map) +{ + shash_init(map); + const struct nbrec_logical_router *lr; + NBREC_LOGICAL_ROUTER_FOR_EACH (lr, ctx->ovnnb_idl) { + if (!smap_get(&lr->options, "interconn-tr")) { + continue; + } + for (size_t i = 0; i < lr->n_ports; i++) { + shash_replace(map, lr->ports[i]->name, CONST_CAST(char *, + lr->name)); + } + } +} + +/* If 'lrp' is an interconnection port of a transit-router mirror, adds that + * transit router's name (looked up in 'lrp_tr_map') to 'tr_scope'. */ +static void +port_binding_collect_lrp_tr(const struct shash *lrp_tr_map, + const struct nbrec_logical_router_port *lrp, + struct sset *tr_scope) +{ + const char *tr_name = shash_find_data(lrp_tr_map, lrp->name); + if (tr_name) { + sset_add(tr_scope, tr_name); + } +} + +/* Builds 'map': a mapping from every SB port-binding name whose state a + * transit-switch sync reads, to the owning transit switch's name. For each + * TS-mirror port this covers the port itself and, for router-typed ports, the + * peer logical_router_port (its MAC/datapath) and that port's chassisredirect + * (the gateway chassis) - i.e. exactly the SB rows read by sync_local_port() / + * sync_ts_isb_pb(). Transit-router syncs do not read SB port bindings, so + * only transit switches contribute. Values are IC-NB-owned strings; destroy + * with port_binding_sb_name_ts_map_destroy(). */ +static void +port_binding_sb_name_ts_map_init(struct ic_context *ctx, struct shash *map) +{ + shash_init(map); + + const struct icnbrec_transit_switch *ts; + ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) { + const struct nbrec_logical_switch *ls = find_ts_in_nb(ctx, ts->name); + if (!ls) { + continue; + } + for (size_t i = 0; i < ls->n_ports; i++) { + const struct nbrec_logical_switch_port *lsp = ls->ports[i]; + if (!shash_find(map, lsp->name)) { + shash_add(map, lsp->name, ts->name); + } + if (strcmp(lsp->type, "router")) { + continue; + } + const char *rport = smap_get(&lsp->options, "router-port"); + if (!rport) { + continue; + } + if (!shash_find(map, rport)) { + shash_add(map, rport, ts->name); + } + char *crp = ovn_chassis_redirect_name(rport); + if (!shash_find(map, crp)) { + shash_add(map, crp, ts->name); + } + free(crp); + } + } +} + +static void +port_binding_sb_name_ts_map_destroy(struct shash *map) +{ + shash_destroy(map); +} + +/* en_port_binding tracked data. 'synced_ts'/'synced_tr' hold the transit + * switches / routers already reconciled during the current engine run. Two + * different input change handlers of this node can map their changes to the + * same entity (e.g. an IC-SB port_binding change and the NB Logical_Switch + * mirror change both point at the same transit switch), and reconciling it a + * second time in the same NB transaction re-creates its NB ports: pending row + * inserts are not visible on the IDL until the transaction commits, so the + * second pass does not see the ports created by the first. These sets make + * each entity reconciled at most once per run. Cleared between runs by + * en_port_binding_clear_tracked_data(). */ +struct ed_type_port_binding { + struct sset synced_ts; + struct sset synced_tr; +}; + enum engine_node_state en_port_binding_run(struct engine_node *node, void *data OVS_UNUSED) { @@ -40,14 +1159,492 @@ en_port_binding_run(struct engine_node *node, void *data OVS_UNUSED) return EN_UPDATED; } +/* Returns the ic_context, or NULL when there is no availability zone yet (in + * which case the caller should report EN_HANDLED_UNCHANGED). On success, sets + * '*runned_az' to the AZ resolved by the upstream en_az node. */ +static struct ic_context * +port_binding_handler_ctx(struct engine_node *node, + const struct icsbrec_availability_zone **runned_az) +{ + struct ic_context *ctx = engine_get_context()->client_ctx; + const struct ed_type_az *az = engine_get_input_data("az", node); + *runned_az = az->runned_az; + return az->runned_az ? ctx : NULL; +} + +/* The change handlers translate a changed input row into the set of transit + * switches ('ts_scope') and transit routers ('tr_scope') that must be + * reconciled, then call port_binding_sync_scope() on those sets instead of + * forcing a full recompute. port_binding_sync_ts()/_tr() are already scoped + * to a single entity, so reconciling only the affected ones is sufficient. */ + +/* Runs the scoped sync for the collected scopes and maps it to an engine + * result. When there is work to do but the transactions sync needs are not + * available yet, returns EN_UNHANDLED so the change is retried. */ +static enum engine_input_handler_result +port_binding_scope_finish(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + struct sset *ts_scope, + struct sset *tr_scope, void *node_data) +{ + struct ed_type_port_binding *pb = node_data; + + /* Drop transit switches / routers already reconciled earlier in this + * engine run: port_binding_sync_ts()/_tr() fully reconcile an entity, so a + * repeat sync in the same NB transaction is redundant and, because pending + * row inserts are not visible until commit, would re-create its NB ports + * (duplicate insert -> constraint violation -> recompute -> abort). + * See 'synced_ts'/'synced_tr'. */ + const char *name; + SSET_FOR_EACH_SAFE (name, ts_scope) { + if (sset_contains(&pb->synced_ts, name)) { + sset_find_and_delete(ts_scope, name); + } + } + SSET_FOR_EACH_SAFE (name, tr_scope) { + if (sset_contains(&pb->synced_tr, name)) { + sset_find_and_delete(tr_scope, name); + } + } + + if (sset_is_empty(ts_scope) && sset_is_empty(tr_scope)) { + return EN_HANDLED_UNCHANGED; + } + if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { + return EN_UNHANDLED; + } + SSET_FOR_EACH (name, ts_scope) { + sset_add(&pb->synced_ts, name); + } + SSET_FOR_EACH (name, tr_scope) { + sset_add(&pb->synced_tr, name); + } + port_binding_sync_scope(ctx, runned_az, ts_scope, tr_scope); + return EN_HANDLED_UPDATED; +} + +/* IC-NB Transit_Switch: a new/deleted/renamed transit switch must have its + * port bindings reconciled (a deletion is honoured via the scoped GC). */ +enum engine_input_handler_result +port_binding_icnb_transit_switch_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = port_binding_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icnbrec_transit_switch_table *tbl = + EN_OVSDB_GET(engine_get_input("ICNB_transit_switch", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct icnbrec_transit_switch *ts; + ICNBREC_TRANSIT_SWITCH_TABLE_FOR_EACH_TRACKED (ts, tbl) { + sset_add(&ts_scope, ts->name); + } + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, runned_az, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* IC-NB Transit_Router: analogous to the transit-switch handler. */ +enum engine_input_handler_result +port_binding_icnb_transit_router_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = port_binding_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icnbrec_transit_router_table *tbl = + EN_OVSDB_GET(engine_get_input("ICNB_transit_router", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct icnbrec_transit_router *tr; + ICNBREC_TRANSIT_ROUTER_TABLE_FOR_EACH_TRACKED (tr, tbl) { + sset_add(&tr_scope, tr->name); + } + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, runned_az, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* IC-NB Transit_Router_Port: a column change on an existing port reconciles + * its owning transit router (resolved via tr_uuid). Port creation/deletion + * also updates the transit router's 'ports' column and is handled by the + * transit-router handler; handling it here too would reconcile the same router + * twice in one iteration and double-delete rows, so new/deleted ports are + * skipped. */ +enum engine_input_handler_result +port_binding_icnb_transit_router_port_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = port_binding_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icnbrec_transit_router_port_table *tbl = + EN_OVSDB_GET(engine_get_input("ICNB_transit_router_port", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct icnbrec_transit_router_port *trp; + ICNBREC_TRANSIT_ROUTER_PORT_TABLE_FOR_EACH_TRACKED (trp, tbl) { + if (icnbrec_transit_router_port_is_new(trp) || + icnbrec_transit_router_port_is_deleted(trp)) { + continue; + } + const struct icnbrec_transit_router *tr = + icnbrec_transit_router_get_for_uuid(ctx->ovninb_idl, + &trp->tr_uuid); + if (tr) { + sset_add(&tr_scope, tr->name); + } + } + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, runned_az, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* IC-NB Transit_Switch_Port: a column change on an existing port reconciles + * its owning transit switch. Unlike Transit_Router_Port (which back- + * references its router via tr_uuid), Transit_Switch_Port has no owning-switch + * column, so the owner is resolved by scanning every transit switch's 'ports'. + * Port creation/deletion also updates the transit switch's 'ports' column and + * is handled by the transit-switch handler; handling it here too would + * reconcile the same switch twice in one iteration and double-delete rows, so + * new/deleted ports are skipped. */ +enum engine_input_handler_result +port_binding_icnb_transit_switch_port_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = port_binding_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icnbrec_transit_switch_port_table *tbl = + EN_OVSDB_GET(engine_get_input("ICNB_transit_switch_port", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + + /* Only a modify of an existing port needs owner resolution; a new/deleted + * port is reconciled through the transit-switch handler. Build the + * reverse (port name -> transit switch name) map only if such a modify + * exists. */ + bool need_map = false; + const struct icnbrec_transit_switch_port *tsp; + ICNBREC_TRANSIT_SWITCH_PORT_TABLE_FOR_EACH_TRACKED (tsp, tbl) { + if (!icnbrec_transit_switch_port_is_new(tsp) && + !icnbrec_transit_switch_port_is_deleted(tsp)) { + need_map = true; + break; + } + } + + if (need_map) { + struct shash tsp_to_ts = SHASH_INITIALIZER(&tsp_to_ts); + const struct icnbrec_transit_switch *ts; + ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) { + for (size_t i = 0; i < ts->n_ports; i++) { + shash_add_once(&tsp_to_ts, ts->ports[i]->name, ts->name); + } + } + + ICNBREC_TRANSIT_SWITCH_PORT_TABLE_FOR_EACH_TRACKED (tsp, tbl) { + if (icnbrec_transit_switch_port_is_new(tsp) || + icnbrec_transit_switch_port_is_deleted(tsp)) { + continue; + } + const char *ts_name = shash_find_data(&tsp_to_ts, tsp->name); + if (ts_name) { + sset_add(&ts_scope, ts_name); + } + } + shash_destroy(&tsp_to_ts); + } + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, runned_az, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* IC-SB Port_Binding: the binding's transit_switch column names its entity; + * ic_pb_get_type() tells whether it is a transit-switch or transit-router + * port. The address/gateway/external_ids write-backs by the sync are + * idempotent (guarded by value comparisons), so they converge without a + * feedback loop. */ +enum engine_input_handler_result +port_binding_icsb_port_binding_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = port_binding_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icsbrec_port_binding_table *tbl = + EN_OVSDB_GET(engine_get_input("ICSB_port_binding", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct icsbrec_port_binding *isb_pb; + ICSBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (isb_pb, tbl) { + if (ic_pb_get_type(isb_pb) == IC_ROUTER_PORT) { + sset_add(&tr_scope, isb_pb->transit_switch); + } else { + sset_add(&ts_scope, isb_pb->transit_switch); + } + } + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, runned_az, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* NB Logical_Switch: only transit-switch mirrors (other_config:interconn-ts) + * are read by the sync. A change to such a mirror (its ports, name, or its + * deletion) reconciles the transit switch; tenant switches are irrelevant. */ +enum engine_input_handler_result +port_binding_nb_logical_switch_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = port_binding_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_switch_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_switch", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct nbrec_logical_switch *ls; + NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH_TRACKED (ls, tbl) { + if (smap_get(&ls->other_config, "interconn-ts")) { + sset_add(&ts_scope, ls->name); + } + } + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, runned_az, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* NB Logical_Switch_Port: a column change on an existing transit-switch port + * reconciles its transit switch. Port creation/deletion changes the mirror's + * 'ports' column and is handled by the NB logical_switch handler. */ +enum engine_input_handler_result +port_binding_nb_logical_switch_port_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = port_binding_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_switch_port_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_switch_port", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + struct shash lsp_ts_map; + port_binding_lsp_ts_map_init(ctx, &lsp_ts_map); + const struct nbrec_logical_switch_port *lsp; + NBREC_LOGICAL_SWITCH_PORT_TABLE_FOR_EACH_TRACKED (lsp, tbl) { + if (nbrec_logical_switch_port_is_new(lsp) || + nbrec_logical_switch_port_is_deleted(lsp)) { + continue; + } + port_binding_collect_lsp_ts(&lsp_ts_map, lsp, &ts_scope); + } + shash_destroy(&lsp_ts_map); + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, runned_az, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* NB Logical_Router: only transit-router mirrors (options:interconn-tr) are + * read by the sync. */ +enum engine_input_handler_result +port_binding_nb_logical_router_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = port_binding_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_router_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_router", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct nbrec_logical_router *lr; + NBREC_LOGICAL_ROUTER_TABLE_FOR_EACH_TRACKED (lr, tbl) { + if (smap_get(&lr->options, "interconn-tr")) { + sset_add(&tr_scope, lr->name); + } + } + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, runned_az, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* NB Logical_Router_Port: a column change on an existing transit-router port + * reconciles its transit router. Port creation/deletion changes the mirror's + * 'ports' column and is handled by the NB logical_router handler. */ +enum engine_input_handler_result +port_binding_nb_logical_router_port_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = port_binding_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_router_port_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_router_port", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + struct shash lrp_tr_map; + port_binding_lrp_tr_map_init(ctx, &lrp_tr_map); + const struct nbrec_logical_router_port *lrp; + NBREC_LOGICAL_ROUTER_PORT_TABLE_FOR_EACH_TRACKED (lrp, tbl) { + if (nbrec_logical_router_port_is_new(lrp) || + nbrec_logical_router_port_is_deleted(lrp)) { + continue; + } + port_binding_collect_lrp_tr(&lrp_tr_map, lrp, &tr_scope); + } + shash_destroy(&lrp_tr_map); + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, runned_az, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* SB Port_Binding: a transit-switch sync reads SB state of its ports (the port + * itself, the peer router port's MAC/datapath, and the chassisredirect's + * gateway chassis). Map each changed SB port binding back to its transit + * switch via the name map; SB bindings of no IC port are irrelevant. Transit- + * router syncs do not read SB port bindings. */ +enum engine_input_handler_result +port_binding_sb_port_binding_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = port_binding_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct sbrec_port_binding_table *tbl = + EN_OVSDB_GET(engine_get_input("SB_port_binding", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + + struct shash name_map; + port_binding_sb_name_ts_map_init(ctx, &name_map); + + const struct sbrec_port_binding *sb_pb; + SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sb_pb, tbl) { + const char *ts_name = shash_find_data(&name_map, sb_pb->logical_port); + if (ts_name) { + sset_add(&ts_scope, ts_name); + } + } + port_binding_sb_name_ts_map_destroy(&name_map); + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, runned_az, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* SB Chassis: the sync only reads a chassis' existence and its + * other_config:is-remote flag (via chassis_is_remote()), plus its (immutable) + * name for the gateway field. A chassis being created or deleted, or its + * other_config changing, can therefore change the output; any other column + * change (the frequent heartbeat-style updates: nb_cfg, encaps, external_ids, + * hostname, ...) cannot. So recompute only on insert/delete or an + * other_config change, and ignore the rest instead of forcing a full + * recompute on every chassis update. */ +enum engine_input_handler_result +port_binding_sb_chassis_handler(struct engine_node *node, + void *data OVS_UNUSED) +{ + const struct ed_type_az *az = engine_get_input_data("az", node); + if (!az->runned_az) { + return EN_HANDLED_UNCHANGED; + } + + const struct sbrec_chassis_table *tbl = + EN_OVSDB_GET(engine_get_input("SB_chassis", node)); + const struct sbrec_chassis *chassis; + SBREC_CHASSIS_TABLE_FOR_EACH_TRACKED (chassis, tbl) { + if (sbrec_chassis_is_new(chassis) + || sbrec_chassis_is_deleted(chassis) + || sbrec_chassis_is_updated(chassis, + SBREC_CHASSIS_COL_OTHER_CONFIG)) { + return EN_UNHANDLED; + } + } + return EN_HANDLED_UNCHANGED; +} + void * en_port_binding_init(struct engine_node *node OVS_UNUSED, struct engine_arg *arg OVS_UNUSED) { - return NULL; + struct ed_type_port_binding *data = xzalloc(sizeof *data); + sset_init(&data->synced_ts); + sset_init(&data->synced_tr); + return data; +} + +void +en_port_binding_clear_tracked_data(void *data_) +{ + struct ed_type_port_binding *data = data_; + sset_clear(&data->synced_ts); + sset_clear(&data->synced_tr); } void -en_port_binding_cleanup(void *data OVS_UNUSED) +en_port_binding_cleanup(void *data_) { + struct ed_type_port_binding *data = data_; + sset_destroy(&data->synced_ts); + sset_destroy(&data->synced_tr); } diff --git a/ic/en-port-binding.h b/ic/en-port-binding.h index d5fc2552c..08bb8effa 100644 --- a/ic/en-port-binding.h +++ b/ic/en-port-binding.h @@ -20,6 +20,34 @@ enum engine_node_state en_port_binding_run(struct engine_node *node, void *data); void *en_port_binding_init(struct engine_node *node, struct engine_arg *arg); +void en_port_binding_clear_tracked_data(void *data); void en_port_binding_cleanup(void *data); +enum engine_input_handler_result +port_binding_icnb_transit_switch_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +port_binding_icnb_transit_router_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +port_binding_icnb_transit_router_port_handler(struct engine_node *node, + void *data); +enum engine_input_handler_result +port_binding_icnb_transit_switch_port_handler(struct engine_node *node, + void *data); +enum engine_input_handler_result +port_binding_icsb_port_binding_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +port_binding_nb_logical_switch_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +port_binding_nb_logical_switch_port_handler(struct engine_node *node, + void *data); +enum engine_input_handler_result +port_binding_nb_logical_router_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +port_binding_nb_logical_router_port_handler(struct engine_node *node, + void *data); +enum engine_input_handler_result +port_binding_sb_port_binding_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +port_binding_sb_chassis_handler(struct engine_node *node, void *data); + #endif /* EN_IC_PORT_BINDING_H */ diff --git a/ic/inc-proc-ic.c b/ic/inc-proc-ic.c index 88c866f4b..f860ed821 100644 --- a/ic/inc-proc-ic.c +++ b/ic/inc-proc-ic.c @@ -99,7 +99,8 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_ic); ICNB_NODE(ic_nb_global, "ic_nb_global") \ ICNB_NODE(transit_switch, "transit_switch") \ ICNB_NODE(transit_router, "transit_router") \ - ICNB_NODE(transit_router_port, "transit_router_port") + ICNB_NODE(transit_router_port, "transit_router_port") \ + ICNB_NODE(transit_switch_port, "transit_switch_port") enum icnb_engine_node { #define ICNB_NODE(NAME, NAME_STR) ICNB_##NAME, @@ -179,7 +180,7 @@ static ENGINE_NODE(gateway); static ENGINE_NODE(ts); static ENGINE_NODE(tr); static ENGINE_NODE(tunnel_key); -static ENGINE_NODE(port_binding); +static ENGINE_NODE(port_binding, CLEAR_TRACKED_DATA); static ENGINE_NODE(route); static ENGINE_NODE(service_monitor); static ENGINE_NODE(address_set); @@ -275,19 +276,42 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, ic_nb_global_options_handler); engine_add_input(&en_tunnel_key, &en_icsb_encap, NULL); - /* en_port_binding: sync cross-AZ port bindings. */ + /* en_port_binding: sync cross-AZ port bindings. + * + * Like en_gateway, this node uses only the AZ identity (en_az's resolved + * AZ, plus the by-AZ port-binding index) and does not read + * the Availability_Zone table itself, so it does not depend on + * en_icsb_availability_zone and is not churned by the nb_ic_cfg sequence + * number bumped there on every change. */ engine_add_input(&en_port_binding, &en_az, NULL); - engine_add_input(&en_port_binding, &en_icsb_availability_zone, NULL); - engine_add_input(&en_port_binding, &en_icsb_port_binding, NULL); - engine_add_input(&en_port_binding, &en_icnb_transit_switch, NULL); - engine_add_input(&en_port_binding, &en_icnb_transit_router, NULL); - engine_add_input(&en_port_binding, &en_icnb_transit_router_port, NULL); - engine_add_input(&en_port_binding, &en_nb_logical_switch, NULL); - engine_add_input(&en_port_binding, &en_nb_logical_switch_port, NULL); - engine_add_input(&en_port_binding, &en_nb_logical_router, NULL); - engine_add_input(&en_port_binding, &en_nb_logical_router_port, NULL); - engine_add_input(&en_port_binding, &en_sb_port_binding, NULL); - engine_add_input(&en_port_binding, &en_sb_chassis, NULL); + engine_add_input(&en_port_binding, &en_icsb_port_binding, + port_binding_icsb_port_binding_handler); + engine_add_input(&en_port_binding, &en_icnb_transit_switch, + port_binding_icnb_transit_switch_handler); + engine_add_input(&en_port_binding, &en_icnb_transit_router, + port_binding_icnb_transit_router_handler); + engine_add_input(&en_port_binding, &en_icnb_transit_router_port, + port_binding_icnb_transit_router_port_handler); + engine_add_input(&en_port_binding, &en_icnb_transit_switch_port, + port_binding_icnb_transit_switch_port_handler); + engine_add_input(&en_port_binding, &en_nb_logical_switch, + port_binding_nb_logical_switch_handler); + engine_add_input(&en_port_binding, &en_nb_logical_switch_port, + port_binding_nb_logical_switch_port_handler); + engine_add_input(&en_port_binding, &en_nb_logical_router, + port_binding_nb_logical_router_handler); + engine_add_input(&en_port_binding, &en_nb_logical_router_port, + port_binding_nb_logical_router_port_handler); + engine_add_input(&en_port_binding, &en_sb_port_binding, + port_binding_sb_port_binding_handler); + /* SB chassis affects gateways and chassis_is_remote() across many ports. + * The reverse mapping chassis -> affected ports is impractical, but the + * sync only reads a chassis' existence and its other_config (is-remote); + * so the handler recomputes only on chassis insert/delete or an + * other_config change and ignores the frequent heartbeat-style + * updates. */ + engine_add_input(&en_port_binding, &en_sb_chassis, + port_binding_sb_chassis_handler); /* en_route: advertise/learn cross-AZ routes. */ engine_add_input(&en_route, &en_az, NULL); diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c index 9dc6797f0..f8b0a8b1a 100644 --- a/ic/ovn-ic.c +++ b/ic/ovn-ic.c @@ -68,9 +68,6 @@ static const char *ssl_private_key_file; static const char *ssl_certificate_file; static const char *ssl_ca_cert_file; -static const struct sbrec_port_binding * find_sb_pb_by_name( - struct ovsdb_idl_index *sbrec_port_binding_by_name, const char *name); - static void usage(void) @@ -119,7 +116,7 @@ ic_dp_get_type(const struct icsbrec_datapath_binding *isb_dp) return IC_SWITCH; } -static enum ic_port_binding_type +enum ic_port_binding_type ic_pb_get_type(const struct icsbrec_port_binding *isb_pb) { if (isb_pb->type && !strcmp(isb_pb->type, "transit-router-port")) { @@ -504,31 +501,7 @@ find_ts_in_nb(struct ic_context *ctx, char *ts_name) return NULL; } -static const struct nbrec_logical_router * -find_tr_in_nb(struct ic_context *ctx, char *tr_name) -{ - const struct nbrec_logical_router *key = - nbrec_logical_router_index_init_row(ctx->nbrec_lr_by_name); - nbrec_logical_router_index_set_name(key, tr_name); - - const struct nbrec_logical_router *lr; - bool found = false; - NBREC_LOGICAL_ROUTER_FOR_EACH_EQUAL (lr, key, ctx->nbrec_lr_by_name) { - if (smap_get(&lr->options, "interconn-tr")) { - found = true; - break; - } - } - - nbrec_logical_router_index_destroy_row(key); - if (found) { - return lr; - } - - return NULL; -} - -static const struct sbrec_port_binding * +const struct sbrec_port_binding * find_sb_pb_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name, const char *name) { @@ -559,44 +532,7 @@ find_sb_dp_by_nb_uuid(struct ovsdb_idl_index *sbrec_datapath_binding, return dp; } -static const struct sbrec_port_binding * -find_peer_port(struct ic_context *ctx, - const struct sbrec_port_binding *sb_pb) -{ - const char *peer_name = smap_get(&sb_pb->options, "peer"); - if (!peer_name) { - return NULL; - } - - return find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, peer_name); -} - -static const struct sbrec_port_binding * -find_crp_from_lrp(struct ic_context *ctx, - const struct sbrec_port_binding *lrp_pb) -{ - char *crp_name = ovn_chassis_redirect_name(lrp_pb->logical_port); - - const struct sbrec_port_binding *pb = - find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, crp_name); - - free(crp_name); - return pb; -} - -static const struct sbrec_port_binding * -find_crp_for_sb_pb(struct ic_context *ctx, - const struct sbrec_port_binding *sb_pb) -{ - const struct sbrec_port_binding *peer = find_peer_port(ctx, sb_pb); - if (!peer) { - return NULL; - } - - return find_crp_from_lrp(ctx, peer); -} - -static const struct nbrec_logical_switch_port * +const struct nbrec_logical_switch_port * get_lsp_by_ts_port_name(struct ic_context *ctx, const char *ts_port_name) { const struct nbrec_logical_switch_port *lsp, *key; @@ -609,27 +545,6 @@ get_lsp_by_ts_port_name(struct ic_context *ctx, const char *ts_port_name) return lsp; } -static const char * -get_lp_address_for_sb_pb(struct ic_context *ctx, - const struct sbrec_port_binding *sb_pb) -{ - const struct nbrec_logical_switch_port *nb_lsp; - - nb_lsp = get_lsp_by_ts_port_name(ctx, sb_pb->logical_port); - if (!strcmp(nb_lsp->type, "switch")) { - /* Switches always have implicit "unknown" address, and IC-SB port - * binding can only have one address specified. */ - return "unknown"; - } - - const struct sbrec_port_binding *peer = find_peer_port(ctx, sb_pb); - if (!peer) { - return NULL; - } - - return peer->n_mac ? *peer->mac : NULL; -} - const struct sbrec_chassis * find_sb_chassis(struct ic_context *ctx, const char *name) { @@ -644,386 +559,7 @@ find_sb_chassis(struct ic_context *ctx, const char *name) return chassis; } -static void -sync_lsp_tnl_key(const struct nbrec_logical_switch_port *lsp, - int64_t isb_tnl_key) -{ - int64_t tnl_key = smap_get_int(&lsp->options, "requested-tnl-key", 0); - if (tnl_key != isb_tnl_key) { - VLOG_DBG("Set options:requested-tnl-key %"PRId64 - " for lsp %s in NB.", isb_tnl_key, lsp->name); - char *tnl_key_str = xasprintf("%"PRId64, isb_tnl_key); - nbrec_logical_switch_port_update_options_setkey(lsp, - "requested-tnl-key", - tnl_key_str); - free(tnl_key_str); - } - -} - -static inline void -sync_lrp_tnl_key(const struct nbrec_logical_router_port *lrp, - int64_t isb_tnl_key) -{ - int64_t tnl_key = smap_get_int(&lrp->options, "requested-tnl-key", 0); - if (tnl_key != isb_tnl_key) { - VLOG_DBG("Set options:requested-tnl-key %" PRId64 " for lrp %s in NB.", - isb_tnl_key, lrp->name); - char *tnl_key_str = xasprintf("%"PRId64, isb_tnl_key); - nbrec_logical_router_port_update_options_setkey( - lrp, "requested-tnl-key", tnl_key_str); - free(tnl_key_str); - } -} - -static bool -get_router_uuid_by_sb_pb(struct ic_context *ctx, - const struct sbrec_port_binding *sb_pb, - struct uuid *router_uuid) -{ - const struct sbrec_port_binding *router_pb = find_peer_port(ctx, sb_pb); - if (!router_pb || !router_pb->datapath) { - return NULL; - } - - return datapath_get_nb_uuid(router_pb->datapath, router_uuid); -} - -static void -update_isb_pb_external_ids(struct ic_context *ctx, - const struct sbrec_port_binding *sb_pb, - const struct icsbrec_port_binding *isb_pb) -{ - struct uuid lr_uuid; - if (!get_router_uuid_by_sb_pb(ctx, sb_pb, &lr_uuid)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Can't get router uuid for transit switch port %s.", - isb_pb->logical_port); - return; - } - - struct uuid current_lr_uuid; - if (smap_get_uuid(&isb_pb->external_ids, "router-id", ¤t_lr_uuid) && - uuid_equals(&lr_uuid, ¤t_lr_uuid)) { - return; - } - - char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&lr_uuid)); - icsbrec_port_binding_update_external_ids_setkey(isb_pb, "router-id", - uuid_s); - free(uuid_s); -} - -/* Sync a local port's fields from TSP INB->ISB. */ -static void -sync_tsp_pb(const struct icnbrec_transit_switch_port *tsp, - const struct icsbrec_port_binding *isb_pb) -{ - if (!isb_pb) { - return; - } - - /* Sync address to ISB. */ - if (!tsp->n_addresses && isb_pb->address[0]) { - icsbrec_port_binding_set_address(isb_pb, ""); - } else if (tsp->n_addresses && - strcmp(tsp->addresses[0], isb_pb->address)) { - icsbrec_port_binding_set_address(isb_pb, tsp->addresses[0]); - } - - /* Sync gateway to ISB. */ - if (strcmp(tsp->chassis, isb_pb->gateway)) { - icsbrec_port_binding_set_gateway(isb_pb, tsp->chassis); - } -} - -/* For each local port: - * - Sync from NB to ISB. - * - Sync gateway from SB to ISB. - * - Sync tunnel key from ISB to NB. - */ -static void -sync_local_port(struct ic_context *ctx, - const struct icsbrec_port_binding *isb_pb, - const struct sbrec_port_binding *sb_pb, - const struct nbrec_logical_switch_port *lsp) -{ - /* Sync address from NB to ISB */ - const char *address = get_lp_address_for_sb_pb(ctx, sb_pb); - if (!address) { - VLOG_DBG("Can't get router/switch port address for logical" - " switch port %s", sb_pb->logical_port); - if (isb_pb->address[0]) { - icsbrec_port_binding_set_address(isb_pb, ""); - } - } else { - if (strcmp(address, isb_pb->address)) { - icsbrec_port_binding_set_address(isb_pb, address); - } - } - - /* Sync gateway from SB to ISB */ - const struct sbrec_port_binding *crp = find_crp_for_sb_pb(ctx, sb_pb); - if (crp && crp->chassis) { - if (strcmp(crp->chassis->name, isb_pb->gateway)) { - icsbrec_port_binding_set_gateway(isb_pb, crp->chassis->name); - } - } else if (!strcmp(lsp->type, "switch") && sb_pb->chassis) { - if (strcmp(sb_pb->chassis->name, isb_pb->gateway)) { - icsbrec_port_binding_set_gateway(isb_pb, sb_pb->chassis->name); - } - } else { - if (isb_pb->gateway[0]) { - icsbrec_port_binding_set_gateway(isb_pb, ""); - } - } - - /* Sync external_ids:router-id to ISB */ - update_isb_pb_external_ids(ctx, sb_pb, isb_pb); - - /* Sync back tunnel key from ISB to NB */ - sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); -} - -static void -sync_tsp_router_peer(const struct icnbrec_transit_switch_port *tsp, - const struct nbrec_logical_switch_port *lsp) -{ - /* Clear the peer for router port type. */ - if (lsp->peer && lsp->peer[0]) { - nbrec_logical_switch_port_set_peer(lsp, NULL); - } - - if (tsp->peer[0]) { - const char *rp = smap_get(&lsp->options, "router-port"); - if (!rp || strcmp(rp, tsp->peer)) { - nbrec_logical_switch_port_update_options_setkey( - lsp, "router-port", tsp->peer); - } - } else if (smap_get(&lsp->options, "router-port")) { - nbrec_logical_switch_port_update_options_delkey(lsp, "router-port"); - } -} - -static void -sync_tsp_non_router_peer(const struct icnbrec_transit_switch_port *tsp, - const struct nbrec_logical_switch_port *lsp) -{ - /* Clear router-port setting for non-routers. */ - if (smap_get(&lsp->options, "router-port")) { - nbrec_logical_switch_port_update_options_delkey(lsp, "router-port"); - } - - if (tsp->peer[0]) { - if (!lsp->peer || strcmp(lsp->peer, tsp->peer)) { - nbrec_logical_switch_port_set_peer(lsp, tsp->peer); - } - } else if (lsp->peer && lsp->peer[0]) { - nbrec_logical_switch_port_set_peer(lsp, NULL); - } -} - -static void -sync_tsp_common(const struct icnbrec_transit_switch_port *tsp, - const struct icsbrec_port_binding *isb_pb, - const struct nbrec_logical_switch_port *lsp) -{ - /* Sync addresses from TSP to NB. */ - nbrec_logical_switch_port_set_addresses( - lsp, (const char **) tsp->addresses, tsp->n_addresses); - - if (tsp->chassis[0]) { - const char *current = smap_get(&lsp->options, "requested-chassis"); - if (!current || strcmp(current, tsp->chassis)) { - nbrec_logical_switch_port_update_options_setkey( - lsp, "requested-chassis", tsp->chassis); - } - } else if (smap_get(&lsp->options, "requested-chassis")) { - nbrec_logical_switch_port_update_options_delkey( - lsp, "requested-chassis"); - } - - /* Sync tunnel key from ISB to NB. */ - sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); - - if (!strcmp(tsp->type, "router")) { - sync_tsp_router_peer(tsp, lsp); - } else { - sync_tsp_non_router_peer(tsp, lsp); - } -} - -static void -sync_tsp_local(const struct icnbrec_transit_switch_port *tsp, - const struct icsbrec_port_binding *isb_pb, - const struct nbrec_logical_switch_port *lsp) -{ - if (strcmp(lsp->type, tsp->type)) { - nbrec_logical_switch_port_set_type(lsp, tsp->type); - } - sync_tsp_common(tsp, isb_pb, lsp); -} - -static void -sync_tsp_remote(const struct icnbrec_transit_switch_port *tsp, - const struct icsbrec_port_binding *isb_pb, - const struct nbrec_logical_switch_port *lsp) -{ - if (strcmp(lsp->type, "remote")) { - nbrec_logical_switch_port_set_type(lsp, "remote"); - } - sync_tsp_common(tsp, isb_pb, lsp); -} - -/* For each remote port: - * - Sync from ISB to NB - * - Sync gateway from ISB to SB - */ -static void -sync_remote_port(struct ic_context *ctx, - const struct icsbrec_port_binding *isb_pb, - const struct nbrec_logical_switch_port *lsp, - const struct sbrec_port_binding *sb_pb) -{ - /* Sync address from ISB to NB */ - if (isb_pb->address[0]) { - if (lsp->n_addresses != 1 || - strcmp(isb_pb->address, lsp->addresses[0])) { - nbrec_logical_switch_port_set_addresses( - lsp, (const char **)&isb_pb->address, 1); - } - } else { - if (lsp->n_addresses != 0) { - nbrec_logical_switch_port_set_addresses(lsp, NULL, 0); - } - } - - /* Sync tunnel key from ISB to NB */ - sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); - - /* Skip port binding if it is already requested by the CMS. */ - if (smap_get(&lsp->options, "requested-chassis")) { - return; - } - - /* Sync gateway from ISB to SB */ - if (isb_pb->gateway[0]) { - if (!sb_pb->chassis || strcmp(sb_pb->chassis->name, isb_pb->gateway)) { - const struct sbrec_chassis *chassis = - find_sb_chassis(ctx, isb_pb->gateway); - if (!chassis) { - VLOG_DBG("Chassis %s is not found in SB, syncing from ISB " - "to SB skipped for logical port %s.", - isb_pb->gateway, lsp->name); - return; - } - sbrec_port_binding_set_chassis(sb_pb, chassis); - } - } else { - if (sb_pb->chassis) { - sbrec_port_binding_set_chassis(sb_pb, NULL); - } - } -} - -/* For each remote port: - * - Sync from ISB to NB - */ -static void -sync_router_port(const struct icsbrec_port_binding *isb_pb, - const struct icnbrec_transit_router_port *trp, - const struct nbrec_logical_router_port *lrp) -{ - /* Sync from ICNB to NB */ - if (trp->chassis[0]) { - const char *chassis_name = - smap_get_def(&lrp->options, "requested-chassis", ""); - if (strcmp(trp->chassis, chassis_name)) { - nbrec_logical_router_port_update_options_setkey( - lrp, "requested-chassis", trp->chassis); - } - } else { - nbrec_logical_router_port_update_options_delkey( - lrp, "requested-chassis"); - } - - if (strcmp(trp->mac, lrp->mac)) { - nbrec_logical_router_port_set_mac(lrp, trp->mac); - } - - bool sync_networks = false; - if (trp->n_networks != lrp->n_networks) { - sync_networks = true; - } else { - for (size_t i = 0; i < trp->n_networks; i++) { - if (strcmp(trp->networks[i], lrp->networks[i])) { - sync_networks |= true; - break; - } - } - } - - if (sync_networks) { - nbrec_logical_router_port_set_networks( - lrp, (const char **) trp->networks, trp->n_networks); - } - - /* Sync tunnel key from ISB to NB */ - sync_lrp_tnl_key(lrp, isb_pb->tunnel_key); -} - -static void -create_nb_lsp(struct ic_context *ctx, - const struct icsbrec_port_binding *isb_pb, - const struct nbrec_logical_switch *ls) -{ - const struct nbrec_logical_switch_port *lsp = - nbrec_logical_switch_port_insert(ctx->ovnnb_txn); - nbrec_logical_switch_port_set_name(lsp, isb_pb->logical_port); - nbrec_logical_switch_port_set_type(lsp, "remote"); - - bool up = true; - nbrec_logical_switch_port_set_up(lsp, &up, 1); - - if (isb_pb->address[0]) { - nbrec_logical_switch_port_set_addresses( - lsp, (const char **)&isb_pb->address, 1); - } - sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); - nbrec_logical_switch_update_ports_addvalue(ls, lsp); -} - -static uint32_t -allocate_port_key(struct hmap *pb_tnlids) -{ - static uint32_t hint; - return ovn_allocate_tnlid(pb_tnlids, "transit port", - 1, (1u << 15) - 1, &hint); -} - -static const struct icsbrec_port_binding * -create_isb_pb(struct ovsdb_idl_txn *isb_txn, const char *logical_port, - const struct icsbrec_availability_zone *az, const char *ts_name, - const struct uuid *nb_ic_uuid, const char *type, - struct hmap *pb_tnlids) -{ - uint32_t pb_tnl_key = allocate_port_key(pb_tnlids); - if (!pb_tnl_key) { - return NULL; - } - - const struct icsbrec_port_binding *isb_pb = - icsbrec_port_binding_insert(isb_txn); - icsbrec_port_binding_set_availability_zone(isb_pb, az); - icsbrec_port_binding_set_transit_switch(isb_pb, ts_name); - icsbrec_port_binding_set_logical_port(isb_pb, logical_port); - icsbrec_port_binding_set_tunnel_key(isb_pb, pb_tnl_key); - icsbrec_port_binding_set_nb_ic_uuid(isb_pb, nb_ic_uuid, 1); - icsbrec_port_binding_set_type(isb_pb, type); - return isb_pb; -} - -static const struct nbrec_logical_router_port * +const struct nbrec_logical_router_port * get_lrp_by_lrp_name(struct ic_context *ctx, const char *lrp_name) { const struct nbrec_logical_router_port *lrp; @@ -1037,356 +573,42 @@ get_lrp_by_lrp_name(struct ic_context *ctx, const char *lrp_name) return lrp; } -static bool -chassis_is_remote(struct ic_context *ctx, const char *chassis_name) -{ - if (chassis_name) { - const struct sbrec_chassis *chassis = - find_sb_chassis(ctx, chassis_name); - if (chassis) { - return smap_get_bool(&chassis->other_config, "is-remote", false); - } else { - return true; - } - } - - return false; -} - -static struct nbrec_logical_router_port * -lrp_create(struct ic_context *ctx, const struct nbrec_logical_router *lr, - const struct icnbrec_transit_router_port *trp) -{ - struct nbrec_logical_router_port *lrp = - nbrec_logical_router_port_insert(ctx->ovnnb_txn); - nbrec_logical_router_port_set_name(lrp, trp->name); - - nbrec_logical_router_port_update_options_setkey(lrp, "interconn-tr", - trp->name); - nbrec_logical_router_update_ports_addvalue(lr, lrp); - return lrp; -} - -static void -sync_ts_isb_pb(struct ic_context *ctx, const struct sbrec_port_binding *sb_pb, - const struct icsbrec_port_binding *isb_pb) -{ - const char *address = get_lp_address_for_sb_pb(ctx, sb_pb); - if (address) { - icsbrec_port_binding_set_address(isb_pb, address); - } - - const struct sbrec_port_binding *crp = find_crp_for_sb_pb(ctx, sb_pb); - if (crp && crp->chassis) { - icsbrec_port_binding_set_gateway(isb_pb, crp->chassis->name); - } - - update_isb_pb_external_ids(ctx, sb_pb, isb_pb); - - /* XXX: Sync encap so that multiple encaps can be used for the same - * gateway. However, it is not needed for now, since we don't yet - * support specifying encap type/ip for gateway chassis or ha-chassis - * for logical router port in NB DB, and now encap should always be - * empty. The sync can be added if we add such support for gateway - * chassis/ha-chassis in NB DB. */ -} - -static struct nbrec_logical_switch_port * -lsp_create(struct ic_context *ctx, const struct nbrec_logical_switch *ls, - const struct icnbrec_transit_switch_port *tsp) -{ - struct nbrec_logical_switch_port *lsp = - nbrec_logical_switch_port_insert(ctx->ovnnb_txn); - nbrec_logical_switch_port_set_name(lsp, tsp->name); - - nbrec_logical_switch_port_update_options_setkey(lsp, "interconn-tsp", - tsp->name); - nbrec_logical_switch_update_ports_addvalue(ls, lsp); - return lsp; -} - -static const struct sbrec_port_binding * -find_lsp_in_sb(struct ic_context *ctx, - const struct nbrec_logical_switch_port *lsp) -{ - return find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, lsp->name); -} - +/* Build once per handler invocation, a map from every NB + * transit-switch-mirror port name to its transit switch name, by walking the + * logical switches directly. This replaces the previous reverse lookup that + * scanned every transit switch (with a per-switch index lookup) for each + * changed port -- an O(changed-ports x transit-switches) cost that dominated + * the profile (json_string / skiplist / compare_3way) and blocked the main + * loop for minutes at large fan-out. The values point into IDL-owned smaps + * and are valid for the lifetime of the map; destroy with shash_destroy(). */ void -port_binding_run(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az) +port_binding_lsp_ts_map_init(struct ic_context *ctx, struct shash *map) { - if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { - return; - } - - struct shash switch_all_local_pbs = - SHASH_INITIALIZER(&switch_all_local_pbs); - struct shash router_all_local_pbs = - SHASH_INITIALIZER(&router_all_local_pbs); - struct hmap pb_tnlids = HMAP_INITIALIZER(&pb_tnlids); - struct shash_node *node; - - const struct icsbrec_port_binding *isb_pb; - const struct icsbrec_port_binding *isb_pb_key = - icsbrec_port_binding_index_init_row(ctx->icsbrec_port_binding_by_az); - icsbrec_port_binding_index_set_availability_zone(isb_pb_key, - runned_az); - - ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, - ctx->icsbrec_port_binding_by_az) { - ic_pb_get_type(isb_pb) != IC_ROUTER_PORT - ? shash_add(&switch_all_local_pbs, isb_pb->logical_port, isb_pb) - : shash_add(&router_all_local_pbs, isb_pb->logical_port, isb_pb); - - ovn_add_tnlid(&pb_tnlids, isb_pb->tunnel_key); - } - icsbrec_port_binding_index_destroy_row(isb_pb_key); - - const struct icnbrec_transit_switch *ts; - ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) { - const struct nbrec_logical_switch *ls = find_ts_in_nb(ctx, ts->name); - if (!ls) { - VLOG_DBG("Transit switch %s not found in NB.", ts->name); + shash_init(map); + const struct nbrec_logical_switch *ls; + NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->ovnnb_idl) { + const char *ts_name = smap_get(&ls->other_config, "interconn-ts"); + if (!ts_name) { continue; } - struct shash nb_ports = SHASH_INITIALIZER(&nb_ports); - struct shash old_nb_ports = SHASH_INITIALIZER(&old_nb_ports); - struct shash local_pbs = SHASH_INITIALIZER(&local_pbs); - struct shash remote_pbs = SHASH_INITIALIZER(&remote_pbs); - for (size_t i = 0; i < ls->n_ports; i++) { - const struct nbrec_logical_switch_port *lsp = ls->ports[i]; - if (smap_get(&lsp->options, "interconn-tsp")) { - shash_add(&nb_ports, lsp->name, lsp); - } else { - shash_add(&old_nb_ports, lsp->name, lsp); - } - } - - isb_pb_key = icsbrec_port_binding_index_init_row( - ctx->icsbrec_port_binding_by_ts); - icsbrec_port_binding_index_set_transit_switch(isb_pb_key, ts->name); - - ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, - ctx->icsbrec_port_binding_by_ts) { - if (isb_pb->availability_zone == runned_az) { - shash_add(&local_pbs, isb_pb->logical_port, isb_pb); - shash_find_and_delete(&switch_all_local_pbs, - isb_pb->logical_port); - } else { - shash_add(&remote_pbs, isb_pb->logical_port, isb_pb); - } - } - icsbrec_port_binding_index_destroy_row(isb_pb_key); - - for (size_t i = 0; i < ts->n_ports; i++) { - struct icnbrec_transit_switch_port *tsp = ts->ports[i]; - - if (!tsp->chassis[0]) { - isb_pb = shash_find_and_delete(&local_pbs, tsp->name); - if (!isb_pb) { - isb_pb = shash_find_and_delete(&remote_pbs, tsp->name); - } - - if (ctx->ovnisb_txn && is_az_leader(ctx->ovnisb_txn)) { - if (!isb_pb) { - isb_pb = create_isb_pb( - ctx->ovnisb_txn, tsp->name, - runned_az, ts->name, - &ts->header_.uuid, "transit-switch-port", - &pb_tnlids); - } - sync_tsp_pb(tsp, isb_pb); - } - } else if (!chassis_is_remote(ctx, tsp->chassis)) { - /* Create ISB port_binding as its chassis is local. */ - isb_pb = shash_find_and_delete(&local_pbs, tsp->name); - if (!isb_pb) { - isb_pb = create_isb_pb(ctx->ovnisb_unlocked_txn, tsp->name, - runned_az, - ts->name, &ts->header_.uuid, - "transit-switch-port", &pb_tnlids); - } - sync_tsp_pb(tsp, isb_pb); - } else { - isb_pb = shash_find_and_delete(&remote_pbs, tsp->name); - } - - if (!isb_pb) { - continue; - } - - const struct nbrec_logical_switch_port *lsp = - shash_find_and_delete(&nb_ports, tsp->name); - if (!lsp) { - lsp = lsp_create(ctx, ls, tsp); - } - - if (!tsp->chassis[0] || !chassis_is_remote(ctx, tsp->chassis)) { - sync_tsp_local(tsp, isb_pb, lsp); - } else { - sync_tsp_remote(tsp, isb_pb, lsp); - } - } - - /* Support legacy way of adding transit switch ports. */ - const struct sbrec_port_binding *sb_pb; - const struct nbrec_logical_switch_port *lsp; - SHASH_FOR_EACH (node, &old_nb_ports) { - lsp = node->data; - - if (!strcmp(lsp->type, "router") - || !strcmp(lsp->type, "switch")) { - /* The port is local. */ - sb_pb = find_lsp_in_sb(ctx, lsp); - if (!sb_pb) { - continue; - } - isb_pb = shash_find_and_delete(&local_pbs, lsp->name); - if (!isb_pb) { - isb_pb = create_isb_pb( - ctx->ovnisb_unlocked_txn, sb_pb->logical_port, - runned_az, ts->name, - &ts->header_.uuid, "transit-switch-port", &pb_tnlids); - sync_ts_isb_pb(ctx, sb_pb, isb_pb); - } else { - sync_local_port(ctx, isb_pb, sb_pb, lsp); - } - - if (isb_pb->type) { - icsbrec_port_binding_set_type(isb_pb, - "transit-switch-port"); - } - - if (isb_pb->nb_ic_uuid) { - icsbrec_port_binding_set_nb_ic_uuid(isb_pb, - &ts->header_.uuid, 1); - } - } else if (!strcmp(lsp->type, "remote")) { - /* The port is remote. */ - isb_pb = shash_find_and_delete(&remote_pbs, lsp->name); - if (!isb_pb) { - nbrec_logical_switch_update_ports_delvalue(ls, lsp); - } else { - sb_pb = find_lsp_in_sb(ctx, lsp); - if (!sb_pb) { - continue; - } - sync_remote_port(ctx, isb_pb, lsp, sb_pb); - } - } else { - VLOG_DBG("Ignore lsp %s on ts %s with type %s.", - lsp->name, ts->name, lsp->type); - } - } - - SHASH_FOR_EACH (node, &nb_ports) { - nbrec_logical_switch_port_delete(node->data); - nbrec_logical_switch_update_ports_delvalue(ls, node->data); + shash_replace(map, ls->ports[i]->name, CONST_CAST(char *, + ts_name)); } - - /* Delete extra port-binding from ISB */ - SHASH_FOR_EACH (node, &local_pbs) { - icsbrec_port_binding_delete(node->data); - } - - /* Create lsp in NB for remote ports */ - SHASH_FOR_EACH (node, &remote_pbs) { - create_nb_lsp(ctx, node->data, ls); - } - - shash_destroy(&nb_ports); - shash_destroy(&local_pbs); - shash_destroy(&remote_pbs); - shash_destroy(&old_nb_ports); - } - - SHASH_FOR_EACH (node, &switch_all_local_pbs) { - icsbrec_port_binding_delete(node->data); - } - shash_destroy(&switch_all_local_pbs); - - const struct icnbrec_transit_router *tr; - ICNBREC_TRANSIT_ROUTER_FOR_EACH (tr, ctx->ovninb_idl) { - const struct nbrec_logical_router *lr = find_tr_in_nb(ctx, tr->name); - if (!lr) { - VLOG_DBG("Transit router %s not found in NB.", tr->name); - continue; - } - - struct shash nb_ports = SHASH_INITIALIZER(&nb_ports); - struct shash local_pbs = SHASH_INITIALIZER(&local_pbs); - struct shash remote_pbs = SHASH_INITIALIZER(&remote_pbs); - - for (size_t i = 0; i < lr->n_ports; i++) { - const struct nbrec_logical_router_port *lrp = lr->ports[i]; - if (smap_get_def(&lrp->options, "interconn-tr", NULL)) { - shash_add(&nb_ports, lrp->name, lrp); - } - } - - isb_pb_key = icsbrec_port_binding_index_init_row( - ctx->icsbrec_port_binding_by_ts); - icsbrec_port_binding_index_set_transit_switch(isb_pb_key, tr->name); - - ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, - ctx->icsbrec_port_binding_by_ts) { - if (isb_pb->availability_zone == runned_az) { - shash_add(&local_pbs, isb_pb->logical_port, isb_pb); - shash_find_and_delete(&router_all_local_pbs, - isb_pb->logical_port); - } else { - shash_add(&remote_pbs, isb_pb->logical_port, isb_pb); - } - } - icsbrec_port_binding_index_destroy_row(isb_pb_key); - - for (size_t i = 0; i < tr->n_ports; i++) { - const struct icnbrec_transit_router_port *trp = tr->ports[i]; - - if (chassis_is_remote(ctx, trp->chassis)) { - isb_pb = shash_find_and_delete(&remote_pbs, trp->name); - } else { - isb_pb = shash_find_and_delete(&local_pbs, trp->name); - if (!isb_pb) { - isb_pb = create_isb_pb(ctx->ovnisb_unlocked_txn, trp->name, - runned_az, - tr->name, &tr->header_.uuid, - "transit-router-port", &pb_tnlids); - icsbrec_port_binding_set_address(isb_pb, trp->mac); - } - } - - /* Don't allow remote ports to create NB LRP until ICSB entry is - * created in the appropriate AZ. */ - if (isb_pb) { - const struct nbrec_logical_router_port *lrp = - shash_find_and_delete(&nb_ports, trp->name); - if (!lrp) { - lrp = lrp_create(ctx, lr, trp); - } - - sync_router_port(isb_pb, trp, lrp); - } - } - - SHASH_FOR_EACH(node, &nb_ports) { - nbrec_logical_router_port_delete(node->data); - nbrec_logical_router_update_ports_delvalue(lr, node->data); - } - - shash_destroy(&nb_ports); - shash_destroy(&local_pbs); - shash_destroy(&remote_pbs); } +} - SHASH_FOR_EACH (node, &router_all_local_pbs) { - icsbrec_port_binding_delete(node->data); +/* If 'lsp' is an interconnection port of a transit-switch mirror, adds that + * transit switch's name (looked up in 'lsp_ts_map') to 'ts_scope'. */ +void +port_binding_collect_lsp_ts(const struct shash *lsp_ts_map, + const struct nbrec_logical_switch_port *lsp, + struct sset *ts_scope) +{ + const char *ts_name = shash_find_data(lsp_ts_map, lsp->name); + if (ts_name) { + sset_add(ts_scope, ts_name); } - - ovn_destroy_tnlids(&pb_tnlids); - shash_destroy(&router_all_local_pbs); } struct ic_router_info { @@ -3988,6 +3210,10 @@ main(int argc, char *argv[]) = ovsdb_idl_index_create1(ovninb_idl_loop.idl, &icnbrec_transit_switch_col_name); + struct ovsdb_idl_index *icnbrec_transit_router_by_name + = ovsdb_idl_index_create1(ovninb_idl_loop.idl, + &icnbrec_transit_router_col_name); + struct ovsdb_idl_index *icsbrec_port_binding_by_az = ovsdb_idl_index_create1(ovnisb_unlocked_idl_loop.idl, &icsbrec_port_binding_col_availability_zone); @@ -4197,6 +3423,8 @@ main(int argc, char *argv[]) sbrec_service_monitor_by_remote_type_logical_port, .icnbrec_transit_switch_by_name = icnbrec_transit_switch_by_name, + .icnbrec_transit_router_by_name = + icnbrec_transit_router_by_name, .icsbrec_port_binding_by_az = icsbrec_port_binding_by_az, .icsbrec_port_binding_by_ts = icsbrec_port_binding_by_ts, .icsbrec_port_binding_by_ts_az = icsbrec_port_binding_by_ts_az, diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h index b18637022..6c1622971 100644 --- a/ic/ovn-ic.h +++ b/ic/ovn-ic.h @@ -41,6 +41,7 @@ struct ic_context { struct ovsdb_idl_index *sbrec_service_monitor_by_ic_learned; struct ovsdb_idl_index *sbrec_service_monitor_by_remote_type_logical_port; struct ovsdb_idl_index *icnbrec_transit_switch_by_name; + struct ovsdb_idl_index *icnbrec_transit_router_by_name; struct ovsdb_idl_index *icsbrec_port_binding_by_az; struct ovsdb_idl_index *icsbrec_port_binding_by_ts; struct ovsdb_idl_index *icsbrec_port_binding_by_ts_az; @@ -68,12 +69,17 @@ struct icsbrec_availability_zone; struct icsbrec_datapath_binding; struct sbrec_chassis_table; struct icsbrec_gateway_table; +struct nbrec_logical_switch_port; +struct nbrec_logical_router_port; enum ic_datapath_type ic_dp_get_type( const struct icsbrec_datapath_binding *isb_dp); void address_set_run(struct ic_context *ctx, const struct icsbrec_availability_zone *runned_az); +struct icsbrec_port_binding; +enum ic_port_binding_type ic_pb_get_type( + const struct icsbrec_port_binding *isb_pb); /* Reconciles the AZ NB Logical_Switch mirrors of the transit switches named in * 'ts_scope' (NULL reconciles every transit switch). 'isb_ts_dps' is the @@ -88,8 +94,13 @@ void ts_sync_scope(struct ic_context *ctx, struct shash *isb_ts_dps, * requests vxlan_mode and the IC-SB actually has a VXLAN encap. */ bool ic_ts_compute_vxlan_mode(struct ic_context *ctx); -void port_binding_run(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az); +/* Scope-resolution helpers for the en_port_binding change handlers. Build the + * reverse maps once per handler invocation, then resolve each changed port + * with the *_collect_* helpers. */ +void port_binding_lsp_ts_map_init(struct ic_context *ctx, struct shash *map); +void port_binding_collect_lsp_ts(const struct shash *lsp_ts_map, + const struct nbrec_logical_switch_port *lsp, + struct sset *ts_scope); void route_run(struct ic_context *ctx, const struct icsbrec_availability_zone *runned_az); void sync_service_monitor(struct ic_context *ctx, @@ -100,8 +111,15 @@ uint32_t allocate_dp_key(struct hmap *dp_tnlids, bool vxlan_mode, const char *name); const struct sbrec_chassis * find_sb_chassis(struct ic_context *ctx, const char *name); +const struct sbrec_port_binding * +find_sb_pb_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name, + const char *name); const struct nbrec_logical_switch * find_ts_in_nb(struct ic_context *ctx, char *ts_name); +const struct nbrec_logical_router_port * +get_lrp_by_lrp_name(struct ic_context *ctx, const char *lrp_name); +const struct nbrec_logical_switch_port * +get_lsp_by_ts_port_name(struct ic_context *ctx, const char *ts_port_name); bool is_az_leader(struct ovsdb_idl_txn *txn); diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at index 793b18163..7f987eb84 100644 --- a/tests/ovn-ic.at +++ b/tests/ovn-ic.at @@ -1012,6 +1012,153 @@ OVN_CLEANUP_IC([az1]) AT_CLEANUP ]) +AT_SETUP([ovn-ic -- incremental processing - port binding handler scoping]) + +# Exercises the per-transit-switch/router scoping of the en_port_binding change +# handlers on a single AZ. Helpers quiesce the engine, reset the stats, apply +# one change, and assert whether the port_binding node was fully recomputed. + +# CHECK_PB_RECOMPUTE(CHANGE_CMD, EXPECT) - run CHANGE_CMD and assert the +# port_binding node's recompute count is "0" (EXPECT=norecompute) or non-zero +# (EXPECT=recompute) afterwards. +m4_define([CHECK_PB_RECOMPUTE], [ + check ovn-ic-nbctl --wait=sb sync + check ovn-appctl -t ic/ovn-ic inc-engine/recompute + check ovn-ic-nbctl --wait=sb sync + check ovn-appctl -t ic/ovn-ic inc-engine/clear-stats + $1 + check ovn-ic-nbctl --wait=sb sync + rc=$(ovn-appctl -t ic/ovn-ic inc-engine/show-stats port_binding recompute) + echo "port_binding recompute count after '$1': $rc (expect $2)" + if test "$2" = norecompute; then + AT_CHECK([test "$rc" = 0]) + else + AT_CHECK([test "$rc" -gt 0]) + fi +]) + +ovn_init_ic_db +ovn-ic-nbctl ts-add ts1 +ovn-ic-nbctl ts-add ts2 +ovn_start az1 +as az1 +check ovn-ic-nbctl --wait=sb sync + +# One logical router with a port on each transit switch. +check ovn-nbctl lr-add lr1 +check ovn-nbctl lrp-add lr1 lrp-lr1-ts1 aa:aa:aa:aa:aa:01 169.254.100.1/24 +check ovn-nbctl lsp-add-router-port ts1 lsp-ts1-lr1 lrp-lr1-ts1 +check ovn-nbctl lrp-add lr1 lrp-lr1-ts2 aa:aa:aa:aa:aa:02 169.254.101.1/24 +check ovn-nbctl --wait=sb lsp-add-router-port ts2 lsp-ts2-lr1 lrp-lr1-ts2 + +# Wait until both transit-switch ports are mirrored to IC-SB. +OVS_WAIT_UNTIL([test "$(ovn-ic-sbctl --bare --columns=logical_port find \ + port_binding | grep -c lsp-ts)" = 2]) + +# --- Changes handled incrementally (no full recompute of port_binding) --- + +# IC-NB Transit_Switch handler: a new transit switch is scoped to itself. +CHECK_PB_RECOMPUTE([check ovn-ic-nbctl --wait=sb ts-add ts3], [norecompute]) + +# NB Logical_Switch_Port handler: a column change on an existing transit-switch +# port is scoped to its transit switch. +CHECK_PB_RECOMPUTE( + [check ovn-nbctl set logical_switch_port lsp-ts1-lr1 options:foo=bar], + [norecompute]) + +# NB Logical_Switch handler: a tenant switch (no other_config:interconn-ts) is +# irrelevant. +CHECK_PB_RECOMPUTE([check ovn-nbctl --wait=sb ls-add sw0], [norecompute]) + +# --- Fallback: an SB chassis change still triggers a full recompute --- +CHECK_PB_RECOMPUTE([check ovn-sbctl chassis-add ch-test geneve 192.0.2.10], + [recompute]) + +# --- Cross-transit-switch isolation --- + +# A change scoped to ts1 must leave ts2's port binding untouched. +ovn-ic-sbctl --bare --columns=logical_port,tunnel_key,address find \ + port_binding transit_switch=ts2 | sort > ts2_before +CHECK_PB_RECOMPUTE( + [check ovn-nbctl set logical_switch_port lsp-ts1-lr1 options:bar=baz], + [norecompute]) +ovn-ic-sbctl --bare --columns=logical_port,tunnel_key,address find \ + port_binding transit_switch=ts2 | sort > ts2_after +AT_CHECK([diff ts2_before ts2_after]) + +# --- Recompute equivalence: scoped syncs produced the same state a full +# recompute would. --- +check ovn-ic-nbctl --wait=sb sync +m4_define([_PB_DUMP], [ + ovn-ic-sbctl --columns=logical_port,transit_switch,type,address \ + list port_binding | sort > $1 +]) +_PB_DUMP([pb_before]) +check ovn-appctl -t ic/ovn-ic inc-engine/recompute +check ovn-ic-nbctl --wait=sb sync +_PB_DUMP([pb_after]) +AT_CHECK([diff pb_before pb_after]) + +OVN_CLEANUP_IC([az1]) +AT_CLEANUP + +AT_SETUP([ovn-ic -- incremental processing - transit switch port handler]) + +# A Transit_Switch_Port column change that does not also change the owning +# Transit_Switch must still be reconciled by the en_port_binding +# en_icnb_transit_switch_port change handler, without a full recompute. +# Transit_Switch_Port carries no back-reference to its switch, so the handler +# resolves the owner by scanning Transit_Switch.ports; a missing handler here +# would silently drop the change (only a Transit_Switch change would apply it). + +ovn_init_ic_db +net_add n1 +ovn_start az1 +sim_add gw-az1 +as gw-az1 +check ovs-vsctl add-br br-phys +ovn_az_attach az1 n1 br-phys 192.168.1.1 +check ovs-vsctl set open . external-ids:ovn-is-interconn=true +as az1 + +# A router with a port on the transit switch, exposed as an explicit +# transit-switch port carrying an address. +check ovn-ic-nbctl --wait=sb ts-add ts1 +check ovn-nbctl lr-add lr1 +check ovn-nbctl lrp-add lr1 lrp1 00:00:00:00:00:01 10.0.0.1/24 +check ovn-nbctl lrp-set-gateway-chassis lrp1 gw-az1 +check ovn-ic-nbctl tsp-add ts1 tsp1 type=router chassis=gw-az1 peer=lrp1 +check ovn-ic-nbctl tsp-set-addr tsp1 "00:00:00:00:00:02 10.0.1.2/24" +check ovn-ic-nbctl --wait=sb sync + +# The transit-switch port's address reached its IC-SB port binding. +OVS_WAIT_UNTIL([test "$(ovn-ic-sbctl --bare --columns=address find \ + port_binding logical_port=tsp1)" \ + = "00:00:00:00:00:02 10.0.1.2/24"]) + +# Quiesce the engine and reset stats so only the change under test counts. +check ovn-ic-nbctl --wait=sb sync +check ovn-appctl -t ic/ovn-ic inc-engine/recompute +check ovn-ic-nbctl --wait=sb sync +check ovn-appctl -t ic/ovn-ic inc-engine/clear-stats + +# Change ONLY the transit-switch port's address; the owning Transit_Switch row +# is left untouched. +check ovn-ic-nbctl --wait=sb tsp-set-addr tsp1 "00:00:00:00:00:03 10.0.1.3/24" + +# The change is applied (it propagates to the IC-SB port binding) ... +OVS_WAIT_UNTIL([test "$(ovn-ic-sbctl --bare --columns=address find \ + port_binding logical_port=tsp1)" \ + = "00:00:00:00:00:03 10.0.1.3/24"]) +# ... and handled incrementally, without a full recompute of port_binding. +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats port_binding compute)" -gt 0]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats port_binding recompute)" = 0]) + +OVN_CLEANUP_IC([az1]) +AT_CLEANUP + OVN_FOR_EACH_NORTHD([ AT_SETUP([ovn-ic -- port sync]) From patchwork Wed Sep 2 21:17:42 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297042 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=OcO2/7hF; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138]) (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 4hZwc04VVJz1xwZ for ; Thu, 03 Sep 2026 07:20:00 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 0EF6D81098; Wed, 2 Sep 2026 21:19:59 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id jb4qUpw7Az97; Wed, 2 Sep 2026 21:19:52 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp1.osuosl.org CB27B81026 Authentication-Results: smtp1.osuosl.org; arc=fail smtp.remote-ip=140.211.9.56 ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383976; b=BeCN5tZB8ypTcQ53lrx5KQG57gMpqLeszEotnsiPxt1JZaB+E8BbOLP5xWHbE7zv6F8C VSv9/XjgELPhrBL6WqKrqChcCoFjr3EDvycYU5UIPumIHUpfBDzIY78R4K2tfdEij8Eom URT1LRUzGcQarcxOD/YMEVk2v9gp+GEGltpvN8CNco0p9APqE6dkh1Pb6HDQ3Zvj4mcAv MyLWffneTFJXoSr40/5dZqvxdrDzUs95y58Q0uHldyTS7Y4GteCNsR6PgF/fioIlCjRem FYu6LeuisUr3KDpeQoPyOFXzAvd6jUrqE52hTWPhJ80C4Bd7VkQVsi8O7qacw5Sg2XQ== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383976; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=US3fcPdCfiogP/tRa3O5H9IEclULuha2PhvLBguDG2g=; b=epbkWzLCj0XBmTMaPLB+esoF3PW/U6hgkIyt7+FLC7EsndbBGYzv+Ba4PGzFFPRuBY+O 4nFLCcdx7R/ZQ4rOadCBKqWR8JQv+9hZewzf6ze955wCnA/445iGDYJhEWhfR6n+qIKYJ YSX/VbeGlbWi9zHlH32CnB8p1zZEuNdr4g1MMfGScIBk6Mu7dwj7a+GWiygvGheG8zYF4 qX9g3xMeZq85/FWd/OzvtSiukFB9v4eIazck0cWEixdUHVoC/4bAHhoVxiARY3FSHRBih 7yTiSr963ZwLti0Y4xFFC7BMV9b9bheY2Wj2w0srgZGBYiMrRljmtLy+NDR8Y0WpdiQ== ARC-Authentication-Results: i=2; smtp1.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b="OcO2/7hF"; arc=fail smtp.remote-ip=140.211.9.56 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= Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=OcO2/7hF Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp1.osuosl.org (Postfix) with ESMTPS id CB27B81026; Wed, 2 Sep 2026 21:19:36 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id AE6FFC0CFD; Wed, 2 Sep 2026 21:19:36 +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 8CA97C041B for ; Wed, 2 Sep 2026 21:19:34 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 6746340BC6 for ; Wed, 2 Sep 2026 21:19:34 +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 O36NojFY-9Uf for ; Wed, 2 Sep 2026 21:19:25 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp4.osuosl.org AC20F40B9B Authentication-Results: smtp4.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::112d" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383959; b=U7Y9ghTuy1oiHd0nkt79gGrwnV1mikUL16KkFCtpX0R3eYBHixdtCUaFwYwyg2WovVOU AnNQehE4EuOo4WknlrAWntma9xfnnIGJzHTuseWAJ29ulmWO7zh2plU6Ssk5GRWtzBvda dkDm52adKWY+I5CBa8XjDK2oAaa20TQRCMBh1hXX4kcHDkw1y5k/YkmqhYNYQQN9lqaPZ krugB68GDU7qJt/RG8/Zywl5odnkFLtyhUKHpR93SZXi+ipmnGbOhS2+/zjcXw1VlaWWv FmpyaASC97eQ8dj7Xl0NFjoLUPbV+B/ITfSNXTRIghWgAxzRL95lev7q2X8dYuVCk/w== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383959; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=mhIzXh7nEhb2VTpdsHp8t3jx/pdUYN2uHlPAbrZjnhk=; b=XlaCmmh4ACw6C81MITivFgLOzVsi8jXMWsE7oxNItnvfdt1QonR7qH43hTmlDrWUtYtp fAjiN02d+0XA08+gzRsBdkD7xzTaJgsdt3XO2l+xRnV/U68i8ltVE7j2ZotEyIstrP8sJ eHZGAsRqqqcU9ZPG+v97ddnBfrZ6AmUm5hbpBOcoOa+NO1IZa1C5xjVsuZQ/K3kQKStHL IYqDa2ULCcF7X6CW48QjX3i7uJTqnNw35/mCCWHsq+AzHOr860cl0yAGluKWcm+Vue3lJ FdtViILGMR/xnHdx60QLfomtpLOJB+MKDep2nioG/l+Tl4MRUyjqfypue+Bxid16F3w== ARC-Authentication-Results: i=1; smtp4.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b="OcO2/7hF"; arc=none smtp.remote-ip="2607:f8b0:4864:20::112d" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::112d; helo=mail-yw1-x112d.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp4.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Authentication-Results: smtp4.osuosl.org; dkim=pass (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=OcO2/7hF Received: from mail-yw1-x112d.google.com (mail-yw1-x112d.google.com [IPv6:2607:f8b0:4864:20::112d]) by smtp4.osuosl.org (Postfix) with ESMTPS id AC20F40B9B for ; Wed, 2 Sep 2026 21:19:18 +0000 (UTC) Received: by mail-yw1-x112d.google.com with SMTP id 00721157ae682-855de2d0d4dso4852817b3.0 for ; Wed, 02 Sep 2026 14:19:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383957; x=1788988757; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=mhIzXh7nEhb2VTpdsHp8t3jx/pdUYN2uHlPAbrZjnhk=; b=OcO2/7hF3sIeXN74fyWh/R/EOrbXI6KABklAfkJj4m2Ac7e5+jk2yf89pJ5GJEnUyN XVb+5aYPkE05+TsNX+zwTMBu9jSxK6D+l59zecNjvWepsM2+uRy91c5NcLXXxXIn50A8 fCJS+WaewLUlC461iZeGlQuaeLQ0ehiDnDe1ObgqedoqoUNvcOHCkUnjpRV7+XgD39VU yAY0PYpHlQvu3/zKgGwdJTJ357CFUOYju6Q9aXYtlp1+Z5NDZE0BqMltn4gXJJawF5br bG9cG3v1kJuWLWlYG/LzM4BZAHsi3Zv6qJIEGPrg5hd623DZRQV45/kkleTrD41yIIBM akNQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383957; x=1788988757; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=mhIzXh7nEhb2VTpdsHp8t3jx/pdUYN2uHlPAbrZjnhk=; b=FxMZxIlhSyRdjd7/my87RmW3OEhbo3VqBtPakmKst6DBR8/2jWqH/uFdT5swPDPNjq 05plIvFvft5jj7g+TpEhTvrf30dC8HuVoN897pfDZmERB2UlVKCtzvdrxDN5e/2ICPRu pVe0eRt9FhXqJZC5H6LGMUx/LLxBEFNE1AEB5mtvlotv/J00UVL90OSEUMlIC11vCbp/ TZAvBUWEJGhJfjWpVZh9HGztfTA3ai5Vy1a9WV1yrNQGS4RAuLzYsCaGuE/GDQpzQ7qS +0E4yponQx6UEgID28d7gKZ27io1DgEbDh+8lyZtF4N6HtH49MAV/ozPyLxy5h+oBrjJ MSWw== X-Gm-Message-State: AFuF++n6CmYrd7jwGqVEKrLzU4S095bVMHdd22EutaDQ2mbv20tDOE59 3es/L6CnYviJQd5tg0VipH8h8FiNipCmGenVKmIaO6inl5ZGW+JUgrhq6Mu1tgSY7OXoYA4K+BD Em+GsSQExj7/kGvu2pIaChTNZqJkFYYWFkRPyS9m4B/g4laPBhEk/JcyVUcR+lVs= X-Gm-Gg: AYBFou2T/FLLyLtMFoFJjqP9VbICIUxS6ZgplINM76oqWPxXpWaL43EqgdFsWfFFEMX fvTygpPqMoy57RgzDVb3TJt6FqM7RNw1a7pDuJfDYsjbvJ8mcwnVXZy/bLgWiugCVmmKJo+DYK+ iYVtgJYxXx8XbSGAFuowwd/BUCCQ+pqIKUX1lL2D1zNAhKtwSPJ9USvOKXamxab50IhCGveJFMk 4IFcdCvMASrYWgziCY3t1q6ebDbRz7WUmSacPFEpp+Hq8Dg1sqsKbeBVA4o7gJKXMP1keSVu8Xv iA4eJpohy3pVP0MewMyIXT8l6zgQGiAj2jug8ZifqgJWW9QVHi4FbKO8RuxCZnTinYbrtp2kNnm 8szOzO9RHSvh7hzEDB2+oUr+pWjJjM40GZ770Uzk5HWWiHCRGfK4q6UnOLr0tdgFTC/A6gJNrIe PyRzeewR3Ep7PZ1UEOCsZNdPHnmEZIXV+3ZuX0M5NblILsj7BzAFl5WyaZVN5IOKPryvqP/A+Z+ yjlOMZE41EGRUyvDCu9lfgHOzDZV7sl X-Received: by 2002:a05:690c:e217:10b0:861:850e:dd59 with SMTP id 00721157ae682-86e7081d3famr7117927b3.9.1788383955277; Wed, 02 Sep 2026 14:19:15 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.19.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:19:14 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:42 -0300 Message-Id: <20260902211744.2159261-13-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 12/14] ic: Add incremental handler for the en_route node. 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: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Add the incremental change handlers for the en_route node and scope its processing per transit switch: IC-SB Route/Port_Binding, IC-NB transit switch, NB Global (route gates only), NB Logical_Router(/port/static route), NB Logical_Switch(/port), NB load balancer(/group), SB Datapath_Binding and SB Learned_Route changes reconcile only the affected transit switches instead of forcing a full recompute. Supporting changes folded in: - Gate the node on the AZ identity (drop the Availability_Zone table dependency) and scope the NB Global handler to the route gate options. - Reuse the prebuilt transit-port map and the scale safeguard so scoped reconciles stay O(scope) at large fan-out. - Stop re-advertising connected IPv6 routes with a link-local next hop. Add tests/ovn-ic.at tests for the route handlers, route handler scoping, and route learn/withdraw. Assisted-by: Claude Opus 4.8, Claude Code Signed-off-by: Paulo Guilherme Silva --- ic/en-route.c | 2730 ++++++++++++++++++++++++++++++++++++++++++- ic/en-route.h | 29 + ic/inc-proc-ic.c | 52 +- ic/ovn-ic.c | 1586 ------------------------- ic/ovn-ic.h | 2 - tests/ovn-ic.at | 236 ++++ tests/ovn-macros.at | 32 + 7 files changed, 3059 insertions(+), 1608 deletions(-) diff --git a/ic/en-route.c b/ic/en-route.c index a6d8ccf6e..9c1c3ec0d 100644 --- a/ic/en-route.c +++ b/ic/en-route.c @@ -14,19 +14,1883 @@ #include -#include "en-route.h" #include "en-az.h" +#include "en-route.h" #include "lib/inc-proc-eng.h" +#include "lib/ovn-ic-nb-idl.h" +#include "lib/ovn-ic-sb-idl.h" +#include "lib/ovn-nb-idl.h" +#include "lib/ovn-sb-idl.h" +#include "openvswitch/hmap.h" #include "openvswitch/vlog.h" #include "ovn-ic.h" +#include "ovsdb-idl.h" +#include "smap.h" +#include "vec.h" +#include "sset.h" +#include "uuid.h" + +VLOG_DEFINE_THIS_MODULE(en_ic_route); + +struct ic_route_info { + struct hmap_node node; + struct in6_addr prefix; + unsigned int plen; + struct in6_addr nexthop; + const char *origin; + const char *route_table; + const char *route_tag; + bool override_connected; + struct uuid ic_route_uuid; + + const struct nbrec_logical_router *nb_lr; + + /* One of nb_route, nb_lrp, nb_lb is set and the other ones must be NULL. + * - For a route that is learned from IC-SB, or a static route that is + * generated from a route that is configured in NB, the "nb_route" + * is set. + * - For a route that is generated from a direct-connect subnet of + * a logical router port, the "nb_lrp" is set. + * - For a route that is generated from a load-balancer vip of + * a logical router, the "nb_lb" is set. */ + const struct nbrec_logical_router_static_route *nb_route; + const struct nbrec_logical_router_port *nb_lrp; + const struct nbrec_load_balancer *nb_lb; +}; + +struct ic_router_info { + struct hmap_node node; + const struct nbrec_logical_router *lr; /* key of hmap */ + struct vector isb_pbs; /* Vector of const struct icsbrec_port_binding *. */ + struct hmap routes_learned; +}; + +static const struct sbrec_datapath_binding * +find_sb_dp_by_nb_uuid(struct ovsdb_idl_index *sbrec_datapath_binding, + const struct uuid *nb_uuid) +{ + const struct sbrec_datapath_binding *key = + sbrec_datapath_binding_index_init_row(sbrec_datapath_binding); + + sbrec_datapath_binding_set_nb_uuid(key, nb_uuid, 1); + + const struct sbrec_datapath_binding *dp = + sbrec_datapath_binding_index_find(sbrec_datapath_binding, key); + sbrec_datapath_binding_index_destroy_row(key); + + return dp; +} + +static inline bool +get_override_connected(const struct smap *options) +{ + return smap_get_bool(options, ROUTE_OVERRIDE_CONNECTED, false); +} + +static uint32_t +ic_route_hash(const struct in6_addr *prefix, unsigned int plen, + const struct in6_addr *nexthop, const char *origin, + const char *route_table, + const struct uuid *ic_route_uuid, + bool override_connected) +{ + uint32_t basis = ic_route_uuid ? uuid_hash(ic_route_uuid) : 0; + basis = hash_bytes(prefix, sizeof *prefix, basis); + basis = hash_int((uint32_t) plen, basis); + basis = hash_string(origin, basis); + basis = hash_string(route_table, basis); + basis = hash_boolean(override_connected, basis); + return hash_bytes(nexthop, sizeof *nexthop, basis); +} + +static struct ic_route_info * +ic_route_find(struct hmap *routes, const struct in6_addr *prefix, + unsigned int plen, const struct in6_addr *nexthop, + const char *origin, const char *route_table, + const struct uuid *ic_route_uuid, + bool override_connected, uint32_t hash) +{ + struct ic_route_info *r; + if (!hash) { + hash = ic_route_hash(prefix, plen, nexthop, origin, route_table, + ic_route_uuid, override_connected); + } + HMAP_FOR_EACH_WITH_HASH (r, node, hash, routes) { + if (ipv6_addr_equals(&r->prefix, prefix) && + r->plen == plen && + r->override_connected == override_connected && + ipv6_addr_equals(&r->nexthop, nexthop) && + !strcmp(r->origin, origin) && + !strcmp(r->route_table ? r->route_table : "", route_table) && + (!ic_route_uuid || uuid_equals(&r->ic_route_uuid, + ic_route_uuid))) { + return r; + } + } + return NULL; +} + +static struct ic_router_info * +ic_router_find(struct hmap *ic_lrs, const struct nbrec_logical_router *lr) +{ + struct ic_router_info *ic_lr; + HMAP_FOR_EACH_WITH_HASH (ic_lr, node, uuid_hash(&lr->header_.uuid), + ic_lrs) { + if (ic_lr->lr == lr) { + return ic_lr; + } + } + return NULL; +} + +static bool +parse_route(const char *s_prefix, const char *s_nexthop, + struct in6_addr *prefix, unsigned int *plen, + struct in6_addr *nexthop, bool reject_lla) +{ + if (!ip46_parse_cidr(s_prefix, prefix, plen)) { + return false; + } + + unsigned int nlen; + if (strcmp(s_nexthop, "discard") && + !ip46_parse_cidr(s_nexthop, nexthop, &nlen)) { + return false; + } + + /* Reject routes with a link-local next hop only on the learn path: a + * link-local next hop is not useful as a local static route. On the + * advertise path it must be accepted, because advertised connected IPv6 + * routes legitimately use the router port's link-local address as next + * hop (see get_nexthop_from_lport_addresses()); rejecting them there + * would make advertise_routes() delete and re-create the same route on + * every run. */ + return !reject_lla || !in6_is_lla(nexthop); +} + +/* Return false if can't be added due to bad format. */ +static bool +add_to_routes_learned(struct hmap *routes_learned, + const struct nbrec_logical_router_static_route *nb_route, + const struct nbrec_logical_router *nb_lr, + const struct uuid *ic_route_uuid) +{ + struct in6_addr prefix, nexthop; + unsigned int plen; + if (!parse_route(nb_route->ip_prefix, nb_route->nexthop, + &prefix, &plen, &nexthop, true)) { + return false; + } + const char *origin = smap_get_def(&nb_route->options, "origin", ""); + bool override_connected = get_override_connected(&nb_route->options); + + uint32_t hash = ic_route_hash(&prefix, plen, &nexthop, origin, + nb_route->route_table, ic_route_uuid, + override_connected); + if (ic_route_find(routes_learned, &prefix, plen, &nexthop, origin, + nb_route->route_table, ic_route_uuid, + override_connected, hash)) { + /* Route was added to learned on previous iteration. */ + return true; + } + + struct ic_route_info *ic_route = xzalloc(sizeof *ic_route); + ic_route->prefix = prefix; + ic_route->plen = plen; + ic_route->nexthop = nexthop; + ic_route->nb_route = nb_route; + ic_route->origin = origin; + ic_route->route_table = nb_route->route_table; + ic_route->nb_lr = nb_lr; + ic_route->ic_route_uuid = *ic_route_uuid; + ic_route->override_connected = override_connected; + hmap_insert(routes_learned, &ic_route->node, hash); + + return true; +} + +/* Returns true if IPv4 prefixes of 'nb_lr' should be advertised with the + * interconnect port's IPv6 address as next hop ("IPv4 over IPv6"). + * The option is read from the Logical_Router and may be overridden on the + * transit Logical_Router_Port. + */ +static bool +route_ipv4_next_hop_ipv6(const struct nbrec_logical_router *nb_lr, + const struct nbrec_logical_router_port *ts_lrp) +{ + const char *opt_name = "ic-route-ipv4-next-hop-ipv6"; + bool lr_opt = smap_get_bool(&nb_lr->options, opt_name, false); + + return ts_lrp ? smap_get_bool(&ts_lrp->options, opt_name, lr_opt) : lr_opt; +} + +static bool +get_nexthop_from_lport_addresses(bool is_v4_prefix, + const struct lport_addresses *laddr, + bool ipv4_next_hop_ipv6, + struct in6_addr *nexthop) +{ + /* For an IPv4 prefix, pick an IPv4 next hop by default. When + * 'ipv4_next_hop_ipv6' is enabled and the interconnect port has an IPv6 + * address, advertise the IPv4 prefix with that IPv6 address as next hop + * instead ("IPv4 over IPv6"). */ + if (is_v4_prefix && !(ipv4_next_hop_ipv6 && laddr->n_ipv6_addrs)) { + if (!laddr->n_ipv4_addrs) { + return false; + } + in6_addr_set_mapped_ipv4(nexthop, laddr->ipv4_addrs[0].addr); + return true; + } + + /* ipv6 */ + if (laddr->n_ipv6_addrs) { + *nexthop = laddr->ipv6_addrs[0].addr; + return true; + } + + return false; +} + +static bool +prefix_is_filtered(struct in6_addr *prefix, + unsigned int plen, + const struct nbrec_logical_router *nb_lr, + const struct nbrec_logical_router_port *ts_lrp, + bool is_advertisement) +{ + struct ds filter_list = DS_EMPTY_INITIALIZER; + const char *filter_direction = is_advertisement ? "ic-route-filter-adv" : + "ic-route-filter-learn"; + if (ts_lrp) { + const char *lrp_route_filter = smap_get(&ts_lrp->options, + filter_direction); + if (lrp_route_filter) { + ds_put_format(&filter_list, "%s,", lrp_route_filter); + } + } + const char *lr_route_filter = smap_get(&nb_lr->options, + filter_direction); + if (lr_route_filter) { + ds_put_format(&filter_list, "%s,", lr_route_filter); + } + + struct sset prefix_set = SSET_INITIALIZER(&prefix_set); + sset_from_delimited_string(&prefix_set, ds_cstr(&filter_list), ","); + + bool matched = true; + if (!sset_is_empty(&prefix_set)) { + matched = find_prefix_in_set(prefix, plen, &prefix_set, + filter_direction); + } + + ds_destroy(&filter_list); + sset_destroy(&prefix_set); + return matched; +} + +static bool +prefix_is_deny_filtered(struct in6_addr *prefix, + unsigned int plen, + const struct smap *nb_options, + const struct nbrec_logical_router *nb_lr, + const struct nbrec_logical_router_port *ts_lrp, + bool is_advertisement) +{ + struct ds deny_list = DS_EMPTY_INITIALIZER; + const char *deny_key = is_advertisement ? "ic-route-deny-adv" : + "ic-route-deny-learn"; + + if (ts_lrp) { + const char *lrp_deny_filter = smap_get(&ts_lrp->options, deny_key); + if (lrp_deny_filter) { + ds_put_format(&deny_list, "%s,", lrp_deny_filter); + } + } + + if (nb_lr) { + const char *lr_deny_filter = smap_get(&nb_lr->options, deny_key); + if (lr_deny_filter) { + ds_put_format(&deny_list, "%s,", lr_deny_filter); + } + } + + if (nb_options) { + const char *global_deny = smap_get(nb_options, "ic-route-denylist"); + if (!global_deny || !global_deny[0]) { + global_deny = smap_get(nb_options, "ic-route-blacklist"); + } + if (global_deny && global_deny[0]) { + ds_put_format(&deny_list, "%s,", global_deny); + } + } + + struct sset prefix_set = SSET_INITIALIZER(&prefix_set); + sset_from_delimited_string(&prefix_set, ds_cstr(&deny_list), ","); + + bool denied = false; + if (!sset_is_empty(&prefix_set)) { + denied = find_prefix_in_set(prefix, plen, &prefix_set, deny_key); + } + + ds_destroy(&deny_list); + sset_destroy(&prefix_set); + return denied; +} + +static bool +route_need_advertise(const char *policy, + struct in6_addr *prefix, + unsigned int plen, + const struct smap *nb_options, + const struct nbrec_logical_router *nb_lr, + const struct nbrec_logical_router_port *ts_lrp) +{ + if (!smap_get_bool(nb_options, "ic-route-adv", false)) { + return false; + } + + if (plen == 0 && + !smap_get_bool(nb_options, "ic-route-adv-default", false)) { + return false; + } + + if (policy && !strcmp(policy, "src-ip")) { + return false; + } + + if (prefix_is_link_local(prefix, plen)) { + return false; + } + + if (prefix_is_deny_filtered(prefix, plen, nb_options, + nb_lr, ts_lrp, true)) { + return false; + } + + if (!prefix_is_filtered(prefix, plen, nb_lr, ts_lrp, true)) { + return false; + } + + return true; +} + +static void +add_to_routes_ad(struct hmap *routes_ad, const struct in6_addr prefix, + unsigned int plen, const struct in6_addr nexthop, + const char *origin, const char *route_table, + const struct nbrec_logical_router_port *nb_lrp, + const struct nbrec_logical_router_static_route *nb_route, + const struct nbrec_logical_router *nb_lr, + const struct nbrec_load_balancer *nb_lb, + const char *route_tag, bool override_connected) +{ + ovs_assert(nb_route || nb_lrp || nb_lb || nb_lr); + + if (route_table == NULL) { + route_table = ""; + } + + uint hash = ic_route_hash(&prefix, plen, &nexthop, origin, + route_table, NULL, override_connected); + + if (!ic_route_find(routes_ad, &prefix, plen, &nexthop, origin, route_table, + NULL, override_connected, hash)) { + struct ic_route_info *ic_route = xzalloc(sizeof *ic_route); + ic_route->prefix = prefix; + ic_route->plen = plen; + ic_route->nexthop = nexthop; + ic_route->nb_route = nb_route; + ic_route->origin = origin; + ic_route->route_table = route_table; + ic_route->nb_lrp = nb_lrp; + ic_route->nb_lr = nb_lr; + ic_route->nb_lb = nb_lb; + ic_route->route_tag = route_tag; + ic_route->override_connected = override_connected; + hmap_insert(routes_ad, &ic_route->node, hash); + } else { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + const char *msg_fmt = "Duplicate %s route advertisement was " + "suppressed! NB %s uuid: "UUID_FMT; + if (nb_route) { + VLOG_WARN_RL(&rl, msg_fmt, origin, "route", + UUID_ARGS(&nb_route->header_.uuid)); + } else if (nb_lb) { + VLOG_WARN_RL(&rl, msg_fmt, origin, "loadbalancer", + UUID_ARGS(&nb_lb->header_.uuid)); + } else if (nb_lrp) { + VLOG_WARN_RL(&rl, msg_fmt, origin, "lrp", + UUID_ARGS(&nb_lrp->header_.uuid)); + } else { + VLOG_WARN_RL(&rl, msg_fmt, origin, "lr", + UUID_ARGS(&nb_lr->header_.uuid)); + } + } +} + +static void +add_static_to_routes_ad( + struct hmap *routes_ad, + const struct nbrec_logical_router_static_route *nb_route, + const struct nbrec_logical_router *nb_lr, + const struct lport_addresses *nexthop_addresses, + const struct smap *nb_options, + const char *route_tag, + const struct nbrec_logical_router_port *ts_lrp) +{ + struct in6_addr prefix, nexthop; + unsigned int plen; + bool override_connected = get_override_connected(&nb_route->options); + + if (!parse_route(nb_route->ip_prefix, nb_route->nexthop, + &prefix, &plen, &nexthop, true)) { + return; + } + + if (!route_need_advertise(nb_route->policy, &prefix, plen, nb_options, + nb_lr, ts_lrp)) { + return; + } + + if (!get_nexthop_from_lport_addresses(IN6_IS_ADDR_V4MAPPED(&prefix), + nexthop_addresses, + route_ipv4_next_hop_ipv6(nb_lr, + ts_lrp), + &nexthop)) { + return; + } + + if (VLOG_IS_DBG_ENABLED()) { + struct ds msg = DS_EMPTY_INITIALIZER; + + ds_put_format(&msg, "Advertising static route: %s -> %s, ic nexthop: ", + nb_route->ip_prefix, nb_route->nexthop); + ipv6_format_mapped(&nexthop, &msg); + ds_put_format(&msg, ", route_table: %s", nb_route->route_table[0] + ? nb_route->route_table + : "

"); + + VLOG_DBG("%s", ds_cstr(&msg)); + ds_destroy(&msg); + } + + add_to_routes_ad(routes_ad, prefix, plen, nexthop, ROUTE_ORIGIN_STATIC, + nb_route->route_table, NULL, nb_route, nb_lr, + NULL, route_tag, override_connected); +} + +static void +add_network_to_routes_ad(struct hmap *routes_ad, const char *network, + const struct nbrec_logical_router_port *nb_lrp, + const struct lport_addresses *nexthop_addresses, + const struct smap *nb_options, + const struct nbrec_logical_router *nb_lr, + const struct nbrec_logical_router_port *ts_lrp, + const char *route_tag, + const char *ts_route_table, + bool is_src_dynamic) +{ + struct in6_addr prefix, nexthop; + unsigned int plen; + if (!ip46_parse_cidr(network, &prefix, &plen)) { + return; + } + + if (!route_need_advertise(NULL, &prefix, plen, nb_options, + nb_lr, ts_lrp)) { + if (VLOG_IS_DBG_ENABLED()) { + struct ds msg = DS_EMPTY_INITIALIZER; + ds_put_format(&msg, "Route ad: skip network %s", network); + if (nb_lrp) { + ds_put_format(&msg, " of lrp %s", nb_lrp->name); + } + ds_put_format(&msg, "."); + VLOG_DBG("%s", ds_cstr(&msg)); + ds_destroy(&msg); + } + return; + } + + if (!get_nexthop_from_lport_addresses(IN6_IS_ADDR_V4MAPPED(&prefix), + nexthop_addresses, + route_ipv4_next_hop_ipv6(nb_lr, + ts_lrp), + &nexthop)) { + return; + } + + if (VLOG_IS_DBG_ENABLED()) { + struct ds msg = DS_EMPTY_INITIALIZER; + + ds_put_format(&msg, "Adding direct network route to
routing " + "table: %s", network); + + if (nb_lrp) { + ds_put_format(&msg, " of lrp %s,", nb_lrp->name); + } + ds_put_format(&msg, " nexthop "); + ipv6_format_mapped(&nexthop, &msg); + + VLOG_DBG("%s", ds_cstr(&msg)); + ds_destroy(&msg); + } + + /* Create additional route to local and remote networks that have + * common route table name with port attached to transit switch having + * same route table name. + * As a result, traffic that is processed within such route table and is + * routed cross az will select port attached to transit switch specially + * allocated to process traffic within that route table. + */ + + if (*ts_route_table) { + add_to_routes_ad(routes_ad, prefix, plen, nexthop, + ROUTE_ORIGIN_CONNECTED, ts_route_table, + nb_lrp, NULL, nb_lr, NULL, route_tag, true); + } + + const char *origin = is_src_dynamic ? ROUTE_ORIGIN_CONNECTED_DYNAMIC : + ROUTE_ORIGIN_CONNECTED; + /* directly-connected routes go to
route table */ + add_to_routes_ad(routes_ad, prefix, plen, nexthop, origin, + NULL, nb_lrp, NULL, nb_lr, NULL, route_tag, false); +} + +static void +add_lb_vip_to_routes_ad(struct hmap *routes_ad, const char *vip_key, + const struct nbrec_load_balancer *nb_lb, + const struct lport_addresses *nexthop_addresses, + const struct smap *nb_options, + const struct nbrec_logical_router *nb_lr, + const char *route_tag, + const struct nbrec_logical_router_port *ts_lrp) +{ + char *vip_str = NULL; + struct in6_addr vip_ip, nexthop; + uint16_t vip_port; + int addr_family; + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + + if (!ip_address_and_port_from_lb_key(vip_key, &vip_str, &vip_ip, + &vip_port, &addr_family)) { + VLOG_WARN_RL(&rl, "Route ad: Parsing failed for lb vip %s", vip_key); + return; + } + if (vip_str == NULL) { + return; + } + unsigned int plen = (addr_family == AF_INET) ? 32 : 128; + if (!route_need_advertise(NULL, &vip_ip, plen, nb_options, + nb_lr, ts_lrp)) { + VLOG_DBG("Route ad: skip lb vip %s.", vip_key); + goto out; + } + if (!get_nexthop_from_lport_addresses(IN6_IS_ADDR_V4MAPPED(&vip_ip), + nexthop_addresses, + route_ipv4_next_hop_ipv6(nb_lr, + ts_lrp), + &nexthop)) { + VLOG_DBG("Route ad: failed to get nexthop for lb vip %s", vip_key); + goto out; + } + + if (VLOG_IS_DBG_ENABLED()) { + struct ds msg = DS_EMPTY_INITIALIZER; + + ds_put_format(&msg, "Adding lb vip route to
routing " + "table: %s, nexthop ", vip_str); + ipv6_format_mapped(&nexthop, &msg); + + VLOG_DBG("%s", ds_cstr(&msg)); + ds_destroy(&msg); + } + + /* Lb vip routes go to
route table */ + add_to_routes_ad(routes_ad, vip_ip, plen, nexthop, ROUTE_ORIGIN_LB, + NULL, NULL, NULL, nb_lr, nb_lb, route_tag, false); +out: + free(vip_str); +} + +static bool +route_has_local_gw(const struct nbrec_logical_router *lr, + const char *route_table, const char *ip_prefix) { + + const struct nbrec_logical_router_static_route *route; + for (int i = 0; i < lr->n_static_routes; i++) { + route = lr->static_routes[i]; + if (!smap_get(&route->external_ids, "ic-learned-route") && + !strcmp(route->route_table, route_table) && + !strcmp(route->ip_prefix, ip_prefix)) { + return true; + } + } + return false; +} + +static bool +lrp_has_neighbor_in_ts(const struct nbrec_logical_router_port *lrp, + struct in6_addr *nexthop) +{ + if (!lrp || !nexthop) { + return false; + } + + struct lport_addresses lrp_networks; + if (!extract_lrp_networks(lrp, &lrp_networks)) { + destroy_lport_addresses(&lrp_networks); + return false; + } + + if (IN6_IS_ADDR_V4MAPPED(nexthop)) { + ovs_be32 neigh_prefix_v4 = in6_addr_get_mapped_ipv4(nexthop); + for (size_t i = 0; i < lrp_networks.n_ipv4_addrs; i++) { + struct ipv4_netaddr address = lrp_networks.ipv4_addrs[i]; + if (address.network == (neigh_prefix_v4 & address.mask)) { + destroy_lport_addresses(&lrp_networks); + return true; + } + } + } else { + for (size_t i = 0; i < lrp_networks.n_ipv6_addrs; i++) { + struct ipv6_netaddr address = lrp_networks.ipv6_addrs[i]; + struct in6_addr neigh_prefix = ipv6_addr_bitand(nexthop, + &address.mask); + if (ipv6_addr_equals(&address.network, &neigh_prefix)) { + destroy_lport_addresses(&lrp_networks); + return true; + } + } + } + + destroy_lport_addresses(&lrp_networks); + return false; +} + +static bool +route_matches_local_lb(const struct nbrec_load_balancer *nb_lb, + const char *ip_prefix) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + struct in6_addr prefix; + unsigned int plen; + + if (!ip46_parse_cidr(ip_prefix, &prefix, &plen)) { + return false; + } + + struct smap_node *node; + SMAP_FOR_EACH (node, &nb_lb->vips) { + char *vip_str = NULL; + struct in6_addr vip_ip; + uint16_t vip_port; + int addr_family; + if (ip_address_and_port_from_lb_key(node->key, &vip_str, + &vip_ip, &vip_port, + &addr_family)) { + if (IN6_IS_ADDR_V4MAPPED(&prefix) && addr_family == AF_INET) { + ovs_be32 vip = in6_addr_get_mapped_ipv4(&vip_ip); + ovs_be32 mask = be32_prefix_mask(plen); + + if ((vip & mask) == in6_addr_get_mapped_ipv4(&prefix)) { + free(vip_str); + return true; + } + } else if (!IN6_IS_ADDR_V4MAPPED(&prefix) + && addr_family == AF_INET6) { + struct in6_addr mask = ipv6_create_mask(plen); + struct in6_addr vip_prefix = ipv6_addr_bitand(&vip_ip, &mask); + if (ipv6_addr_equals(&prefix, &vip_prefix)) { + free(vip_str); + return true; + } + } + free(vip_str); + } else { + VLOG_WARN_RL(&rl, + "Route learn: Parsing failed for local lb vip %s", + node->key); + } + } + return false; +} + +static bool +route_need_learn(struct ic_context *ctx, + const struct nbrec_logical_router *lr, + const struct icsbrec_route *isb_route, + struct in6_addr *prefix, unsigned int plen, + const struct smap *nb_options, + const struct nbrec_logical_router_port *ts_lrp, + struct in6_addr *nexthop) +{ + if (!smap_get_bool(nb_options, "ic-route-learn", false)) { + return false; + } + + if (plen == 0 && + !smap_get_bool(nb_options, "ic-route-learn-default", false)) { + return false; + } + + if (!strcmp(isb_route->origin, ROUTE_ORIGIN_LB) && + !smap_get_bool(nb_options, "ic-route-learn-lb", false)) { + return false; + } + + if (!lrouter_is_enabled(lr)) { + return false; + } + + if (prefix_is_link_local(prefix, plen)) { + return false; + } + + if (prefix_is_deny_filtered(prefix, plen, nb_options, lr, ts_lrp, false)) { + return false; + } + + if (!prefix_is_filtered(prefix, plen, lr, ts_lrp, false)) { + return false; + } + + if (route_has_local_gw(lr, isb_route->route_table, isb_route->ip_prefix)) { + VLOG_DBG("Skip learning %s (rtb:%s) route, as we've got one with " + "local GW", isb_route->ip_prefix, isb_route->route_table); + return false; + } + + if (!lrp_has_neighbor_in_ts(ts_lrp, nexthop)) { + return false; + } + + for (size_t i = 0; i < lr->n_load_balancer; i++) { + if (route_matches_local_lb(lr->load_balancer[i], + isb_route->ip_prefix)) { + VLOG_DBG("Skip learning %s (rtb:%s) route, as we've got local" + " LB with matching VIP", isb_route->ip_prefix, + isb_route->route_table); + return false; + } + } + for (size_t i = 0; i < lr->n_load_balancer_group; i++) { + const struct nbrec_load_balancer_group *nb_lbg = + lr->load_balancer_group[i]; + for (size_t j = 0; j < nb_lbg->n_load_balancer; j++) { + if (route_matches_local_lb(nb_lbg->load_balancer[j], + isb_route->ip_prefix)) { + VLOG_DBG("Skip learning %s (rtb:%s) route, as we've got local" + " LB with matching VIP", isb_route->ip_prefix, + isb_route->route_table); + return false; + } + } + } + + const struct sbrec_datapath_binding *dp = + find_sb_dp_by_nb_uuid(ctx->sbrec_datapath_binding_by_nb_uuid, + &lr->header_.uuid); + if (!dp) { + return true; + } + + struct sbrec_learned_route *filter = sbrec_learned_route_index_init_row( + ctx->sbrec_learned_route_by_datapath); + sbrec_learned_route_index_set_datapath(filter, dp); + struct sbrec_learned_route *sb_route; + SBREC_LEARNED_ROUTE_FOR_EACH_EQUAL (sb_route, filter, + ctx->sbrec_learned_route_by_datapath) { + if (!strcmp(isb_route->ip_prefix, sb_route->ip_prefix)) { + sbrec_learned_route_index_destroy_row(filter); + VLOG_DBG("Skip learning %s (rtb:%s) route, as we've got" + " dynamic routing learned", isb_route->ip_prefix, + isb_route->route_table); + return false; + } + } + sbrec_learned_route_index_destroy_row(filter); + + return true; +} + +static const char * +get_lrp_name_by_ts_port_name(struct ic_context *ctx, const char *ts_port_name) +{ + const struct nbrec_logical_switch_port *nb_lsp; + + nb_lsp = get_lsp_by_ts_port_name(ctx, ts_port_name); + if (!nb_lsp) { + return NULL; + } + + return smap_get(&nb_lsp->options, "router-port"); +} + +static const struct nbrec_logical_router_port * +find_lrp_of_nexthop(struct ic_context *ctx, + const struct icsbrec_route *isb_route) +{ + const struct nbrec_logical_router_port *lrp; + const struct nbrec_logical_switch *ls; + ls = find_ts_in_nb(ctx, isb_route->transit_switch); + if (!ls) { + return NULL; + } + + struct in6_addr nexthop; + if (!ip46_parse(isb_route->nexthop, &nexthop)) { + return NULL; + } + + for (size_t i = 0; i < ls->n_ports; i++) { + char *lsp_name = ls->ports[i]->name; + const char *lrp_name = get_lrp_name_by_ts_port_name(ctx, + lsp_name); + if (!lrp_name) { + continue; + } + + lrp = get_lrp_by_lrp_name(ctx, lrp_name); + if (!lrp) { + continue; + } + + struct lport_addresses lrp_networks; + if (!extract_lrp_networks(lrp, &lrp_networks)) { + destroy_lport_addresses(&lrp_networks); + continue; + } + + if (IN6_IS_ADDR_V4MAPPED(&nexthop)) { + ovs_be32 nexthop_v4 = in6_addr_get_mapped_ipv4(&nexthop); + for (size_t i_v4 = 0; i_v4 < lrp_networks.n_ipv4_addrs; i_v4++) { + struct ipv4_netaddr address = lrp_networks.ipv4_addrs[i_v4]; + if (address.addr == nexthop_v4) { + destroy_lport_addresses(&lrp_networks); + return lrp; + } + } + } else { + for (size_t i_v6 = 0; i_v6 < lrp_networks.n_ipv6_addrs; i_v6++) { + struct ipv6_netaddr address = lrp_networks.ipv6_addrs[i_v6]; + struct in6_addr nexthop_v6 = ipv6_addr_bitand(&nexthop, + &address.mask); + if (ipv6_addr_equals(&address.network, &nexthop_v6)) { + destroy_lport_addresses(&lrp_networks); + return lrp; + } + } + } + destroy_lport_addresses(&lrp_networks); + } + + return NULL; +} + +static bool +lrp_is_ts_port(struct ic_context *ctx, struct ic_router_info *ic_lr, + const char *lrp_name) +{ + const struct icsbrec_port_binding *isb_pb; + const char *ts_lrp_name; + VECTOR_FOR_EACH (&ic_lr->isb_pbs, isb_pb) { + ts_lrp_name = get_lrp_name_by_ts_port_name(ctx, isb_pb->logical_port); + if (ts_lrp_name && !strcmp(ts_lrp_name, lrp_name)) { + return true; + } + } + return false; +} + +#define IC_ROUTE_LEARN_TAG_RULES "ic-route-learn-tag-rules" +#define IC_ROUTE_LEARN_TAG_ALLOW "allow:" +#define IC_ROUTE_LEARN_TAG_BLOCK "block:" +#define IC_ROUTE_FILTER_TAG "ic-route-filter-tag" + +/* Route tag rules of a TS LRP, as configured through its + * "ic-route-learn-tag-rules" option. */ +struct route_learn_tag_rules { + const char *config; /* Option value, for logging purposes. */ + bool configured; /* False if the option is unset or invalid, in + * which case no route is filtered. */ + bool allow; /* True: only routes tagged with one of 'tags' are + * learned. False: routes tagged with one of + * 'tags' are not learned. */ + struct sset tags; +}; + +/* Initializes 'rules' from the "ic-route-learn-tag-rules" option of 'lrp', + * which may be NULL. The option value must be either + * "allow:" or "block:"; any other + * value is logged and ignored. Route tags cannot contain commas. */ +static void +route_learn_tag_rules_init(struct route_learn_tag_rules *rules, + const struct nbrec_logical_router_port *lrp) +{ + static struct vlog_rate_limit bad_value_rl = VLOG_RATE_LIMIT_INIT(5, 1); + static struct vlog_rate_limit no_tag_rl = VLOG_RATE_LIMIT_INIT(5, 1); + const char *tags = ""; + + rules->config = lrp ? smap_get(&lrp->options, IC_ROUTE_LEARN_TAG_RULES) + : NULL; + rules->configured = false; + rules->allow = false; + + if (rules->config) { + if (!strncmp(rules->config, IC_ROUTE_LEARN_TAG_ALLOW, + strlen(IC_ROUTE_LEARN_TAG_ALLOW))) { + tags = rules->config + strlen(IC_ROUTE_LEARN_TAG_ALLOW); + rules->allow = true; + rules->configured = true; + } else if (!strncmp(rules->config, IC_ROUTE_LEARN_TAG_BLOCK, + strlen(IC_ROUTE_LEARN_TAG_BLOCK))) { + tags = rules->config + strlen(IC_ROUTE_LEARN_TAG_BLOCK); + rules->configured = true; + } else { + VLOG_WARN_RL(&bad_value_rl, + "Ignoring invalid %s value [%s] of logical " + "router port %s: expected \"%s\" or " + "\"%s\".", IC_ROUTE_LEARN_TAG_RULES, + rules->config, lrp->name, + IC_ROUTE_LEARN_TAG_ALLOW, IC_ROUTE_LEARN_TAG_BLOCK); + } + } + + sset_from_delimited_string(&rules->tags, tags, ","); + + if (rules->configured && sset_is_empty(&rules->tags)) { + VLOG_WARN_RL(&no_tag_rl, + "Ignoring %s value [%s] of logical router port %s: " + "no route tag specified.", IC_ROUTE_LEARN_TAG_RULES, + rules->config, lrp->name); + rules->configured = false; + } +} + +static void +route_learn_tag_rules_destroy(struct route_learn_tag_rules *rules) +{ + sset_destroy(&rules->tags); +} + +/* Returns true if a route tagged with 'route_tag' (NULL if the route + * carries no tag) can be learned according to 'rules'. */ +static bool +route_learn_tag_rules_allow(const struct route_learn_tag_rules *rules, + const char *route_tag) +{ + if (!rules->configured) { + return true; + } + + bool listed = route_tag && sset_contains(&rules->tags, route_tag); + return rules->allow ? listed : !listed; +} + +/* A NULL 'ts_scope' means "all transit switches" (full recompute); a non-NULL + * scope restricts processing to the transit switches whose names it + * contains. */ +static bool +route_ts_in_scope(const struct sset *ts_scope, const char *ts_name) +{ + return !ts_scope || sset_contains(ts_scope, ts_name); +} + +static void +sync_learned_routes(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + struct ic_router_info *ic_lr, + const struct sset *ts_scope) +{ + ovs_assert(ctx->ovnnb_txn); + const struct icsbrec_route *isb_route, *isb_route_key; + + const struct nbrec_nb_global *nb_global = + nbrec_nb_global_first(ctx->ovnnb_idl); + ovs_assert(nb_global); + + const char *lrp_name, *ts_route_table, *route_filter_tag; + const struct icsbrec_port_binding *isb_pb; + const struct nbrec_logical_router_port *lrp; + VECTOR_FOR_EACH (&ic_lr->isb_pbs, isb_pb) { + if (!route_ts_in_scope(ts_scope, isb_pb->transit_switch)) { + continue; + } + if (!strcmp(isb_pb->address, "")) { + continue; + } + lrp_name = get_lrp_name_by_ts_port_name(ctx, isb_pb->logical_port); + lrp = lrp_name ? get_lrp_by_lrp_name(ctx, lrp_name) : NULL; + if (lrp) { + ts_route_table = smap_get_def(&lrp->options, "route_table", ""); + route_filter_tag = smap_get_def(&lrp->options, + IC_ROUTE_FILTER_TAG, ""); + } else { + ts_route_table = ""; + route_filter_tag = ""; + } + + struct route_learn_tag_rules learn_tag_rules; + route_learn_tag_rules_init(&learn_tag_rules, lrp); + + if (route_filter_tag[0]) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + if (learn_tag_rules.configured) { + VLOG_WARN_RL(&rl, "The deprecated %s option of logical router " + "port %s is ignored as %s is also configured.", + IC_ROUTE_FILTER_TAG, lrp->name, + IC_ROUTE_LEARN_TAG_RULES); + } else { + VLOG_WARN_RL(&rl, "The %s option of logical router port %s is " + "deprecated and will be removed in the 28.09 " + "release; use %s instead.", IC_ROUTE_FILTER_TAG, + lrp->name, IC_ROUTE_LEARN_TAG_RULES); + } + } + + isb_route_key = icsbrec_route_index_init_row(ctx->icsbrec_route_by_ts); + icsbrec_route_index_set_transit_switch(isb_route_key, + isb_pb->transit_switch); + + ICSBREC_ROUTE_FOR_EACH_EQUAL (isb_route, isb_route_key, + ctx->icsbrec_route_by_ts) { + /* Filters ICSB routes, skipping those that either belong to + * current logical router or are legacy routes from the current + * availability zone (without lr-id). + */ + const char *lr_id = smap_get(&isb_route->external_ids, "lr-id"); + struct uuid lr_uuid; + if (lr_id) { + if (!uuid_from_string(&lr_uuid, lr_id) + || uuid_equals(&ic_lr->lr->header_.uuid, &lr_uuid)) { + continue; + } + } else if (isb_route->availability_zone == runned_az) { + continue; + } + + const char *isb_route_tag = smap_get(&isb_route->external_ids, + "ic-route-tag"); + /* The deprecated filter tag is honored only when the route learn + * tag rules are not in effect. */ + if (!learn_tag_rules.configured && isb_route_tag && + !strcmp(isb_route_tag, route_filter_tag)) { + VLOG_DBG("Skip learning route %s -> %s as its route tag " + "[%s] is filtered by the filter tag [%s] of TS LRP", + isb_route->ip_prefix, isb_route->nexthop, + isb_route_tag, route_filter_tag); + continue; + } + + if (!route_learn_tag_rules_allow(&learn_tag_rules, + isb_route_tag)) { + VLOG_DBG("Skip learning route %s -> %s as its route tag " + "[%s] is filtered by the %s [%s] of TS LRP", + isb_route->ip_prefix, isb_route->nexthop, + isb_route_tag ? isb_route_tag : "", + IC_ROUTE_LEARN_TAG_RULES, learn_tag_rules.config); + continue; + } + + if (isb_route->route_table[0] && + strcmp(isb_route->route_table, ts_route_table)) { + if (VLOG_IS_DBG_ENABLED()) { + VLOG_DBG("Skip learning static route %s -> %s as either " + "its route table %s != %s of TS port or ", + isb_route->ip_prefix, isb_route->nexthop, + isb_route->route_table, ts_route_table); + } + continue; + } + + struct in6_addr prefix, nexthop; + unsigned int plen; + if (!parse_route(isb_route->ip_prefix, isb_route->nexthop, + &prefix, &plen, &nexthop, true)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Bad route format in IC-SB: %s -> %s. " + "Ignored.", isb_route->ip_prefix, + isb_route->nexthop); + continue; + } + if (!route_need_learn(ctx, ic_lr->lr, isb_route, &prefix, plen, + &nb_global->options, lrp, &nexthop)) { + continue; + } + + bool override_connected = + get_override_connected(&isb_route->options); + struct ic_route_info *route_learned + = ic_route_find(&ic_lr->routes_learned, &prefix, plen, + &nexthop, isb_route->origin, + isb_route->route_table, + &isb_route->header_.uuid, + override_connected, 0); + + if (route_learned) { + hmap_remove(&ic_lr->routes_learned, &route_learned->node); + free(route_learned); + } else { + /* Create the missing route in NB. */ + const struct nbrec_logical_router_static_route *nb_route = + nbrec_logical_router_static_route_insert(ctx->ovnnb_txn); + nbrec_logical_router_static_route_set_ip_prefix(nb_route, + isb_route->ip_prefix); + nbrec_logical_router_static_route_set_nexthop(nb_route, + isb_route->nexthop); + char *uuid_s = xasprintf(UUID_FMT, + UUID_ARGS(&isb_route->header_.uuid)); + nbrec_logical_router_static_route_set_route_table(nb_route, + isb_route->route_table); + nbrec_logical_router_static_route_update_external_ids_setkey( + nb_route, "ic-learned-route", uuid_s); + nbrec_logical_router_static_route_update_options_setkey( + nb_route, "origin", isb_route->origin); + free(uuid_s); + if (override_connected) { + nbrec_logical_router_static_route_update_options_setkey( + nb_route, ROUTE_OVERRIDE_CONNECTED, "true"); + } + nbrec_logical_router_update_static_routes_addvalue(ic_lr->lr, + nb_route); + } + } + icsbrec_route_index_destroy_row(isb_route_key); + route_learn_tag_rules_destroy(&learn_tag_rules); + } + + /* Delete extra learned routes. */ + struct ic_route_info *route_learned; + HMAP_FOR_EACH_SAFE (route_learned, node, &ic_lr->routes_learned) { + VLOG_DBG("Delete route %s -> %s that is not in IC-SB from NB.", + route_learned->nb_route->ip_prefix, + route_learned->nb_route->nexthop); + nbrec_logical_router_update_static_routes_delvalue( + ic_lr->lr, route_learned->nb_route); + hmap_remove(&ic_lr->routes_learned, &route_learned->node); + free(route_learned); + } +} + +static void +ad_route_sync_external_ids(const struct ic_route_info *route_adv, + const struct icsbrec_route *isb_route) +{ + struct uuid isb_ext_id, nb_id, isb_ext_lr_id, lr_id; + const char *route_tag; + smap_get_uuid(&isb_route->external_ids, "nb-id", &isb_ext_id); + smap_get_uuid(&isb_route->external_ids, "lr-id", &isb_ext_lr_id); + nb_id = route_adv->nb_lb ? route_adv->nb_lb->header_.uuid : + route_adv->nb_route ? route_adv->nb_route->header_.uuid : + route_adv->nb_lrp ? route_adv->nb_lrp->header_.uuid : + route_adv->nb_lr->header_.uuid; + + lr_id = route_adv->nb_lr->header_.uuid; + if (!uuid_equals(&isb_ext_id, &nb_id)) { + char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&nb_id)); + icsbrec_route_update_external_ids_setkey(isb_route, "nb-id", + uuid_s); + free(uuid_s); + } + if (!uuid_equals(&isb_ext_lr_id, &lr_id)) { + char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&lr_id)); + icsbrec_route_update_external_ids_setkey(isb_route, "lr-id", + uuid_s); + free(uuid_s); + } + if (strcmp(route_adv->route_tag, "")) { + icsbrec_route_update_external_ids_setkey(isb_route, "ic-route-tag", + route_adv->route_tag); + } else { + route_tag = smap_get(&isb_route->external_ids, "ic-route-tag"); + if (route_tag) { + icsbrec_route_update_external_ids_delkey(isb_route, + "ic-route-tag"); + } + } +} + +/* Sync routes from routes_ad to IC-SB. */ +static void +advertise_routes(struct ic_context *ctx, + const struct icsbrec_availability_zone *az, + const char *ts_name, + struct hmap *routes_ad) +{ + ovs_assert(ctx->ovnisb_unlocked_txn); + const struct icsbrec_route *isb_route; + const struct icsbrec_route *isb_route_key = + icsbrec_route_index_init_row(ctx->icsbrec_route_by_ts_az); + icsbrec_route_index_set_transit_switch(isb_route_key, ts_name); + icsbrec_route_index_set_availability_zone(isb_route_key, az); -VLOG_DEFINE_THIS_MODULE(en_ic_route); + ICSBREC_ROUTE_FOR_EACH_EQUAL (isb_route, isb_route_key, + ctx->icsbrec_route_by_ts_az) { + struct in6_addr prefix, nexthop; + unsigned int plen; + + if (!parse_route(isb_route->ip_prefix, isb_route->nexthop, + &prefix, &plen, &nexthop, false)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Bad route format in IC-SB: %s -> %s. " + "Delete it.", + isb_route->ip_prefix, isb_route->nexthop); + icsbrec_route_delete(isb_route); + continue; + } + bool override_connected = get_override_connected(&isb_route->options); + struct ic_route_info *route_adv = + ic_route_find(routes_ad, &prefix, plen, &nexthop, + isb_route->origin, isb_route->route_table, + NULL, override_connected, 0); + if (!route_adv) { + /* Delete the extra route from IC-SB. */ + VLOG_DBG("Delete route %s -> %s from IC-SB, which is not found" + " in local routes to be advertised.", + isb_route->ip_prefix, isb_route->nexthop); + icsbrec_route_delete(isb_route); + } else { + ad_route_sync_external_ids(route_adv, isb_route); + + hmap_remove(routes_ad, &route_adv->node); + free(route_adv); + } + } + icsbrec_route_index_destroy_row(isb_route_key); + + struct ds prefix = DS_EMPTY_INITIALIZER; + + /* Create the missing routes in IC-SB */ + struct ic_route_info *route_adv; + HMAP_FOR_EACH_SAFE (route_adv, node, routes_ad) { + ds_clear(&prefix); + ipv6_format_mapped(&route_adv->prefix, &prefix); + ds_put_format(&prefix, "/%d", route_adv->plen); + char *nexthop_s = normalize_v46(&route_adv->nexthop); + + isb_route = icsbrec_route_insert(ctx->ovnisb_unlocked_txn); + icsbrec_route_set_transit_switch(isb_route, ts_name); + icsbrec_route_set_availability_zone(isb_route, az); + icsbrec_route_set_ip_prefix(isb_route, ds_cstr(&prefix)); + icsbrec_route_set_nexthop(isb_route, nexthop_s); + icsbrec_route_set_origin(isb_route, route_adv->origin); + icsbrec_route_set_route_table(isb_route, route_adv->route_table + ? route_adv->route_table + : ""); + if (route_adv->override_connected) { + icsbrec_route_update_options_setkey(isb_route, + ROUTE_OVERRIDE_CONNECTED, "true"); + } + free(nexthop_s); + + ad_route_sync_external_ids(route_adv, isb_route); + + hmap_remove(routes_ad, &route_adv->node); + free(route_adv); + } + + ds_destroy(&prefix); +} + +static void +collect_learned_routes(struct ic_context *ctx, + struct ic_router_info *ic_lr, + const struct sset *ts_scope) +{ + const struct nbrec_logical_router *lr = ic_lr->lr; + + /* Check static routes of the LR and collect learned routes */ + for (int i = 0; i < lr->n_static_routes; i++) { + const struct nbrec_logical_router_static_route *nb_route + = lr->static_routes[i]; + struct uuid isb_uuid; + if (smap_get_uuid(&nb_route->external_ids, "ic-learned-route", + &isb_uuid)) { + /* It is a learned route. As in build_ts_routes_to_adv(), under a + * restricted transit switch scope do not collect a learned route + * whose originating IC-SB route still exists on an out-of-scope + * transit switch: sync_learned_routes() deletes whatever is left + * in routes_learned, so collecting it here would wrongly delete + * another transit switch's route. This is the fallback path, + * taken when the router has no in-scope port binding with valid + * addresses (e.g. a router attached only to out-of-scope transit + * switches); without this guard a scoped sync of one transit + * switch wipes the learned routes of every other one. */ + if (ts_scope) { + const struct icsbrec_route *isb_route = + icsbrec_route_get_for_uuid(ctx->ovnisb_unlocked_idl, + &isb_uuid); + if (isb_route && + !route_ts_in_scope(ts_scope, isb_route->transit_switch)) { + continue; + } + } + if (!add_to_routes_learned(&ic_lr->routes_learned, nb_route, lr, + &isb_uuid)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Bad format of learned route in NB: " + "%s -> %s. Delete it.", nb_route->ip_prefix, + nb_route->nexthop); + nbrec_logical_router_update_static_routes_delvalue(lr, + nb_route); + } + } + } +} + +static void +build_ts_routes_to_adv(struct ic_context *ctx, + struct ic_router_info *ic_lr, + struct hmap *routes_ad, + struct lport_addresses *ts_port_addrs, + const struct nbrec_nb_global *nb_global, + const char *ts_route_table, + const char *route_tag, + const struct nbrec_logical_router_port *ts_lrp, + const struct sset *ts_scope) +{ + const struct nbrec_logical_router *lr = ic_lr->lr; + + /* Check static routes of the LR */ + for (int i = 0; i < lr->n_static_routes; i++) { + const struct nbrec_logical_router_static_route *nb_route + = lr->static_routes[i]; + struct uuid isb_uuid; + if (smap_get_uuid(&nb_route->external_ids, "ic-learned-route", + &isb_uuid)) { + /* It is a learned route. When processing a restricted transit + * switch scope, don't track a learned route whose originating + * IC-SB route still exists on an out-of-scope transit switch: + * sync_learned_routes() deletes whatever is left in + * routes_learned, so tracking it here would wrongly delete another + * TS's route. A + * learned route whose source IC-SB route is gone (e.g. it was + * withdrawn and re-advertised under a new UUID) IS tracked, so it + * gets cleaned up as a stale leftover instead of leaking as a + * duplicate. */ + if (ts_scope) { + const struct icsbrec_route *isb_route = + icsbrec_route_get_for_uuid(ctx->ovnisb_unlocked_idl, + &isb_uuid); + if (isb_route && + !route_ts_in_scope(ts_scope, isb_route->transit_switch)) { + continue; + } + } + if (!add_to_routes_learned(&ic_lr->routes_learned, nb_route, lr, + &isb_uuid)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Bad format of learned route in NB: " + "%s -> %s. Delete it.", nb_route->ip_prefix, + nb_route->nexthop); + nbrec_logical_router_update_static_routes_delvalue(lr, + nb_route); + } + } else if (!strcmp(ts_route_table, nb_route->route_table)) { + /* It may be a route to be advertised */ + add_static_to_routes_ad(routes_ad, nb_route, lr, ts_port_addrs, + &nb_global->options, route_tag, ts_lrp); + } + } + + /* Check directly-connected subnets of the LR */ + for (int i = 0; i < lr->n_ports; i++) { + const struct nbrec_logical_router_port *lrp = lr->ports[i]; + if (!lrp_is_ts_port(ctx, ic_lr, lrp->name)) { + for (int j = 0; j < lrp->n_networks; j++) { + add_network_to_routes_ad(routes_ad, lrp->networks[j], lrp, + ts_port_addrs, + &nb_global->options, + lr, ts_lrp, route_tag, + ts_route_table, false); + } + } else { + /* The router port of the TS port is ignored. */ + VLOG_DBG("Skip advertising direct route of lrp %s (TS port)", + lrp->name); + } + } + + /* Check loadbalancers associated with the LR */ + if (smap_get_bool(&nb_global->options, "ic-route-adv-lb", false)) { + for (size_t i = 0; i < lr->n_load_balancer; i++) { + const struct nbrec_load_balancer *nb_lb = lr->load_balancer[i]; + struct smap_node *node; + SMAP_FOR_EACH (node, &nb_lb->vips) { + add_lb_vip_to_routes_ad(routes_ad, node->key, nb_lb, + ts_port_addrs, + &nb_global->options, + lr, route_tag, ts_lrp); + } + } + + for (size_t i = 0; i < lr->n_load_balancer_group; i++) { + const struct nbrec_load_balancer_group *nb_lbg = + lr->load_balancer_group[i]; + for (size_t j = 0; j < nb_lbg->n_load_balancer; j++) { + const struct nbrec_load_balancer *nb_lb = + nb_lbg->load_balancer[j]; + struct smap_node *node; + SMAP_FOR_EACH (node, &nb_lb->vips) { + add_lb_vip_to_routes_ad(routes_ad, node->key, nb_lb, + ts_port_addrs, + &nb_global->options, + lr, route_tag, ts_lrp); + } + } + } + } + + const struct sbrec_datapath_binding *dp = + find_sb_dp_by_nb_uuid(ctx->sbrec_datapath_binding_by_nb_uuid, + &lr->header_.uuid); + if (!dp) { + return; + } + + struct sbrec_learned_route *filter = sbrec_learned_route_index_init_row( + ctx->sbrec_learned_route_by_datapath); + sbrec_learned_route_index_set_datapath(filter, dp); + struct sbrec_learned_route *sb_route; + SBREC_LEARNED_ROUTE_FOR_EACH_EQUAL (sb_route, filter, + ctx->sbrec_learned_route_by_datapath) { + add_network_to_routes_ad(routes_ad, sb_route->ip_prefix, NULL, + ts_port_addrs, + &nb_global->options, + lr, ts_lrp, route_tag, ts_route_table, true); + } + sbrec_learned_route_index_destroy_row(filter); +} + +static void +collect_lr_routes(struct ic_context *ctx, + struct ic_router_info *ic_lr, + struct shash *routes_ad_by_ts, + const struct sset *ts_scope) +{ + const struct nbrec_nb_global *nb_global = + nbrec_nb_global_first(ctx->ovnnb_idl); + ovs_assert(nb_global); + + const struct icsbrec_port_binding *isb_pb; + const char *lrp_name, *ts_name, *route_table, *route_tag; + struct lport_addresses ts_port_addrs; + const struct icnbrec_transit_switch *key; + const struct nbrec_logical_router_port *lrp; + + struct hmap *routes_ad; + const struct icnbrec_transit_switch *t_sw; + bool routes_built = false; + VECTOR_FOR_EACH (&ic_lr->isb_pbs, isb_pb) { + key = icnbrec_transit_switch_index_init_row( + ctx->icnbrec_transit_switch_by_name); + icnbrec_transit_switch_index_set_name(key, isb_pb->transit_switch); + t_sw = icnbrec_transit_switch_index_find( + ctx->icnbrec_transit_switch_by_name, key); + icnbrec_transit_switch_index_destroy_row(key); + if (!t_sw) { + continue; + } + ts_name = t_sw->name; + if (!route_ts_in_scope(ts_scope, ts_name)) { + continue; + } + routes_ad = shash_find_data(routes_ad_by_ts, ts_name); + if (!routes_ad) { + routes_ad = xzalloc(sizeof *routes_ad); + hmap_init(routes_ad); + shash_add(routes_ad_by_ts, ts_name, routes_ad); + } + + if (!lrouter_is_enabled(ic_lr->lr)) { + continue; + } + + if (!extract_lsp_addresses(isb_pb->address, &ts_port_addrs)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_INFO_RL(&rl, "Route sync ignores port %s on ts %s for router" + " %s because the addresses are invalid.", + isb_pb->logical_port, isb_pb->transit_switch, + ic_lr->lr->name); + continue; + } + lrp_name = get_lrp_name_by_ts_port_name(ctx, isb_pb->logical_port); + lrp = lrp_name ? get_lrp_by_lrp_name(ctx, lrp_name) : NULL; + if (lrp) { + route_table = smap_get_def(&lrp->options, "route_table", ""); + route_tag = smap_get_def(&lrp->options, "ic-route-tag", ""); + } else { + route_table = ""; + route_tag = ""; + } + routes_built = true; + build_ts_routes_to_adv(ctx, ic_lr, routes_ad, &ts_port_addrs, + nb_global, route_table, route_tag, lrp, + ts_scope); + destroy_lport_addresses(&ts_port_addrs); + } + /* If no port binding had valid addresses (e.g. LR disabled + * and PB address cleared simultaneously), collect learned routes so + * they can be deleted by sync_learned_routes(). */ + if (!routes_built) { + collect_learned_routes(ctx, ic_lr, ts_scope); + } +} + +/* Deletes 'isb_route' if it is orphaned: its transit switch no longer exists, + * or no local LRP matches its nexthop. */ +static void +delete_orphan_ic_route(struct ic_context *ctx, + const struct icsbrec_route *isb_route) +{ + const struct icnbrec_transit_switch *t_sw_key = + icnbrec_transit_switch_index_init_row( + ctx->icnbrec_transit_switch_by_name); + icnbrec_transit_switch_index_set_name(t_sw_key, isb_route->transit_switch); + const struct icnbrec_transit_switch *t_sw = + icnbrec_transit_switch_index_find(ctx->icnbrec_transit_switch_by_name, + t_sw_key); + icnbrec_transit_switch_index_destroy_row(t_sw_key); + + if (!t_sw || !find_lrp_of_nexthop(ctx, isb_route)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_INFO_RL(&rl, "Deleting orphan ICDB:Route: %s->%s (%s, rtb:%s," + " transit switch: %s)", isb_route->ip_prefix, + isb_route->nexthop, isb_route->origin, + isb_route->route_table, isb_route->transit_switch); + icsbrec_route_delete(isb_route); + } +} + +static void +delete_orphan_ic_routes(struct ic_context *ctx, + const struct icsbrec_availability_zone *az, + const struct sset *ts_scope) +{ + const struct icsbrec_route *isb_route, *isb_route_key; + + if (ts_scope) { + /* Scoped: walk only the routes on the in-scope transit switches, via + * the (transit_switch, availability_zone) index - O + * (routes-on-scoped-TS) instead of O(all routes advertised by the + * AZ). */ + const char *ts_name; + SSET_FOR_EACH (ts_name, ts_scope) { + isb_route_key = + icsbrec_route_index_init_row(ctx->icsbrec_route_by_ts_az); + icsbrec_route_index_set_transit_switch(isb_route_key, ts_name); + icsbrec_route_index_set_availability_zone(isb_route_key, az); + ICSBREC_ROUTE_FOR_EACH_EQUAL (isb_route, isb_route_key, + ctx->icsbrec_route_by_ts_az) { + delete_orphan_ic_route(ctx, isb_route); + } + icsbrec_route_index_destroy_row(isb_route_key); + } + return; + } + + /* Full scope: every route advertised by this AZ. */ + isb_route_key = icsbrec_route_index_init_row(ctx->icsbrec_route_by_az); + icsbrec_route_index_set_availability_zone(isb_route_key, az); + ICSBREC_ROUTE_FOR_EACH_EQUAL (isb_route, isb_route_key, + ctx->icsbrec_route_by_az) { + delete_orphan_ic_route(ctx, isb_route); + } + icsbrec_route_index_destroy_row(isb_route_key); +} + +/* Synchronizes advertised and learned routes. When 'ts_scope' is NULL every + * transit switch in the AZ is reconciled (full recompute). When 'ts_scope' is + * non-NULL, only the transit switches it names are reconciled, together with + * every local logical router attached to them. Scoping by transit switch + * keeps advertise_routes() correct: it deletes any IC-SB route on a (transit + * switch, AZ) pair that is not in the locally computed set, so a reconciled + * transit switch must include the routes of all local routers attached to + * it. */ +static void +route_sync_scope(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + const struct sset *ts_scope) +{ + if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { + return; + } + + if (ts_scope && sset_is_empty(ts_scope)) { + return; + } + + delete_orphan_ic_routes(ctx, runned_az, ts_scope); + + /* Under a restricted scope, collect only the logical routers attached to + * an in-scope transit switch. Their 'router-id's are gathered from the + * IC-SB port bindings of the in-scope transit switches + * (icsbrec_port_binding_by_ts_az, O(ports-on-scoped-TS)); every other + * router is skipped in the collection loop below before any expensive + * lookup. This keeps route_sync_scope's cost proportional to the scope + * instead of to the whole AZ, while still building each in-scope router's + * FULL port set (the by-AZ walk visits all its ports), which the + * connected-route logic requires. Collecting a superset of routers would + * only be wasteful, never wrong; collecting a subset could make + * advertise_routes() wrongly delete an in-scope TS route, so the set is + * built from every port binding on the in-scope transit switches. */ + struct sset scope_lr_ids = SSET_INITIALIZER(&scope_lr_ids); + if (ts_scope) { + const char *ts_name; + SSET_FOR_EACH (ts_name, ts_scope) { + const struct icsbrec_port_binding *pb_ts_key = + icsbrec_port_binding_index_init_row( + ctx->icsbrec_port_binding_by_ts_az); + icsbrec_port_binding_index_set_transit_switch(pb_ts_key, ts_name); + icsbrec_port_binding_index_set_availability_zone(pb_ts_key, + runned_az); + const struct icsbrec_port_binding *pb; + ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, pb_ts_key, + ctx->icsbrec_port_binding_by_ts_az) { + const char *lr_id = smap_get(&pb->external_ids, "router-id"); + if (lr_id) { + sset_add(&scope_lr_ids, lr_id); + } + } + icsbrec_port_binding_index_destroy_row(pb_ts_key); + } + } + + struct hmap ic_lrs = HMAP_INITIALIZER(&ic_lrs); + const struct icsbrec_port_binding *isb_pb; + const struct icsbrec_port_binding *isb_pb_key = + icsbrec_port_binding_index_init_row(ctx->icsbrec_port_binding_by_az); + icsbrec_port_binding_index_set_availability_zone(isb_pb_key, + runned_az); + + /* Each port on TS maps to a logical router, which is stored in the + * external_ids:router-id of the IC SB port_binding record. + * Here we build info for interconnected Logical Router: + * collect IC Port Binding to process routes sync later on. */ + ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, + ctx->icsbrec_port_binding_by_az) + { + /* Scoped sync: skip routers not attached to any in-scope transit + * switch before doing any expensive lookup. */ + if (ts_scope) { + const char *lr_id = smap_get(&isb_pb->external_ids, "router-id"); + if (!lr_id || !sset_contains(&scope_lr_ids, lr_id)) { + continue; + } + } + + /* Build the full per-router port set regardless of scope: the + * connected route logic (lrp_is_ts_port) needs to recognise every one + * of a router's transit-switch ports, not just the in-scope ones, + * otherwise an out-of-scope TS port's subnet would be wrongly + * advertised. The scope is applied later, per transit switch, in + * collect_lr_routes() and sync_learned_routes(). */ + if (ic_pb_get_type(isb_pb) == IC_ROUTER_PORT) { + continue; + } + const struct nbrec_logical_switch_port *nb_lsp; + + nb_lsp = get_lsp_by_ts_port_name(ctx, isb_pb->logical_port); + if (!nb_lsp) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_DBG_RL(&rl, + "IC-SB Port_Binding '%s' on ts '%s': " + "NB LSP not found, skipping route collection.", + isb_pb->logical_port, isb_pb->transit_switch); + continue; + } + + if (!strcmp(nb_lsp->type, "switch") || !strcmp(nb_lsp->type, "")) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_DBG_RL(&rl, + "IC-SB Port_Binding '%s' on ts '%s' corresponds to a " + "switch port, not considering for route collection.", + isb_pb->logical_port, isb_pb->transit_switch); + continue; + } + + const char *ts_lrp_name = + get_lrp_name_by_ts_port_name(ctx, isb_pb->logical_port); + if (!ts_lrp_name) { + if (!strcmp(isb_pb->type, "transit-switch-port")) { + continue; + } + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Route sync ignores port %s on ts %s because " + "logical router port is not found in NB. Deleting it", + isb_pb->logical_port, isb_pb->transit_switch); + icsbrec_port_binding_delete(isb_pb); + continue; + } + + struct uuid lr_uuid; + if (!smap_get_uuid(&isb_pb->external_ids, "router-id", &lr_uuid)) { + VLOG_DBG("IC-SB Port_Binding %s doesn't have " + "external_ids:router-id set.", isb_pb->logical_port); + continue; + } + + const struct nbrec_logical_router *lr + = nbrec_logical_router_get_for_uuid(ctx->ovnnb_idl, &lr_uuid); + if (!lr) { + continue; + } + + struct ic_router_info *ic_lr = ic_router_find(&ic_lrs, lr); + if (!ic_lr) { + ic_lr = xzalloc(sizeof *ic_lr); + ic_lr->lr = lr; + ic_lr->isb_pbs = + VECTOR_EMPTY_INITIALIZER(const struct icsbrec_port_binding *); + hmap_init(&ic_lr->routes_learned); + hmap_insert(&ic_lrs, &ic_lr->node, uuid_hash(&lr->header_.uuid)); + } + vector_push(&ic_lr->isb_pbs, &isb_pb); + } + icsbrec_port_binding_index_destroy_row(isb_pb_key); + + struct ic_router_info *ic_lr; + struct shash routes_ad_by_ts = SHASH_INITIALIZER(&routes_ad_by_ts); + HMAP_FOR_EACH_SAFE (ic_lr, node, &ic_lrs) { + collect_lr_routes(ctx, ic_lr, &routes_ad_by_ts, ts_scope); + sync_learned_routes(ctx, runned_az, ic_lr, ts_scope); + vector_destroy(&ic_lr->isb_pbs); + hmap_destroy(&ic_lr->routes_learned); + hmap_remove(&ic_lrs, &ic_lr->node); + free(ic_lr); + } + struct shash_node *node; + SHASH_FOR_EACH (node, &routes_ad_by_ts) { + advertise_routes(ctx, runned_az, node->name, node->data); + hmap_destroy(node->data); + } + shash_destroy_free_data(&routes_ad_by_ts); + hmap_destroy(&ic_lrs); + sset_destroy(&scope_lr_ids); +} + +static void +route_run(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az) +{ + route_sync_scope(ctx, runned_az, NULL); +} + +/* Builds 'lr_ts_map': a map from the canonical UUID string of each NB logical + * router interconnected in this AZ to a 'struct sset *' of the transit-switch + * names it is attached to. Used by the en_route change handlers to translate + * a changed NB/SB row into the set of transit switches that must be + * reconciled. + * Destroy with route_lr_ts_map_destroy(). */ +static void +route_lr_ts_map_init(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + struct shash *lr_ts_map) +{ + shash_init(lr_ts_map); + + if (!runned_az) { + return; + } + + const struct icsbrec_port_binding *isb_pb; + const struct icsbrec_port_binding *isb_pb_key = + icsbrec_port_binding_index_init_row(ctx->icsbrec_port_binding_by_az); + icsbrec_port_binding_index_set_availability_zone(isb_pb_key, + runned_az); + ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, + ctx->icsbrec_port_binding_by_az) { + const char *lr_id = smap_get(&isb_pb->external_ids, "router-id"); + if (!lr_id) { + continue; + } + struct sset *ts_set = shash_find_data(lr_ts_map, lr_id); + if (!ts_set) { + ts_set = xmalloc(sizeof *ts_set); + sset_init(ts_set); + shash_add(lr_ts_map, lr_id, ts_set); + } + sset_add(ts_set, isb_pb->transit_switch); + } + icsbrec_port_binding_index_destroy_row(isb_pb_key); +} + +static void +route_lr_ts_map_destroy(struct shash *lr_ts_map) +{ + struct shash_node *node; + SHASH_FOR_EACH (node, lr_ts_map) { + sset_destroy(node->data); + free(node->data); + } + shash_destroy(lr_ts_map); +} + +/* en_route tracked data. 'synced_ts' holds the transit switches already + * reconciled by route_sync_scope() during the current engine run. Because + * several en_route input handlers can fire in one iteration and each maps its + * change to a transit-switch scope, the same TS could be synced more than once + * per iteration. A repeat sync is not only redundant - it duplicates learned + * routes: the NB static routes inserted by the first sync are not yet visible + * on the logical router's static_routes column within the same transaction, so + * sync_learned_routes() would re-insert them. The engine clears this set at + * the start of every engine run (engine_init_run -> clear_tracked_data). */ +struct ed_type_route { + struct sset synced_ts; + /* Snapshot of the NB Global 'options' keys that gate route processing + * (the 'ic-route-*' set listed in route_nb_global_opt_keys), taken at the + * end of the last en_route_run(). The NB Global handler compares the live + * options against this snapshot so that churn on unrelated keys - such as + * ovn-northd's 'max_tunid'/'mac_prefix'/'northd_internal_version' + * bookkeeping - does not force a full route recompute. Persistent across + * runs (not cleared by clear_tracked_data). */ + struct smap nb_global_opts; +}; + +/* NB Global 'options' keys read by route processing (see route_need_advertise, + * route_need_learn and prefix_is_deny_filtered in ovn-ic.c). A change to any + * of these alters the global advertise/learn gates of every transit switch, so + * it warrants a full route recompute; a change to any other 'options' key does + * not concern route processing. */ +static const char *route_nb_global_opt_keys[] = { + "ic-route-adv", + "ic-route-adv-default", + "ic-route-adv-lb", + "ic-route-learn", + "ic-route-learn-default", + "ic-route-learn-lb", + "ic-route-denylist", + "ic-route-blacklist", +}; + +/* Fills 'snap' with the route-relevant NB Global 'options' keys (those in + * route_nb_global_opt_keys that are set) from the current NB Global row. */ +static void +route_snapshot_nb_global_opts(struct engine_node *node, struct smap *snap) +{ + smap_clear(snap); + const struct nbrec_nb_global_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_nb_global", node)); + const struct nbrec_nb_global *nb_global = + nbrec_nb_global_table_first(tbl); + if (!nb_global) { + return; + } + for (size_t i = 0; i < ARRAY_SIZE(route_nb_global_opt_keys); i++) { + const char *key = route_nb_global_opt_keys[i]; + const char *val = smap_get(&nb_global->options, key); + if (val) { + smap_add(snap, key, val); + } + } +} enum engine_node_state -en_route_run(struct engine_node *node, void *data OVS_UNUSED) +en_route_run(struct engine_node *node, void *data_) { const struct engine_context *eng_ctx = engine_get_context(); struct ic_context *ctx = eng_ctx->client_ctx; + struct ed_type_route *data = data_; const struct ed_type_az *az = engine_get_input_data("az", node); /* runned_az is resolved by the upstream en_az node. Without an AZ there @@ -37,17 +1901,873 @@ en_route_run(struct engine_node *node, void *data OVS_UNUSED) route_run(ctx, az->runned_az); + /* Refresh the snapshot of route-relevant NB Global options so the + * NB Global handler can detect a real gate change on the next iteration. + */ + route_snapshot_nb_global_opts(node, &data->nb_global_opts); + return EN_UPDATED; } +/* Returns the ic_context, or NULL when there is no availability zone yet (in + * which case the caller should report EN_HANDLED_UNCHANGED: there is nothing + * for the route engine to do). */ +static struct ic_context * +route_handler_ctx(struct engine_node *node, + const struct icsbrec_availability_zone **runned_az) +{ + struct ic_context *ctx = engine_get_context()->client_ctx; + const struct ed_type_az *az = engine_get_input_data("az", node); + *runned_az = az->runned_az; + return az->runned_az ? ctx : NULL; +} + +/* The route handlers translate a changed input row into the set of transit + * switches that must be reconciled, then call route_sync_scope() on that set + * instead of forcing a full recompute. Reconciling per transit switch keeps + * both directions correct: advertise_routes() reconciles all local routers + * attached to a transit switch at once, and sync_learned_routes() reconciles a + * router against the IC-SB routes of the transit switches in scope. */ + +/* Unions the transit-switch names that the interconnected logical router + * 'lr_uuid' attaches to (per 'lr_ts_map') into 'affected'. */ +static void +route_scope_add_lr_uuid(struct shash *lr_ts_map, const struct uuid *lr_uuid, + struct sset *affected) +{ + char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(lr_uuid)); + const struct sset *ts_set = shash_find_data(lr_ts_map, uuid_s); + free(uuid_s); + if (!ts_set) { + return; + } + const char *ts_name; + SSET_FOR_EACH (ts_name, ts_set) { + sset_add(affected, ts_name); + } +} + +typedef bool (*route_lr_match_fn)(const struct nbrec_logical_router *lr, + const void *aux); + +/* For each interconnected logical router (per 'lr_ts_map') whose NB row + * matches 'match', unions its transit-switch names into 'affected'. Used when + * the changed row (an LRP, a static route, a load balancer) does not itself + * name a router, so the owning router is found by membership. */ +static void +route_scope_add_matching_lrs(struct ic_context *ctx, struct shash *lr_ts_map, + route_lr_match_fn match, const void *aux, + struct sset *affected) +{ + struct shash_node *node; + SHASH_FOR_EACH (node, lr_ts_map) { + struct uuid lr_uuid; + if (!uuid_from_string(&lr_uuid, node->name)) { + continue; + } + const struct nbrec_logical_router *lr = + nbrec_logical_router_get_for_uuid(ctx->ovnnb_idl, &lr_uuid); + if (!lr || !match(lr, aux)) { + continue; + } + const struct sset *ts_set = node->data; + const char *ts_name; + SSET_FOR_EACH (ts_name, ts_set) { + sset_add(affected, ts_name); + } + } +} + +static bool +lr_has_port(const struct nbrec_logical_router *lr, const void *aux) +{ + const struct nbrec_logical_router_port *lrp = aux; + for (size_t i = 0; i < lr->n_ports; i++) { + if (lr->ports[i] == lrp) { + return true; + } + } + return false; +} + +static bool +lr_has_static_route(const struct nbrec_logical_router *lr, const void *aux) +{ + const struct nbrec_logical_router_static_route *sr = aux; + for (size_t i = 0; i < lr->n_static_routes; i++) { + if (lr->static_routes[i] == sr) { + return true; + } + } + return false; +} + +static bool +lr_uses_lb(const struct nbrec_logical_router *lr, const void *aux) +{ + const struct nbrec_load_balancer *lb = aux; + for (size_t i = 0; i < lr->n_load_balancer; i++) { + if (lr->load_balancer[i] == lb) { + return true; + } + } + for (size_t i = 0; i < lr->n_load_balancer_group; i++) { + const struct nbrec_load_balancer_group *lbg = + lr->load_balancer_group[i]; + for (size_t j = 0; j < lbg->n_load_balancer; j++) { + if (lbg->load_balancer[j] == lb) { + return true; + } + } + } + return false; +} + +static bool +lr_uses_lbg(const struct nbrec_logical_router *lr, const void *aux) +{ + const struct nbrec_load_balancer_group *lbg = aux; + for (size_t i = 0; i < lr->n_load_balancer_group; i++) { + if (lr->load_balancer_group[i] == lbg) { + return true; + } + } + return false; +} + +/* Runs the scoped route sync for the transit switches in 'affected' and maps + * it to an engine result. 'updated' is set when the caller already applied a + * direct NB change. When there is work to do but the transactions needed by + * route_sync_scope() are not available yet, returns EN_UNHANDLED so the change + * is retried (rather than silently dropped). */ +static enum engine_input_handler_result +route_scope_finish(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + struct sset *affected, bool updated, + void *node_data) +{ + struct ed_type_route *rt = node_data; + + /* Drop transit switches already reconciled earlier in this engine run: + * route_sync_scope() fully reconciles a TS, so a repeat sync is redundant + * and would duplicate learned routes (see 'synced_ts'). */ + const char *ts_name; + SSET_FOR_EACH_SAFE (ts_name, affected) { + if (sset_contains(&rt->synced_ts, ts_name)) { + sset_find_and_delete(affected, ts_name); + } + } + + if (!sset_is_empty(affected)) { + if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { + return EN_UNHANDLED; + } + SSET_FOR_EACH (ts_name, affected) { + sset_add(&rt->synced_ts, ts_name); + } + route_sync_scope(ctx, runned_az, affected); + updated = true; + } + return updated ? EN_HANDLED_UPDATED : EN_HANDLED_UNCHANGED; +} + +/* NB Global: only the 'options' smap holds the route gates + * (ic-route-adv/-learn/-denylist...). Other columns (connections, ssl, + * external_ids) do not affect route processing, and neither do options keys + * outside route_nb_global_opt_keys - in particular ovn-northd routinely bumps + * unrelated keys such as 'max_tunid', 'mac_prefix' and + * 'northd_internal_version'. Compare the live route gates against the + * snapshot taken on the last run: a change to any of them affects the global + * filters of every transit switch, so it falls back to a full recompute; + * otherwise the options change is irrelevant to routes and is handled as + * a no-op. */ +enum engine_input_handler_result +route_nb_nb_global_handler(struct engine_node *node, void *data_) +{ + const struct ed_type_az *az = engine_get_input_data("az", node); + if (!az->runned_az) { + return EN_HANDLED_UNCHANGED; + } + + struct ed_type_route *data = data_; + struct smap cur = SMAP_INITIALIZER(&cur); + route_snapshot_nb_global_opts(node, &cur); + + bool changed = !smap_equal(&cur, &data->nb_global_opts); + smap_destroy(&data->nb_global_opts); + smap_clone(&data->nb_global_opts, &cur); + smap_destroy(&cur); + + return changed ? EN_UNHANDLED : EN_HANDLED_UNCHANGED; +} + +/* Returns the IC-NB transit switch named 'name', or NULL. */ +static const struct icnbrec_transit_switch * +route_find_ts_in_icnb(struct ic_context *ctx, const char *name) +{ + const struct icnbrec_transit_switch *key = + icnbrec_transit_switch_index_init_row( + ctx->icnbrec_transit_switch_by_name); + icnbrec_transit_switch_index_set_name(key, name); + const struct icnbrec_transit_switch *ts = + icnbrec_transit_switch_index_find(ctx->icnbrec_transit_switch_by_name, + key); + icnbrec_transit_switch_index_destroy_row(key); + return ts; +} + +/* NB Logical_Switch: only transit-switch mirrors (other_config:interconn-ts) + * are read by route_run (via find_ts_in_nb()/find_lrp_of_nexthop()). A change + * to such a switch's name, ports or other_config needs the corresponding + * transit switch re-synced; any other logical switch (tenant churn) is + * irrelevant. */ +enum engine_input_handler_result +route_nb_logical_switch_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = route_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_switch_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_switch", node)); + struct sset affected = SSET_INITIALIZER(&affected); + const struct nbrec_logical_switch *ls; + NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH_TRACKED (ls, tbl) { + bool oc_updated = ovsdb_idl_track_is_updated(&ls->header_, + &nbrec_logical_switch_col_other_config); + const char *ts_name = smap_get(&ls->other_config, "interconn-ts"); + + /* The tracked row only carries the new other_config, so a switch that + * just stopped mirroring a transit switch - or that now mirrors a + * different one - no longer names the transit switch that has to be + * re-synced. A mirror is always created named after its transit + * switch (ts_sync_one()), so fall back to the switch name to find the + * transit switch it used to mirror. */ + if (oc_updated && (!ts_name || strcmp(ts_name, ls->name)) + && route_find_ts_in_icnb(ctx, ls->name)) { + sset_add(&affected, ls->name); + } + + if (!ts_name) { + continue; + } + if (nbrec_logical_switch_is_new(ls) || + nbrec_logical_switch_is_deleted(ls) || + oc_updated || + ovsdb_idl_track_is_updated(&ls->header_, + &nbrec_logical_switch_col_name) || + ovsdb_idl_track_is_updated(&ls->header_, + &nbrec_logical_switch_col_ports)) { + sset_add(&affected, ts_name); + } + } + + enum engine_input_handler_result ret = + route_scope_finish(ctx, runned_az, &affected, false, data); + sset_destroy(&affected); + return ret; +} + +/* NB Logical_Router: a new router, or a change to options, ports, + * load_balancer or load_balancer_group affects which routes the router + * advertises. Scope to the transit switches the router attaches to. A change + * limited to the static_routes column is delegated to the static-route handler + * (which co-fires in the same iteration). A router deletion can no longer be + * mapped to its transit switches (its IC-SB port bindings are being removed), + * so it falls back to a full recompute. */ +enum engine_input_handler_result +route_nb_logical_router_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = route_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_router_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_router", node)); + struct shash lr_ts_map; + route_lr_ts_map_init(ctx, runned_az, &lr_ts_map); + struct sset affected = SSET_INITIALIZER(&affected); + bool unhandled = false; + + const struct nbrec_logical_router *lr; + NBREC_LOGICAL_ROUTER_TABLE_FOR_EACH_TRACKED (lr, tbl) { + if (nbrec_logical_router_is_deleted(lr)) { + unhandled = true; + break; + } + if (nbrec_logical_router_is_new(lr) || + ovsdb_idl_track_is_updated(&lr->header_, + &nbrec_logical_router_col_options) || + ovsdb_idl_track_is_updated(&lr->header_, + &nbrec_logical_router_col_enabled) || + ovsdb_idl_track_is_updated(&lr->header_, + &nbrec_logical_router_col_ports) || + ovsdb_idl_track_is_updated( + &lr->header_, &nbrec_logical_router_col_load_balancer) || + ovsdb_idl_track_is_updated( + &lr->header_, + &nbrec_logical_router_col_load_balancer_group)) { + route_scope_add_lr_uuid(&lr_ts_map, &lr->header_.uuid, &affected); + } + } + + enum engine_input_handler_result ret = + unhandled ? EN_UNHANDLED + : route_scope_finish(ctx, runned_az, &affected, false, data); + sset_destroy(&affected); + route_lr_ts_map_destroy(&lr_ts_map); + return ret; +} + +/* NB Logical_Router_Port: networks and options (route_table, ic-route-tag, + * ic-route-learn-tag-rules, and the deprecated ic-route-filter-tag) affect + * connected-route advertising and learning for the owning router. Port + * creation/deletion changes the router's 'ports' column and is handled by + * route_nb_logical_router_handler; here only column updates to an existing + * port need to be scoped to the owning router. */ +enum engine_input_handler_result +route_nb_logical_router_port_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = route_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_router_port_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_router_port", node)); + struct shash lr_ts_map; + route_lr_ts_map_init(ctx, runned_az, &lr_ts_map); + struct sset affected = SSET_INITIALIZER(&affected); + + const struct nbrec_logical_router_port *lrp; + NBREC_LOGICAL_ROUTER_PORT_TABLE_FOR_EACH_TRACKED (lrp, tbl) { + if (nbrec_logical_router_port_is_new(lrp) || + nbrec_logical_router_port_is_deleted(lrp)) { + continue; + } + route_scope_add_matching_lrs(ctx, &lr_ts_map, lr_has_port, lrp, + &affected); + } + + enum engine_input_handler_result ret = + route_scope_finish(ctx, runned_az, &affected, false, data); + sset_destroy(&affected); + route_lr_ts_map_destroy(&lr_ts_map); + return ret; +} + +/* NB Logical_Router_Static_Route: routes this engine created when learning + * remote routes carry external_ids:ic-learned-route, so their own + * create/update/delete events must be ignored (otherwise the engine ping-pongs + * on its own write-backs). A user-authored static route may need + * (re)advertising on the transit switches of its owning router. A deletion + * can no longer be mapped to its owner (it was removed from the router's + * static_routes column), so it falls back to a full recompute. */ +enum engine_input_handler_result +route_nb_logical_router_static_route_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = route_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_router_static_route_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_router_static_route", node)); + struct shash lr_ts_map; + route_lr_ts_map_init(ctx, runned_az, &lr_ts_map); + struct sset affected = SSET_INITIALIZER(&affected); + bool unhandled = false; + + const struct nbrec_logical_router_static_route *sr; + NBREC_LOGICAL_ROUTER_STATIC_ROUTE_TABLE_FOR_EACH_TRACKED (sr, tbl) { + if (smap_get(&sr->external_ids, "ic-learned-route")) { + /* Our own learned-route write-back: ignore. */ + continue; + } + if (nbrec_logical_router_static_route_is_deleted(sr)) { + unhandled = true; + break; + } + /* User route - may need (re)advertising on its router's TSes. */ + route_scope_add_matching_lrs(ctx, &lr_ts_map, lr_has_static_route, sr, + &affected); + } + + enum engine_input_handler_result ret = + unhandled ? EN_UNHANDLED + : route_scope_finish(ctx, runned_az, &affected, false, data); + sset_destroy(&affected); + route_lr_ts_map_destroy(&lr_ts_map); + return ret; +} + +/* NB Logical_Switch_Port: route_run reads a transit-switch-mirror port's + * 'type' and 'options:router-port' (via get_lsp_by_ts_port_name() and + * find_lrp_of_nexthop()) to map IC-SB port bindings to their owning logical + * router and to resolve learned-route nexthops. A column change on such a + * port reconciles its transit switch; ports that do not belong to a + * transit-switch mirror (ordinary tenant ports) are irrelevant. Port + * creation/deletion changes the mirror's 'ports' column and is handled by + * route_nb_logical_switch_handler. */ +enum engine_input_handler_result +route_nb_logical_switch_port_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = route_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_switch_port_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_switch_port", node)); + struct sset affected = SSET_INITIALIZER(&affected); + struct shash lsp_ts_map; + port_binding_lsp_ts_map_init(ctx, &lsp_ts_map); + const struct nbrec_logical_switch_port *lsp; + NBREC_LOGICAL_SWITCH_PORT_TABLE_FOR_EACH_TRACKED (lsp, tbl) { + if (nbrec_logical_switch_port_is_new(lsp) || + nbrec_logical_switch_port_is_deleted(lsp)) { + continue; + } + port_binding_collect_lsp_ts(&lsp_ts_map, lsp, &affected); + } + shash_destroy(&lsp_ts_map); + + enum engine_input_handler_result ret = + route_scope_finish(ctx, runned_az, &affected, false, data); + sset_destroy(&affected); + return ret; +} + +/* NB Load_Balancer: when ic-route-adv-lb is set, VIPs of LBs attached to a + * router (directly or via a load-balancer group) are advertised. A VIP change + * on an already-referenced LB does not touch the router's columns, so scope to + * the routers that use this LB. LB deletion changes the router's + * load_balancer column and is handled by route_nb_logical_router_handler. */ +enum engine_input_handler_result +route_nb_load_balancer_handler(struct engine_node *node, void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = route_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_load_balancer_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_load_balancer", node)); + struct shash lr_ts_map; + route_lr_ts_map_init(ctx, runned_az, &lr_ts_map); + struct sset affected = SSET_INITIALIZER(&affected); + + const struct nbrec_load_balancer *lb; + NBREC_LOAD_BALANCER_TABLE_FOR_EACH_TRACKED (lb, tbl) { + if (nbrec_load_balancer_is_deleted(lb)) { + continue; + } + route_scope_add_matching_lrs(ctx, &lr_ts_map, lr_uses_lb, lb, + &affected); + } + + enum engine_input_handler_result ret = + route_scope_finish(ctx, runned_az, &affected, false, data); + sset_destroy(&affected); + route_lr_ts_map_destroy(&lr_ts_map); + return ret; +} + +/* NB Load_Balancer_Group: a membership change affects routers that reference + * the group. Group removal changes the router's load_balancer_group column + * and is handled by route_nb_logical_router_handler. */ +enum engine_input_handler_result +route_nb_load_balancer_group_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = route_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_load_balancer_group_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_load_balancer_group", node)); + struct shash lr_ts_map; + route_lr_ts_map_init(ctx, runned_az, &lr_ts_map); + struct sset affected = SSET_INITIALIZER(&affected); + + const struct nbrec_load_balancer_group *lbg; + NBREC_LOAD_BALANCER_GROUP_TABLE_FOR_EACH_TRACKED (lbg, tbl) { + if (nbrec_load_balancer_group_is_deleted(lbg)) { + continue; + } + route_scope_add_matching_lrs(ctx, &lr_ts_map, lr_uses_lbg, lbg, + &affected); + } + + enum engine_input_handler_result ret = + route_scope_finish(ctx, runned_az, &affected, false, data); + sset_destroy(&affected); + route_lr_ts_map_destroy(&lr_ts_map); + return ret; +} + +/* Removes the engine-created NB static route that learned 'isb_route' (matched + * by external_ids:ic-learned-route == isb_route's UUID), detaching it from its + * logical router. Returns true if one was found and removed. + * + * Only a local router attached to the withdrawn route's transit switch can + * hold the learned NB route, so the candidate routers are looked up via the + * (transit_switch, availability_zone) index rather than scanning every logical + * router in the AZ (which is O(routers) per withdrawal). The withdrawal of a + * remote route does not remove the local router's port binding, so the router + * is still present on the transit switch here; a transit-switch teardown that + * removes the port binding is reconciled by the recompute paths instead. */ +static bool +route_remove_learned_nb_route( + struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + const struct icsbrec_route *isb_route) +{ + char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&isb_route->header_.uuid)); + bool removed = false; + + struct sset lr_ids = SSET_INITIALIZER(&lr_ids); + const struct icsbrec_port_binding *pb_key = + icsbrec_port_binding_index_init_row(ctx->icsbrec_port_binding_by_ts_az); + icsbrec_port_binding_index_set_transit_switch(pb_key, + isb_route->transit_switch); + icsbrec_port_binding_index_set_availability_zone(pb_key, runned_az); + const struct icsbrec_port_binding *pb; + ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, pb_key, + ctx->icsbrec_port_binding_by_ts_az) { + const char *lr_id = smap_get(&pb->external_ids, "router-id"); + if (lr_id) { + sset_add(&lr_ids, lr_id); + } + } + icsbrec_port_binding_index_destroy_row(pb_key); + + const char *lr_id; + SSET_FOR_EACH (lr_id, &lr_ids) { + struct uuid lr_uuid; + if (!uuid_from_string(&lr_uuid, lr_id)) { + continue; + } + const struct nbrec_logical_router *lr = + nbrec_logical_router_get_for_uuid(ctx->ovnnb_idl, &lr_uuid); + if (!lr) { + continue; + } + for (size_t i = 0; i < lr->n_static_routes; i++) { + const struct nbrec_logical_router_static_route *sr = + lr->static_routes[i]; + const char *learned = + smap_get(&sr->external_ids, "ic-learned-route"); + if (learned && !strcmp(learned, uuid_s)) { + nbrec_logical_router_update_static_routes_delvalue(lr, sr); + removed = true; + break; + } + } + if (removed) { + break; + } + } + + sset_destroy(&lr_ids); + free(uuid_s); + return removed; +} + +/* IC-SB Route: the most impactful input. Each relevant change is scoped to + * the route's transit switch. See the per-case comments. */ +enum engine_input_handler_result +route_icsb_route_handler(struct engine_node *node, void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = route_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icsbrec_route_table *tbl = + EN_OVSDB_GET(engine_get_input("ICSB_route", node)); + struct sset affected = SSET_INITIALIZER(&affected); + const struct icsbrec_route *isb_route; + bool updated = false; + + ICSBREC_ROUTE_TABLE_FOR_EACH_TRACKED (isb_route, tbl) { + bool ours = isb_route->availability_zone == runned_az; + + if (icsbrec_route_is_deleted(isb_route)) { + if (ours) { + /* A route advertised from this AZ was removed: re-advertise + * and let same-AZ learners un-learn, scoped to its TS. */ + sset_add(&affected, isb_route->transit_switch); + } else { + /* A remote route we had learned is gone: drop the matching NB + * static route directly. */ + if (!ctx->ovnnb_txn) { + sset_destroy(&affected); + return EN_UNHANDLED; + } + if (route_remove_learned_nb_route(ctx, runned_az, isb_route)) { + updated = true; + } + } + } else if (icsbrec_route_is_new(isb_route)) { + /* A new route needs the full router context to decide learning + * (route_need_learn / NB Global options / filters), but only for + * routers attached to its transit switch. */ + sset_add(&affected, isb_route->transit_switch); + } else if (!ours) { + /* A remote route changed. Re-sync its TS only when a column that + * route learning actually reads changed: the + * prefix/nexthop/origin/route_table/transit_switch/AZ, the + * ic-route-tag in external_ids used by learn filters, or the + * connected-route override in options. The IDL routinely reports + * a tracked row as updated even when no column value changed + * (e.g. a no-op write echoed back on a monitor update); at scale + * every AZ's route sync surfaces every other AZ's routes as such + * no-op changes, so re-syncing unconditionally would reconcile + * every transit switch on every change (O(number of interconnected + * routes)). */ + if (ovsdb_idl_track_is_updated(&isb_route->header_, + &icsbrec_route_col_ip_prefix) || + ovsdb_idl_track_is_updated(&isb_route->header_, + &icsbrec_route_col_nexthop) || + ovsdb_idl_track_is_updated(&isb_route->header_, + &icsbrec_route_col_origin) || + ovsdb_idl_track_is_updated(&isb_route->header_, + &icsbrec_route_col_route_table) || + ovsdb_idl_track_is_updated(&isb_route->header_, + &icsbrec_route_col_transit_switch) || + ovsdb_idl_track_is_updated(&isb_route->header_, + &icsbrec_route_col_availability_zone) + || ovsdb_idl_track_is_updated(&isb_route->header_, + &icsbrec_route_col_external_ids) || + ovsdb_idl_track_is_updated(&isb_route->header_, + &icsbrec_route_col_options)) { + sset_add(&affected, isb_route->transit_switch); + } + } else { + /* Our own route changed. A core-field change can affect what + * other local routers learn; an external_ids/options-only change + * is our own advertise write-back (ad_route_sync_external_ids), + * whose triggering NB change already drove a sync - ignore it to + * avoid a feedback recompute. */ + if (ovsdb_idl_track_is_updated(&isb_route->header_, + &icsbrec_route_col_ip_prefix) || + ovsdb_idl_track_is_updated(&isb_route->header_, + &icsbrec_route_col_nexthop) || + ovsdb_idl_track_is_updated(&isb_route->header_, + &icsbrec_route_col_origin) || + ovsdb_idl_track_is_updated(&isb_route->header_, + &icsbrec_route_col_route_table) || + ovsdb_idl_track_is_updated(&isb_route->header_, + &icsbrec_route_col_transit_switch) + || ovsdb_idl_track_is_updated( + &isb_route->header_, + &icsbrec_route_col_availability_zone)) { + sset_add(&affected, isb_route->transit_switch); + } + } + } + + enum engine_input_handler_result ret = + route_scope_finish(ctx, runned_az, &affected, updated, data); + sset_destroy(&affected); + return ret; +} + +/* IC-SB Port_Binding: route_run maps a port binding to its NB logical router + * via external_ids:router-id and reads logical_port/transit_switch/ + * availability_zone/address/type. A change to any of those (or a new/deleted + * binding) needs its transit switch re-synced; tunnel_key/gateway/nb_ic_uuid/ + * encap are not read by route_run. */ +enum engine_input_handler_result +route_icsb_port_binding_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = route_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icsbrec_port_binding_table *tbl = + EN_OVSDB_GET(engine_get_input("ICSB_port_binding", node)); + struct sset affected = SSET_INITIALIZER(&affected); + const struct icsbrec_port_binding *isb_pb; + ICSBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (isb_pb, tbl) { + if (icsbrec_port_binding_is_new(isb_pb) || + icsbrec_port_binding_is_deleted(isb_pb) || + ovsdb_idl_track_is_updated( + &isb_pb->header_, &icsbrec_port_binding_col_logical_port) || + ovsdb_idl_track_is_updated( + &isb_pb->header_, &icsbrec_port_binding_col_transit_switch) || + ovsdb_idl_track_is_updated( + &isb_pb->header_, + &icsbrec_port_binding_col_availability_zone) || + ovsdb_idl_track_is_updated(&isb_pb->header_, + &icsbrec_port_binding_col_address) || + ovsdb_idl_track_is_updated(&isb_pb->header_, + &icsbrec_port_binding_col_type) || + ovsdb_idl_track_is_updated( + &isb_pb->header_, &icsbrec_port_binding_col_external_ids)) { + sset_add(&affected, isb_pb->transit_switch); + } + } + + enum engine_input_handler_result ret = + route_scope_finish(ctx, runned_az, &affected, false, data); + sset_destroy(&affected); + return ret; +} + +/* IC-NB Transit_Switch: route_run keys advertise/learn off the transit switch + * name. A new or deleted transit switch is scoped to its own name; a rename + * cannot be scoped (the old name is no longer available) and falls back to a + * full recompute. Its other_config/external_ids are not read. */ +enum engine_input_handler_result +route_icnb_transit_switch_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = route_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icnbrec_transit_switch_table *tbl = + EN_OVSDB_GET(engine_get_input("ICNB_transit_switch", node)); + struct sset affected = SSET_INITIALIZER(&affected); + bool unhandled = false; + const struct icnbrec_transit_switch *ts; + ICNBREC_TRANSIT_SWITCH_TABLE_FOR_EACH_TRACKED (ts, tbl) { + if (icnbrec_transit_switch_is_new(ts) || + icnbrec_transit_switch_is_deleted(ts)) { + sset_add(&affected, ts->name); + } else if (ovsdb_idl_track_is_updated( + &ts->header_, &icnbrec_transit_switch_col_name)) { + unhandled = true; + break; + } + } + + enum engine_input_handler_result ret = + unhandled ? EN_UNHANDLED + : route_scope_finish(ctx, runned_az, &affected, false, data); + sset_destroy(&affected); + return ret; +} + +/* SB Datapath_Binding: build_ts_routes_to_adv() maps a logical router to its + * SB datapath (via nb_uuid) to read its SB learned routes. A datapath created + * or removed for an interconnected router changes that lookup, so scope to + * that router's transit switches. */ +enum engine_input_handler_result +route_sb_datapath_binding_handler(struct engine_node *node, + void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = route_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct sbrec_datapath_binding_table *tbl = + EN_OVSDB_GET(engine_get_input("SB_datapath_binding", node)); + struct shash lr_ts_map; + route_lr_ts_map_init(ctx, runned_az, &lr_ts_map); + struct sset affected = SSET_INITIALIZER(&affected); + + const struct sbrec_datapath_binding *dp; + SBREC_DATAPATH_BINDING_TABLE_FOR_EACH_TRACKED (dp, tbl) { + if (!sbrec_datapath_binding_is_new(dp) && + !sbrec_datapath_binding_is_deleted(dp) && + !ovsdb_idl_track_is_updated( + &dp->header_, &sbrec_datapath_binding_col_nb_uuid)) { + continue; + } + if (dp->n_nb_uuid < 1) { + continue; + } + route_scope_add_lr_uuid(&lr_ts_map, &dp->nb_uuid[0], &affected); + } + + enum engine_input_handler_result ret = + route_scope_finish(ctx, runned_az, &affected, false, data); + sset_destroy(&affected); + route_lr_ts_map_destroy(&lr_ts_map); + return ret; +} + +/* SB Learned_Route: dynamic (e.g. BGP-learned) routes on a router's datapath + * are advertised by build_ts_routes_to_adv(). Scope a change to the transit + * switches of the interconnected router owning the route's datapath. */ +enum engine_input_handler_result +route_sb_learned_route_handler(struct engine_node *node, void *data) +{ + const struct icsbrec_availability_zone *runned_az; + struct ic_context *ctx = route_handler_ctx(node, &runned_az); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct sbrec_learned_route_table *tbl = + EN_OVSDB_GET(engine_get_input("SB_learned_route", node)); + struct shash lr_ts_map; + route_lr_ts_map_init(ctx, runned_az, &lr_ts_map); + struct sset affected = SSET_INITIALIZER(&affected); + + const struct sbrec_learned_route *sb_route; + SBREC_LEARNED_ROUTE_TABLE_FOR_EACH_TRACKED (sb_route, tbl) { + const struct sbrec_datapath_binding *dp = sb_route->datapath; + if (!dp || dp->n_nb_uuid < 1) { + continue; + } + route_scope_add_lr_uuid(&lr_ts_map, &dp->nb_uuid[0], &affected); + } + + enum engine_input_handler_result ret = + route_scope_finish(ctx, runned_az, &affected, false, data); + sset_destroy(&affected); + route_lr_ts_map_destroy(&lr_ts_map); + return ret; +} + void * en_route_init(struct engine_node *node OVS_UNUSED, struct engine_arg *arg OVS_UNUSED) { - return NULL; + struct ed_type_route *data = xzalloc(sizeof *data); + sset_init(&data->synced_ts); + smap_init(&data->nb_global_opts); + return data; +} + +void +en_route_clear_tracked_data(void *data_) +{ + struct ed_type_route *data = data_; + sset_clear(&data->synced_ts); } void -en_route_cleanup(void *data OVS_UNUSED) +en_route_cleanup(void *data_) { + struct ed_type_route *data = data_; + sset_destroy(&data->synced_ts); + smap_destroy(&data->nb_global_opts); } diff --git a/ic/en-route.h b/ic/en-route.h index 9e9921850..dfcd71cd3 100644 --- a/ic/en-route.h +++ b/ic/en-route.h @@ -19,6 +19,35 @@ enum engine_node_state en_route_run(struct engine_node *node, void *data); void *en_route_init(struct engine_node *node, struct engine_arg *arg); +void en_route_clear_tracked_data(void *data); void en_route_cleanup(void *data); +enum engine_input_handler_result +route_nb_nb_global_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +route_nb_logical_switch_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +route_nb_logical_router_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +route_nb_logical_router_static_route_handler(struct engine_node *node, + void *data); +enum engine_input_handler_result +route_nb_logical_router_port_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +route_nb_logical_switch_port_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +route_nb_load_balancer_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +route_nb_load_balancer_group_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +route_icsb_route_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +route_icsb_port_binding_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +route_icnb_transit_switch_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +route_sb_datapath_binding_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +route_sb_learned_route_handler(struct engine_node *node, void *data); + #endif /* EN_IC_ROUTE_H */ diff --git a/ic/inc-proc-ic.c b/ic/inc-proc-ic.c index f860ed821..92b368153 100644 --- a/ic/inc-proc-ic.c +++ b/ic/inc-proc-ic.c @@ -181,7 +181,7 @@ static ENGINE_NODE(ts); static ENGINE_NODE(tr); static ENGINE_NODE(tunnel_key); static ENGINE_NODE(port_binding, CLEAR_TRACKED_DATA); -static ENGINE_NODE(route); +static ENGINE_NODE(route, CLEAR_TRACKED_DATA); static ENGINE_NODE(service_monitor); static ENGINE_NODE(address_set); static ENGINE_NODE(ic); @@ -313,21 +313,43 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_port_binding, &en_sb_chassis, port_binding_sb_chassis_handler); - /* en_route: advertise/learn cross-AZ routes. */ + /* en_route: advertise/learn cross-AZ routes. + * + * Like en_gateway and en_port_binding, this node uses only the AZ identity + * (en_az's resolved AZ and the by-AZ route/port-binding indexes) and + * does not read the Availability_Zone table, so it does not depend on + * en_icsb_availability_zone and is not churned by its nb_ic_cfg sequence + * number. */ engine_add_input(&en_route, &en_az, NULL); - engine_add_input(&en_route, &en_icsb_availability_zone, NULL); - engine_add_input(&en_route, &en_icsb_port_binding, NULL); - engine_add_input(&en_route, &en_icsb_route, NULL); - engine_add_input(&en_route, &en_icnb_transit_switch, NULL); - engine_add_input(&en_route, &en_nb_nb_global, NULL); - engine_add_input(&en_route, &en_nb_logical_router, NULL); - engine_add_input(&en_route, &en_nb_logical_router_port, NULL); - engine_add_input(&en_route, &en_nb_logical_router_static_route, NULL); - engine_add_input(&en_route, &en_nb_logical_switch_port, NULL); - engine_add_input(&en_route, &en_nb_load_balancer, NULL); - engine_add_input(&en_route, &en_nb_load_balancer_group, NULL); - engine_add_input(&en_route, &en_sb_datapath_binding, NULL); - engine_add_input(&en_route, &en_sb_learned_route, NULL); + /* en_port_binding is an ordering dependency only: en_route reads IC-SB + * port bindings synced by en_port_binding, so it must run after it. The + * real port-binding data arrives via en_icsb_port_binding, so a no-op + * handler avoids forcing a recompute. */ + engine_add_input(&en_route, &en_port_binding, engine_noop_handler); + engine_add_input(&en_route, &en_icsb_port_binding, + route_icsb_port_binding_handler); + engine_add_input(&en_route, &en_icsb_route, route_icsb_route_handler); + engine_add_input(&en_route, &en_icnb_transit_switch, + route_icnb_transit_switch_handler); + engine_add_input(&en_route, &en_nb_nb_global, route_nb_nb_global_handler); + engine_add_input(&en_route, &en_nb_logical_switch, + route_nb_logical_switch_handler); + engine_add_input(&en_route, &en_nb_logical_router, + route_nb_logical_router_handler); + engine_add_input(&en_route, &en_nb_logical_router_port, + route_nb_logical_router_port_handler); + engine_add_input(&en_route, &en_nb_logical_router_static_route, + route_nb_logical_router_static_route_handler); + engine_add_input(&en_route, &en_nb_logical_switch_port, + route_nb_logical_switch_port_handler); + engine_add_input(&en_route, &en_nb_load_balancer, + route_nb_load_balancer_handler); + engine_add_input(&en_route, &en_nb_load_balancer_group, + route_nb_load_balancer_group_handler); + engine_add_input(&en_route, &en_sb_datapath_binding, + route_sb_datapath_binding_handler); + engine_add_input(&en_route, &en_sb_learned_route, + route_sb_learned_route_handler); /* en_service_monitor: sync load-balancer health checks across AZs. */ engine_add_input(&en_service_monitor, &en_az, NULL); diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c index f8b0a8b1a..a73a30723 100644 --- a/ic/ovn-ic.c +++ b/ic/ovn-ic.c @@ -516,22 +516,6 @@ find_sb_pb_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name, return pb; } -static const struct sbrec_datapath_binding * -find_sb_dp_by_nb_uuid(struct ovsdb_idl_index *sbrec_datapath_binding, - const struct uuid *nb_uuid) -{ - const struct sbrec_datapath_binding *key = - sbrec_datapath_binding_index_init_row(sbrec_datapath_binding); - - sbrec_datapath_binding_set_nb_uuid(key, nb_uuid, 1); - - const struct sbrec_datapath_binding *dp = - sbrec_datapath_binding_index_find(sbrec_datapath_binding, key); - sbrec_datapath_binding_index_destroy_row(key); - - return dp; -} - const struct nbrec_logical_switch_port * get_lsp_by_ts_port_name(struct ic_context *ctx, const char *ts_port_name) { @@ -611,1576 +595,6 @@ port_binding_collect_lsp_ts(const struct shash *lsp_ts_map, } } -struct ic_router_info { - struct hmap_node node; - const struct nbrec_logical_router *lr; /* key of hmap */ - struct vector isb_pbs; /* Vector of const struct icsbrec_port_binding *. */ - struct hmap routes_learned; -}; - -/* Represents an interconnection route entry. */ -struct ic_route_info { - struct hmap_node node; - struct in6_addr prefix; - unsigned int plen; - struct in6_addr nexthop; - const char *origin; - const char *route_table; - const char *route_tag; - bool override_connected; - struct uuid ic_route_uuid; - - const struct nbrec_logical_router *nb_lr; - - /* One of nb_route, nb_lrp, nb_lb is set and the other ones must be NULL. - * - For a route that is learned from IC-SB, or a static route that is - * generated from a route that is configured in NB, the "nb_route" - * is set. - * - For a route that is generated from a direct-connect subnet of - * a logical router port, the "nb_lrp" is set. - * - For a route that is generated from a load-balancer vip of - * a logical router, the "nb_lb" is set. */ - const struct nbrec_logical_router_static_route *nb_route; - const struct nbrec_logical_router_port *nb_lrp; - const struct nbrec_load_balancer *nb_lb; -}; - -static inline bool -get_override_connected(const struct smap *options) -{ - return smap_get_bool(options, ROUTE_OVERRIDE_CONNECTED, false); -} - -static uint32_t -ic_route_hash(const struct in6_addr *prefix, unsigned int plen, - const struct in6_addr *nexthop, const char *origin, - const char *route_table, - const struct uuid *ic_route_uuid, - bool override_connected) -{ - uint32_t basis = ic_route_uuid ? uuid_hash(ic_route_uuid) : 0; - basis = hash_bytes(prefix, sizeof *prefix, basis); - basis = hash_int((uint32_t) plen, basis); - basis = hash_string(origin, basis); - basis = hash_string(route_table, basis); - basis = hash_boolean(override_connected, basis); - return hash_bytes(nexthop, sizeof *nexthop, basis); -} - -static struct ic_route_info * -ic_route_find(struct hmap *routes, const struct in6_addr *prefix, - unsigned int plen, const struct in6_addr *nexthop, - const char *origin, const char *route_table, - const struct uuid *ic_route_uuid, - bool override_connected, uint32_t hash) -{ - struct ic_route_info *r; - if (!hash) { - hash = ic_route_hash(prefix, plen, nexthop, origin, route_table, - ic_route_uuid, override_connected); - } - HMAP_FOR_EACH_WITH_HASH (r, node, hash, routes) { - if (ipv6_addr_equals(&r->prefix, prefix) && - r->plen == plen && - r->override_connected == override_connected && - ipv6_addr_equals(&r->nexthop, nexthop) && - !strcmp(r->origin, origin) && - !strcmp(r->route_table ? r->route_table : "", route_table) && - (!ic_route_uuid || uuid_equals(&r->ic_route_uuid, - ic_route_uuid))) { - return r; - } - } - return NULL; -} - -static struct ic_router_info * -ic_router_find(struct hmap *ic_lrs, const struct nbrec_logical_router *lr) -{ - struct ic_router_info *ic_lr; - HMAP_FOR_EACH_WITH_HASH (ic_lr, node, uuid_hash(&lr->header_.uuid), - ic_lrs) { - if (ic_lr->lr == lr) { - return ic_lr; - } - } - return NULL; -} - -static bool -parse_route(const char *s_prefix, const char *s_nexthop, - struct in6_addr *prefix, unsigned int *plen, - struct in6_addr *nexthop) -{ - if (!ip46_parse_cidr(s_prefix, prefix, plen)) { - return false; - } - - unsigned int nlen; - if (strcmp(s_nexthop, "discard") && - !ip46_parse_cidr(s_nexthop, nexthop, &nlen)) { - return false; - } - - /* Do not learn routes with link-local next hop. */ - return !in6_is_lla(nexthop); -} - -/* Return false if can't be added due to bad format. */ -static bool -add_to_routes_learned(struct hmap *routes_learned, - const struct nbrec_logical_router_static_route *nb_route, - const struct nbrec_logical_router *nb_lr, - const struct uuid *ic_route_uuid) -{ - struct in6_addr prefix, nexthop; - unsigned int plen; - if (!parse_route(nb_route->ip_prefix, nb_route->nexthop, - &prefix, &plen, &nexthop)) { - return false; - } - const char *origin = smap_get_def(&nb_route->options, "origin", ""); - bool override_connected = get_override_connected(&nb_route->options); - - uint32_t hash = ic_route_hash(&prefix, plen, &nexthop, origin, - nb_route->route_table, ic_route_uuid, - override_connected); - if (ic_route_find(routes_learned, &prefix, plen, &nexthop, origin, - nb_route->route_table, ic_route_uuid, - override_connected, hash)) { - /* Route was added to learned on previous iteration. */ - return true; - } - - struct ic_route_info *ic_route = xzalloc(sizeof *ic_route); - ic_route->prefix = prefix; - ic_route->plen = plen; - ic_route->nexthop = nexthop; - ic_route->nb_route = nb_route; - ic_route->origin = origin; - ic_route->route_table = nb_route->route_table; - ic_route->nb_lr = nb_lr; - ic_route->ic_route_uuid = *ic_route_uuid; - ic_route->override_connected = override_connected; - hmap_insert(routes_learned, &ic_route->node, hash); - - return true; -} - -/* Returns true if IPv4 prefixes of 'nb_lr' should be advertised with the - * interconnect port's IPv6 address as next hop ("IPv4 over IPv6"). - * The option is read from the Logical_Router and may be overridden on the - * transit Logical_Router_Port. - */ -static bool -route_ipv4_next_hop_ipv6(const struct nbrec_logical_router *nb_lr, - const struct nbrec_logical_router_port *ts_lrp) -{ - const char *opt_name = "ic-route-ipv4-next-hop-ipv6"; - bool lr_opt = smap_get_bool(&nb_lr->options, opt_name, false); - - return ts_lrp ? smap_get_bool(&ts_lrp->options, opt_name, lr_opt) : lr_opt; -} - -static bool -get_nexthop_from_lport_addresses(bool is_v4_prefix, - const struct lport_addresses *laddr, - bool ipv4_next_hop_ipv6, - struct in6_addr *nexthop) -{ - /* For an IPv4 prefix, pick an IPv4 next hop by default. When - * 'ipv4_next_hop_ipv6' is enabled and the interconnect port has an IPv6 - * address, advertise the IPv4 prefix with that IPv6 address as next hop - * instead ("IPv4 over IPv6"). */ - if (is_v4_prefix && !(ipv4_next_hop_ipv6 && laddr->n_ipv6_addrs)) { - if (!laddr->n_ipv4_addrs) { - return false; - } - in6_addr_set_mapped_ipv4(nexthop, laddr->ipv4_addrs[0].addr); - return true; - } - - /* ipv6 */ - if (laddr->n_ipv6_addrs) { - *nexthop = laddr->ipv6_addrs[0].addr; - return true; - } - - return false; -} - -static bool -prefix_is_filtered(struct in6_addr *prefix, - unsigned int plen, - const struct nbrec_logical_router *nb_lr, - const struct nbrec_logical_router_port *ts_lrp, - bool is_advertisement) -{ - struct ds filter_list = DS_EMPTY_INITIALIZER; - const char *filter_direction = is_advertisement ? "ic-route-filter-adv" : - "ic-route-filter-learn"; - if (ts_lrp) { - const char *lrp_route_filter = smap_get(&ts_lrp->options, - filter_direction); - if (lrp_route_filter) { - ds_put_format(&filter_list, "%s,", lrp_route_filter); - } - } - const char *lr_route_filter = smap_get(&nb_lr->options, - filter_direction); - if (lr_route_filter) { - ds_put_format(&filter_list, "%s,", lr_route_filter); - } - - struct sset prefix_set = SSET_INITIALIZER(&prefix_set); - sset_from_delimited_string(&prefix_set, ds_cstr(&filter_list), ","); - - bool matched = true; - if (!sset_is_empty(&prefix_set)) { - matched = find_prefix_in_set(prefix, plen, &prefix_set, - filter_direction); - } - - ds_destroy(&filter_list); - sset_destroy(&prefix_set); - return matched; -} - -static bool -prefix_is_deny_filtered(struct in6_addr *prefix, - unsigned int plen, - const struct smap *nb_options, - const struct nbrec_logical_router *nb_lr, - const struct nbrec_logical_router_port *ts_lrp, - bool is_advertisement) -{ - struct ds deny_list = DS_EMPTY_INITIALIZER; - const char *deny_key = is_advertisement ? "ic-route-deny-adv" : - "ic-route-deny-learn"; - - if (ts_lrp) { - const char *lrp_deny_filter = smap_get(&ts_lrp->options, deny_key); - if (lrp_deny_filter) { - ds_put_format(&deny_list, "%s,", lrp_deny_filter); - } - } - - if (nb_lr) { - const char *lr_deny_filter = smap_get(&nb_lr->options, deny_key); - if (lr_deny_filter) { - ds_put_format(&deny_list, "%s,", lr_deny_filter); - } - } - - if (nb_options) { - const char *global_deny = smap_get(nb_options, "ic-route-denylist"); - if (!global_deny || !global_deny[0]) { - global_deny = smap_get(nb_options, "ic-route-blacklist"); - } - if (global_deny && global_deny[0]) { - ds_put_format(&deny_list, "%s,", global_deny); - } - } - - struct sset prefix_set = SSET_INITIALIZER(&prefix_set); - sset_from_delimited_string(&prefix_set, ds_cstr(&deny_list), ","); - - bool denied = false; - if (!sset_is_empty(&prefix_set)) { - denied = find_prefix_in_set(prefix, plen, &prefix_set, deny_key); - } - - ds_destroy(&deny_list); - sset_destroy(&prefix_set); - return denied; -} - -static bool -route_need_advertise(const char *policy, - struct in6_addr *prefix, - unsigned int plen, - const struct smap *nb_options, - const struct nbrec_logical_router *nb_lr, - const struct nbrec_logical_router_port *ts_lrp) -{ - if (!smap_get_bool(nb_options, "ic-route-adv", false)) { - return false; - } - - if (plen == 0 && - !smap_get_bool(nb_options, "ic-route-adv-default", false)) { - return false; - } - - if (policy && !strcmp(policy, "src-ip")) { - return false; - } - - if (prefix_is_link_local(prefix, plen)) { - return false; - } - - if (prefix_is_deny_filtered(prefix, plen, nb_options, - nb_lr, ts_lrp, true)) { - return false; - } - - if (!prefix_is_filtered(prefix, plen, nb_lr, ts_lrp, true)) { - return false; - } - - return true; -} - -static void -add_to_routes_ad(struct hmap *routes_ad, const struct in6_addr prefix, - unsigned int plen, const struct in6_addr nexthop, - const char *origin, const char *route_table, - const struct nbrec_logical_router_port *nb_lrp, - const struct nbrec_logical_router_static_route *nb_route, - const struct nbrec_logical_router *nb_lr, - const struct nbrec_load_balancer *nb_lb, - const char *route_tag, bool override_connected) -{ - ovs_assert(nb_route || nb_lrp || nb_lb || nb_lr); - - if (route_table == NULL) { - route_table = ""; - } - - uint hash = ic_route_hash(&prefix, plen, &nexthop, origin, - route_table, NULL, override_connected); - - if (!ic_route_find(routes_ad, &prefix, plen, &nexthop, origin, route_table, - NULL, override_connected, hash)) { - struct ic_route_info *ic_route = xzalloc(sizeof *ic_route); - ic_route->prefix = prefix; - ic_route->plen = plen; - ic_route->nexthop = nexthop; - ic_route->nb_route = nb_route; - ic_route->origin = origin; - ic_route->route_table = route_table; - ic_route->nb_lrp = nb_lrp; - ic_route->nb_lr = nb_lr; - ic_route->nb_lb = nb_lb; - ic_route->route_tag = route_tag; - ic_route->override_connected = override_connected; - hmap_insert(routes_ad, &ic_route->node, hash); - } else { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - const char *msg_fmt = "Duplicate %s route advertisement was " - "suppressed! NB %s uuid: "UUID_FMT; - if (nb_route) { - VLOG_WARN_RL(&rl, msg_fmt, origin, "route", - UUID_ARGS(&nb_route->header_.uuid)); - } else if (nb_lb) { - VLOG_WARN_RL(&rl, msg_fmt, origin, "loadbalancer", - UUID_ARGS(&nb_lb->header_.uuid)); - } else if (nb_lrp) { - VLOG_WARN_RL(&rl, msg_fmt, origin, "lrp", - UUID_ARGS(&nb_lrp->header_.uuid)); - } else { - VLOG_WARN_RL(&rl, msg_fmt, origin, "lr", - UUID_ARGS(&nb_lr->header_.uuid)); - } - } -} - -static void -add_static_to_routes_ad( - struct hmap *routes_ad, - const struct nbrec_logical_router_static_route *nb_route, - const struct nbrec_logical_router *nb_lr, - const struct lport_addresses *nexthop_addresses, - const struct smap *nb_options, - const char *route_tag, - const struct nbrec_logical_router_port *ts_lrp) -{ - struct in6_addr prefix, nexthop; - unsigned int plen; - bool override_connected = get_override_connected(&nb_route->options); - - if (!parse_route(nb_route->ip_prefix, nb_route->nexthop, - &prefix, &plen, &nexthop)) { - return; - } - - if (!route_need_advertise(nb_route->policy, &prefix, plen, nb_options, - nb_lr, ts_lrp)) { - return; - } - - if (!get_nexthop_from_lport_addresses(IN6_IS_ADDR_V4MAPPED(&prefix), - nexthop_addresses, - route_ipv4_next_hop_ipv6(nb_lr, - ts_lrp), - &nexthop)) { - return; - } - - if (VLOG_IS_DBG_ENABLED()) { - struct ds msg = DS_EMPTY_INITIALIZER; - - ds_put_format(&msg, "Advertising static route: %s -> %s, ic nexthop: ", - nb_route->ip_prefix, nb_route->nexthop); - ipv6_format_mapped(&nexthop, &msg); - ds_put_format(&msg, ", route_table: %s", nb_route->route_table[0] - ? nb_route->route_table - : "
"); - - VLOG_DBG("%s", ds_cstr(&msg)); - ds_destroy(&msg); - } - - add_to_routes_ad(routes_ad, prefix, plen, nexthop, ROUTE_ORIGIN_STATIC, - nb_route->route_table, NULL, nb_route, nb_lr, - NULL, route_tag, override_connected); -} - -static void -add_network_to_routes_ad(struct hmap *routes_ad, const char *network, - const struct nbrec_logical_router_port *nb_lrp, - const struct lport_addresses *nexthop_addresses, - const struct smap *nb_options, - const struct nbrec_logical_router *nb_lr, - const struct nbrec_logical_router_port *ts_lrp, - const char *route_tag, - const char *ts_route_table, - bool is_src_dynamic) -{ - struct in6_addr prefix, nexthop; - unsigned int plen; - if (!ip46_parse_cidr(network, &prefix, &plen)) { - return; - } - - if (!route_need_advertise(NULL, &prefix, plen, nb_options, - nb_lr, ts_lrp)) { - if (VLOG_IS_DBG_ENABLED()) { - struct ds msg = DS_EMPTY_INITIALIZER; - ds_put_format(&msg, "Route ad: skip network %s", network); - if (nb_lrp) { - ds_put_format(&msg, " of lrp %s", nb_lrp->name); - } - ds_put_format(&msg, "."); - VLOG_DBG("%s", ds_cstr(&msg)); - ds_destroy(&msg); - } - return; - } - - if (!get_nexthop_from_lport_addresses(IN6_IS_ADDR_V4MAPPED(&prefix), - nexthop_addresses, - route_ipv4_next_hop_ipv6(nb_lr, - ts_lrp), - &nexthop)) { - return; - } - - if (VLOG_IS_DBG_ENABLED()) { - struct ds msg = DS_EMPTY_INITIALIZER; - - ds_put_format(&msg, "Adding direct network route to
routing " - "table: %s", network); - - if (nb_lrp) { - ds_put_format(&msg, " of lrp %s,", nb_lrp->name); - } - ds_put_format(&msg, " nexthop "); - ipv6_format_mapped(&nexthop, &msg); - - VLOG_DBG("%s", ds_cstr(&msg)); - ds_destroy(&msg); - } - - /* Create additional route to local and remote networks that have - * common route table name with port attached to transit switch having - * same route table name. - * As a result, traffic that is processed within such route table and is - * routed cross az will select port attached to transit switch specially - * allocated to process traffic within that route table. - */ - - if (*ts_route_table) { - add_to_routes_ad(routes_ad, prefix, plen, nexthop, - ROUTE_ORIGIN_CONNECTED, ts_route_table, - nb_lrp, NULL, nb_lr, NULL, route_tag, true); - } - - const char *origin = is_src_dynamic ? ROUTE_ORIGIN_CONNECTED_DYNAMIC : - ROUTE_ORIGIN_CONNECTED; - /* directly-connected routes go to
route table */ - add_to_routes_ad(routes_ad, prefix, plen, nexthop, origin, - NULL, nb_lrp, NULL, nb_lr, NULL, route_tag, false); -} - -static void -add_lb_vip_to_routes_ad(struct hmap *routes_ad, const char *vip_key, - const struct nbrec_load_balancer *nb_lb, - const struct lport_addresses *nexthop_addresses, - const struct smap *nb_options, - const struct nbrec_logical_router *nb_lr, - const char *route_tag, - const struct nbrec_logical_router_port *ts_lrp) -{ - char *vip_str = NULL; - struct in6_addr vip_ip, nexthop; - uint16_t vip_port; - int addr_family; - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - - if (!ip_address_and_port_from_lb_key(vip_key, &vip_str, &vip_ip, - &vip_port, &addr_family)) { - VLOG_WARN_RL(&rl, "Route ad: Parsing failed for lb vip %s", vip_key); - return; - } - if (vip_str == NULL) { - return; - } - unsigned int plen = (addr_family == AF_INET) ? 32 : 128; - if (!route_need_advertise(NULL, &vip_ip, plen, nb_options, - nb_lr, ts_lrp)) { - VLOG_DBG("Route ad: skip lb vip %s.", vip_key); - goto out; - } - if (!get_nexthop_from_lport_addresses(IN6_IS_ADDR_V4MAPPED(&vip_ip), - nexthop_addresses, - route_ipv4_next_hop_ipv6(nb_lr, - ts_lrp), - &nexthop)) { - VLOG_DBG("Route ad: failed to get nexthop for lb vip %s", vip_key); - goto out; - } - - if (VLOG_IS_DBG_ENABLED()) { - struct ds msg = DS_EMPTY_INITIALIZER; - - ds_put_format(&msg, "Adding lb vip route to
routing " - "table: %s, nexthop ", vip_str); - ipv6_format_mapped(&nexthop, &msg); - - VLOG_DBG("%s", ds_cstr(&msg)); - ds_destroy(&msg); - } - - /* Lb vip routes go to
route table */ - add_to_routes_ad(routes_ad, vip_ip, plen, nexthop, ROUTE_ORIGIN_LB, - NULL, NULL, NULL, nb_lr, nb_lb, route_tag, false); -out: - free(vip_str); -} - -static bool -route_has_local_gw(const struct nbrec_logical_router *lr, - const char *route_table, const char *ip_prefix) { - - const struct nbrec_logical_router_static_route *route; - for (int i = 0; i < lr->n_static_routes; i++) { - route = lr->static_routes[i]; - if (!smap_get(&route->external_ids, "ic-learned-route") && - !strcmp(route->route_table, route_table) && - !strcmp(route->ip_prefix, ip_prefix)) { - return true; - } - } - return false; -} - -static bool -lrp_has_neighbor_in_ts(const struct nbrec_logical_router_port *lrp, - struct in6_addr *nexthop) -{ - if (!lrp || !nexthop) { - return false; - } - - struct lport_addresses lrp_networks; - if (!extract_lrp_networks(lrp, &lrp_networks)) { - destroy_lport_addresses(&lrp_networks); - return false; - } - - if (IN6_IS_ADDR_V4MAPPED(nexthop)) { - ovs_be32 neigh_prefix_v4 = in6_addr_get_mapped_ipv4(nexthop); - for (size_t i = 0; i < lrp_networks.n_ipv4_addrs; i++) { - struct ipv4_netaddr address = lrp_networks.ipv4_addrs[i]; - if (address.network == (neigh_prefix_v4 & address.mask)) { - destroy_lport_addresses(&lrp_networks); - return true; - } - } - } else { - for (size_t i = 0; i < lrp_networks.n_ipv6_addrs; i++) { - struct ipv6_netaddr address = lrp_networks.ipv6_addrs[i]; - struct in6_addr neigh_prefix = ipv6_addr_bitand(nexthop, - &address.mask); - if (ipv6_addr_equals(&address.network, &neigh_prefix)) { - destroy_lport_addresses(&lrp_networks); - return true; - } - } - } - - destroy_lport_addresses(&lrp_networks); - return false; -} - -static bool -route_matches_local_lb(const struct nbrec_load_balancer *nb_lb, - const char *ip_prefix) -{ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - struct in6_addr prefix; - unsigned int plen; - - if (!ip46_parse_cidr(ip_prefix, &prefix, &plen)) { - return false; - } - - struct smap_node *node; - SMAP_FOR_EACH (node, &nb_lb->vips) { - char *vip_str = NULL; - struct in6_addr vip_ip; - uint16_t vip_port; - int addr_family; - if (ip_address_and_port_from_lb_key(node->key, &vip_str, - &vip_ip, &vip_port, - &addr_family)) { - if (IN6_IS_ADDR_V4MAPPED(&prefix) && addr_family == AF_INET) { - ovs_be32 vip = in6_addr_get_mapped_ipv4(&vip_ip); - ovs_be32 mask = be32_prefix_mask(plen); - - if ((vip & mask) == in6_addr_get_mapped_ipv4(&prefix)) { - free(vip_str); - return true; - } - } else if (!IN6_IS_ADDR_V4MAPPED(&prefix) - && addr_family == AF_INET6) { - struct in6_addr mask = ipv6_create_mask(plen); - struct in6_addr vip_prefix = ipv6_addr_bitand(&vip_ip, &mask); - if (ipv6_addr_equals(&prefix, &vip_prefix)) { - free(vip_str); - return true; - } - } - free(vip_str); - } else { - VLOG_WARN_RL(&rl, - "Route learn: Parsing failed for local lb vip %s", - node->key); - } - } - return false; -} - -static bool -route_need_learn(struct ic_context *ctx, - const struct nbrec_logical_router *lr, - const struct icsbrec_route *isb_route, - struct in6_addr *prefix, unsigned int plen, - const struct smap *nb_options, - const struct nbrec_logical_router_port *ts_lrp, - struct in6_addr *nexthop) -{ - if (!smap_get_bool(nb_options, "ic-route-learn", false)) { - return false; - } - - if (plen == 0 && - !smap_get_bool(nb_options, "ic-route-learn-default", false)) { - return false; - } - - if (!strcmp(isb_route->origin, ROUTE_ORIGIN_LB) && - !smap_get_bool(nb_options, "ic-route-learn-lb", false)) { - return false; - } - - if (!lrouter_is_enabled(lr)) { - return false; - } - - if (prefix_is_link_local(prefix, plen)) { - return false; - } - - if (prefix_is_deny_filtered(prefix, plen, nb_options, lr, ts_lrp, false)) { - return false; - } - - if (!prefix_is_filtered(prefix, plen, lr, ts_lrp, false)) { - return false; - } - - if (route_has_local_gw(lr, isb_route->route_table, isb_route->ip_prefix)) { - VLOG_DBG("Skip learning %s (rtb:%s) route, as we've got one with " - "local GW", isb_route->ip_prefix, isb_route->route_table); - return false; - } - - if (!lrp_has_neighbor_in_ts(ts_lrp, nexthop)) { - return false; - } - - for (size_t i = 0; i < lr->n_load_balancer; i++) { - if (route_matches_local_lb(lr->load_balancer[i], - isb_route->ip_prefix)) { - VLOG_DBG("Skip learning %s (rtb:%s) route, as we've got local" - " LB with matching VIP", isb_route->ip_prefix, - isb_route->route_table); - return false; - } - } - for (size_t i = 0; i < lr->n_load_balancer_group; i++) { - const struct nbrec_load_balancer_group *nb_lbg = - lr->load_balancer_group[i]; - for (size_t j = 0; j < nb_lbg->n_load_balancer; j++) { - if (route_matches_local_lb(nb_lbg->load_balancer[j], - isb_route->ip_prefix)) { - VLOG_DBG("Skip learning %s (rtb:%s) route, as we've got local" - " LB with matching VIP", isb_route->ip_prefix, - isb_route->route_table); - return false; - } - } - } - - const struct sbrec_datapath_binding *dp = - find_sb_dp_by_nb_uuid(ctx->sbrec_datapath_binding_by_nb_uuid, - &lr->header_.uuid); - if (!dp) { - return true; - } - - - struct sbrec_learned_route *filter = sbrec_learned_route_index_init_row( - ctx->sbrec_learned_route_by_datapath); - sbrec_learned_route_index_set_datapath(filter, dp); - struct sbrec_learned_route *sb_route; - SBREC_LEARNED_ROUTE_FOR_EACH_EQUAL (sb_route, filter, - ctx->sbrec_learned_route_by_datapath) { - if (!strcmp(isb_route->ip_prefix, sb_route->ip_prefix)) { - sbrec_learned_route_index_destroy_row(filter); - VLOG_DBG("Skip learning %s (rtb:%s) route, as we've got" - " dynamic routing learned", isb_route->ip_prefix, - isb_route->route_table); - return false; - } - } - sbrec_learned_route_index_destroy_row(filter); - - return true; -} - -static const char * -get_lrp_name_by_ts_port_name(struct ic_context *ctx, const char *ts_port_name) -{ - const struct nbrec_logical_switch_port *nb_lsp; - - nb_lsp = get_lsp_by_ts_port_name(ctx, ts_port_name); - if (!nb_lsp) { - return NULL; - } - - return smap_get(&nb_lsp->options, "router-port"); -} - -static const struct nbrec_logical_router_port * -find_lrp_of_nexthop(struct ic_context *ctx, - const struct icsbrec_route *isb_route) -{ - const struct nbrec_logical_router_port *lrp; - const struct nbrec_logical_switch *ls; - ls = find_ts_in_nb(ctx, isb_route->transit_switch); - if (!ls) { - return NULL; - } - - struct in6_addr nexthop; - if (!ip46_parse(isb_route->nexthop, &nexthop)) { - return NULL; - } - - for (size_t i = 0; i < ls->n_ports; i++) { - char *lsp_name = ls->ports[i]->name; - const char *lrp_name = get_lrp_name_by_ts_port_name(ctx, - lsp_name); - if (!lrp_name) { - continue; - } - - lrp = get_lrp_by_lrp_name(ctx, lrp_name); - if (!lrp) { - continue; - } - - struct lport_addresses lrp_networks; - if (!extract_lrp_networks(lrp, &lrp_networks)) { - destroy_lport_addresses(&lrp_networks); - continue; - } - - if (IN6_IS_ADDR_V4MAPPED(&nexthop)) { - ovs_be32 nexthop_v4 = in6_addr_get_mapped_ipv4(&nexthop); - for (size_t i_v4 = 0; i_v4 < lrp_networks.n_ipv4_addrs; i_v4++) { - struct ipv4_netaddr address = lrp_networks.ipv4_addrs[i_v4]; - if (address.addr == nexthop_v4) { - destroy_lport_addresses(&lrp_networks); - return lrp; - } - } - } else { - for (size_t i_v6 = 0; i_v6 < lrp_networks.n_ipv6_addrs; i_v6++) { - struct ipv6_netaddr address = lrp_networks.ipv6_addrs[i_v6]; - struct in6_addr nexthop_v6 = ipv6_addr_bitand(&nexthop, - &address.mask); - if (ipv6_addr_equals(&address.network, &nexthop_v6)) { - destroy_lport_addresses(&lrp_networks); - return lrp; - } - } - } - destroy_lport_addresses(&lrp_networks); - } - - return NULL; -} - -static bool -lrp_is_ts_port(struct ic_context *ctx, struct ic_router_info *ic_lr, - const char *lrp_name) -{ - const struct icsbrec_port_binding *isb_pb; - const char *ts_lrp_name; - VECTOR_FOR_EACH (&ic_lr->isb_pbs, isb_pb) { - ts_lrp_name = get_lrp_name_by_ts_port_name(ctx, isb_pb->logical_port); - if (!strcmp(ts_lrp_name, lrp_name)) { - return true; - } - } - return false; -} - -#define IC_ROUTE_LEARN_TAG_RULES "ic-route-learn-tag-rules" -#define IC_ROUTE_LEARN_TAG_ALLOW "allow:" -#define IC_ROUTE_LEARN_TAG_BLOCK "block:" -#define IC_ROUTE_FILTER_TAG "ic-route-filter-tag" - -/* Route tag rules of a TS LRP, as configured through its - * "ic-route-learn-tag-rules" option. */ -struct route_learn_tag_rules { - const char *config; /* Option value, for logging purposes. */ - bool configured; /* False if the option is unset or invalid, in - * which case no route is filtered. */ - bool allow; /* True: only routes tagged with one of 'tags' are - * learned. False: routes tagged with one of - * 'tags' are not learned. */ - struct sset tags; -}; - -/* Initializes 'rules' from the "ic-route-learn-tag-rules" option of 'lrp', - * which may be NULL. The option value must be either - * "allow:" or "block:"; any other - * value is logged and ignored. Route tags cannot contain commas. */ -static void -route_learn_tag_rules_init(struct route_learn_tag_rules *rules, - const struct nbrec_logical_router_port *lrp) -{ - static struct vlog_rate_limit bad_value_rl = VLOG_RATE_LIMIT_INIT(5, 1); - static struct vlog_rate_limit no_tag_rl = VLOG_RATE_LIMIT_INIT(5, 1); - const char *tags = ""; - - rules->config = lrp ? smap_get(&lrp->options, IC_ROUTE_LEARN_TAG_RULES) - : NULL; - rules->configured = false; - rules->allow = false; - - if (rules->config) { - if (!strncmp(rules->config, IC_ROUTE_LEARN_TAG_ALLOW, - strlen(IC_ROUTE_LEARN_TAG_ALLOW))) { - tags = rules->config + strlen(IC_ROUTE_LEARN_TAG_ALLOW); - rules->allow = true; - rules->configured = true; - } else if (!strncmp(rules->config, IC_ROUTE_LEARN_TAG_BLOCK, - strlen(IC_ROUTE_LEARN_TAG_BLOCK))) { - tags = rules->config + strlen(IC_ROUTE_LEARN_TAG_BLOCK); - rules->configured = true; - } else { - VLOG_WARN_RL(&bad_value_rl, - "Ignoring invalid %s value [%s] of logical " - "router port %s: expected \"%s\" or " - "\"%s\".", IC_ROUTE_LEARN_TAG_RULES, - rules->config, lrp->name, - IC_ROUTE_LEARN_TAG_ALLOW, IC_ROUTE_LEARN_TAG_BLOCK); - } - } - - sset_from_delimited_string(&rules->tags, tags, ","); - - if (rules->configured && sset_is_empty(&rules->tags)) { - VLOG_WARN_RL(&no_tag_rl, - "Ignoring %s value [%s] of logical router port %s: " - "no route tag specified.", IC_ROUTE_LEARN_TAG_RULES, - rules->config, lrp->name); - rules->configured = false; - } -} - -static void -route_learn_tag_rules_destroy(struct route_learn_tag_rules *rules) -{ - sset_destroy(&rules->tags); -} - -/* Returns true if a route tagged with 'route_tag' (NULL if the route - * carries no tag) can be learned according to 'rules'. */ -static bool -route_learn_tag_rules_allow(const struct route_learn_tag_rules *rules, - const char *route_tag) -{ - if (!rules->configured) { - return true; - } - - bool listed = route_tag && sset_contains(&rules->tags, route_tag); - return rules->allow ? listed : !listed; -} - -static void -sync_learned_routes(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az, - struct ic_router_info *ic_lr) -{ - ovs_assert(ctx->ovnnb_txn); - const struct icsbrec_route *isb_route, *isb_route_key; - - const struct nbrec_nb_global *nb_global = - nbrec_nb_global_first(ctx->ovnnb_idl); - ovs_assert(nb_global); - - const char *lrp_name, *ts_route_table, *route_filter_tag; - const struct icsbrec_port_binding *isb_pb; - const struct nbrec_logical_router_port *lrp; - VECTOR_FOR_EACH (&ic_lr->isb_pbs, isb_pb) { - if (!strcmp(isb_pb->address, "")) { - continue; - } - lrp_name = get_lrp_name_by_ts_port_name(ctx, isb_pb->logical_port); - lrp = get_lrp_by_lrp_name(ctx, lrp_name); - if (lrp) { - ts_route_table = smap_get_def(&lrp->options, "route_table", ""); - route_filter_tag = smap_get_def(&lrp->options, - IC_ROUTE_FILTER_TAG, ""); - } else { - ts_route_table = ""; - route_filter_tag = ""; - } - - struct route_learn_tag_rules learn_tag_rules; - route_learn_tag_rules_init(&learn_tag_rules, lrp); - - if (route_filter_tag[0]) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); - if (learn_tag_rules.configured) { - VLOG_WARN_RL(&rl, "The deprecated %s option of logical router " - "port %s is ignored as %s is also configured.", - IC_ROUTE_FILTER_TAG, lrp->name, - IC_ROUTE_LEARN_TAG_RULES); - } else { - VLOG_WARN_RL(&rl, "The %s option of logical router port %s is " - "deprecated and will be removed in the 28.09 " - "release; use %s instead.", IC_ROUTE_FILTER_TAG, - lrp->name, IC_ROUTE_LEARN_TAG_RULES); - } - } - - isb_route_key = icsbrec_route_index_init_row(ctx->icsbrec_route_by_ts); - icsbrec_route_index_set_transit_switch(isb_route_key, - isb_pb->transit_switch); - - ICSBREC_ROUTE_FOR_EACH_EQUAL (isb_route, isb_route_key, - ctx->icsbrec_route_by_ts) { - /* Filters ICSB routes, skipping those that either belong to - * current logical router or are legacy routes from the current - * availability zone (withoud lr-id). - */ - const char *lr_id = smap_get(&isb_route->external_ids, "lr-id"); - struct uuid lr_uuid; - if (lr_id) { - if (!uuid_from_string(&lr_uuid, lr_id) - || uuid_equals(&ic_lr->lr->header_.uuid, &lr_uuid)) { - continue; - } - } else if (isb_route->availability_zone == - runned_az) { - continue; - } - - const char *isb_route_tag = smap_get(&isb_route->external_ids, - "ic-route-tag"); - /* The deprecated filter tag is honored only when the route learn - * tag rules are not in effect. */ - if (!learn_tag_rules.configured && isb_route_tag && - !strcmp(isb_route_tag, route_filter_tag)) { - VLOG_DBG("Skip learning route %s -> %s as its route tag " - "[%s] is filtered by the filter tag [%s] of TS LRP", - isb_route->ip_prefix, isb_route->nexthop, - isb_route_tag, route_filter_tag); - continue; - } - - if (!route_learn_tag_rules_allow(&learn_tag_rules, - isb_route_tag)) { - VLOG_DBG("Skip learning route %s -> %s as its route tag " - "[%s] is filtered by the %s [%s] of TS LRP", - isb_route->ip_prefix, isb_route->nexthop, - isb_route_tag ? isb_route_tag : "", - IC_ROUTE_LEARN_TAG_RULES, learn_tag_rules.config); - continue; - } - - if (isb_route->route_table[0] && - strcmp(isb_route->route_table, ts_route_table)) { - if (VLOG_IS_DBG_ENABLED()) { - VLOG_DBG("Skip learning static route %s -> %s as either " - "its route table %s != %s of TS port or ", - isb_route->ip_prefix, isb_route->nexthop, - isb_route->route_table, ts_route_table); - } - continue; - } - - struct in6_addr prefix, nexthop; - unsigned int plen; - if (!parse_route(isb_route->ip_prefix, isb_route->nexthop, - &prefix, &plen, &nexthop)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Bad route format in IC-SB: %s -> %s. " - "Ignored.", isb_route->ip_prefix, - isb_route->nexthop); - continue; - } - if (!route_need_learn(ctx, ic_lr->lr, isb_route, &prefix, plen, - &nb_global->options, lrp, &nexthop)) { - continue; - } - - bool override_connected = - get_override_connected(&isb_route->options); - struct ic_route_info *route_learned - = ic_route_find(&ic_lr->routes_learned, &prefix, plen, - &nexthop, isb_route->origin, - isb_route->route_table, - &isb_route->header_.uuid, - override_connected, 0); - - if (route_learned) { - hmap_remove(&ic_lr->routes_learned, &route_learned->node); - free(route_learned); - } else { - /* Create the missing route in NB. */ - const struct nbrec_logical_router_static_route *nb_route = - nbrec_logical_router_static_route_insert(ctx->ovnnb_txn); - nbrec_logical_router_static_route_set_ip_prefix(nb_route, - isb_route->ip_prefix); - nbrec_logical_router_static_route_set_nexthop(nb_route, - isb_route->nexthop); - char *uuid_s = xasprintf(UUID_FMT, - UUID_ARGS(&isb_route->header_.uuid)); - nbrec_logical_router_static_route_set_route_table(nb_route, - isb_route->route_table); - nbrec_logical_router_static_route_update_external_ids_setkey( - nb_route, "ic-learned-route", uuid_s); - nbrec_logical_router_static_route_update_options_setkey( - nb_route, "origin", isb_route->origin); - free(uuid_s); - if (override_connected) { - nbrec_logical_router_static_route_update_options_setkey( - nb_route, ROUTE_OVERRIDE_CONNECTED, "true"); - } - nbrec_logical_router_update_static_routes_addvalue(ic_lr->lr, - nb_route); - } - } - icsbrec_route_index_destroy_row(isb_route_key); - route_learn_tag_rules_destroy(&learn_tag_rules); - } - - /* Delete extra learned routes. */ - struct ic_route_info *route_learned; - HMAP_FOR_EACH_SAFE (route_learned, node, &ic_lr->routes_learned) { - VLOG_DBG("Delete route %s -> %s that is not in IC-SB from NB.", - route_learned->nb_route->ip_prefix, - route_learned->nb_route->nexthop); - nbrec_logical_router_update_static_routes_delvalue( - ic_lr->lr, route_learned->nb_route); - hmap_remove(&ic_lr->routes_learned, &route_learned->node); - free(route_learned); - } -} - -static void -ad_route_sync_external_ids(const struct ic_route_info *route_adv, - const struct icsbrec_route *isb_route) -{ - struct uuid isb_ext_id, nb_id, isb_ext_lr_id, lr_id; - const char *route_tag; - smap_get_uuid(&isb_route->external_ids, "nb-id", &isb_ext_id); - smap_get_uuid(&isb_route->external_ids, "lr-id", &isb_ext_lr_id); - nb_id = route_adv->nb_lb ? route_adv->nb_lb->header_.uuid : - route_adv->nb_route ? route_adv->nb_route->header_.uuid : - route_adv->nb_lrp ? route_adv->nb_lrp->header_.uuid : - route_adv->nb_lr->header_.uuid; - - lr_id = route_adv->nb_lr->header_.uuid; - if (!uuid_equals(&isb_ext_id, &nb_id)) { - char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&nb_id)); - icsbrec_route_update_external_ids_setkey(isb_route, "nb-id", - uuid_s); - free(uuid_s); - } - if (!uuid_equals(&isb_ext_lr_id, &lr_id)) { - char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&lr_id)); - icsbrec_route_update_external_ids_setkey(isb_route, "lr-id", - uuid_s); - free(uuid_s); - } - if (strcmp(route_adv->route_tag, "")) { - icsbrec_route_update_external_ids_setkey(isb_route, "ic-route-tag", - route_adv->route_tag); - } else { - route_tag = smap_get(&isb_route->external_ids, "ic-route-tag"); - if (route_tag) { - icsbrec_route_update_external_ids_delkey(isb_route, - "ic-route-tag"); - } - } -} - -/* Sync routes from routes_ad to IC-SB. */ -static void -advertise_routes(struct ic_context *ctx, - const struct icsbrec_availability_zone *az, - const char *ts_name, - struct hmap *routes_ad) -{ - ovs_assert(ctx->ovnisb_unlocked_txn); - const struct icsbrec_route *isb_route; - const struct icsbrec_route *isb_route_key = - icsbrec_route_index_init_row(ctx->icsbrec_route_by_ts_az); - icsbrec_route_index_set_transit_switch(isb_route_key, ts_name); - icsbrec_route_index_set_availability_zone(isb_route_key, az); - - ICSBREC_ROUTE_FOR_EACH_EQUAL (isb_route, isb_route_key, - ctx->icsbrec_route_by_ts_az) { - struct in6_addr prefix, nexthop; - unsigned int plen; - - if (!parse_route(isb_route->ip_prefix, isb_route->nexthop, - &prefix, &plen, &nexthop)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Bad route format in IC-SB: %s -> %s. " - "Delete it.", - isb_route->ip_prefix, isb_route->nexthop); - icsbrec_route_delete(isb_route); - continue; - } - bool override_connected = get_override_connected(&isb_route->options); - struct ic_route_info *route_adv = - ic_route_find(routes_ad, &prefix, plen, &nexthop, - isb_route->origin, isb_route->route_table, - NULL, override_connected, 0); - if (!route_adv) { - /* Delete the extra route from IC-SB. */ - VLOG_DBG("Delete route %s -> %s from IC-SB, which is not found" - " in local routes to be advertised.", - isb_route->ip_prefix, isb_route->nexthop); - icsbrec_route_delete(isb_route); - } else { - ad_route_sync_external_ids(route_adv, isb_route); - - hmap_remove(routes_ad, &route_adv->node); - free(route_adv); - } - } - icsbrec_route_index_destroy_row(isb_route_key); - - struct ds prefix = DS_EMPTY_INITIALIZER; - - /* Create the missing routes in IC-SB */ - struct ic_route_info *route_adv; - HMAP_FOR_EACH_SAFE (route_adv, node, routes_ad) { - ds_clear(&prefix); - ipv6_format_mapped(&route_adv->prefix, &prefix); - ds_put_format(&prefix, "/%d", route_adv->plen); - char *nexthop_s = normalize_v46(&route_adv->nexthop); - - isb_route = icsbrec_route_insert(ctx->ovnisb_unlocked_txn); - icsbrec_route_set_transit_switch(isb_route, ts_name); - icsbrec_route_set_availability_zone(isb_route, az); - icsbrec_route_set_ip_prefix(isb_route, ds_cstr(&prefix)); - icsbrec_route_set_nexthop(isb_route, nexthop_s); - icsbrec_route_set_origin(isb_route, route_adv->origin); - icsbrec_route_set_route_table(isb_route, route_adv->route_table - ? route_adv->route_table - : ""); - if (route_adv->override_connected) { - icsbrec_route_update_options_setkey(isb_route, - ROUTE_OVERRIDE_CONNECTED, "true"); - } - free(nexthop_s); - - ad_route_sync_external_ids(route_adv, isb_route); - - hmap_remove(routes_ad, &route_adv->node); - free(route_adv); - } - - ds_destroy(&prefix); -} - -static void -collect_learned_routes(struct ic_router_info *ic_lr) -{ - const struct nbrec_logical_router *lr = ic_lr->lr; - - /* Check static routes of the LR and collect learned routes */ - for (int i = 0; i < lr->n_static_routes; i++) { - const struct nbrec_logical_router_static_route *nb_route - = lr->static_routes[i]; - struct uuid isb_uuid; - if (smap_get_uuid(&nb_route->external_ids, "ic-learned-route", - &isb_uuid)) { - /* It is a learned route */ - if (!add_to_routes_learned(&ic_lr->routes_learned, nb_route, lr, - &isb_uuid)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Bad format of learned route in NB: " - "%s -> %s. Delete it.", nb_route->ip_prefix, - nb_route->nexthop); - nbrec_logical_router_update_static_routes_delvalue(lr, - nb_route); - } - } - } -} - -static void -build_ts_routes_to_adv(struct ic_context *ctx, - struct ic_router_info *ic_lr, - struct hmap *routes_ad, - struct lport_addresses *ts_port_addrs, - const struct nbrec_nb_global *nb_global, - const char *ts_route_table, - const char *route_tag, - const struct nbrec_logical_router_port *ts_lrp) -{ - const struct nbrec_logical_router *lr = ic_lr->lr; - - /* Check static routes of the LR */ - for (int i = 0; i < lr->n_static_routes; i++) { - const struct nbrec_logical_router_static_route *nb_route - = lr->static_routes[i]; - struct uuid isb_uuid; - if (smap_get_uuid(&nb_route->external_ids, "ic-learned-route", - &isb_uuid)) { - /* It is a learned route */ - if (!add_to_routes_learned(&ic_lr->routes_learned, nb_route, lr, - &isb_uuid)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Bad format of learned route in NB: " - "%s -> %s. Delete it.", nb_route->ip_prefix, - nb_route->nexthop); - nbrec_logical_router_update_static_routes_delvalue(lr, - nb_route); - } - } else if (!strcmp(ts_route_table, nb_route->route_table)) { - /* It may be a route to be advertised */ - add_static_to_routes_ad(routes_ad, nb_route, lr, ts_port_addrs, - &nb_global->options, route_tag, ts_lrp); - } - } - - /* Check directly-connected subnets of the LR */ - for (int i = 0; i < lr->n_ports; i++) { - const struct nbrec_logical_router_port *lrp = lr->ports[i]; - if (!lrp_is_ts_port(ctx, ic_lr, lrp->name)) { - for (int j = 0; j < lrp->n_networks; j++) { - add_network_to_routes_ad(routes_ad, lrp->networks[j], lrp, - ts_port_addrs, - &nb_global->options, - lr, ts_lrp, route_tag, - ts_route_table, false); - } - } else { - /* The router port of the TS port is ignored. */ - VLOG_DBG("Skip advertising direct route of lrp %s (TS port)", - lrp->name); - } - } - - /* Check loadbalancers associated with the LR */ - if (smap_get_bool(&nb_global->options, "ic-route-adv-lb", false)) { - for (size_t i = 0; i < lr->n_load_balancer; i++) { - const struct nbrec_load_balancer *nb_lb = lr->load_balancer[i]; - struct smap_node *node; - SMAP_FOR_EACH (node, &nb_lb->vips) { - add_lb_vip_to_routes_ad(routes_ad, node->key, nb_lb, - ts_port_addrs, - &nb_global->options, - lr, route_tag, ts_lrp); - } - } - - for (size_t i = 0; i < lr->n_load_balancer_group; i++) { - const struct nbrec_load_balancer_group *nb_lbg = - lr->load_balancer_group[i]; - for (size_t j = 0; j < nb_lbg->n_load_balancer; j++) { - const struct nbrec_load_balancer *nb_lb = - nb_lbg->load_balancer[j]; - struct smap_node *node; - SMAP_FOR_EACH (node, &nb_lb->vips) { - add_lb_vip_to_routes_ad(routes_ad, node->key, nb_lb, - ts_port_addrs, - &nb_global->options, - lr, route_tag, ts_lrp); - } - } - } - } - - const struct sbrec_datapath_binding *dp = - find_sb_dp_by_nb_uuid(ctx->sbrec_datapath_binding_by_nb_uuid, - &lr->header_.uuid); - if (!dp) { - return; - } - - struct sbrec_learned_route *filter = sbrec_learned_route_index_init_row( - ctx->sbrec_learned_route_by_datapath); - sbrec_learned_route_index_set_datapath(filter, dp); - struct sbrec_learned_route *sb_route; - SBREC_LEARNED_ROUTE_FOR_EACH_EQUAL (sb_route, filter, - ctx->sbrec_learned_route_by_datapath) { - add_network_to_routes_ad(routes_ad, sb_route->ip_prefix, NULL, - ts_port_addrs, - &nb_global->options, - lr, ts_lrp, route_tag, ts_route_table, true); - } - sbrec_learned_route_index_destroy_row(filter); -} - -static void -collect_lr_routes(struct ic_context *ctx, - struct ic_router_info *ic_lr, - struct shash *routes_ad_by_ts) -{ - const struct nbrec_nb_global *nb_global = - nbrec_nb_global_first(ctx->ovnnb_idl); - ovs_assert(nb_global); - - const struct icsbrec_port_binding *isb_pb; - const char *lrp_name, *ts_name, *route_table, *route_tag; - struct lport_addresses ts_port_addrs; - const struct icnbrec_transit_switch *key; - const struct nbrec_logical_router_port *lrp; - - struct hmap *routes_ad; - const struct icnbrec_transit_switch *t_sw; - bool routes_built = false; - VECTOR_FOR_EACH (&ic_lr->isb_pbs, isb_pb) { - key = icnbrec_transit_switch_index_init_row( - ctx->icnbrec_transit_switch_by_name); - icnbrec_transit_switch_index_set_name(key, isb_pb->transit_switch); - t_sw = icnbrec_transit_switch_index_find( - ctx->icnbrec_transit_switch_by_name, key); - icnbrec_transit_switch_index_destroy_row(key); - if (!t_sw) { - continue; - } - ts_name = t_sw->name; - routes_ad = shash_find_data(routes_ad_by_ts, ts_name); - if (!routes_ad) { - routes_ad = xzalloc(sizeof *routes_ad); - hmap_init(routes_ad); - shash_add(routes_ad_by_ts, ts_name, routes_ad); - } - - if (!lrouter_is_enabled(ic_lr->lr)) { - continue; - } - - if (!extract_lsp_addresses(isb_pb->address, &ts_port_addrs)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_INFO_RL(&rl, "Route sync ignores port %s on ts %s for router" - " %s because the addresses are invalid.", - isb_pb->logical_port, isb_pb->transit_switch, - ic_lr->lr->name); - continue; - } - lrp_name = get_lrp_name_by_ts_port_name(ctx, isb_pb->logical_port); - lrp = get_lrp_by_lrp_name(ctx, lrp_name); - if (lrp) { - route_table = smap_get_def(&lrp->options, "route_table", ""); - route_tag = smap_get_def(&lrp->options, "ic-route-tag", ""); - } else { - route_table = ""; - route_tag = ""; - } - routes_built = true; - build_ts_routes_to_adv(ctx, ic_lr, routes_ad, &ts_port_addrs, - nb_global, route_table, route_tag, lrp); - destroy_lport_addresses(&ts_port_addrs); - } - /* If no port binding had valid addresses (e.g. LR disabled - * and PB address cleared simultaneously), collect learned routes so - * they can be deleted by sync_learned_routes(). */ - if (!routes_built) { - collect_learned_routes(ic_lr); - } -} - -static void -delete_orphan_ic_routes(struct ic_context *ctx, - const struct icsbrec_availability_zone *az) -{ - const struct icsbrec_route *isb_route, *isb_route_key = - icsbrec_route_index_init_row(ctx->icsbrec_route_by_az); - icsbrec_route_index_set_availability_zone(isb_route_key, az); - - const struct icnbrec_transit_switch *t_sw, *t_sw_key; - - ICSBREC_ROUTE_FOR_EACH_EQUAL (isb_route, isb_route_key, - ctx->icsbrec_route_by_az) - { - t_sw_key = icnbrec_transit_switch_index_init_row( - ctx->icnbrec_transit_switch_by_name); - icnbrec_transit_switch_index_set_name(t_sw_key, - isb_route->transit_switch); - t_sw = icnbrec_transit_switch_index_find( - ctx->icnbrec_transit_switch_by_name, t_sw_key); - icnbrec_transit_switch_index_destroy_row(t_sw_key); - - if (!t_sw || !find_lrp_of_nexthop(ctx, isb_route)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_INFO_RL(&rl, "Deleting orphan ICDB:Route: %s->%s (%s, rtb:%s," - " transit switch: %s)", isb_route->ip_prefix, - isb_route->nexthop, isb_route->origin, - isb_route->route_table, isb_route->transit_switch); - icsbrec_route_delete(isb_route); - } - } - icsbrec_route_index_destroy_row(isb_route_key); -} - -void -route_run(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az) -{ - if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { - return; - } - - delete_orphan_ic_routes(ctx, runned_az); - - struct hmap ic_lrs = HMAP_INITIALIZER(&ic_lrs); - const struct icsbrec_port_binding *isb_pb; - const struct icsbrec_port_binding *isb_pb_key = - icsbrec_port_binding_index_init_row(ctx->icsbrec_port_binding_by_az); - icsbrec_port_binding_index_set_availability_zone(isb_pb_key, - runned_az); - - /* Each port on TS maps to a logical router, which is stored in the - * external_ids:router-id of the IC SB port_binding record. - * Here we build info for interconnected Logical Router: - * collect IC Port Binding to process routes sync later on. */ - ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, - ctx->icsbrec_port_binding_by_az) - { - if (ic_pb_get_type(isb_pb) == IC_ROUTER_PORT) { - continue; - } - const struct nbrec_logical_switch_port *nb_lsp; - - nb_lsp = get_lsp_by_ts_port_name(ctx, isb_pb->logical_port); - if (!nb_lsp) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_DBG_RL(&rl, - "IC-SB Port_Binding '%s' on ts '%s': " - "NB LSP not found, skipping route collection.", - isb_pb->logical_port, isb_pb->transit_switch); - continue; - } - - if (!strcmp(nb_lsp->type, "switch") || !strcmp(nb_lsp->type, "")) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_DBG_RL(&rl, - "IC-SB Port_Binding '%s' on ts '%s' corresponds to a " - "switch port, not considering for route collection.", - isb_pb->logical_port, isb_pb->transit_switch); - continue; - } - - const char *ts_lrp_name = - get_lrp_name_by_ts_port_name(ctx, isb_pb->logical_port); - if (!ts_lrp_name) { - if (!strcmp(isb_pb->type, "transit-switch-port")) { - continue; - } - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Route sync ignores port %s on ts %s because " - "logical router port is not found in NB. Deleting it", - isb_pb->logical_port, isb_pb->transit_switch); - icsbrec_port_binding_delete(isb_pb); - continue; - } - - struct uuid lr_uuid; - if (!smap_get_uuid(&isb_pb->external_ids, "router-id", &lr_uuid)) { - VLOG_DBG("IC-SB Port_Binding %s doesn't have " - "external_ids:router-id set.", isb_pb->logical_port); - continue; - } - - const struct nbrec_logical_router *lr - = nbrec_logical_router_get_for_uuid(ctx->ovnnb_idl, &lr_uuid); - if (!lr) { - continue; - } - - struct ic_router_info *ic_lr = ic_router_find(&ic_lrs, lr); - if (!ic_lr) { - ic_lr = xzalloc(sizeof *ic_lr); - ic_lr->lr = lr; - ic_lr->isb_pbs = - VECTOR_EMPTY_INITIALIZER(const struct icsbrec_port_binding *); - hmap_init(&ic_lr->routes_learned); - hmap_insert(&ic_lrs, &ic_lr->node, uuid_hash(&lr->header_.uuid)); - } - vector_push(&ic_lr->isb_pbs, &isb_pb); - } - icsbrec_port_binding_index_destroy_row(isb_pb_key); - - struct ic_router_info *ic_lr; - struct shash routes_ad_by_ts = SHASH_INITIALIZER(&routes_ad_by_ts); - HMAP_FOR_EACH_SAFE (ic_lr, node, &ic_lrs) { - collect_lr_routes(ctx, ic_lr, &routes_ad_by_ts); - sync_learned_routes(ctx, runned_az, ic_lr); - vector_destroy(&ic_lr->isb_pbs); - hmap_destroy(&ic_lr->routes_learned); - hmap_remove(&ic_lrs, &ic_lr->node); - free(ic_lr); - } - struct shash_node *node; - SHASH_FOR_EACH (node, &routes_ad_by_ts) { - advertise_routes(ctx, runned_az, node->name, - node->data); - hmap_destroy(node->data); - } - shash_destroy_free_data(&routes_ad_by_ts); - hmap_destroy(&ic_lrs); -} - /* * Data structures and functions related to * synchronize health checks for load balancers diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h index 6c1622971..69ffa34d4 100644 --- a/ic/ovn-ic.h +++ b/ic/ovn-ic.h @@ -101,8 +101,6 @@ void port_binding_lsp_ts_map_init(struct ic_context *ctx, struct shash *map); void port_binding_collect_lsp_ts(const struct shash *lsp_ts_map, const struct nbrec_logical_switch_port *lsp, struct sset *ts_scope); -void route_run(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az); void sync_service_monitor(struct ic_context *ctx, const struct icsbrec_availability_zone *runned_az); diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at index 7f987eb84..7f557f171 100644 --- a/tests/ovn-ic.at +++ b/tests/ovn-ic.at @@ -1012,6 +1012,192 @@ OVN_CLEANUP_IC([az1]) AT_CLEANUP ]) +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn-ic -- incremental processing - route handlers]) + +# Exercises the en_route change handlers on a single AZ (so engine stats are +# not perturbed by other AZs' route write-backs). CHECK_ROUTE_RECOMPUTE() +# (ovn-macros.at) quiesces the engine, resets the stats, applies one change and +# asserts whether the route node was fully recomputed. A route advertised +# from this AZ has its metadata (ad_route_sync_external_ids) ignored by the +# IC-SB Route handler, so the stats stay clean between checks. + +ovn_init_ic_db +ovn-ic-nbctl ts-add ts1 +ovn_start az1 +as az1 +check ovn-ic-nbctl --wait=sb sync +check ovn-nbctl set nb_global . options:ic-route-adv=true +check ovn-nbctl set nb_global . options:ic-route-learn=true +check ovn-nbctl lr-add lr11 +check ovn-nbctl lrp-add lr11 lrp-lr11-ts1 aa:aa:aa:aa:aa:01 169.254.100.1/24 +check ovn-nbctl lsp-add-router-port ts1 lsp-ts1-lr11 lrp-lr11-ts1 +check ovn-nbctl lrp-add lr11 lrp-local 00:00:00:00:00:01 192.168.1.1/24 +# A tenant logical switch unrelated to interconnection. +check ovn-nbctl --wait=sb ls-add sw0 + +# Wait until lr11's connected subnet is advertised to IC-SB. +OVS_WAIT_UNTIL([test "$(ovn-ic-sbctl --bare --columns=ip_prefix \ + find route ip_prefix=192.168.1.1/24)" \ + = 192.168.1.1/24]) + +# --- Changes that must NOT recompute the route node --- + +# NB Global handler: a non-'options' column change is irrelevant. +CHECK_ROUTE_RECOMPUTE([check ovn-nbctl set nb_global . external_ids:foo=bar], + [norecompute]) + +# NB Global handler: an 'options' change that touches no route gate key (e.g. +# ovn-northd's 'max_tunid' bookkeeping) is irrelevant and must not recompute. +CHECK_ROUTE_RECOMPUTE([check ovn-nbctl set nb_global . options:max_tunid=1023], + [norecompute]) + +# NB Logical_Switch handler: a switch without other_config:interconn-ts is not +# a transit-switch mirror and is irrelevant. +CHECK_ROUTE_RECOMPUTE( + [check ovn-nbctl set logical_switch sw0 other_config:foo=bar], + [norecompute]) + +# NB Logical_Router handler: a column the route engine does not read +# (external_ids) is irrelevant. +CHECK_ROUTE_RECOMPUTE([check ovn-nbctl set logical_router lr11 \ + external_ids:foo=bar], + [norecompute]) + +# --- Changes handled incrementally (scoped per transit switch, NOT a full +# recompute of the route node) --- + +# NB Logical_Router_Static_Route handler: a user route is (re)advertised on the +# transit switches of its owning router, scoped rather than fully recomputed. +CHECK_ROUTE_RECOMPUTE( + [check ovn-nbctl lr-route-add lr11 10.99.0.0/24 192.168.1.10], + [norecompute]) + +# NB Logical_Router handler: an 'options' change re-syncs only the router's +# transit switches. +CHECK_ROUTE_RECOMPUTE([check ovn-nbctl set logical_router lr11 options:foo=bar], + [norecompute]) + +# IC-NB Transit_Switch handler: a new transit switch is scoped to itself. +CHECK_ROUTE_RECOMPUTE([check ovn-ic-nbctl --wait=sb ts-add ts2], + [norecompute]) + +# --- Changes that MUST fall back to a full recompute of the route node --- + +# NB Global handler: an 'options' change affects the global advertise/learn +# filters of every transit switch, so it cannot be scoped. +CHECK_ROUTE_RECOMPUTE( + [check ovn-nbctl set nb_global . options:ic-route-adv-default=true], + [recompute]) + +# Recompute equivalence: a forced full recompute must not change the routing +# tables this AZ manages. +check ovn-ic-nbctl --wait=sb sync +OVN_IC_ROUTE_DUMP([route_before]) +check ovn-appctl -t ic/ovn-ic inc-engine/recompute +check ovn-ic-nbctl --wait=sb sync +OVN_IC_ROUTE_DUMP([route_after]) +AT_CHECK([diff route_before route_after]) + +OVN_CLEANUP_IC([az1]) +AT_CLEANUP +]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn-ic -- incremental processing - route handler scoping]) + +# Exercises the per-transit-switch scoping of the en_route handlers on a single +# AZ, including the inputs that previously forced a full recompute (NB +# load_balancer, NB logical_router_port). Two routers share ts1 and a third +# router lives on ts2, so changes can be confined to one transit switch while +# leaving the other untouched. CHECK_ROUTE_RECOMPUTE() and OVN_IC_ROUTE_DUMP() +# are defined in ovn-macros.at. + +ovn_init_ic_db +ovn-ic-nbctl ts-add ts1 +ovn-ic-nbctl ts-add ts2 +ovn_start az1 +as az1 +check ovn-ic-nbctl --wait=sb sync +check ovn-nbctl set nb_global . options:ic-route-adv=true +check ovn-nbctl set nb_global . options:ic-route-learn=true +check ovn-nbctl set nb_global . options:ic-route-adv-lb=true + +# Two routers on ts1. +check ovn-nbctl lr-add lr11 +check ovn-nbctl lrp-add lr11 lrp-lr11-ts1 aa:aa:aa:aa:aa:01 169.254.100.1/24 +check ovn-nbctl lsp-add-router-port ts1 lsp-ts1-lr11 lrp-lr11-ts1 +check ovn-nbctl lrp-add lr11 lrp-lr11-local 00:00:00:00:01:01 192.168.1.1/24 + +check ovn-nbctl lr-add lr12 +check ovn-nbctl lrp-add lr12 lrp-lr12-ts1 aa:aa:aa:aa:aa:02 169.254.100.2/24 +check ovn-nbctl lsp-add-router-port ts1 lsp-ts1-lr12 lrp-lr12-ts1 +check ovn-nbctl lrp-add lr12 lrp-lr12-local 00:00:00:00:01:02 192.168.2.1/24 + +# One router on ts2. +check ovn-nbctl lr-add lr21 +check ovn-nbctl lrp-add lr21 lrp-lr21-ts2 aa:aa:aa:aa:aa:03 169.254.200.1/24 +check ovn-nbctl lsp-add-router-port ts2 lsp-ts2-lr21 lrp-lr21-ts2 +check ovn-nbctl --wait=sb lrp-add lr21 lrp-lr21-local 00:00:00:00:02:01 \ + 192.168.3.1/24 + +# Wait until all three connected subnets are advertised to IC-SB. +OVS_WAIT_UNTIL([test "$(ovn-ic-sbctl --bare --columns=ip_prefix find route \ + | grep -c 192.168)" = 3]) + +# A load balancer attached to lr11: its VIP must be advertised on ts1 only. +check ovn-nbctl lb-add lb0 10.0.0.10:80 192.168.1.5:80 +check ovn-nbctl --wait=sb lr-lb-add lr11 lb0 +OVS_WAIT_UNTIL([test "$(ovn-ic-sbctl --bare --columns=transit_switch find \ + route ip_prefix=10.0.0.10/32)" = ts1]) + +# --- New handlers must be incremental (no full recompute of the route node) --- + +# NB Load_Balancer handler: a VIP change on an attached LB is scoped to its +# router's transit switch. +CHECK_ROUTE_RECOMPUTE( + [check ovn-nbctl set load_balancer lb0 \ + vips:'"10.0.0.20:80"'='"192.168.1.6:80"'], + [norecompute]) +OVS_WAIT_UNTIL([test "$(ovn-ic-sbctl --bare --columns=transit_switch find \ + route ip_prefix=10.0.0.20/32)" = ts1]) + +# NB Logical_Router_Port handler: an options change on an existing port is +# scoped to the owning router's transit switch. +CHECK_ROUTE_RECOMPUTE( + [check ovn-nbctl set logical_router_port lrp-lr11-local options:foo=bar], + [norecompute]) + +# A load balancer not attached to any router is irrelevant. +CHECK_ROUTE_RECOMPUTE([check ovn-nbctl lb-add lb1 10.1.0.10:80 192.168.9.9:80], + [norecompute]) + +# --- Cross-transit-switch isolation --- + +# A new route on lr11 (ts1) must leave lr21's ts2 routes intact and not force a +# full recompute. +ovn-ic-sbctl --bare --columns=ip_prefix,transit_switch list route \ + | grep ts2 | sort > ts2_before +CHECK_ROUTE_RECOMPUTE( + [check ovn-nbctl lr-route-add lr11 10.99.0.0/24 192.168.1.10], + [norecompute]) +ovn-ic-sbctl --bare --columns=ip_prefix,transit_switch list route \ + | grep ts2 | sort > ts2_after +AT_CHECK([diff ts2_before ts2_after]) + +# --- Recompute equivalence: scoped syncs produced the same state a full +# recompute would. --- +check ovn-ic-nbctl --wait=sb sync +OVN_IC_ROUTE_DUMP([route_before]) +check ovn-appctl -t ic/ovn-ic inc-engine/recompute +check ovn-ic-nbctl --wait=sb sync +OVN_IC_ROUTE_DUMP([route_after]) +AT_CHECK([diff route_before route_after]) + +OVN_CLEANUP_IC([az1]) +AT_CLEANUP +]) + AT_SETUP([ovn-ic -- incremental processing - port binding handler scoping]) # Exercises the per-transit-switch/router scoping of the en_port_binding change @@ -1159,6 +1345,56 @@ AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ OVN_CLEANUP_IC([az1]) AT_CLEANUP +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn-ic -- incremental processing - route learn and withdraw]) + +# Covers the IC-SB Route handler's cheap remote-route delete path: when a +# remote AZ withdraws a route, the learned NB static route is removed by the +# handler (the route node computes) without re-learning anything. + +ovn_init_ic_db +ovn-ic-nbctl ts-add ts1 +for i in 1 2; do + ovn_start az$i + ovn_as az$i + check ovn-ic-nbctl --wait=sb sync + check ovn-nbctl set nb_global . options:ic-route-adv=true + check ovn-nbctl set nb_global . options:ic-route-learn=true + lr=lr1$i + check ovn-nbctl lr-add $lr + check ovn-nbctl lrp-add $lr lrp-$lr-ts1 aa:aa:aa:aa:aa:0$i 169.254.100.$i/24 + check ovn-nbctl lsp-add-router-port ts1 lsp-ts1-$lr lrp-$lr-ts1 + check ovn-nbctl --wait=sb lrp-add $lr lrp-local 00:00:00:00:00:0$i \ + 192.168.$i.1/24 +done +check ovn-ic-nbctl --wait=sb sync + +# az1 learns az2's connected subnet. +ovn_as az1 +OVS_WAIT_UNTIL([ovn-nbctl lr-route-list lr11 | grep -q "192.168.2.0/24"]) +as az1 + +# Withdraw: az2 stops advertising. az1 must un-learn the route through the +# IC-SB Route delete handler, which computes (handles the change) rather than +# being skipped. +check ovn-appctl -t ic/ovn-ic inc-engine/clear-stats +check ovn_as az2 ovn-nbctl set nb_global . options:ic-route-adv=false +as az1 +OVS_WAIT_UNTIL([! ovn-nbctl lr-route-list lr11 | grep -q "192.168.2.0/24"]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats route compute)" -gt 0]) + +# A forced recompute confirms the incremental result is correct (the route +# stays withdrawn). +check ovn-appctl -t ic/ovn-ic inc-engine/recompute +check ovn-ic-nbctl --wait=sb sync +AT_CHECK([test "$(ovn_as az1 ovn-nbctl lr-route-list lr11 \ + | grep -c '192.168.2.0/24')" = 0]) + +OVN_CLEANUP_IC([az1], [az2]) +AT_CLEANUP +]) + OVN_FOR_EACH_NORTHD([ AT_SETUP([ovn-ic -- port sync]) diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at index 560f02fc1..1fc3ef5f3 100644 --- a/tests/ovn-macros.at +++ b/tests/ovn-macros.at @@ -572,6 +572,38 @@ m4_define([OVN_CLEANUP_IC],[ fi ]) +# CHECK_ROUTE_RECOMPUTE(CHANGE_CMD, EXPECT) +# +# Quiesces the ovn-ic engine and clears its stats, runs CHANGE_CMD, and then +# asserts that the en_route node's recompute count is "0" (EXPECT=norecompute) +# or non-zero (EXPECT=recompute). +m4_define([CHECK_ROUTE_RECOMPUTE], [ + check ovn-ic-nbctl --wait=sb sync + check ovn-appctl -t ic/ovn-ic inc-engine/recompute + check ovn-ic-nbctl --wait=sb sync + check ovn-appctl -t ic/ovn-ic inc-engine/clear-stats + $1 + check ovn-ic-nbctl --wait=sb sync + rc=$(ovn-appctl -t ic/ovn-ic inc-engine/show-stats route recompute) + echo "route recompute count after '$1': $rc (expect $2)" + if test "$2" = norecompute; then + AT_CHECK([test "$rc" = 0]) + else + AT_CHECK([test "$rc" -gt 0]) + fi +]) + +# OVN_IC_ROUTE_DUMP(FILE) +# +# Dumps the routing state this AZ manages - its NB static routes and the IC-SB +# routes - into FILE, so two dumps can be compared. +m4_define([OVN_IC_ROUTE_DUMP], [ + ovn-nbctl --columns=ip_prefix,nexthop list logical_router_static_route \ + | sort > $1 + ovn-ic-sbctl --columns=ip_prefix,nexthop,transit_switch list route \ + | sort >> $1 +]) + m4_define([CHECK_SCAPY],[ AT_SKIP_IF([test $HAVE_SCAPY = no]) start_scapy_server From patchwork Wed Sep 2 21:17:43 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297043 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=o/fz/rXv; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) (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 4hZwc52mZTz1xvg for ; Thu, 03 Sep 2026 07:20:05 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 0A892810FD; Wed, 2 Sep 2026 21:20:04 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id Hw7VYuYvh2Ky; Wed, 2 Sep 2026 21:20:00 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp1.osuosl.org 79E81810A6 Authentication-Results: smtp1.osuosl.org; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383981; b=n21t/W3TAkkJ5Cb8ERb5ELxWVJhRTVzKphtKMoIhRIOYsLD+dyXSWkDTMIbq/i02+mwE OUUaEpGALyMDTCeao6bczFr6Dj8K9TzriOmem2BQFqv3yjtHLnRyTNaTLrTmE+oZtcB9C AHBVTPoeAsr2D4nJ5FzAdTFRoUT7RRJ6FnIipXFGKjSZF/73uDr2jwVTJJXbWK1Uhy6XO ECMZEOmvyGpUFjB15pHd8LNldg5bXmbQ/mG6TCD4Q1rUzQQjuuogGeY+my+zQuXtE70BN Kx6Yu/O8H6B0FmibANL03N7pj5FRy1AiID9t2r4Bufr2cUgD9CZBE4/CtNJTDEqq3ZA== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383981; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=TsJ/ycftu+uR8pKrQa8r8sR357yLjODtqBB+ptJ1+rU=; b=JZKC5th4pDKkvLdRNr5SKprmlceEec46f+tneJV9QabgDz3KtJ3stfidViqzRWVxvUgC nDe9wQWwmY/jkhxmU+hHq8HmK8HByGUzcqTJmuJAcGsoMwvjBf2QePTS9keal2M2OjM6J MtRF/y8A72GEj5mHJ5o2Y3i5STFNdJ7y7UN480lHA4OHoM6uKKZy9FpqiAvM8yhzFrE+m CUKjr4BbxGYjE1kZdOuCrY3TVn0E0a+FoK52bvtzmxuvZIC/3KEa3J3YkcudZFWHMGM5B v/QqrNxN52f/iijizj6/oHgiBunkXpHqm1F5BXx7/F8LfLUcML3IctQ1tFtsrrhqAfA== ARC-Authentication-Results: i=2; smtp1.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b="o/fz/rXv"; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" 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= Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=o/fz/rXv Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp1.osuosl.org (Postfix) with ESMTPS id 79E81810A6; Wed, 2 Sep 2026 21:19:41 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 2F826C0D23; Wed, 2 Sep 2026 21:19:41 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133]) by lists.linuxfoundation.org (Postfix) with ESMTP id 0DDF6C0419 for ; Wed, 2 Sep 2026 21:19:36 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id DFD91401B0 for ; Wed, 2 Sep 2026 21:19:35 +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 2wOkdWpUspeQ for ; Wed, 2 Sep 2026 21:19:32 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp2.osuosl.org D8CBC404AC Authentication-Results: smtp2.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::1136" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383960; b=pgv3VPsAv9G5VS9omn9/Hj7Tit9+Lo3Mq15w4iRlZZJbvh06KT9xoVdFpg8Wy4aVRB/Q hlcyxfZuIYxbT0UxyFNKI6zMRqHRO+tXwO88bGLY8FXmmPApwFcsQJRlRdLA/KB+ITulr XikkZ2mP8DuAkCnVDFAO8jAwt8SbYoFpPZNZ0gvCqwKHHL7LbqmQYTyVKHcXiW5KN60Rg YWQt0l/ruoBstJYyqiMyUKH+ivKPahAsBLYrJCyscf+xjx2JbygjhDMnJmBciXMnEG9wN qQP87s9juC4QROrPmR4CdB/ieJ7yyjaR+poosPm1XNYA6qgGzwasMUHhytcsGsNahEw== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383960; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=qnjlOfo2jFlhwk01Q3JaKW/Q8Lz4KmlKc66pr2hLzIs=; b=RBVqiiIz2uaLroPiD9lOHfTSVkP/EsyqJtG0FgAMoXWk3sxBCHzaqbUucsbJpZEmcm3s r0P1xHiVwvFSyZkcToAmD990UfssMm14GLxeBw5YwPkIQ4pVTIbxscZvOMT3U2yuugJmq ren9ShZwtdeLj+QG8qlIVrCpeRism3Hf4/dZ4RmU/GeEhUZou+SHvkKFeqGa/i0pQPK0C SrYm4y8QL19of48FP5LYshjkiNrtvOI4Z8NrxSI+cHi+reFgVxvTJyPvIspkgItAXwrw/ 2CJ7l2GtadR6LBWlcYYl2IrE4QRJFTFTJqbr7Yj4U1FcPvGcgBglnSFv5c2Fdw3t1TA== ARC-Authentication-Results: i=1; smtp2.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b="o/fz/rXv"; arc=none smtp.remote-ip="2607:f8b0:4864:20::1136" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::1136; helo=mail-yw1-x1136.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp2.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Authentication-Results: smtp2.osuosl.org; dkim=pass (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=o/fz/rXv Received: from mail-yw1-x1136.google.com (mail-yw1-x1136.google.com [IPv6:2607:f8b0:4864:20::1136]) by smtp2.osuosl.org (Postfix) with ESMTPS id D8CBC404AC for ; Wed, 2 Sep 2026 21:19:19 +0000 (UTC) Received: by mail-yw1-x1136.google.com with SMTP id 00721157ae682-81ecf499af9so22169877b3.1 for ; Wed, 02 Sep 2026 14:19:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383959; x=1788988759; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=qnjlOfo2jFlhwk01Q3JaKW/Q8Lz4KmlKc66pr2hLzIs=; b=o/fz/rXvfwVTHRVplrdspIBrspqfgIOMpON80VXDsxzaJ9KSYVLAIQG+Al/FUptkbV iE+VyXCZ6mUYCUYUMs1sRsTgjRhUv6w3fP3HRS+pDJ+JC0XTJeFpAGMgHiLXPKHikT7X EMuGLYR1zkQfY8E0AIyFWL4ZTraV0XspNh7HQH5DU2FCy/jQuMZFBBcGtEv5Pk/5ie7e +QzPvN6oXdkuDb+DuyWVMihJtO/pPZRIWYoT8EhzRFr/+LLgFSNAo8UfUVFcMVpwuaza 3z8NOeGmiECyPNjk0uNqve+fLOsjpgOsk9HKJz7qIecw0EIjF1Bopt9uyLDM3/LBeY8x Npkg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383959; x=1788988759; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=qnjlOfo2jFlhwk01Q3JaKW/Q8Lz4KmlKc66pr2hLzIs=; b=q6urVGVhl+Cm0IehtdChFLT81MftvrlpR4nvi78aYhy5lQccwOLQK/oN6HNRTRlpve iOdmrGX/znQcuBiU+grr0RA4lPdaKmZHtjo3fXT8DJeKIBQUI7317A4rN/zzF9Plnu+w Ilo+AJZvdZy/ChvHOnJwfgfXRJELLwn1vNV9Lg65t+G3u21DNVTuZmxgQ/oR1PHNL8oY Y+400nfMjqNDyIqsJAIdENMExx2zpP6Fx29BEhlpsMA221dl39S1vQQEtutVdURx6Aqh Dglfl98MVyptdLH9CLNEV0X/isFPUXNyEPBE93a4dcbhDsFFB/HkghD669C8Gn0ny1tX 1WQg== X-Gm-Message-State: AFuF++l9EAuhxABWB/e1grMPaB6R1e6vD6RbfPUyKXOM9yJvxaoTFxTe 2QBw0ILMeXuvE6fKkew85v9tEs3ZnfvQRWVkwp8DJJs7QkiBv5ptVgyFsuBqd6MzoREf6dTklZQ ZkjTA0rDznPzvtq30HbBQ1gXLXHg0cZw88MurPP3eFBZr18hy+UomkrsMeKb+U6Q= X-Gm-Gg: AYBFou0K8IWSAZDiNTz8rhZwWVEy00m9OHWcTLisLx636mfZqMtShmv4olPMTyNu6o/ A5CQyJbDL6M29T8llhmkwjF0lqoJgM/RcQ3cbEa3+lCVCviOSC+kUMfD/eTcPIbZvz4UmdoT3Dt +1FG+LlrsKBlBnkLwc4ijAYyds+xmqzb8VMMLCGcNTzYOXcV7qtpq9VRsGw0mfnyOgJTpve55W2 LZDyJ9Cou9UE6CMWuFVMg5ZDhSnJMnNHozfRIC2B5RshLq+LcL9/hBV0wn/b5h2SJyIMAtJuhAj OG/LUQGOa/DvCDfJTdL55geUCHRf55ARRs4+wFI3FB9RJwcDDDs5Mr3FTI/LXGdhUxu5WESM5N1 AcXsPOsWWIuR0ZUH0hZ0m8TNF04C4QOzP4RLE/qRyqThIqnEiZHgYDkIdluDrAoJr8BMkFpkChS cg8+RyWheUd8oZUm4a5fSZOzXXDe839EQ9GIqQXJCS4KMcd5qnzSalvkHp/APr0+d3Cp4UIiWte /AohtcV+nqXszkgMlXpR3/A/qr+kcQ8EEzUNHQYlt1f X-Received: by 2002:a53:cf11:0:b0:66e:5d2f:8235 with SMTP id 956f58d0204a3-66f9be76ca6mr1984669d50.35.1788383958245; Wed, 02 Sep 2026 14:19:18 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.19.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:19:17 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:43 -0300 Message-Id: <20260902211744.2159261-14-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 13/14] tests: Add ovn-ic incremental-processing basic scale 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: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Add tests/perf-ovn-ic.at, a perf-testsuite scenario that loads N VPCs across 3 AZs and, on the same loaded state, measures the cost of applying one more VPC through the incremental change handlers versus a forced full recompute (ovn-appctl inc-engine/recompute). Both paths run in the same daemon, so the before/after is directly comparable without rebuilding a recompute-only binary. The dominant nodes are en_ts (transit switch -> NB Logical_Switch mirror) and en_route (route advertise/learn); every engine stopwatch is dumped so the incremental change-handler timings and the recompute node timings are both captured in the results file. Run with: make check-perf TESTSUITEFLAGS='-k ovn-ic' Assisted-by: Claude Opus 4.8, Claude Code Signed-off-by: Paulo Guilherme Silva Acked-by: Lucas Vargas Dias --- tests/automake.mk | 3 +- tests/perf-ovn-ic.at | 254 ++++++++++++++++++++++++++++++++++++++++ tests/perf-testsuite.at | 1 + 3 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 tests/perf-ovn-ic.at diff --git a/tests/automake.mk b/tests/automake.mk index d4c1d3710..8f7a8c4fa 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -69,7 +69,8 @@ SYSTEM_TESTSUITE_AT = \ PERF_TESTSUITE_AT = \ tests/perf-testsuite.at \ - tests/perf-northd.at + tests/perf-northd.at \ + tests/perf-ovn-ic.at MULTINODE_TESTSUITE_AT = \ tests/multinode-bgp-macros.at \ diff --git a/tests/perf-ovn-ic.at b/tests/perf-ovn-ic.at new file mode 100644 index 000000000..e39e4e3fc --- /dev/null +++ b/tests/perf-ovn-ic.at @@ -0,0 +1,254 @@ +AT_BANNER([ovn-ic performance tests]) + +# =========================================================================== +# ovn-ic incremental-processing performance tests. +# +# These tests load a large ovn-ic scenario (N VPCs across 3 AZs) and then, +# ON THE SAME LOADED STATE, measure the two engine paths that the same daemon +# already exposes: +# +# * incremental: cost of applying ONE more VPC via the change handlers +# (inc-engine/show-stats recompute == 0 proves the +# full recompute was skipped); +# * recompute: cost of a forced full recompute of the whole state +# (ovn-appctl inc-engine/recompute). +# +# This gives an apples-to-apples before/after on identical state with a single +# binary -- no need to rebuild the pre-I-P daemon. The dominant nodes are +# en_ts (transit switch -> NB Logical_Switch mirror) and en_route (route +# advertise/learn), which are exactly the paths this work optimizes. +# +# Results land in ${at_suite_dir}/results (see `make check-perf`). For each +# phase we dump ALL engine stopwatches, so the incremental change-handler +# timings and the recompute node timings are both captured; extract the rows +# you need (e.g. "ts", "route") for the cover letter. +# =========================================================================== + +# All appctl to az1's ovn-ic daemon (run after `as az1`). +m4_define([IC_APPCTL], [ovn-appctl -t ic/ovn-ic]) + +# PERF_IC_NODE([NODE], [LABEL]) +# +# Append Maximum + short-term-average of the NODE stopwatch (recompute time of +# that engine node) to the results file. PARSE_STOPWATCH is defined in +# perf-northd.at. +m4_define([PERF_IC_NODE], [ + PERF_RECORD_RESULT([$2 Maximum (msec)], + [`IC_APPCTL stopwatch/show $1 | PARSE_STOPWATCH(["Maximum"])`]) + PERF_RECORD_RESULT([$2 Average (msec)], + [`IC_APPCTL stopwatch/show $1 | PARSE_STOPWATCH(["Short term average"])`]) +]) + +# PERF_IC_NODE_INCR([NODE], [LABEL]) +# +# Append the incremental cost of NODE: the maximum over all of that node's +# change-handler stopwatches (the node's own stopwatch records recompute time, +# not incremental time, so the per-input handler stopwatches are aggregated). +m4_define([PERF_IC_NODE_INCR], [ + PERF_RECORD_RESULT([$2 Maximum (msec)], [`ic_node_incr $1 "Maximum"`]) + PERF_RECORD_RESULT([$2 Average (msec)], + [`ic_node_incr $1 "Short term average"`]) +]) + +OVS_START_SHELL_HELPERS +# ic_build_vpc INDEX +# +# Create one "VPC" identified by INDEX: 4 transit switches (global, in IC-NB) +# plus, in each of az1/az2/az3, a logical router connected to all 4 TS and a +# tenant switch with a connected subnet (advertised via ovn-ic route adv). +# Addresses are fixed per AZ/TS-index and only names vary by INDEX -- each VPC +# owns its own switches, so reusing the same subnet/MAC across VPCs is fine. +# +# The 4 transit switches live in the global IC-NB and are mirrored into every +# AZ's NB as Logical_Switches (the en_ts node). Each AZ router peers ALL 4 +# transit switches (full mesh); each router also owns a tenant switch whose +# connected subnet is advertised to / learned from the other AZs by ovn-ic +# (the en_route node). +# +# +-----------------------------------+ +# | IC-NB (global) | +# | ts{i}-1 ts{i}-2 ts{i}-3 ts{i}-4 | <- en_ts +# +--+---------+--------+--------+----+ mirrors +# +--------------+ | | +--------------+ +# (all 4 TS) (all 4 TS) (all 4 TS) +# | | | +# +------+------+ +------+------+ +------+------+ +# | lr{i}-1 | | lr{i}-2 | | lr{i}-3 | +# | (az1) | | (az2) | | (az3) | +# +------+------+ +------+------+ +------+------+ +# | rp{i}-1-bar | rp{i}-2-bar | rp{i}-3-bar +# +------+------+ +------+------+ +------+------+ +# | bar{i}-1 | | bar{i}-2 | | bar{i}-3 | +# |192.168.10/24| |192.168.20/24| |192.168.30/24| +# +-------------+ +-------------+ +-------------+ +# +-------- advertised / learned across AZs (en_route) ------+ +# +# Port naming: +# LRP on router : ts{i}-{t}-lr{i}-{a} (169.254.1{t}.{a}/24) +# router LSP on TS : ts{i}-{t}-lr{i}-{a}-rp (type=router) +# tenant router port : rp{i}-{a}-bar (192.168.{a}0.1/24) +# tenant VM port : bar{i}-{a} (192.168.{a}0.2) +ic_build_vpc () { + local i=$1 t a + + # 4 transit switches in the global IC-NB. + for t in 1 2 3 4; do + ovn-ic-nbctl ts-add ts$i-$t + done + + # Per-AZ: wait for the TS mirrors to appear locally, then wire the router. + for a in 1 2 3; do + for t in 1 2 3 4; do + OVS_WAIT_UNTIL([test -n "`ovn_as az$a ovn-nbctl --bare \ + --columns=name find logical_switch name=ts$i-$t`"]) + done + + check ovn_as az$a sh -c ' + check () { "$@" || exit 1; } + i='"$i"'; a='"$a"' + check ovn-nbctl lr-add lr${i}-${a} + check ovn-nbctl ls-add bar${i}-${a} + check ovn-nbctl lrp-add lr${i}-${a} rp${i}-${a}-bar \ + 00:01:ff:2${a}:00:01 192.168.${a}0.1/24 + check ovn-nbctl lsp-add bar${i}-${a} bar${i}-${a} \ + -- lsp-set-addresses bar${i}-${a} "00:00:ff:ff:ff:1${a} \ + 192.168.${a}0.2" + check ovn-nbctl lsp-add bar${i}-${a} bar-rp${i}-${a} \ + -- set Logical_Switch_Port bar-rp${i}-${a} type=router \ + options:router-port=rp${i}-${a}-bar \ + -- lsp-set-addresses bar-rp${i}-${a} router + + for t in 1 2 3 4; do + check ovn-nbctl lrp-add lr${i}-${a} ts${i}-${t}-lr${i}-${a} \ + 00:01:${t}1:00:2${a}:01 169.254.1${t}.${a}/24 + check ovn-nbctl lsp-add ts${i}-${t} ts${i}-${t}-lr${i}-${a}-rp \ + -- set Logical_Switch_Port ts${i}-${t}-lr${i}-${a}-rp \ + type=router \ + options:router-port=ts${i}-${t}-lr${i}-${a} \ + -- lsp-set-addresses ts${i}-${t}-lr${i}-${a}-rp router + done + ' + done +} + +# ic_build_scale N : create VPCs 1..N and enable route advertise/learn so the +# en_route node performs real work. +ic_build_scale () { + local n=$1 a i + for a in 1 2 3; do + check ovn_as az$a ovn-nbctl set nb_global . \ + options:ic-route-adv=true options:ic-route-learn=true \ + options:ic-route-adv-default=true \ + options:ic-route-learn-default=true + done + for i in $(seq 1 $n); do + ic_build_vpc $i + done +} + +# ic_node_incr NODE METRIC +# +# Print the maximum value of stopwatch METRIC ("Maximum" or "Short term +# average") across all change-handler stopwatches of engine NODE. The node's +# own stopwatch only records recompute time, so the per-input handler +# stopwatches are aggregated to represent the node's incremental cost. +ic_node_incr () { + local node=$1 metric=$2 max=0 h v + for h in $(ovn-appctl -t ic/ovn-ic inc-engine/list-stopwatches "$node" \ + 2>/dev/null | tail -n +2); do + v=$(ovn-appctl -t ic/ovn-ic stopwatch/show "$h" 2>/dev/null \ + | grep "$metric" | awk '{print $(NF-1)}') + test -n "$v" || v=0 + if awk "BEGIN{exit !($v > $max)}"; then + max=$v + fi + done + echo "$max" +} +OVS_END_SHELL_HELPERS + +# MEASURE_IC_SCALE(N) +# +# Build N VPCs, then measure incremental (+1 VPC) vs forced full recompute on +# the resulting state. +m4_define([MEASURE_IC_SCALE], [ + ovn_init_ic_db + ovn_start az1 + ovn_start az2 + ovn_start az3 + OVS_WAIT_UNTIL([test 3 = `ovn-ic-sbctl show | grep -c availability-zone`]) + + # ---- Build the scale (cost of building N VPCs incrementally) ---- + ic_build_scale $1 + check ovn-ic-nbctl --wait=sb sync + as az1 + check ovn-nbctl --wait=sb sync + + # The engine stopwatches are disabled by default, so turn them on before + # measuring anything or every timing below reads back as zero. This is + # done only after the scale has been built, to keep the build itself free + # of the per-node stopwatch overhead. + check ovn-appctl -t ic/ovn-ic inc-engine/enable-stopwatch + + # Quiesce: force one recompute so the engine is fully settled, then reset + # the counters/stopwatches so the next measurement is clean. + check ovn-appctl -t ic/ovn-ic inc-engine/recompute + check ovn-ic-nbctl --wait=sb sync + check ovn-appctl -t ic/ovn-ic inc-engine/clear-stats + check ovn-appctl -t ic/ovn-ic stopwatch/reset + + # ---- AFTER: incremental cost of adding ONE more VPC at $1-VPC scale ---- + ic_build_vpc m4_incr($1) + check ovn-ic-nbctl --wait=sb sync + as az1 + check ovn-nbctl --wait=sb sync + + # Prove the full recompute was skipped for the hot nodes. + AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats ts compute)" -gt 0]) + AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats ts recompute)" = 0]) + AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats route compute)" -gt 0]) + AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats route recompute)" = 0]) + + PERF_RECORD_START([Incremental: +1 VPC at $1-VPC scale (node handler times)]) + PERF_IC_NODE_INCR([ts], [en_ts incremental]) + PERF_IC_NODE_INCR([tr], [en_tr incremental]) + PERF_IC_NODE_INCR([route], [en_route incremental]) + PERF_IC_NODE_INCR([port_binding], [en_port_binding incremental]) + PERF_IC_NODE_INCR([tunnel_key], [en_tunnel_key incremental]) + + # ---- BEFORE: forced full recompute of the whole state ---- + check ovn-appctl -t ic/ovn-ic inc-engine/clear-stats + check ovn-appctl -t ic/ovn-ic stopwatch/reset + check ovn-appctl -t ic/ovn-ic inc-engine/recompute + check ovn-ic-nbctl --wait=sb sync + as az1 + check ovn-nbctl --wait=sb sync + + AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats ts recompute)" -ge 1]) + + PERF_RECORD_START([Full recompute at m4_incr($1)-VPC scale (node times)]) + PERF_IC_NODE([ts], [en_ts recompute]) + PERF_IC_NODE([tr], [en_tr recompute]) + PERF_IC_NODE([route], [en_route recompute]) + PERF_IC_NODE([port_binding], [en_port_binding recompute]) + PERF_IC_NODE([tunnel_key], [en_tunnel_key recompute]) + + OVN_CLEANUP_IC([az1], [az2], [az3]) +]) + +OVN_FOR_EACH_NORTHD_NO_HV([ +AT_SETUP([ovn-ic basic scale test -- 200 VPCs x 3 AZs x 4 TSs: incremental vs recompute]) +MEASURE_IC_SCALE(200) +AT_CLEANUP +]) + +OVN_FOR_EACH_NORTHD_NO_HV([ +AT_SETUP([ovn-ic basic scale test -- 400 VPCs x 3 AZs x 4 TSs: incremental vs recompute]) +MEASURE_IC_SCALE(400) +AT_CLEANUP +]) diff --git a/tests/perf-testsuite.at b/tests/perf-testsuite.at index 31cdc850c..3ae86fc18 100644 --- a/tests/perf-testsuite.at +++ b/tests/perf-testsuite.at @@ -23,4 +23,5 @@ m4_include([tests/ofproto-macros.at]) m4_include([tests/ovn-macros.at]) m4_include([tests/perf-northd.at]) +m4_include([tests/perf-ovn-ic.at]) From patchwork Wed Sep 2 21:17:44 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paulo Guilherme Silva X-Patchwork-Id: 2297045 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" (2048-bit key; unprotected) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=MpdGeDYo; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) (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 4hZwc91WzFz1xvg for ; Thu, 03 Sep 2026 07:20:09 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id C791A810B6; Wed, 2 Sep 2026 21:20:07 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id zRUWNXfyn6PM; Wed, 2 Sep 2026 21:20:01 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp1.osuosl.org 9356C810F6 Authentication-Results: smtp1.osuosl.org; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" ARC-Seal: i=2; d=osuosl.org; s=arc; a=rsa-sha256; cv=fail; t=1788383983; b=bf2bc6YQAytAF8U3N7ny9+KJNvcCrhCwYKL4CMxFeHyHgEByoPndVZxMcAlpQIWKsD8R orUuJP7oWWRnKbpe2lP2D+b+FO8edn/R17DHtSxdpOi8y8zy+/61ctBAM8ugFFT97fBWv nbJoU1MSnFHJmg94mHVaPqhOeYo8wId6MHVJi5LDS7t0GqG7R769gLIpSm6uHwWtjxjoc tHJL8e07rlz0U9oKZhcDPmO1PZnQE11V4UqxvWVXz2k6HEAQBoz46z/lkxzBryxNAxfX3 VAW7PhUkYWIhWcOQy7qex16TGfRHRRAdu0q2UObXWIxoCyZ7thJcepundnxHMIFAloQ== ARC-Message-Signature: i=2; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383983; h=X-Comment:Received:X-Original-To:Delivered-To:Received:Received: X-Virus-Scanned:X-Spam-Flag:X-Spam-Score:X-Spam-Level:X-Spam-Status: Received:ARC-Filter:Received-SPF:Received:Received:DKIM-Signature: X-Google-DKIM-Signature:X-Gm-Message-State:X-Gm-Gg:X-Received: Received:From:To:Date:Message-Id:X-Mailer:In-Reply-To:References: MIME-Version:Subject:X-BeenThere:X-Mailman-Version:Precedence:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: Content-Type:Content-Transfer-Encoding:Errors-To:Sender; bh=smWMOa4rzPWZYet7GK1GWK3gNZaQphVIR/9mV20I4sM=; b=qRHLGWucWRd2Gtp8+tCVVrGcrmBBH8QGy/PuzmHmGGZODoYlkacaLWgd6a4WlkisuU6c r2ipgKrqEHieFUKG9O+0GK9beMYSbVMVYJZCwG+MTb5b7B3hVmpyegEgFxtx3V5PDWYWk TC36E659SRaY/DO+1OAXiwJP4CJGT21FVgwUd26t4vwuH6purcGYPt8/bHPXFWDS3hTU1 HGGKdL7CkctQ5+Ywysqd6/RDGqvcb8IovMFla/nNLWSZJnUJiQ4E4ppXoH9cmzbe0k5ki zafU3ITF2UtM/48HLj3ayNB216YsWyTAN9NgkgRzeuoxxffS76VWNcRFCQsAfVg1DIQ== ARC-Authentication-Results: i=2; smtp1.osuosl.org; dkim=fail reason="signature verification failed" header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=MpdGeDYo; arc=fail smtp.remote-ip="2605:bc80:3010:104::8cd3:938" 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= Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=MpdGeDYo Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp1.osuosl.org (Postfix) with ESMTPS id 9356C810F6; Wed, 2 Sep 2026 21:19:43 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id F3A31C0D10; Wed, 2 Sep 2026 21:19:42 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp2.osuosl.org (smtp2.osuosl.org [140.211.166.133]) by lists.linuxfoundation.org (Postfix) with ESMTP id 8439FC0D2B for ; Wed, 2 Sep 2026 21:19:41 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 5F82D4051A for ; Wed, 2 Sep 2026 21:19:41 +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 NzTPKm41e1RN for ; Wed, 2 Sep 2026 21:19:35 +0000 (UTC) ARC-Filter: OpenARC Filter v1.3.0 smtp2.osuosl.org 5F88E4038C Authentication-Results: smtp2.osuosl.org; arc=none smtp.remote-ip="2607:f8b0:4864:20::1136" ARC-Seal: i=1; d=osuosl.org; s=arc; a=rsa-sha256; cv=none; t=1788383963; b=BE6yTUWcbvShsoM4CwDv9OxPEZIncs+OQ5qOiL9sjGU0IyWSH2hCMel0xQqfsUTIYzA/ Yyi+MQc8gEuDde3PINwYPgBhvz7H5RpXBXkpMKWwDfUEC1DZCoLgQ/wV2p17ZYF8v6Ndv NEcL8vVXQFroS4BJtJP3Q5ENwNw9mm/UWtu+Z8pkbAg0gyBokE7XxadWAaTkjVp0VvvVV Q8YMiC7U1XWj419kyqGgMQJj7Mc8PMnfMnkMbdV3zEwuvDU11Aa7Ha4rDIyXtlfiIMvNw Nlhs0kSClKQw1ouTBtsC3f6z6u5C1qDoxOkSCVrPFMOJWkaniIVYVWPkHqxfaREUvxQ== ARC-Message-Signature: i=1; d=osuosl.org; s=arc; a=rsa-sha256; c=relaxed/relaxed; t=1788383963; h=Received-SPF:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:To:Cc:Subject: Date:Message-Id:X-Mailer:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding:Content-Type; bh=hj8rs1Sg/u+uVZu2MIoZlLpjEJVSc5nXC0t6WWDLnFQ=; b=k+O7Kd3dRqoqMz+ZxJhpuEqFEE/aTKH3HCabvbGw/rhYMv/LLBRbc8XuyLkFgox3YZpf 9gJvNEE4lUA02/JprqD41VF4ijt22jQBhXO5oF+sZ9SvYdH+b8qj9ZvPk7QpzaR6dORkd LHmeFtRCSfLfwhz4emMej42OVbY6XRUua7czV4fpqK3oVUNdvXB+aJXVOh+NSaIcva6dQ wJPwzlj/toPulxPLgFX+bjNvQrFfdVKmRI5ZjQOljvz/+J0vH+feOtT8KiVX/4M4kgiqd lzWqUlXmCZ2AunzQt+u8QoO/Ag++PUI4Cp6r/+D4U+kt6n9BigKGYn+BLMdGVt7I2iA== ARC-Authentication-Results: i=1; smtp2.osuosl.org; dmarc=pass header.from=magalu.cloud; dkim=pass header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=MpdGeDYo; arc=none smtp.remote-ip="2607:f8b0:4864:20::1136" Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::1136; helo=mail-yw1-x1136.google.com; envelope-from=guilherme.paulo@magalu.cloud; receiver= Authentication-Results: smtp2.osuosl.org; dmarc=pass (p=none dis=none) header.from=magalu.cloud Authentication-Results: smtp2.osuosl.org; dkim=pass (2048-bit key) header.d=magalu-cloud.20251104.gappssmtp.com header.i=@magalu-cloud.20251104.gappssmtp.com header.a=rsa-sha256 header.s=20251104 header.b=MpdGeDYo Received: from mail-yw1-x1136.google.com (mail-yw1-x1136.google.com [IPv6:2607:f8b0:4864:20::1136]) by smtp2.osuosl.org (Postfix) with ESMTPS id 5F88E4038C for ; Wed, 2 Sep 2026 21:19:22 +0000 (UTC) Received: by mail-yw1-x1136.google.com with SMTP id 00721157ae682-7dbcb505578so21270007b3.3 for ; Wed, 02 Sep 2026 14:19:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=magalu-cloud.20251104.gappssmtp.com; s=20251104; t=1788383962; x=1788988762; darn=openvswitch.org; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=hj8rs1Sg/u+uVZu2MIoZlLpjEJVSc5nXC0t6WWDLnFQ=; b=MpdGeDYoHhyoB+kQ7uGVebCUcQcLn+2II7FVsMD3xEZS0drK5WoZMZAKgkffXPqTrz QvCEMAhzLKjw8mSUwSwtrds5LxylDB+StorHr2+HqtSucMLFr4UEkh8tuWjeDSiHL1/8 IRe0HtTAKetoBtO0sh6pMG1fSWzabKORqoAMhZb0u1aSVLDL/Zl2BZbnCfcHnzqi+r1j kBn6u3J3Zm9lOEWLEaV4uSdQZfOVzM57rhbxRn0b4HDiytoGEm3jiV54/R5KdZN5jZGH tyN42L409ZLqlP6G3QlhpBvDBDpcqXzxHxi+nbIPlXcLkt2PEpP0OvM+l/E1hvlT7d5p eXzQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788383962; x=1788988762; h=content-type:content-transfer-encoding:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=hj8rs1Sg/u+uVZu2MIoZlLpjEJVSc5nXC0t6WWDLnFQ=; b=EJHcEnJiJYug/Q+ISvLpkFFou1DeIiq/50NarujljpQHGySdhnG2FhfKc5t2+LdNi3 k6BlYgVSLncTGlwT70h+3Y+7oA1+s4cpG9j3Lpu+lVzy47Tgbad/BktOhVqM/WFnkRpb R/UbTIkxO9AjSDmMILB2Qjo3HNeGg/y9G4qB8D5oeJXM0D+JoSWYtyiKEiWHFD9Nj4u/ +xxFBxLBcqd3HWnaXUODqNlDYB82VtthyZ3SkbagCLKEmm6bAnFu9c6pMzf3czC9p9OY R+QI8M/WE9r9JjQG1AQVyyWH/1Jh2p/nFMDagHs3CoR6niZdib3kR8hTM6zNki4xzUg5 HFUw== X-Gm-Message-State: AFuF++k+o8necKaWsRkP3h1q/nVmiSD8cNF57wjt7B4okx5s1e1N3b9t yYN4isI6pwQuAlu7BrqbY82IZtT8AgusVlGsNLqEZX+x8aecxjbWTRmo6ylT1PRxhsJqNtz3+eH G22zdhR9+XuIOb8ISTNCo/9DOOJnbB9/LIqGNcSVG3Wg8e8R++4ljWKcrYmt8Vn8= X-Gm-Gg: AYBFou2IiepyIkbLYl+rEpTNcu/B6hkdlMce+hXd2r3z8a/vDTYD+ILEsB1vwCgD8b+ fwRBFS8sOyxTX6wkkF59pGMhDBYd4qvFV8k3mH1i2h735BfpUQlocJoM8N5snSGhvqmkvwipnTe wnl+KlYn3pHfkpSOM5D+BGa1jl83JPNBa3/9Yl05pG2fW0XlNiMnoHpacPopK0LFNPxWPnnjK7j JDt7rdQHj658qVzdPufcFrZGBPq8ILr9YnLx2g7ClxFuik1wQc3u7WN/XTDqFe7LN91S5k6aipI 7K3fx2fUNB6ShQWIg8Xg2eH027Z6bel1YuM/iwARkr56VfGgNc0RezGXTAqmldDAkL5a/URPlX+ 9sFY0yMU+NLgi+Gm9u/TSFqmaGWU4lXaKdbPe3HMtQlB3eqyWKb7OAFhufLszp+yshkHyWcEFXR zOUjBHJQNx70sSuwBHZL+lsDWs2bGWgdR5TsxZMgBnubDBfJOa2RHA+8vAjfVJMVgGFtguCl5n5 FR7XkovcUTUlQbGENtoye6M1QvYDh8J X-Received: by 2002:a05:690c:d8a:b0:864:a8f4:faf0 with SMTP id 00721157ae682-86c4f475001mr37750247b3.10.1788383961338; Wed, 02 Sep 2026 14:19:21 -0700 (PDT) Received: from WNLEC-CW22RF4.. (177.75.155.8.rev.mhnet.com.br. [177.75.155.8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-86c178435b5sm26490867b3.29.2026.09.02.14.19.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Sep 2026 14:19:20 -0700 (PDT) From: Paulo Guilherme Silva To: dev@openvswitch.org Date: Wed, 2 Sep 2026 18:17:44 -0300 Message-Id: <20260902211744.2159261-15-guilherme.paulo@magalu.cloud> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> References: <20260902211744.2159261-1-guilherme.paulo@magalu.cloud> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v4 14/14] ic: Add incremental handler for the en_service_mon node. 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: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Move the load-balancer health-check sync out of ovn-ic.c into the en_service_monitor node and give the node a change handler for its busiest input. The sync reads an SB Port_Binding only through create_synced_svcs_mon(), which - for each IC-SB Service_Monitor targeting this AZ - looks up the backing port by 'logical_port' and reads its 'up' and 'chassis'. SB Port_Binding is the busiest table in an availability zone, so answering every change on it with a full recompute charges the whole health-check sync to churn that cannot affect it. Add a handler that falls back only when the change can matter: the row is new or deleted, or its 'up'/'chassis' column changed, and the port backs a Service_Monitor targeting this AZ. The second test resolves through the existing by-target-AZ/logical-port index, so it costs one lookup per changed row. Every other port-binding change is answered as handled and unchanged. Drop the en_icsb_availability_zone input from en_service_monitor and en_address_set, and stop modelling the Availability_Zone table as an engine input altogether: these were its last two consumers. Both nodes need only the AZ identity that en_az reports and neither reads the table, so the input only exposed them to the nb_ic_cfg sequence number bumped on its rows. en_gateway, en_port_binding and en_route already avoid it for the same reason; this completes the pattern. Add tests/ovn-ic.at tests for the port-binding handler scoping and for the sequence-number churn no longer recomputing the node. Assisted-by: Claude Opus 4.8, Claude Code Signed-off-by: Paulo Guilherme Silva Acked-by: Lucas Vargas Dias --- NEWS | 7 + ic/en-service-monitor.c | 500 +++++++++++++++++++++++++++++++++++++++- ic/en-service-monitor.h | 4 + ic/inc-proc-ic.c | 34 ++- ic/ovn-ic.c | 441 ----------------------------------- ic/ovn-ic.h | 2 - tests/ovn-ic.at | 78 +++++++ 7 files changed, 612 insertions(+), 454 deletions(-) diff --git a/NEWS b/NEWS index 819e81a79..4d2c73bbc 100644 --- a/NEWS +++ b/NEWS @@ -133,6 +133,13 @@ OVN v26.09.0 - xxx xx xxxx coalescing bursts of change-driven runs into fewer, larger runs. It defaults to 0, which keeps running as soon as there is work to do. A forced full recompute is never delayed. + - ovn-ic now processes database changes incrementally. Its processing + engine is split into one node per subsystem - availability zone, + gateways, transit switches and routers, datapath tunnel keys, port + bindings, routes, service monitors and address sets - and each node has + change handlers, so a change is reconciled in place instead of re-running + the whole ovn-ic computation. The "inc-engine/show-stats" command + reports the per-node recompute counters. OVN v26.03.0 - xxx xx xxxx -------------------------- diff --git a/ic/en-service-monitor.c b/ic/en-service-monitor.c index d0aab60bb..bd903e709 100644 --- a/ic/en-service-monitor.c +++ b/ic/en-service-monitor.c @@ -14,14 +14,452 @@ #include -#include "en-service-monitor.h" #include "en-az.h" +#include "en-service-monitor.h" #include "lib/inc-proc-eng.h" +#include "lib/ovn-ic-sb-idl.h" +#include "lib/ovn-sb-idl.h" #include "openvswitch/vlog.h" #include "ovn-ic.h" +#include "ovsdb-idl.h" VLOG_DEFINE_THIS_MODULE(en_ic_service_monitor); +struct service_monitor_info { + struct hmap_node hmap_node; + union { + const struct sbrec_service_monitor *sb_rec; + const struct icsbrec_service_monitor *ic_rec; + } db_rec; + /* Destination availability zone name. */ + char *dst_az_name; + /* Source availability zone name. */ + char *src_az_name; + /* Chassis name associated with monitor logical port. */ + char *chassis_name; +}; + +struct sync_service_monitor_data { + /* Map of service monitors to be pushed to other AZs. */ + struct hmap pushed_svcs_map; + /* Map of service monitors synced from other AZs to our. */ + struct hmap synced_svcs_map; + /* Map of local service monitors in the ICSBDB. */ + struct hmap local_ic_svcs_map; + /* Map of local service monitors in SBDB. */ + struct hmap local_sb_svcs_map; + /* MAC address used for service monitor. */ + char *prpg_svc_monitor_mac; +}; + +static void +create_service_monitor_info(struct hmap *svc_map, + const void *db_rec, + const struct uuid *uuid, + const char *src_az_name, + const char *target_az_name, + const char *chassis_name, + bool ic_rec) +{ + struct service_monitor_info *svc_mon = xzalloc(sizeof(*svc_mon)); + size_t hash = uuid_hash(uuid); + + if (ic_rec) { + svc_mon->db_rec.ic_rec = + (const struct icsbrec_service_monitor *) db_rec; + } else { + svc_mon->db_rec.sb_rec = + (const struct sbrec_service_monitor *) db_rec; + } + + svc_mon->dst_az_name = target_az_name ? xstrdup(target_az_name) : NULL; + svc_mon->chassis_name = chassis_name ? xstrdup(chassis_name) : NULL; + svc_mon->src_az_name = xstrdup(src_az_name); + + hmap_insert(svc_map, &svc_mon->hmap_node, hash); +} + +static void +destroy_service_monitor_info(struct service_monitor_info *svc_mon) +{ + free(svc_mon->src_az_name); + free(svc_mon->dst_az_name); + free(svc_mon->chassis_name); + free(svc_mon); +} + +static void +refresh_sb_record_cache(struct hmap *svc_mon_map, + const struct sbrec_service_monitor *lookup_rec) +{ + size_t hash = uuid_hash(&lookup_rec->header_.uuid); + struct service_monitor_info *svc_mon; + + HMAP_FOR_EACH_WITH_HASH (svc_mon, hmap_node, hash, svc_mon_map) { + ovs_assert(svc_mon->db_rec.sb_rec); + if (svc_mon->db_rec.sb_rec == lookup_rec) { + hmap_remove(svc_mon_map, &svc_mon->hmap_node); + destroy_service_monitor_info(svc_mon); + return; + } + } +} + +static void +refresh_ic_record_cache(struct hmap *svc_mon_map, + const struct icsbrec_service_monitor *lookup_rec) +{ + size_t hash = uuid_hash(&lookup_rec->header_.uuid); + struct service_monitor_info *svc_mon; + + HMAP_FOR_EACH_WITH_HASH (svc_mon, hmap_node, hash, svc_mon_map) { + ovs_assert(svc_mon->db_rec.ic_rec); + if (svc_mon->db_rec.ic_rec == lookup_rec) { + hmap_remove(svc_mon_map, &svc_mon->hmap_node); + destroy_service_monitor_info(svc_mon); + return; + } + } +} + +static void +remove_unused_ic_records(struct hmap *local_ic_svcs_map) +{ + struct service_monitor_info *svc_mon; + HMAP_FOR_EACH_SAFE (svc_mon, hmap_node, local_ic_svcs_map) { + icsbrec_service_monitor_delete(svc_mon->db_rec.ic_rec); + destroy_service_monitor_info(svc_mon); + } + + hmap_destroy(local_ic_svcs_map); +} + +static void +remove_unused_sb_records(struct hmap *local_sb_svcs_map) +{ + struct service_monitor_info *svc_mon; + HMAP_FOR_EACH_SAFE (svc_mon, hmap_node, local_sb_svcs_map) { + sbrec_service_monitor_delete(svc_mon->db_rec.sb_rec); + destroy_service_monitor_info(svc_mon); + } + + hmap_destroy(local_sb_svcs_map); +} + +static void +create_pushed_svcs_mon(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + struct hmap *pushed_svcs_map) +{ + struct sbrec_service_monitor *key = + sbrec_service_monitor_index_init_row( + ctx->sbrec_service_monitor_by_remote_type); + + sbrec_service_monitor_index_set_remote(key, true); + + const struct sbrec_service_monitor *sb_rec; + SBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (sb_rec, key, + ctx->sbrec_service_monitor_by_remote_type) { + const char *target_az_name = smap_get(&sb_rec->options, + "az-name"); + if (!target_az_name) { + continue; + } + create_service_monitor_info(pushed_svcs_map, sb_rec, + &sb_rec->header_.uuid, + runned_az->name, target_az_name, + NULL, false); + } + + sbrec_service_monitor_index_destroy_row(key); +} + +static void +create_synced_svcs_mon(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + struct hmap *synced_svcs_map) +{ + struct icsbrec_service_monitor *key = + icsbrec_service_monitor_index_init_row( + ctx->icsbrec_service_monitor_by_target_az); + + icsbrec_service_monitor_index_set_target_availability_zone( + key, runned_az->name); + + const struct icsbrec_service_monitor *ic_rec; + ICSBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (ic_rec, key, + ctx->icsbrec_service_monitor_by_target_az) { + + const struct sbrec_port_binding *pb = + find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, + ic_rec->logical_port); + + if (!pb || !pb->up) { + continue; + } + + const char *chassis_name = pb->chassis ? pb->chassis->name : NULL; + create_service_monitor_info(synced_svcs_map, ic_rec, + &ic_rec->header_.uuid, + runned_az->name, + NULL, chassis_name, true); + } + + icsbrec_service_monitor_index_destroy_row(key); +} + +static void +create_local_ic_svcs_map(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + struct hmap *owned_svc_map) +{ + struct icsbrec_service_monitor *key = + icsbrec_service_monitor_index_init_row( + ctx->icsbrec_service_monitor_by_source_az); + + icsbrec_service_monitor_index_set_source_availability_zone( + key, runned_az->name); + + const struct icsbrec_service_monitor *ic_rec; + ICSBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (ic_rec, key, + ctx->icsbrec_service_monitor_by_source_az) { + create_service_monitor_info(owned_svc_map, ic_rec, + &ic_rec->header_.uuid, + runned_az->name, NULL, + NULL, true); + } + + icsbrec_service_monitor_index_destroy_row(key); +} + +static void +create_local_sb_svcs_map(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + struct hmap *owned_svc_map) +{ + struct sbrec_service_monitor *key = + sbrec_service_monitor_index_init_row( + ctx->sbrec_service_monitor_by_ic_learned); + + sbrec_service_monitor_index_set_ic_learned( + key, true); + + const struct sbrec_service_monitor *sb_rec; + SBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (sb_rec, key, + ctx->sbrec_service_monitor_by_ic_learned) { + create_service_monitor_info(owned_svc_map, sb_rec, + &sb_rec->header_.uuid, + runned_az->name, NULL, + NULL, false); + } + + sbrec_service_monitor_index_destroy_row(key); +} + +static const struct sbrec_service_monitor * +lookup_sb_svc_rec(struct ic_context *ctx, + const struct service_monitor_info *svc_mon) +{ + const struct icsbrec_service_monitor *db_rec = + svc_mon->db_rec.ic_rec; + struct sbrec_service_monitor *key = + sbrec_service_monitor_index_init_row( + ctx->sbrec_service_monitor_by_remote_type_logical_port); + + sbrec_service_monitor_index_set_remote(key, false); + sbrec_service_monitor_index_set_logical_port(key, db_rec->logical_port); + + const struct sbrec_service_monitor *sb_rec; + SBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (sb_rec, key, + ctx->sbrec_service_monitor_by_remote_type_logical_port) { + if (db_rec->port == sb_rec->port && + ((db_rec->type && sb_rec->type && + !strcmp(db_rec->type, sb_rec->type)) || + (!db_rec->type && !sb_rec->type)) && + !strcmp(db_rec->ip, sb_rec->ip) && + !strcmp(db_rec->src_ip, sb_rec->src_ip) && + !strcmp(db_rec->protocol, sb_rec->protocol)) { + sbrec_service_monitor_index_destroy_row(key); + return sb_rec; + } + } + + sbrec_service_monitor_index_destroy_row(key); + + return NULL; +} + +static const struct icsbrec_service_monitor * +lookup_icsb_svc_rec(struct ic_context *ctx, + const struct service_monitor_info *svc_mon) +{ + const struct sbrec_service_monitor *db_rec = + svc_mon->db_rec.sb_rec; + struct icsbrec_service_monitor *key = + icsbrec_service_monitor_index_init_row( + ctx->icsbrec_service_monitor_by_target_az_logical_port); + + ovs_assert(svc_mon->dst_az_name); + icsbrec_service_monitor_index_set_target_availability_zone( + key, svc_mon->dst_az_name); + + icsbrec_service_monitor_index_set_logical_port( + key, db_rec->logical_port); + + const struct icsbrec_service_monitor *ic_rec; + ICSBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (ic_rec, key, + ctx->icsbrec_service_monitor_by_target_az_logical_port) { + if (db_rec->port == ic_rec->port && + ((db_rec->type && ic_rec->type && + !strcmp(db_rec->type, ic_rec->type)) || + (!db_rec->type && !ic_rec->type)) && + !strcmp(db_rec->ip, ic_rec->ip) && + !strcmp(db_rec->src_ip, ic_rec->src_ip) && + !strcmp(db_rec->protocol, ic_rec->protocol) && + !strcmp(db_rec->logical_port, ic_rec->logical_port)) { + icsbrec_service_monitor_index_destroy_row(key); + return ic_rec; + } + } + + icsbrec_service_monitor_index_destroy_row(key); + + return NULL; +} + +static void +create_service_monitor_data(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + struct sync_service_monitor_data *sync_data) +{ + const struct sbrec_sb_global *ic_sb = sbrec_sb_global_first( + ctx->ovnsb_idl); + const char *svc_monitor_mac = smap_get(&ic_sb->options, + "svc_monitor_mac"); + + if (!svc_monitor_mac) { + return; + } + + sync_data->prpg_svc_monitor_mac = xstrdup(svc_monitor_mac); + create_pushed_svcs_mon(ctx, runned_az, &sync_data->pushed_svcs_map); + create_synced_svcs_mon(ctx, runned_az, &sync_data->synced_svcs_map); + create_local_ic_svcs_map(ctx, runned_az, &sync_data->local_ic_svcs_map); + create_local_sb_svcs_map(ctx, runned_az, &sync_data->local_sb_svcs_map); +} + +static void +destroy_service_monitor_data(struct sync_service_monitor_data *sync_data) +{ + struct service_monitor_info *svc_mon; + HMAP_FOR_EACH_SAFE (svc_mon, hmap_node, &sync_data->pushed_svcs_map) { + destroy_service_monitor_info(svc_mon); + } + + HMAP_FOR_EACH_SAFE (svc_mon, hmap_node, &sync_data->synced_svcs_map) { + destroy_service_monitor_info(svc_mon); + } + + hmap_destroy(&sync_data->pushed_svcs_map); + hmap_destroy(&sync_data->synced_svcs_map); + free(sync_data->prpg_svc_monitor_mac); +} + +static void +sync_service_monitor(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az) +{ + if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) { + return; + } + + struct sync_service_monitor_data sync_data; + memset(&sync_data, 0, sizeof(sync_data)); + hmap_init(&sync_data.pushed_svcs_map); + hmap_init(&sync_data.synced_svcs_map); + hmap_init(&sync_data.local_ic_svcs_map); + hmap_init(&sync_data.local_sb_svcs_map); + + create_service_monitor_data(ctx, runned_az, &sync_data); + + struct service_monitor_info *svc_mon; + HMAP_FOR_EACH_SAFE (svc_mon, hmap_node, &sync_data.pushed_svcs_map) { + const struct sbrec_service_monitor *db_rec = svc_mon->db_rec.sb_rec; + const struct icsbrec_service_monitor *ic_rec = + lookup_icsb_svc_rec(ctx, svc_mon); + + if (ic_rec) { + sbrec_service_monitor_set_status(db_rec, ic_rec->status); + } else { + ic_rec = icsbrec_service_monitor_insert(ctx->ovnisb_unlocked_txn); + icsbrec_service_monitor_set_type(ic_rec, db_rec->type); + icsbrec_service_monitor_set_ip(ic_rec, db_rec->ip); + icsbrec_service_monitor_set_port(ic_rec, db_rec->port); + icsbrec_service_monitor_set_src_ip(ic_rec, db_rec->src_ip); + icsbrec_service_monitor_set_src_mac(ic_rec, + sync_data.prpg_svc_monitor_mac); + icsbrec_service_monitor_set_protocol(ic_rec, db_rec->protocol); + icsbrec_service_monitor_set_logical_port(ic_rec, + db_rec->logical_port); + icsbrec_service_monitor_set_target_availability_zone(ic_rec, + svc_mon->dst_az_name); + icsbrec_service_monitor_set_source_availability_zone(ic_rec, + svc_mon->src_az_name); + } + + /* Always update options because they change from NB. */ + icsbrec_service_monitor_set_options(ic_rec, &db_rec->options); + refresh_ic_record_cache(&sync_data.local_ic_svcs_map, ic_rec); + } + + HMAP_FOR_EACH_SAFE (svc_mon, hmap_node, &sync_data.synced_svcs_map) { + const struct icsbrec_service_monitor *db_rec = + svc_mon->db_rec.ic_rec; + const struct sbrec_service_monitor *sb_rec = + lookup_sb_svc_rec(ctx, svc_mon); + + if (sb_rec) { + icsbrec_service_monitor_set_status(svc_mon->db_rec.ic_rec, + sb_rec->status); + } else { + sb_rec = sbrec_service_monitor_insert(ctx->ovnsb_txn); + sbrec_service_monitor_set_type(sb_rec, db_rec->type); + sbrec_service_monitor_set_ip(sb_rec, db_rec->ip); + sbrec_service_monitor_set_port(sb_rec, db_rec->port); + sbrec_service_monitor_set_src_ip(sb_rec, db_rec->src_ip); + /* Set svc_monitor_mac from local SBDB. */ + sbrec_service_monitor_set_src_mac(sb_rec, + sync_data.prpg_svc_monitor_mac); + sbrec_service_monitor_set_protocol(sb_rec, + db_rec->protocol); + sbrec_service_monitor_set_logical_port(sb_rec, + db_rec->logical_port); + sbrec_service_monitor_set_remote(sb_rec, false); + sbrec_service_monitor_set_ic_learned(sb_rec, true); + } + + /* Only update if ic owns it */ + if (sb_rec->ic_learned) { + /* Always update options since they may change via + * NB configuration. Also update chassis_name if + * the port has been reassigned to a different chassis. + */ + if (svc_mon->chassis_name) { + sbrec_service_monitor_set_chassis_name(sb_rec, + svc_mon->chassis_name); + } + sbrec_service_monitor_set_options(sb_rec, &db_rec->options); + refresh_sb_record_cache(&sync_data.local_sb_svcs_map, sb_rec); + } + } + + /* Delete local created records that are no longer used. */ + remove_unused_ic_records(&sync_data.local_ic_svcs_map); + remove_unused_sb_records(&sync_data.local_sb_svcs_map); + + destroy_service_monitor_data(&sync_data); +} + enum engine_node_state en_service_monitor_run(struct engine_node *node, void *data OVS_UNUSED) @@ -41,6 +479,66 @@ en_service_monitor_run(struct engine_node *node, return EN_UPDATED; } +/* True if 'logical_port' backs a service monitor targeting this AZ - i.e. a + * port whose 'up'/'chassis' state create_synced_svcs_mon() reads. */ +static bool +svc_monitor_target_lport(struct ic_context *ctx, + const struct icsbrec_availability_zone *runned_az, + const char *logical_port) +{ + struct icsbrec_service_monitor *key = + icsbrec_service_monitor_index_init_row( + ctx->icsbrec_service_monitor_by_target_az_logical_port); + icsbrec_service_monitor_index_set_target_availability_zone( + key, runned_az->name); + icsbrec_service_monitor_index_set_logical_port(key, logical_port); + const struct icsbrec_service_monitor *ic_rec = + icsbrec_service_monitor_index_find( + ctx->icsbrec_service_monitor_by_target_az_logical_port, key); + icsbrec_service_monitor_index_destroy_row(key); + return ic_rec != NULL; +} + +/* SB Port_Binding: sync_service_monitor() reads a port binding only through + * create_synced_svcs_mon(), which - for each IC-SB service monitor targeting + * this AZ - looks up the backing SB port binding by logical_port and reads its + * 'up' and 'chassis'. So a port-binding change matters only when it is + * new/deleted or its 'up'/'chassis' changed AND the port backs such a service + * monitor. Every other SB port-binding change (other columns, or ports that + * are not service-monitor backends - the vast majority of SB churn) is a + * no-op, instead of forcing a full recompute of this node. */ +enum engine_input_handler_result +en_service_monitor_sb_port_binding_handler(struct engine_node *node, + void *data OVS_UNUSED) +{ + const struct engine_context *eng_ctx = engine_get_context(); + struct ic_context *ctx = eng_ctx->client_ctx; + const struct ed_type_az *az = engine_get_input_data("az", node); + + if (!az->runned_az) { + return EN_HANDLED_UNCHANGED; + } + + const struct sbrec_port_binding_table *tbl = + EN_OVSDB_GET(engine_get_input("SB_port_binding", node)); + const struct sbrec_port_binding *pb; + SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb, tbl) { + if (!sbrec_port_binding_is_new(pb) && + !sbrec_port_binding_is_deleted(pb) && + !ovsdb_idl_track_is_updated(&pb->header_, + &sbrec_port_binding_col_up) && + !ovsdb_idl_track_is_updated(&pb->header_, + &sbrec_port_binding_col_chassis)) { + continue; + } + if (svc_monitor_target_lport(ctx, az->runned_az, pb->logical_port)) { + return EN_UNHANDLED; + } + } + + return EN_HANDLED_UNCHANGED; +} + void * en_service_monitor_init(struct engine_node *node OVS_UNUSED, struct engine_arg *arg OVS_UNUSED) diff --git a/ic/en-service-monitor.h b/ic/en-service-monitor.h index c0d6f0f0b..8b57d995f 100644 --- a/ic/en-service-monitor.h +++ b/ic/en-service-monitor.h @@ -23,4 +23,8 @@ void *en_service_monitor_init(struct engine_node *node, struct engine_arg *arg); void en_service_monitor_cleanup(void *data); +enum engine_input_handler_result +en_service_monitor_sb_port_binding_handler(struct engine_node *node, + void *data); + #endif /* EN_IC_SERVICE_MONITOR_H */ diff --git a/ic/inc-proc-ic.c b/ic/inc-proc-ic.c index 92b368153..af0060ae5 100644 --- a/ic/inc-proc-ic.c +++ b/ic/inc-proc-ic.c @@ -118,12 +118,14 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_ic); ICNB_NODES #undef ICNB_NODE -/* Note: the ic_sb_global table is intentionally not modeled as an engine input - * node. It only carries IC-SB sequence numbers, which are written by - * update_sequence_numbers() in the main loop (outside the engine) and are not - * read by any subsystem node. */ +/* Note: the ic_sb_global and availability_zone tables are intentionally not + * modeled as engine input nodes. ic_sb_global only carries IC-SB sequence + * numbers, written by update_sequence_numbers() in the main loop (outside the + * engine). availability_zone is consumed by the en_az node, which reads it + * directly every iteration; the subsystem nodes depend on en_az for the AZ + * identity rather than on the (sequence-number-bumped) Availability_Zone + * table. */ #define ICSB_NODES \ - ICSB_NODE(availability_zone, "availability_zone") \ ICSB_NODE(service_monitor, "service_monitor") \ ICSB_NODE(route, "route") \ ICSB_NODE(datapath_binding, "datapath_binding") \ @@ -351,13 +353,23 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_route, &en_sb_learned_route, route_sb_learned_route_handler); - /* en_service_monitor: sync load-balancer health checks across AZs. */ + /* en_service_monitor: sync load-balancer health checks across AZs. + * + * Like the other AZ-scoped nodes it uses only the AZ identity + * (en_az's resolved AZ name and the by-source/target-AZ indexes) + * and does not read the Availability_Zone table, so it does not depend on + * en_icsb_availability_zone and is not churned by its nb_ic_cfg sequence + * number. */ engine_add_input(&en_service_monitor, &en_az, NULL); - engine_add_input(&en_service_monitor, &en_icsb_availability_zone, NULL); engine_add_input(&en_service_monitor, &en_icsb_service_monitor, NULL); engine_add_input(&en_service_monitor, &en_sb_sb_global, NULL); engine_add_input(&en_service_monitor, &en_sb_service_monitor, NULL); - engine_add_input(&en_service_monitor, &en_sb_port_binding, NULL); + /* SB port bindings are the busiest table in the AZ, but the sync reads + * only the 'up'/'chassis' of ports backing a service monitor targeting + * this AZ, so a dedicated handler scopes that churn out instead of forcing + * a full recompute on every port-binding change. */ + engine_add_input(&en_service_monitor, &en_sb_port_binding, + en_service_monitor_sb_port_binding_handler); /* en_address_set: advertise/learn address sets across AZs. * @@ -365,9 +377,11 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, * address sets into local/remote by comparing their availability_zone * against this instance's AZ, so it depends on en_az (which reports * EN_UPDATED only when the AZ identity changes) to be re-run when the - * resolved AZ changes. */ + * resolved AZ changes. It uses only that AZ identity and does not read + * the Availability_Zone table, so it does not depend on + * en_icsb_availability_zone and is not churned by its nb_ic_cfg sequence + * number. */ engine_add_input(&en_address_set, &en_az, NULL); - engine_add_input(&en_address_set, &en_icsb_availability_zone, NULL); engine_add_input(&en_address_set, &en_nb_nb_global, NULL); engine_add_input(&en_address_set, &en_nb_address_set, NULL); engine_add_input(&en_address_set, &en_sb_address_set, NULL); diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c index a73a30723..6730c4c79 100644 --- a/ic/ovn-ic.c +++ b/ic/ovn-ic.c @@ -595,447 +595,6 @@ port_binding_collect_lsp_ts(const struct shash *lsp_ts_map, } } -/* - * Data structures and functions related to - * synchronize health checks for load balancers - * between availability zones. - */ -struct sync_service_monitor_data { - /* Map of service monitors to be pushed to other AZs. */ - struct hmap pushed_svcs_map; - /* Map of service monitors synced from other AZs to our. */ - struct hmap synced_svcs_map; - /* Map of local service monitors in the ICSBDB. */ - struct hmap local_ic_svcs_map; - /* Map of local service monitors in SBDB. */ - struct hmap local_sb_svcs_map; - /* MAC address used for service monitor. */ - char *prpg_svc_monitor_mac; -}; - -struct service_monitor_info { - struct hmap_node hmap_node; - union { - const struct sbrec_service_monitor *sb_rec; - const struct icsbrec_service_monitor *ic_rec; - } db_rec; - /* Destination availability zone name. */ - char *dst_az_name; - /* Source availability zone name. */ - char *src_az_name; - /* Chassis name associated with monitor logical port. */ - char *chassis_name; -}; - -static void -create_service_monitor_info(struct hmap *svc_map, - const void *db_rec, - const struct uuid *uuid, - const char *src_az_name, - const char *target_az_name, - const char *chassis_name, - bool ic_rec) -{ - struct service_monitor_info *svc_mon = xzalloc(sizeof(*svc_mon)); - size_t hash = uuid_hash(uuid); - - if (ic_rec) { - svc_mon->db_rec.ic_rec = - (const struct icsbrec_service_monitor *) db_rec; - } else { - svc_mon->db_rec.sb_rec = - (const struct sbrec_service_monitor *) db_rec; - } - - svc_mon->dst_az_name = target_az_name ? xstrdup(target_az_name) : NULL; - svc_mon->chassis_name = chassis_name ? xstrdup(chassis_name) : NULL; - svc_mon->src_az_name = xstrdup(src_az_name); - - hmap_insert(svc_map, &svc_mon->hmap_node, hash); -} - -static void -destroy_service_monitor_info(struct service_monitor_info *svc_mon) -{ - free(svc_mon->src_az_name); - free(svc_mon->dst_az_name); - free(svc_mon->chassis_name); - free(svc_mon); -} - -static void -refresh_sb_record_cache(struct hmap *svc_mon_map, - const struct sbrec_service_monitor *lookup_rec) -{ - size_t hash = uuid_hash(&lookup_rec->header_.uuid); - struct service_monitor_info *svc_mon; - - HMAP_FOR_EACH_WITH_HASH (svc_mon, hmap_node, hash, svc_mon_map) { - ovs_assert(svc_mon->db_rec.sb_rec); - if (svc_mon->db_rec.sb_rec == lookup_rec) { - hmap_remove(svc_mon_map, &svc_mon->hmap_node); - destroy_service_monitor_info(svc_mon); - return; - } - } -} - -static void -refresh_ic_record_cache(struct hmap *svc_mon_map, - const struct icsbrec_service_monitor *lookup_rec) -{ - size_t hash = uuid_hash(&lookup_rec->header_.uuid); - struct service_monitor_info *svc_mon; - - HMAP_FOR_EACH_WITH_HASH (svc_mon, hmap_node, hash, svc_mon_map) { - ovs_assert(svc_mon->db_rec.ic_rec); - if (svc_mon->db_rec.ic_rec == lookup_rec) { - hmap_remove(svc_mon_map, &svc_mon->hmap_node); - destroy_service_monitor_info(svc_mon); - return; - } - } -} - -static void -remove_unused_ic_records(struct hmap *local_ic_svcs_map) -{ - struct service_monitor_info *svc_mon; - HMAP_FOR_EACH_SAFE (svc_mon, hmap_node, local_ic_svcs_map) { - icsbrec_service_monitor_delete(svc_mon->db_rec.ic_rec); - destroy_service_monitor_info(svc_mon); - } - - hmap_destroy(local_ic_svcs_map); -} - -static void -remove_unused_sb_records(struct hmap *local_sb_svcs_map) -{ - struct service_monitor_info *svc_mon; - HMAP_FOR_EACH_SAFE (svc_mon, hmap_node, local_sb_svcs_map) { - sbrec_service_monitor_delete(svc_mon->db_rec.sb_rec); - destroy_service_monitor_info(svc_mon); - } - - hmap_destroy(local_sb_svcs_map); -} - -static void -create_pushed_svcs_mon(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az, - struct hmap *pushed_svcs_map) -{ - struct sbrec_service_monitor *key = - sbrec_service_monitor_index_init_row( - ctx->sbrec_service_monitor_by_remote_type); - - sbrec_service_monitor_index_set_remote(key, true); - - const struct sbrec_service_monitor *sb_rec; - SBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (sb_rec, key, - ctx->sbrec_service_monitor_by_remote_type) { - const char *target_az_name = smap_get(&sb_rec->options, - "az-name"); - if (!target_az_name) { - continue; - } - create_service_monitor_info(pushed_svcs_map, sb_rec, - &sb_rec->header_.uuid, - runned_az->name, - target_az_name, NULL, false); - } - - sbrec_service_monitor_index_destroy_row(key); -} - -static void -create_synced_svcs_mon(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az, - struct hmap *synced_svcs_map) -{ - struct icsbrec_service_monitor *key = - icsbrec_service_monitor_index_init_row( - ctx->icsbrec_service_monitor_by_target_az); - - icsbrec_service_monitor_index_set_target_availability_zone( - key, runned_az->name); - - const struct icsbrec_service_monitor *ic_rec; - ICSBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (ic_rec, key, - ctx->icsbrec_service_monitor_by_target_az) { - - const struct sbrec_port_binding *pb = - find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, - ic_rec->logical_port); - - if (!pb || !pb->up) { - continue; - } - - const char *chassis_name = pb->chassis ? pb->chassis->name : NULL; - create_service_monitor_info(synced_svcs_map, ic_rec, - &ic_rec->header_.uuid, - runned_az->name, - NULL, chassis_name, true); - } - - icsbrec_service_monitor_index_destroy_row(key); -} - -static void -create_local_ic_svcs_map(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az, - struct hmap *owned_svc_map) -{ - struct icsbrec_service_monitor *key = - icsbrec_service_monitor_index_init_row( - ctx->icsbrec_service_monitor_by_source_az); - - icsbrec_service_monitor_index_set_source_availability_zone( - key, runned_az->name); - - const struct icsbrec_service_monitor *ic_rec; - ICSBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (ic_rec, key, - ctx->icsbrec_service_monitor_by_source_az) { - create_service_monitor_info(owned_svc_map, ic_rec, - &ic_rec->header_.uuid, - runned_az->name, NULL, - NULL, true); - } - - icsbrec_service_monitor_index_destroy_row(key); -} - -static void -create_local_sb_svcs_map(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az, - struct hmap *owned_svc_map) -{ - struct sbrec_service_monitor *key = - sbrec_service_monitor_index_init_row( - ctx->sbrec_service_monitor_by_ic_learned); - - sbrec_service_monitor_index_set_ic_learned( - key, true); - - const struct sbrec_service_monitor *sb_rec; - SBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (sb_rec, key, - ctx->sbrec_service_monitor_by_ic_learned) { - create_service_monitor_info(owned_svc_map, sb_rec, - &sb_rec->header_.uuid, - runned_az->name, NULL, - NULL, false); - } - - sbrec_service_monitor_index_destroy_row(key); -} - -static const struct sbrec_service_monitor * -lookup_sb_svc_rec(struct ic_context *ctx, - const struct service_monitor_info *svc_mon) -{ - const struct icsbrec_service_monitor *db_rec = - svc_mon->db_rec.ic_rec; - struct sbrec_service_monitor *key = - sbrec_service_monitor_index_init_row( - ctx->sbrec_service_monitor_by_remote_type_logical_port); - - sbrec_service_monitor_index_set_remote(key, false); - sbrec_service_monitor_index_set_logical_port(key, db_rec->logical_port); - - const struct sbrec_service_monitor *sb_rec; - SBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (sb_rec, key, - ctx->sbrec_service_monitor_by_remote_type_logical_port) { - if (db_rec->port == sb_rec->port && - ((db_rec->type && sb_rec->type && - !strcmp(db_rec->type, sb_rec->type)) || - (!db_rec->type && !sb_rec->type)) && - !strcmp(db_rec->ip, sb_rec->ip) && - !strcmp(db_rec->src_ip, sb_rec->src_ip) && - !strcmp(db_rec->protocol, sb_rec->protocol)) { - sbrec_service_monitor_index_destroy_row(key); - return sb_rec; - } - } - - sbrec_service_monitor_index_destroy_row(key); - - return NULL; -} - -static const struct icsbrec_service_monitor * -lookup_icsb_svc_rec(struct ic_context *ctx, - const struct service_monitor_info *svc_mon) -{ - const struct sbrec_service_monitor *db_rec = - svc_mon->db_rec.sb_rec; - struct icsbrec_service_monitor *key = - icsbrec_service_monitor_index_init_row( - ctx->icsbrec_service_monitor_by_target_az_logical_port); - - ovs_assert(svc_mon->dst_az_name); - icsbrec_service_monitor_index_set_target_availability_zone( - key, svc_mon->dst_az_name); - - icsbrec_service_monitor_index_set_logical_port( - key, db_rec->logical_port); - - const struct icsbrec_service_monitor *ic_rec; - ICSBREC_SERVICE_MONITOR_FOR_EACH_EQUAL (ic_rec, key, - ctx->icsbrec_service_monitor_by_target_az_logical_port) { - if (db_rec->port == ic_rec->port && - ((db_rec->type && ic_rec->type && - !strcmp(db_rec->type, ic_rec->type)) || - (!db_rec->type && !ic_rec->type)) && - !strcmp(db_rec->ip, ic_rec->ip) && - !strcmp(db_rec->src_ip, ic_rec->src_ip) && - !strcmp(db_rec->protocol, ic_rec->protocol) && - !strcmp(db_rec->logical_port, ic_rec->logical_port)) { - icsbrec_service_monitor_index_destroy_row(key); - return ic_rec; - } - } - - icsbrec_service_monitor_index_destroy_row(key); - - return NULL; -} - -static void -create_service_monitor_data(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az, - struct sync_service_monitor_data *sync_data) -{ - const struct sbrec_sb_global *ic_sb = sbrec_sb_global_first( - ctx->ovnsb_idl); - const char *svc_monitor_mac = smap_get(&ic_sb->options, - "svc_monitor_mac"); - - if (!svc_monitor_mac) { - return; - } - - sync_data->prpg_svc_monitor_mac = xstrdup(svc_monitor_mac); - create_pushed_svcs_mon(ctx, runned_az, &sync_data->pushed_svcs_map); - create_synced_svcs_mon(ctx, runned_az, &sync_data->synced_svcs_map); - create_local_ic_svcs_map(ctx, runned_az, &sync_data->local_ic_svcs_map); - create_local_sb_svcs_map(ctx, runned_az, - &sync_data->local_sb_svcs_map); -} - -static void -destroy_service_monitor_data(struct sync_service_monitor_data *sync_data) -{ - struct service_monitor_info *svc_mon; - HMAP_FOR_EACH_SAFE (svc_mon, hmap_node, &sync_data->pushed_svcs_map) { - destroy_service_monitor_info(svc_mon); - } - - HMAP_FOR_EACH_SAFE (svc_mon, hmap_node, &sync_data->synced_svcs_map) { - destroy_service_monitor_info(svc_mon); - } - - hmap_destroy(&sync_data->pushed_svcs_map); - hmap_destroy(&sync_data->synced_svcs_map); - free(sync_data->prpg_svc_monitor_mac); -} - -void -sync_service_monitor(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az) -{ - if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) { - return; - } - - struct sync_service_monitor_data sync_data; - memset(&sync_data, 0, sizeof(sync_data)); - hmap_init(&sync_data.pushed_svcs_map); - hmap_init(&sync_data.synced_svcs_map); - hmap_init(&sync_data.local_ic_svcs_map); - hmap_init(&sync_data.local_sb_svcs_map); - - create_service_monitor_data(ctx, runned_az, &sync_data); - - struct service_monitor_info *svc_mon; - HMAP_FOR_EACH_SAFE (svc_mon, hmap_node, &sync_data.pushed_svcs_map) { - const struct sbrec_service_monitor *db_rec = svc_mon->db_rec.sb_rec; - const struct icsbrec_service_monitor *ic_rec = - lookup_icsb_svc_rec(ctx, svc_mon); - - if (ic_rec) { - sbrec_service_monitor_set_status(db_rec, ic_rec->status); - } else { - ic_rec = icsbrec_service_monitor_insert(ctx->ovnisb_unlocked_txn); - icsbrec_service_monitor_set_type(ic_rec, db_rec->type); - icsbrec_service_monitor_set_ip(ic_rec, db_rec->ip); - icsbrec_service_monitor_set_port(ic_rec, db_rec->port); - icsbrec_service_monitor_set_src_ip(ic_rec, db_rec->src_ip); - icsbrec_service_monitor_set_src_mac(ic_rec, - sync_data.prpg_svc_monitor_mac); - icsbrec_service_monitor_set_protocol(ic_rec, db_rec->protocol); - icsbrec_service_monitor_set_logical_port(ic_rec, - db_rec->logical_port); - icsbrec_service_monitor_set_target_availability_zone(ic_rec, - svc_mon->dst_az_name); - icsbrec_service_monitor_set_source_availability_zone(ic_rec, - svc_mon->src_az_name); - } - - /* Always update options because they change from NB. */ - icsbrec_service_monitor_set_options(ic_rec, &db_rec->options); - refresh_ic_record_cache(&sync_data.local_ic_svcs_map, ic_rec); - } - - HMAP_FOR_EACH_SAFE (svc_mon, hmap_node, &sync_data.synced_svcs_map) { - const struct icsbrec_service_monitor *db_rec = - svc_mon->db_rec.ic_rec; - const struct sbrec_service_monitor *sb_rec = - lookup_sb_svc_rec(ctx, svc_mon); - - if (sb_rec) { - icsbrec_service_monitor_set_status(svc_mon->db_rec.ic_rec, - sb_rec->status); - } else { - sb_rec = sbrec_service_monitor_insert(ctx->ovnsb_txn); - sbrec_service_monitor_set_type(sb_rec, db_rec->type); - sbrec_service_monitor_set_ip(sb_rec, db_rec->ip); - sbrec_service_monitor_set_port(sb_rec, db_rec->port); - sbrec_service_monitor_set_src_ip(sb_rec, db_rec->src_ip); - /* Set svc_monitor_mac from local SBDB. */ - sbrec_service_monitor_set_src_mac(sb_rec, - sync_data.prpg_svc_monitor_mac); - sbrec_service_monitor_set_protocol(sb_rec, - db_rec->protocol); - sbrec_service_monitor_set_logical_port(sb_rec, - db_rec->logical_port); - sbrec_service_monitor_set_remote(sb_rec, false); - sbrec_service_monitor_set_ic_learned(sb_rec, true); - } - - /* Only update if ic owns it */ - if (sb_rec->ic_learned) { - /* Always update options since they may change via - * NB configuration. Also update chassis_name if - * the port has been reassigned to a different chassis. - */ - if (svc_mon->chassis_name) { - sbrec_service_monitor_set_chassis_name(sb_rec, - svc_mon->chassis_name); - } - sbrec_service_monitor_set_options(sb_rec, &db_rec->options); - refresh_sb_record_cache(&sync_data.local_sb_svcs_map, sb_rec); - } - } - - /* Delete local created records that are no longer used. */ - remove_unused_ic_records(&sync_data.local_ic_svcs_map); - remove_unused_sb_records(&sync_data.local_sb_svcs_map); - - destroy_service_monitor_data(&sync_data); -} - /* * This function implements a sequence number protocol that can be used by * the INB end user to verify that ISB is synced with all the changes that diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h index 69ffa34d4..8adb17ad7 100644 --- a/ic/ovn-ic.h +++ b/ic/ovn-ic.h @@ -101,8 +101,6 @@ void port_binding_lsp_ts_map_init(struct ic_context *ctx, struct shash *map); void port_binding_collect_lsp_ts(const struct shash *lsp_ts_map, const struct nbrec_logical_switch_port *lsp, struct sset *ts_scope); -void sync_service_monitor(struct ic_context *ctx, - const struct icsbrec_availability_zone *runned_az); /* Shared IC helpers used by more than one engine node. */ uint32_t diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at index 7f557f171..6d0734b1a 100644 --- a/tests/ovn-ic.at +++ b/tests/ovn-ic.at @@ -1012,6 +1012,84 @@ OVN_CLEANUP_IC([az1]) AT_CLEANUP ]) +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn-ic -- incremental processing - port binding, route, svc monitor]) + +# The en_port_binding, en_route and en_service_monitor nodes use only the AZ +# identity, not the Availability_Zone table, so a change to IC-NB Global +# options and the resulting nb_ic_cfg sequence-number bump must not recompute +# them. + +ovn_init_ic_db +net_add n1 +ovn_start az1 +as az1 +check ovn-ic-nbctl --wait=sb ts-add ts1 +OVS_WAIT_UNTIL([test "$(ovn-nbctl --bare --columns=name \ + find logical_switch name=ts1)" = ts1]) + +check ovn-ic-nbctl --wait=sb sync +check ovn-appctl -t ic/ovn-ic inc-engine/recompute +check ovn-ic-nbctl --wait=sb sync +check ovn-appctl -t ic/ovn-ic inc-engine/clear-stats + +check ovn-ic-nbctl --wait=sb set IC_NB_Global . options:foo=bar +check ovn-ic-nbctl --wait=sb sync + +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats port_binding recompute)" = 0]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats route recompute)" = 0]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats service_monitor recompute)" = 0]) + +OVN_CLEANUP_IC([az1]) +AT_CLEANUP +]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn-ic -- incremental processing - service monitor SB port binding scoping]) + +# SB Port_Binding is the busiest table in the AZ, but the en_service_monitor +# sync reads only the 'up'/'chassis' of the port backing a service monitor +# targeting this AZ. A port binding that backs no such service monitor must be +# handled by the en_service_monitor SB Port_Binding handler without a full +# recompute of the node. + +ovn_init_ic_db +net_add n1 +ovn_start az1 +as az1 +check ovn-ic-nbctl --wait=sb ts-add ts1 +check ovn-nbctl ls-add sw0 +check ovn-nbctl --wait=sb lsp-add sw0 sp0 +OVS_WAIT_UNTIL([test -n "$(ovn-sbctl --bare --columns=_uuid \ + find port_binding logical_port=sp0)"]) + +# Quiesce the engine and reset stats so only the change under test counts. +check ovn-ic-nbctl --wait=sb sync +check ovn-appctl -t ic/ovn-ic inc-engine/recompute +check ovn-ic-nbctl --wait=sb sync +check ovn-appctl -t ic/ovn-ic inc-engine/clear-stats + +# Flip the SB port binding 'up' of a port that backs no service monitor, +# directly in the SB (so SB_Global is not bumped). This is a watched column, +# but the port is not a service-monitor target, so en_service_monitor must not +# recompute. +check ovn-sbctl set port_binding sp0 up=true +OVS_WAIT_UNTIL([test "$(ovn-sbctl --bare --columns=up \ + find port_binding logical_port=sp0)" = true]) +check ovn-ic-nbctl --wait=sb sync + +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats service_monitor compute)" -gt 0]) +AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \ + inc-engine/show-stats service_monitor recompute)" = 0]) + +OVN_CLEANUP_IC([az1]) +AT_CLEANUP +]) + OVN_FOR_EACH_NORTHD([ AT_SETUP([ovn-ic -- incremental processing - route handlers])