Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.2/patches/2225962/?format=api
{ "id": 2225962, "url": "http://patchwork.ozlabs.org/api/1.2/patches/2225962/?format=api", "web_url": "http://patchwork.ozlabs.org/project/ovn/patch/20260421211209.1974247-1-lucas.vdias@luizalabs.com/", "project": { "id": 68, "url": "http://patchwork.ozlabs.org/api/1.2/projects/68/?format=api", "name": "Open Virtual Network development", "link_name": "ovn", "list_id": "ovs-dev.openvswitch.org", "list_email": "ovs-dev@openvswitch.org", "web_url": "http://openvswitch.org/", "scm_url": "", "webscm_url": "", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20260421211209.1974247-1-lucas.vdias@luizalabs.com>", "list_archive_url": null, "date": "2026-04-21T21:12:09", "name": "[ovs-dev,v2,2/2] northd: Incremental processing for static routes.", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "3d45d1b4e6e1c271646716170355e8cd02081e3e", "submitter": { "id": 90169, "url": "http://patchwork.ozlabs.org/api/1.2/people/90169/?format=api", "name": "Lucas Vargas Dias", "email": "lucas.vdias@luizalabs.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/ovn/patch/20260421211209.1974247-1-lucas.vdias@luizalabs.com/mbox/", "series": [ { "id": 500896, "url": "http://patchwork.ozlabs.org/api/1.2/series/500896/?format=api", "web_url": "http://patchwork.ozlabs.org/project/ovn/list/?series=500896", "date": "2026-04-21T21:11:55", "name": "[ovs-dev,v2,1/2] northd: Use uuid hash from source of parsed route.", "version": 2, "mbox": "http://patchwork.ozlabs.org/series/500896/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2225962/comments/", "check": "fail", "checks": "http://patchwork.ozlabs.org/api/patches/2225962/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<ovs-dev-bounces@openvswitch.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "dev@openvswitch.org" ], "Delivered-To": [ "patchwork-incoming@legolas.ozlabs.org", "ovs-dev@lists.linuxfoundation.org" ], "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n unprotected) header.d=luizalabs.com header.i=@luizalabs.com\n header.a=rsa-sha256 header.s=google header.b=OhPC8Qfo;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=2605:bc80:3010::133; helo=smtp2.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)", "smtp2.osuosl.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key)\n header.d=luizalabs.com header.i=@luizalabs.com header.a=rsa-sha256\n header.s=google header.b=OhPC8Qfo", "smtp3.osuosl.org; dmarc=pass (p=quarantine dis=none)\n header.from=luizalabs.com", "smtp3.osuosl.org;\n dkim=pass (1024-bit key) header.d=luizalabs.com header.i=@luizalabs.com\n header.a=rsa-sha256 header.s=google header.b=OhPC8Qfo" ], "Received": [ "from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g0ZnF4Q3kz1yGs\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 22 Apr 2026 07:12:33 +1000 (AEST)", "from localhost (localhost [127.0.0.1])\n\tby smtp2.osuosl.org (Postfix) with ESMTP id D2C25420E5;\n\tTue, 21 Apr 2026 21:12:31 +0000 (UTC)", "from smtp2.osuosl.org ([127.0.0.1])\n by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id s57NDLNWxyBi; Tue, 21 Apr 2026 21:12:29 +0000 (UTC)", "from lists.linuxfoundation.org (lf-lists.osuosl.org\n [IPv6:2605:bc80:3010:104::8cd3:938])\n\tby smtp2.osuosl.org (Postfix) with ESMTPS id 0D44D420E4;\n\tTue, 21 Apr 2026 21:12:29 +0000 (UTC)", "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id D4EAAC058E;\n\tTue, 21 Apr 2026 21:12:28 +0000 (UTC)", "from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136])\n by lists.linuxfoundation.org (Postfix) with ESMTP id A77A6C058D\n for <dev@openvswitch.org>; Tue, 21 Apr 2026 21:12:27 +0000 (UTC)", "from localhost (localhost [127.0.0.1])\n by smtp3.osuosl.org (Postfix) with ESMTP id 12A8A61564\n for <dev@openvswitch.org>; Tue, 21 Apr 2026 21:12:20 +0000 (UTC)", "from smtp3.osuosl.org ([127.0.0.1])\n by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id nUuEvtB8H3TG for <dev@openvswitch.org>;\n Tue, 21 Apr 2026 21:12:18 +0000 (UTC)", "from mail-dy1-x132d.google.com (mail-dy1-x132d.google.com\n [IPv6:2607:f8b0:4864:20::132d])\n by smtp3.osuosl.org (Postfix) with ESMTPS id 1E8F661562\n for <dev@openvswitch.org>; Tue, 21 Apr 2026 21:12:17 +0000 (UTC)", "by mail-dy1-x132d.google.com with SMTP id\n 5a478bee46e88-2b4520f6b32so6141716eec.0\n for <dev@openvswitch.org>; Tue, 21 Apr 2026 14:12:17 -0700 (PDT)", "from WNEC-73GS814.. ([186.237.124.211])\n by smtp.gmail.com with ESMTPSA id\n 5a478bee46e88-2e79c2954f6sm18455763eec.30.2026.04.21.14.12.14\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Tue, 21 Apr 2026 14:12:16 -0700 (PDT)" ], "X-Virus-Scanned": [ "amavis at osuosl.org", "amavis at osuosl.org" ], "X-Comment": "SPF check N/A for local connections -\n client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=<UNKNOWN> ", "DKIM-Filter": [ "OpenDKIM Filter v2.11.0 smtp2.osuosl.org 0D44D420E4", "OpenDKIM Filter v2.11.0 smtp3.osuosl.org 1E8F661562" ], "Received-SPF": "Pass (mailfrom) identity=mailfrom;\n client-ip=2607:f8b0:4864:20::132d; helo=mail-dy1-x132d.google.com;\n envelope-from=lucas.vdias@luizalabs.com; receiver=<UNKNOWN>", "DMARC-Filter": "OpenDMARC Filter v1.4.2 smtp3.osuosl.org 1E8F661562", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=luizalabs.com; s=google; t=1776805937; x=1777410737; darn=openvswitch.org;\n h=content-transfer-encoding:mime-version:message-id:date:subject:cc\n :to:from:from:to:cc:subject:date:message-id:reply-to;\n bh=W7WnPWWPbWInQRHFhHdYb/cTd4DXts6Oc26GrK/C5cA=;\n b=OhPC8QfoA9oeOb/yCCOxqLHk/dx46qg1Qmpy9Evy8w5SgiPO4nOPZcUVRZ0OBkK8gX\n JUA109goTdJe9FwkBBYLKeRMMzuGKyM6dQBprfXe5zN0UjgsCS3xWuayY3UifiSFrQ1z\n mSjwbaS3yaCK+NEK2ohmylTbs7qli+Uj1YY5E=", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1776805937; x=1777410737;\n h=content-transfer-encoding:mime-version:message-id:date:subject:cc\n :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date\n :message-id:reply-to;\n bh=W7WnPWWPbWInQRHFhHdYb/cTd4DXts6Oc26GrK/C5cA=;\n b=Gjv/hk1TBqpiecb/oiX0TtA8kIhO3BoTYBNk1gYSdGuW//YqO/qpWO36UXQ35V/D6P\n B6mqQZK1/XRlzsVhXYQZ82H3x/r73T9wWCnwU6jGnO9ylMIlu1YxFJ4ShcACCZvfKPcd\n 9OydvHCwT+yBGiBEN8oM+S1MbrvbTYQMNzf9z1RR3PKOrCruZEK/ZHy3AgE3vbCnKEot\n wtPz2JFdNCkEnuSb9FpOTyvsEBNsyD/x/tNywfiNbOhm24cuGqFh68WICvoVW8FVuI1S\n MSlMBzyBKrQFktA9AjnqVtUunFcKyK3ZE4qDDQvMhN2s+6bBOEC+mwXtUH5G1I29QYT4\n 3JaA==", "X-Gm-Message-State": "AOJu0YxVclcyUOMOYdz+Ye6igQMxBKbH/dgd16pUHRZn5gpnDht6nwph\n E/YxBSsmukqQ6PJncTr6sRU1FE2ib/S6QOrVNlgIQBHcJuiFdzkvxbZYqIAnd/KHDO65QaCOXBQ\n GbbTnPLSsP7/5bcwA9GSA/0UsD1S9V+bJbY0MKRyHlxsRVWWnrxkDkKFXg6j6", "X-Gm-Gg": "AeBDievOK9hq0bu5geE3FvJUDBD9B/wcVBEuNsLxD/WQFem1HGEkZP8jbCcel2fxfMu\n B6XEX4zTR0/K+X+rRpucnP0v1DAUrmk0wejgFbNBmk5lXGnnTvZAiQp7dJvIp1NlEnAHDxz7Jow\n J/EUsaN2JfltHaV4xJjZZvulbGdElLRlA1XozRFpLTE2f5xn+iQY6WQK2uB7Dj5ipHdep03+UcX\n +MHkfsJxp+QN5uqdmKIbTYC2s7bWacyDs9i96GRRbxJvOmu6U+snGo8vIrPgo59ziG7X9c6jCy+\n sNbSTRI+7seA1vRcOtiTY3IpcOOyGDz2ca7RrRe1ZyvvnRPrdJoN4WTb3ChVQ62a7e9mRA7lLpU\n sTXSxVbKnavztf5+JK+n4RRWLuqLHKAoucv8fbPwGk/KNMQricshhy7unv4i5Fv+Bh/h/Yjeqdo\n s6o2pdTUuDdvD85hnrBRiwVdgaYJzEmyyQbujr3ABgT5ewQ7k=", "X-Received": "by 2002:a05:7300:3211:b0:2dd:db16:478e with SMTP id\n 5a478bee46e88-2e4654880bemr9729916eec.10.1776805936376;\n Tue, 21 Apr 2026 14:12:16 -0700 (PDT)", "To": "dev@openvswitch.org", "Date": "Tue, 21 Apr 2026 18:12:09 -0300", "Message-ID": "<20260421211209.1974247-1-lucas.vdias@luizalabs.com>", "X-Mailer": "git-send-email 2.43.0", "MIME-Version": "1.0", "Subject": "[ovs-dev] [PATCH ovn v2 2/2] northd: Incremental processing for\n static routes.", "X-BeenThere": "ovs-dev@openvswitch.org", "X-Mailman-Version": "2.1.30", "Precedence": "list", "List-Id": "<ovs-dev.openvswitch.org>", "List-Unsubscribe": "<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>", "List-Archive": "<http://mail.openvswitch.org/pipermail/ovs-dev/>", "List-Post": "<mailto:ovs-dev@openvswitch.org>", "List-Help": "<mailto:ovs-dev-request@openvswitch.org?subject=help>", "List-Subscribe": "<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=subscribe>", "From": "Lucas Vargas Dias via dev <ovs-dev@openvswitch.org>", "Reply-To": "Lucas Vargas Dias <lucas.vdias@luizalabs.com>", "Content-Type": "text/plain; charset=\"iso-8859-1\"", "Content-Transfer-Encoding": "quoted-printable", "Errors-To": "ovs-dev-bounces@openvswitch.org", "Sender": "\"dev\" <ovs-dev-bounces@openvswitch.org>" }, "content": "Create a handler for deleted and updated static routes,\nand change the handler from engine route which check if\nit has a new static route created.\nTest with 2000 static routes created in the same logical router\nand add a new one:\nWithout the incremental processing:\novn-nbctl --print-wait-time --wait=sb lr-route-add lr1-2 10.0.0.1/32 192.168.20.2\nTime spent on processing nb_cfg 4:\n\tovn-northd delay before processing:\t4ms\n\tovn-northd completion:\t\t\t62ms\n\nWith the incremental processing:\novn-nbctl --print-wait-time --wait=sb lr-route-add lr1-2 10.0.0.1/32 192.168.20.2\nTime spent on processing nb_cfg 6:\n\tovn-northd delay before processing:\t4ms\n\tovn-northd completion:\t\t\t21ms\n\nTest with 2000 static routes created in the same logical router\nand delete one:\nWithout the incremental processing:\novn-nbctl --print-wait-time --wait=sb lr-route-del lr1-2 10.0.0.1/32 192.168.20.2\nTime spent on processing nb_cfg 5:\n\tovn-northd delay before processing:\t3ms\n\tovn-northd completion:\t\t\t62ms\n\nWith the incremental processing:\novn-nbctl --print-wait-time --wait=sb lr-route-del lr1-2 10.0.0.1/32 192.168.20.2\nTime spent on processing nb_cfg 9:\n\tovn-northd delay before processing:\t2ms\n\tovn-northd completion:\t\t\t32ms\n\nSigned-off-by: Lucas Vargas Dias <lucas.vdias@luizalabs.com>\n---\n northd/en-group-ecmp-route.c | 57 ++++++++++\n northd/en-group-ecmp-route.h | 4 +\n northd/en-lflow.c | 20 ++++\n northd/en-lflow.h | 2 +\n northd/en-northd.c | 186 +++++++++++++++++++++++++++----\n northd/en-northd.h | 5 +-\n northd/inc-proc-northd.c | 13 ++-\n northd/northd.c | 107 +++++++++++++-----\n northd/northd.h | 38 ++++++-\n tests/ovn-inc-proc-graph-dump.at | 6 +-\n tests/ovn-northd.at | 98 +++++++++++++---\n 11 files changed, 466 insertions(+), 70 deletions(-)", "diff": "diff --git a/northd/en-group-ecmp-route.c b/northd/en-group-ecmp-route.c\nindex c4c93fd84..fcc76b076 100644\n--- a/northd/en-group-ecmp-route.c\n+++ b/northd/en-group-ecmp-route.c\n@@ -519,3 +519,60 @@ group_ecmp_route_learned_route_change_handler(struct engine_node *eng_node,\n }\n return EN_HANDLED_UNCHANGED;\n }\n+\n+enum engine_input_handler_result\n+group_ecmp_static_route_change_handler(struct engine_node *eng_node,\n+ void *_data)\n+{\n+ struct routes_data *routes_data\n+ = engine_get_input_data(\"routes\", eng_node);\n+ struct group_ecmp_route_data *data = _data;\n+ if (!routes_data->tracked) {\n+ data->tracked = false;\n+ return EN_UNHANDLED;\n+ }\n+\n+ struct parsed_route *pr;\n+ struct hmapx updated_routes = HMAPX_INITIALIZER(&updated_routes);\n+\n+ const struct hmapx_node *hmapx_node;\n+ HMAPX_FOR_EACH (hmapx_node,\n+ &routes_data->trk_data.trk_deleted_parsed_route) {\n+ pr = hmapx_node->data;\n+ if (!handle_deleted_route(data, pr, &updated_routes)) {\n+ hmapx_destroy(&updated_routes);\n+ return EN_UNHANDLED;\n+ }\n+\n+ if (pr->is_in_parsed_routes) {\n+ hmap_remove(&routes_data->parsed_routes, &pr->key_node);\n+ }\n+ parsed_route_free(pr);\n+ }\n+\n+ HMAPX_FOR_EACH (hmapx_node,\n+ &routes_data->trk_data.trk_crupdated_parsed_route) {\n+ pr = hmapx_node->data;\n+ handle_added_route(data, pr, &updated_routes);\n+ }\n+\n+ HMAPX_FOR_EACH (hmapx_node, &updated_routes) {\n+ struct group_ecmp_datapath *node = hmapx_node->data;\n+ if (hmap_is_empty(&node->unique_routes) &&\n+ hmap_is_empty(&node->ecmp_groups)) {\n+ hmapx_add(&data->trk_data.deleted_datapath_routes, node);\n+ hmap_remove(&data->datapaths, &node->hmap_node);\n+ } else {\n+ hmapx_add(&data->trk_data.crupdated_datapath_routes, node);\n+ }\n+ }\n+\n+ hmapx_destroy(&updated_routes);\n+\n+ if (!hmapx_is_empty(&data->trk_data.crupdated_datapath_routes) ||\n+ !hmapx_is_empty(&data->trk_data.deleted_datapath_routes)) {\n+ data->tracked = true;\n+ return EN_HANDLED_UPDATED;\n+ }\n+ return EN_HANDLED_UNCHANGED;\n+}\ndiff --git a/northd/en-group-ecmp-route.h b/northd/en-group-ecmp-route.h\nindex d4a3248d0..246ca06bf 100644\n--- a/northd/en-group-ecmp-route.h\n+++ b/northd/en-group-ecmp-route.h\n@@ -98,6 +98,10 @@ enum engine_input_handler_result\n group_ecmp_route_learned_route_change_handler(struct engine_node *,\n void *data);\n \n+enum engine_input_handler_result\n+group_ecmp_static_route_change_handler(struct engine_node *,\n+ void *data);\n+\n struct group_ecmp_datapath *group_ecmp_datapath_lookup(\n const struct group_ecmp_route_data *data,\n const struct ovn_datapath *od);\ndiff --git a/northd/en-lflow.c b/northd/en-lflow.c\nindex d4351edb9..22cd8fe91 100644\n--- a/northd/en-lflow.c\n+++ b/northd/en-lflow.c\n@@ -297,6 +297,21 @@ lflow_multicast_igmp_handler(struct engine_node *node, void *data)\n return EN_HANDLED_UPDATED;\n }\n \n+enum engine_input_handler_result\n+lflow_group_route_change_handler(struct engine_node *node,\n+ void *data OVS_UNUSED)\n+{\n+ struct routes_data *route_data =\n+ engine_get_input_data(\"routes\", node);\n+\n+ /* If we do not have tracked data we need to recompute. */\n+ if (!route_data->tracked) {\n+ return EN_UNHANDLED;\n+ }\n+\n+ return EN_HANDLED_UNCHANGED;\n+}\n+\n enum engine_input_handler_result\n lflow_group_ecmp_route_change_handler(struct engine_node *node,\n void *data OVS_UNUSED)\n@@ -346,6 +361,11 @@ lflow_group_ecmp_route_change_handler(struct engine_node *node,\n route_node->od, lflow_data->lflow_table,\n route_node, lflow_input.bfd_ports);\n \n+ build_arp_request_flows_for_lrouter(route_node->od,\n+ lflow_data->lflow_table,\n+ lflow_input.meter_groups,\n+ route_node->lflow_ref);\n+\n bool handled = lflow_ref_sync_lflows(\n route_node->lflow_ref, lflow_data->lflow_table,\n eng_ctx->ovnsb_idl_txn, lflow_input.dps,\ndiff --git a/northd/en-lflow.h b/northd/en-lflow.h\nindex d2a92e49f..aa320615f 100644\n--- a/northd/en-lflow.h\n+++ b/northd/en-lflow.h\n@@ -31,5 +31,7 @@ lflow_multicast_igmp_handler(struct engine_node *node, void *data);\n enum engine_input_handler_result\n lflow_group_ecmp_route_change_handler(struct engine_node *node, void *data);\n enum engine_input_handler_result\n+lflow_group_route_change_handler(struct engine_node *node, void *data);\n+enum engine_input_handler_result\n lflow_ic_learned_svc_mons_handler(struct engine_node *node, void *data);\n #endif /* EN_LFLOW_H */\ndiff --git a/northd/en-northd.c b/northd/en-northd.c\nindex c34818dba..c05939a1d 100644\n--- a/northd/en-northd.c\n+++ b/northd/en-northd.c\n@@ -207,7 +207,8 @@ northd_nb_logical_router_handler(struct engine_node *node,\n }\n \n if (northd_has_lr_nats_in_tracked_data(&nd->trk_data) ||\n- northd_has_lrouters_in_tracked_data(&nd->trk_data)) {\n+ northd_has_lrouters_in_tracked_data(&nd->trk_data) ||\n+ northd_has_lr_route_in_tracked_data(&nd->trk_data)) {\n return EN_HANDLED_UPDATED;\n }\n \n@@ -329,32 +330,174 @@ en_route_policies_run(struct engine_node *node, void *data)\n \n enum engine_input_handler_result\n routes_northd_change_handler(struct engine_node *node,\n- void *data OVS_UNUSED)\n+ void *data)\n {\n struct northd_data *northd_data = engine_get_input_data(\"northd\", node);\n if (!northd_has_tracked_data(&northd_data->trk_data)) {\n return EN_UNHANDLED;\n }\n \n- /* This node uses the below data from the en_northd engine node.\n- * See (lr_stateful_get_input_data())\n- * 1. northd_data->lr_datapaths\n- * 2. northd_data->lr_ports\n- * This data gets updated when a logical router or logical router port\n- * is created or deleted.\n- * Northd engine node presently falls back to full recompute when\n- * this happens and so does this node.\n- * Note: When we add I-P to the created/deleted logical routers or\n- * logical router ports, we need to revisit this handler.\n- *\n- * This node also accesses the static routes of the logical router.\n- * When these static routes gets updated, en_northd engine recomputes\n- * and so does this node.\n- * Note: When we add I-P to handle static routes changes, we need\n- * to revisit this handler.\n- */\n+ if (!northd_has_lr_route_in_tracked_data(&northd_data->trk_data)) {\n+ return EN_HANDLED_UNCHANGED;\n+ }\n+\n+ struct bfd_data *bfd_data = engine_get_input_data(\"bfd\", node);\n+ struct routes_data *routes_data = data;\n+ struct hmapx_node *hmapx_node;\n+ struct ovn_datapath *od;\n+ HMAPX_FOR_EACH (hmapx_node, &northd_data->trk_data.trk_lrs_routes) {\n+ od = hmapx_node->data;\n+ struct parsed_route *pr;\n+\n+ for (int i = 0; i < od->nbr->n_static_routes; i++) {\n+ struct nbrec_logical_router_static_route *static_route =\n+ od->nbr->static_routes[i];\n+ pr = parsed_route_lookup_by_source(ROUTE_SOURCE_STATIC,\n+ &static_route->header_,\n+ &routes_data->parsed_routes);\n+ if (pr) {\n+ pr->stale = false;\n+ continue;\n+ }\n+ pr = parsed_routes_add_static(od, &northd_data->lr_ports,\n+ static_route,\n+ &bfd_data->bfd_connections,\n+ &routes_data->parsed_routes,\n+ &routes_data->route_tables,\n+ &routes_data->bfd_active_connections);\n+ if (!pr) {\n+ continue;\n+ }\n+ hmapx_add(&routes_data->trk_data.trk_crupdated_parsed_route,\n+ pr);\n+ }\n+ }\n+\n+ if (!hmapx_is_empty(&routes_data->trk_data.trk_crupdated_parsed_route)) {\n+ routes_data->tracked = true;\n+ return EN_HANDLED_UPDATED;\n+ }\n+\n return EN_HANDLED_UNCHANGED;\n }\n+enum engine_input_handler_result\n+routes_static_route_change_handler(struct engine_node *node,\n+ void *data)\n+{\n+ struct routes_data *routes_data = data;\n+ struct hmapx created_trk_parsed_route =\n+ HMAPX_INITIALIZER(&created_trk_parsed_route);\n+ const struct nbrec_logical_router_static_route_table *\n+ nb_lr_static_route_table =\n+ EN_OVSDB_GET(engine_get_input(\"NB_logical_router_static_route\", node));\n+\n+ struct northd_data *northd_data = engine_get_input_data(\"northd\", node);\n+ struct bfd_data *bfd_data = engine_get_input_data(\"bfd\", node);\n+\n+ const struct nbrec_logical_router_static_route *changed_static_route;\n+ NBREC_LOGICAL_ROUTER_STATIC_ROUTE_TABLE_FOR_EACH_TRACKED (\n+ changed_static_route, nb_lr_static_route_table) {\n+\n+ bool is_deleted = nbrec_logical_router_static_route_is_deleted(\n+ changed_static_route);\n+ bool is_new = nbrec_logical_router_static_route_is_new(\n+ changed_static_route);\n+\n+ if (is_new && is_deleted) {\n+ continue;\n+ }\n+\n+ if (is_new) {\n+ hmapx_add(&created_trk_parsed_route, &changed_static_route);\n+ continue;\n+ }\n+\n+ if (is_deleted) {\n+ struct parsed_route *pr = parsed_route_lookup_by_source(\n+ ROUTE_SOURCE_STATIC,\n+ &changed_static_route->header_,\n+ &routes_data->parsed_routes);\n+ if (!pr) {\n+ pr = parsed_route_lookup_by_source(ROUTE_SOURCE_IC_DYNAMIC,\n+ &changed_static_route->header_,\n+ &routes_data->parsed_routes);\n+ }\n+ if (pr) {\n+ pr->stale = true;\n+ hmapx_add(&routes_data->trk_data.trk_deleted_parsed_route, pr);\n+ }\n+ continue;\n+ }\n+\n+ if (nbrec_logical_router_static_route_is_updated(\n+ changed_static_route,\n+ NBREC_LOGICAL_ROUTER_STATIC_ROUTE_COL_NEXTHOP)\n+ || nbrec_logical_router_static_route_is_updated(\n+ changed_static_route,\n+ NBREC_LOGICAL_ROUTER_STATIC_ROUTE_COL_IP_PREFIX)\n+ || nbrec_logical_router_static_route_is_updated(\n+ changed_static_route,\n+ NBREC_LOGICAL_ROUTER_STATIC_ROUTE_COL_OUTPUT_PORT)\n+ || nbrec_logical_router_static_route_is_updated(\n+ changed_static_route,\n+ NBREC_LOGICAL_ROUTER_STATIC_ROUTE_COL_POLICY)\n+ || nbrec_logical_router_static_route_is_updated(\n+ changed_static_route,\n+ NBREC_LOGICAL_ROUTER_STATIC_ROUTE_COL_ROUTE_TABLE)\n+ || nbrec_logical_router_static_route_is_updated(\n+ changed_static_route,\n+ NBREC_LOGICAL_ROUTER_STATIC_ROUTE_COL_SELECTION_FIELDS)\n+ || nbrec_logical_router_static_route_is_updated(\n+ changed_static_route,\n+ NBREC_LOGICAL_ROUTER_STATIC_ROUTE_COL_OPTIONS)) {\n+ struct parsed_route *pr = parsed_route_lookup_by_source(\n+ ROUTE_SOURCE_STATIC,\n+ &changed_static_route->header_,\n+ &routes_data->parsed_routes);\n+ if (!pr) {\n+ pr = parsed_route_lookup_by_source(\n+ ROUTE_SOURCE_IC_DYNAMIC,\n+ &changed_static_route->header_,\n+ &routes_data->parsed_routes);\n+ }\n+\n+ if (!pr || !pr->od || !northd_data || !bfd_data) {\n+ continue;\n+ }\n+ struct parsed_route *old_pr = pr;\n+ hmap_remove(&routes_data->parsed_routes, &old_pr->key_node);\n+ pr = parsed_routes_add_static(old_pr->od, &northd_data->lr_ports,\n+ changed_static_route,\n+ &bfd_data->bfd_connections,\n+ &routes_data->parsed_routes,\n+ &routes_data->route_tables,\n+ &routes_data->bfd_active_connections);\n+ old_pr->is_in_parsed_routes = false;\n+ if (!pr) {\n+ continue;\n+ }\n+\n+ hmapx_add(&routes_data->trk_data.trk_crupdated_parsed_route,\n+ pr);\n+ hmapx_add(&routes_data->trk_data.trk_deleted_parsed_route,\n+ old_pr);\n+ }\n+ }\n+ if (!hmapx_is_empty(&routes_data->trk_data.trk_crupdated_parsed_route) ||\n+ !hmapx_is_empty(&routes_data->trk_data.trk_deleted_parsed_route)) {\n+ hmapx_destroy(&created_trk_parsed_route);\n+ routes_data->tracked = true;\n+ return EN_HANDLED_UPDATED;\n+ }\n+\n+ if (!hmapx_is_empty(&created_trk_parsed_route)) {\n+ hmapx_destroy(&created_trk_parsed_route);\n+ return EN_HANDLED_UPDATED;\n+ }\n+\n+ hmapx_destroy(&created_trk_parsed_route);\n+ return EN_UNHANDLED;\n+}\n \n enum engine_node_state\n en_routes_run(struct engine_node *node, void *data)\n@@ -590,6 +733,11 @@ en_routes_cleanup(void *data)\n routes_destroy(data);\n }\n \n+void\n+en_routes_clear_tracked_data(void *data)\n+{\n+ routes_clear_tracked(data);\n+}\n void\n en_bfd_cleanup(void *data)\n {\ndiff --git a/northd/en-northd.h b/northd/en-northd.h\nindex 7794739b9..5247f3e11 100644\n--- a/northd/en-northd.h\n+++ b/northd/en-northd.h\n@@ -39,9 +39,12 @@ enum engine_node_state en_route_policies_run(struct engine_node *node,\n void *data);\n void *en_route_policies_init(struct engine_node *node OVS_UNUSED,\n struct engine_arg *arg OVS_UNUSED);\n+void en_routes_clear_tracked_data(void *data);\n void en_routes_cleanup(void *data);\n enum engine_input_handler_result\n-routes_northd_change_handler(struct engine_node *node, void *data OVS_UNUSED);\n+routes_northd_change_handler(struct engine_node *node, void *data);\n+enum engine_input_handler_result\n+routes_static_route_change_handler(struct engine_node *node, void *data);\n enum engine_node_state en_routes_run(struct engine_node *node, void *data);\n void *en_bfd_init(struct engine_node *node OVS_UNUSED,\n struct engine_arg *arg OVS_UNUSED);\ndiff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c\nindex ece388ce7..52f5dc57f 100644\n--- a/northd/inc-proc-northd.c\n+++ b/northd/inc-proc-northd.c\n@@ -76,7 +76,9 @@ static unixctl_cb_func chassis_features_list;\n NB_NODE(sampling_app) \\\n NB_NODE(network_function) \\\n NB_NODE(network_function_group) \\\n- NB_NODE(logical_switch_port_health_check)\n+ NB_NODE(logical_switch_port_health_check) \\\n+ NB_NODE(logical_router_static_route)\n+\n \n enum nb_engine_node {\n #define NB_NODE(NAME) NB_##NAME,\n@@ -179,7 +181,7 @@ static ENGINE_NODE(lr_stateful, CLEAR_TRACKED_DATA);\n static ENGINE_NODE(ls_stateful, CLEAR_TRACKED_DATA);\n static ENGINE_NODE(ls_arp, CLEAR_TRACKED_DATA);\n static ENGINE_NODE(route_policies);\n-static ENGINE_NODE(routes);\n+static ENGINE_NODE(routes, CLEAR_TRACKED_DATA);\n static ENGINE_NODE(bfd);\n static ENGINE_NODE(bfd_sync, SB_WRITE);\n static ENGINE_NODE(ecmp_nexthop, SB_WRITE);\n@@ -341,6 +343,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,\n engine_add_input(&en_routes, &en_bfd, NULL);\n engine_add_input(&en_routes, &en_northd,\n routes_northd_change_handler);\n+ engine_add_input(&en_routes, &en_nb_logical_router_static_route,\n+ routes_static_route_change_handler);\n \n engine_add_input(&en_bfd_sync, &en_bfd, NULL);\n engine_add_input(&en_bfd_sync, &en_nb_bfd, NULL);\n@@ -380,7 +384,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,\n engine_add_input(&en_learned_route_sync, &en_northd,\n learned_route_sync_northd_change_handler);\n \n- engine_add_input(&en_group_ecmp_route, &en_routes, NULL);\n+ engine_add_input(&en_group_ecmp_route, &en_routes,\n+ group_ecmp_static_route_change_handler);\n engine_add_input(&en_group_ecmp_route, &en_learned_route_sync,\n group_ecmp_route_learned_route_change_handler);\n \n@@ -399,7 +404,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,\n engine_add_input(&en_lflow, &en_sb_logical_dp_group, NULL);\n engine_add_input(&en_lflow, &en_bfd_sync, NULL);\n engine_add_input(&en_lflow, &en_route_policies, NULL);\n- engine_add_input(&en_lflow, &en_routes, NULL);\n+ engine_add_input(&en_lflow, &en_routes, lflow_group_route_change_handler);\n /* XXX: The incremental processing only supports changes to learned routes.\n * All other changes trigger a full recompute. */\n engine_add_input(&en_lflow, &en_group_ecmp_route,\ndiff --git a/northd/northd.c b/northd/northd.c\nindex 4fd4b9de9..ea48bc442 100644\n--- a/northd/northd.c\n+++ b/northd/northd.c\n@@ -4517,6 +4517,7 @@ destroy_northd_data_tracked_changes(struct northd_data *nd)\n destroy_tracked_ovn_ports(&trk_changes->trk_lsps);\n destroy_tracked_lbs(&trk_changes->trk_lbs);\n hmapx_clear(&trk_changes->trk_nat_lrs);\n+ hmapx_clear(&trk_changes->trk_lrs_routes);\n hmapx_clear(&trk_changes->ls_with_changed_lbs);\n hmapx_clear(&trk_changes->ls_with_changed_acls);\n hmapx_clear(&trk_changes->ls_with_changed_ipam);\n@@ -4540,6 +4541,7 @@ init_northd_tracked_data(struct northd_data *nd)\n hmapx_init(&trk_data->trk_lbs.crupdated);\n hmapx_init(&trk_data->trk_lbs.deleted);\n hmapx_init(&trk_data->trk_nat_lrs);\n+ hmapx_init(&trk_data->trk_lrs_routes);\n hmapx_init(&trk_data->ls_with_changed_lbs);\n hmapx_init(&trk_data->ls_with_changed_acls);\n hmapx_init(&trk_data->ls_with_changed_ipam);\n@@ -4558,6 +4560,7 @@ destroy_northd_tracked_data(struct northd_data *nd)\n hmapx_destroy(&trk_data->trk_lbs.crupdated);\n hmapx_destroy(&trk_data->trk_lbs.deleted);\n hmapx_destroy(&trk_data->trk_nat_lrs);\n+ hmapx_destroy(&trk_data->trk_lrs_routes);\n hmapx_destroy(&trk_data->ls_with_changed_lbs);\n hmapx_destroy(&trk_data->ls_with_changed_acls);\n hmapx_destroy(&trk_data->ls_with_changed_ipam);\n@@ -5379,7 +5382,8 @@ lr_changes_can_be_handled(const struct nbrec_logical_router *lr)\n if (nbrec_logical_router_is_updated(lr, col)) {\n if (col == NBREC_LOGICAL_ROUTER_COL_LOAD_BALANCER\n || col == NBREC_LOGICAL_ROUTER_COL_LOAD_BALANCER_GROUP\n- || col == NBREC_LOGICAL_ROUTER_COL_NAT) {\n+ || col == NBREC_LOGICAL_ROUTER_COL_NAT\n+ || col == NBREC_LOGICAL_ROUTER_COL_STATIC_ROUTES) {\n continue;\n }\n return false;\n@@ -5404,12 +5408,7 @@ lr_changes_can_be_handled(const struct nbrec_logical_router *lr)\n return false;\n }\n }\n- for (size_t i = 0; i < lr->n_static_routes; i++) {\n- if (nbrec_logical_router_static_route_row_get_seqno(\n- lr->static_routes[i], OVSDB_IDL_CHANGE_MODIFY) > 0) {\n- return false;\n- }\n- }\n+\n return true;\n }\n \n@@ -5435,6 +5434,13 @@ is_lr_nats_changed(const struct nbrec_logical_router *nbr) {\n || is_lr_nats_seqno_changed(nbr));\n }\n \n+static bool\n+is_lr_static_routes_changed(const struct nbrec_logical_router *nbr) {\n+ return nbrec_logical_router_is_updated(nbr,\n+ NBREC_LOGICAL_ROUTER_COL_STATIC_ROUTES);\n+}\n+\n+\n /* Return true if changes are handled incrementally, false otherwise.\n *\n * Note: Changes to load balancer and load balancer groups associated with\n@@ -5503,6 +5509,22 @@ northd_handle_lr_changes(const struct northd_input *ni,\n \n hmapx_add(&nd->trk_data.trk_nat_lrs, od);\n }\n+\n+ /* Static Route was added or deleted. */\n+ if (is_lr_static_routes_changed(changed_lr)) {\n+ struct ovn_datapath *od = ovn_datapath_find_(\n+ &nd->lr_datapaths.datapaths,\n+ &changed_lr->header_.uuid);\n+\n+ if (!od) {\n+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);\n+ VLOG_WARN_RL(&rl, \"Internal error: a tracked updated LR \"\n+ \"doesn't exist in lr_datapaths: \"UUID_FMT,\n+ UUID_ARGS(&changed_lr->header_.uuid));\n+ goto fail;\n+ }\n+ hmapx_add(&nd->trk_data.trk_lrs_routes, od);\n+ }\n }\n \n HMAPX_FOR_EACH (node, &ni->synced_lrs->deleted) {\n@@ -5543,6 +5565,9 @@ northd_handle_lr_changes(const struct northd_input *ni,\n if (!hmapx_is_empty(&nd->trk_data.trk_nat_lrs)) {\n nd->trk_data.type |= NORTHD_TRACKED_LR_NATS;\n }\n+ if (!hmapx_is_empty(&nd->trk_data.trk_lrs_routes)) {\n+ nd->trk_data.type |= NORTHD_TRACKED_LR_ROUTES;\n+ }\n if (!hmapx_is_empty(&nd->trk_data.trk_routers.crupdated) ||\n !hmapx_is_empty(&nd->trk_data.trk_routers.deleted)) {\n nd->trk_data.type |= NORTHD_TRACKED_ROUTERS;\n@@ -12189,6 +12214,7 @@ parsed_route_init(const struct ovn_datapath *od,\n new_pr->route_table_id = route_table_id;\n new_pr->is_src_route = is_src_route;\n new_pr->od = od;\n+ new_pr->is_in_parsed_routes = false;\n new_pr->ecmp_symmetric_reply = ecmp_symmetric_reply;\n new_pr->is_discard_route = is_discard_route;\n new_pr->lrp_addr_s = nullable_xstrdup(lrp_addr_s);\n@@ -12296,6 +12322,7 @@ parsed_route_add(const struct ovn_datapath *od,\n struct parsed_route *pr = parsed_route_lookup(routes, hash, new_pr);\n if (!pr) {\n hmap_insert(routes, &new_pr->key_node, hash);\n+ new_pr->is_in_parsed_routes = true;\n return new_pr;\n } else {\n pr->stale = false;\n@@ -12304,7 +12331,7 @@ parsed_route_add(const struct ovn_datapath *od,\n }\n }\n \n-static void\n+struct parsed_route *\n parsed_routes_add_static(const struct ovn_datapath *od,\n const struct hmap *lr_ports,\n const struct nbrec_logical_router_static_route *route,\n@@ -12325,8 +12352,9 @@ parsed_routes_add_static(const struct ovn_datapath *od,\n UUID_FMT, route->nexthop,\n UUID_ARGS(&route->header_.uuid));\n free(nexthop);\n- return;\n+ return NULL;\n }\n+\n if ((IN6_IS_ADDR_V4MAPPED(nexthop) && plen != 32) ||\n (!IN6_IS_ADDR_V4MAPPED(nexthop) && plen != 128)) {\n static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);\n@@ -12334,7 +12362,7 @@ parsed_routes_add_static(const struct ovn_datapath *od,\n UUID_FMT, route->nexthop,\n UUID_ARGS(&route->header_.uuid));\n free(nexthop);\n- return;\n+ return NULL;\n }\n }\n \n@@ -12346,7 +12374,7 @@ parsed_routes_add_static(const struct ovn_datapath *od,\n UUID_FMT, route->ip_prefix,\n UUID_ARGS(&route->header_.uuid));\n free(nexthop);\n- return;\n+ return NULL;\n }\n \n /* Verify that ip_prefix and nexthop are on the same network. */\n@@ -12358,7 +12386,7 @@ parsed_routes_add_static(const struct ovn_datapath *od,\n : IN6_IS_ADDR_V4MAPPED(&prefix),\n &lrp_addr_s, &out_port)) {\n free(nexthop);\n- return;\n+ return NULL;\n }\n \n const struct nbrec_bfd *nb_bt = route->bfd;\n@@ -12368,7 +12396,7 @@ parsed_routes_add_static(const struct ovn_datapath *od,\n nb_bt->dst_ip);\n if (!bfd_e) {\n free(nexthop);\n- return;\n+ return NULL;\n }\n \n /* This static route is linked to an active bfd session. */\n@@ -12385,10 +12413,9 @@ parsed_routes_add_static(const struct ovn_datapath *od,\n bfd_set_status(bfd_sr, \"down\");\n }\n \n-\n if (!strcmp(bfd_sr->status, \"down\")) {\n- free(nexthop);\n- return;\n+ free(nexthop);\n+ return NULL;\n }\n }\n \n@@ -12427,11 +12454,15 @@ parsed_routes_add_static(const struct ovn_datapath *od,\n source = ROUTE_SOURCE_STATIC;\n }\n \n- parsed_route_add(od, nexthop, &prefix, plen, is_discard_route, lrp_addr_s,\n- out_port, route_table_id, is_src_route,\n- ecmp_symmetric_reply, &ecmp_selection_fields, source,\n- &route->header_, NULL, routes);\n+ struct parsed_route *pr = parsed_route_add(od, nexthop, &prefix, plen,\n+ is_discard_route, lrp_addr_s,\n+ out_port, route_table_id,\n+ is_src_route,\n+ ecmp_symmetric_reply,\n+ &ecmp_selection_fields, source,\n+ &route->header_, NULL, routes);\n sset_destroy(&ecmp_selection_fields);\n+ return pr;\n }\n \n static void\n@@ -16309,13 +16340,14 @@ build_lr_gateway_redirect_flows_for_nats(\n * In the common case where the Ethernet destination has been resolved,\n * this table outputs the packet (priority 0). Otherwise, it composes\n * and sends an ARP/IPv6 NA request (priority 100). */\n-static void\n+void\n build_arp_request_flows_for_lrouter(\n- struct ovn_datapath *od, struct lflow_table *lflows,\n- struct ds *match, struct ds *actions,\n+ const struct ovn_datapath *od, struct lflow_table *lflows,\n const struct shash *meter_groups,\n struct lflow_ref *lflow_ref)\n {\n+ struct ds match = DS_EMPTY_INITIALIZER;\n+ struct ds actions = DS_EMPTY_INITIALIZER;\n ovs_assert(od->nbr);\n for (int i = 0; i < od->nbr->n_static_routes; i++) {\n const struct nbrec_logical_router_static_route *route;\n@@ -16329,8 +16361,8 @@ build_arp_request_flows_for_lrouter(\n continue;\n }\n \n- ds_clear(match);\n- ds_put_format(match, \"eth.dst == 00:00:00:00:00:00 && \"\n+ ds_clear(&match);\n+ ds_put_format(&match, \"eth.dst == 00:00:00:00:00:00 && \"\n REGBIT_NEXTHOP_IS_IPV4\" == 0 && \"\n REG_NEXT_HOP_IPV6 \" == %s\",\n route->nexthop);\n@@ -16342,8 +16374,8 @@ build_arp_request_flows_for_lrouter(\n char sn_addr_s[INET6_ADDRSTRLEN + 1];\n ipv6_string_mapped(sn_addr_s, &sn_addr);\n \n- ds_clear(actions);\n- ds_put_format(actions,\n+ ds_clear(&actions);\n+ ds_put_format(&actions,\n \"nd_ns { \"\n \"eth.dst = \"ETH_ADDR_FMT\"; \"\n \"ip6.dst = %s; \"\n@@ -16353,7 +16385,7 @@ build_arp_request_flows_for_lrouter(\n route->nexthop);\n \n ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 200,\n- ds_cstr(match), ds_cstr(actions), lflow_ref,\n+ ds_cstr(&match), ds_cstr(&actions), lflow_ref,\n WITH_CTRL_METER(copp_meter_get(COPP_ND_NS_RESOLVE,\n od->nbr->copp,\n meter_groups)),\n@@ -16385,6 +16417,8 @@ build_arp_request_flows_for_lrouter(\n meter_groups)));\n ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, \"1\", \"next;\",\n lflow_ref);\n+ ds_destroy(&match);\n+ ds_destroy(&actions);\n }\n \n static void\n@@ -19508,8 +19542,7 @@ build_lswitch_and_lrouter_iterate_by_lr(struct ovn_datapath *od,\n build_gateway_redirect_flows_for_lrouter(od, lsi->lflows, &lsi->match,\n &lsi->actions,\n od->datapath_lflows);\n- build_arp_request_flows_for_lrouter(od, lsi->lflows, &lsi->match,\n- &lsi->actions,\n+ build_arp_request_flows_for_lrouter(od, lsi->lflows,\n lsi->meter_groups,\n od->datapath_lflows);\n build_ecmp_stateful_egr_flows_for_lrouter(od, lsi->lflows,\n@@ -21067,6 +21100,9 @@ routes_init(struct routes_data *data)\n hmap_init(&data->parsed_routes);\n simap_init(&data->route_tables);\n hmap_init(&data->bfd_active_connections);\n+ data->tracked = false;\n+ hmapx_init(&data->trk_data.trk_deleted_parsed_route);\n+ hmapx_init(&data->trk_data.trk_crupdated_parsed_route);\n }\n \n void\n@@ -21197,6 +21233,17 @@ routes_destroy(struct routes_data *data)\n \n simap_destroy(&data->route_tables);\n __bfd_destroy(&data->bfd_active_connections);\n+ data->tracked = false;\n+ hmapx_destroy(&data->trk_data.trk_crupdated_parsed_route);\n+ hmapx_destroy(&data->trk_data.trk_deleted_parsed_route);\n+}\n+\n+void\n+routes_clear_tracked(struct routes_data *data)\n+{\n+ data->tracked = false;\n+ hmapx_clear(&data->trk_data.trk_crupdated_parsed_route);\n+ hmapx_clear(&data->trk_data.trk_deleted_parsed_route);\n }\n \n void\ndiff --git a/northd/northd.h b/northd/northd.h\nindex a9070d6f6..5b3461840 100644\n--- a/northd/northd.h\n+++ b/northd/northd.h\n@@ -160,6 +160,7 @@ enum northd_tracked_data_type {\n NORTHD_TRACKED_LS_ACLS = (1 << 4),\n NORTHD_TRACKED_SWITCHES = (1 << 5),\n NORTHD_TRACKED_ROUTERS = (1 << 6),\n+ NORTHD_TRACKED_LR_ROUTES = (1 << 7),\n };\n \n /* Track what's changed in the northd engine node.\n@@ -177,6 +178,10 @@ struct northd_tracked_data {\n * hmapx node is 'struct ovn_datapath *'. */\n struct hmapx trk_nat_lrs;\n \n+ /* Tracked logical routers whose static routes have changed.\n+ * hmapx node is 'struct ovn_datapath *'. */\n+ struct hmapx trk_lrs_routes;\n+\n /* Tracked logical switches whose load balancers have changed.\n * hmapx node is 'struct ovn_datapath *'. */\n struct hmapx ls_with_changed_lbs;\n@@ -217,10 +222,23 @@ struct route_policy {\n uint32_t jump_chain_id;\n };\n \n+struct route_tracked_data {\n+ /* Contains references to group_ecmp_route_node. Each of the referenced\n+ * datapaths contains at least one route. */\n+ struct hmapx trk_crupdated_parsed_route;\n+\n+ /* Contains references to group_ecmp_route_node. Each of the referenced\n+ * datapath previously had some routes. The datapath now no longer\n+ * contains any route.*/\n+ struct hmapx trk_deleted_parsed_route;\n+};\n+\n struct routes_data {\n struct hmap parsed_routes; /* Stores struct parsed_route. */\n struct simap route_tables;\n struct hmap bfd_active_connections;\n+ bool tracked;\n+ struct route_tracked_data trk_data;\n };\n \n struct route_policies_data {\n@@ -855,6 +873,7 @@ struct parsed_route {\n char *lrp_addr_s;\n const struct ovn_port *out_port;\n const struct ovn_port *tracked_port; /* May be NULL. */\n+ bool is_in_parsed_routes;\n };\n \n struct parsed_route *parsed_route_clone(const struct parsed_route *);\n@@ -881,6 +900,14 @@ struct parsed_route *parsed_route_add(\n const struct ovn_port *tracked_port,\n struct hmap *routes);\n \n+struct parsed_route * parsed_routes_add_static(\n+ const struct ovn_datapath *od,\n+ const struct hmap *lr_ports,\n+ const struct nbrec_logical_router_static_route *route,\n+ const struct hmap *bfd_connections,\n+ struct hmap *routes, struct simap *route_tables,\n+ struct hmap *bfd_active_connections);\n+\n struct svc_monitors_map_data {\n const struct hmap *local_svc_monitors_map;\n const struct hmap *ic_learned_svc_monitors_map;\n@@ -925,7 +952,7 @@ void build_parsed_routes(const struct ovn_datapath *, const struct hmap *,\n uint32_t get_route_table_id(struct simap *, const char *);\n void routes_init(struct routes_data *);\n void routes_destroy(struct routes_data *);\n-\n+void routes_clear_tracked(struct routes_data *);\n void bfd_init(struct bfd_data *);\n void bfd_destroy(struct bfd_data *);\n \n@@ -951,6 +978,10 @@ void build_route_data_flows_for_lrouter(\n const struct ovn_datapath *od, struct lflow_table *lflows,\n const struct group_ecmp_datapath *route_node,\n const struct sset *bfd_ports);\n+void build_arp_request_flows_for_lrouter(\n+ const struct ovn_datapath *od, struct lflow_table *lflows,\n+ const struct shash *meter_groups,\n+ struct lflow_ref *lflow_ref);\n \n bool lflow_handle_northd_lr_changes(struct ovsdb_idl_txn *ovnsh_txn,\n struct tracked_dps *,\n@@ -1041,6 +1072,11 @@ northd_has_lr_nats_in_tracked_data(struct northd_tracked_data *trk_nd_changes)\n {\n return trk_nd_changes->type & NORTHD_TRACKED_LR_NATS;\n }\n+static inline bool\n+northd_has_lr_route_in_tracked_data(struct northd_tracked_data *trk_nd_changes)\n+{\n+ return trk_nd_changes->type & NORTHD_TRACKED_LR_ROUTES;\n+}\n \n static inline bool\n northd_has_ls_lbs_in_tracked_data(struct northd_tracked_data *trk_nd_changes)\ndiff --git a/tests/ovn-inc-proc-graph-dump.at b/tests/ovn-inc-proc-graph-dump.at\nindex 178310978..fd05c20dc 100644\n--- a/tests/ovn-inc-proc-graph-dump.at\n+++ b/tests/ovn-inc-proc-graph-dump.at\n@@ -151,9 +151,11 @@ digraph \"Incremental-Processing-Engine\" {\n \tbfd [[style=filled, shape=box, fillcolor=white, label=\"bfd\"]];\n \tNB_bfd -> bfd [[label=\"\"]];\n \tSB_bfd -> bfd [[label=\"\"]];\n+\tNB_logical_router_static_route [[style=filled, shape=box, fillcolor=white, label=\"NB_logical_router_static_route\"]];\n \troutes [[style=filled, shape=box, fillcolor=white, label=\"routes\"]];\n \tbfd -> routes [[label=\"\"]];\n \tnorthd -> routes [[label=\"routes_northd_change_handler\"]];\n+\tNB_logical_router_static_route -> routes [[label=\"routes_static_route_change_handler\"]];\n \troute_policies [[style=filled, shape=box, fillcolor=white, label=\"route_policies\"]];\n \tbfd -> route_policies [[label=\"\"]];\n \tnorthd -> route_policies [[label=\"route_policies_northd_change_handler\"]];\n@@ -168,7 +170,7 @@ digraph \"Incremental-Processing-Engine\" {\n \tSB_learned_route -> learned_route_sync [[label=\"learned_route_sync_sb_learned_route_change_handler\"]];\n \tnorthd -> learned_route_sync [[label=\"learned_route_sync_northd_change_handler\"]];\n \tgroup_ecmp_route [[style=filled, shape=box, fillcolor=white, label=\"group_ecmp_route\"]];\n-\troutes -> group_ecmp_route [[label=\"\"]];\n+\troutes -> group_ecmp_route [[label=\"group_ecmp_static_route_change_handler\"]];\n \tlearned_route_sync -> group_ecmp_route [[label=\"group_ecmp_route_learned_route_change_handler\"]];\n \tls_stateful [[style=filled, shape=box, fillcolor=white, label=\"ls_stateful\"]];\n \tnorthd -> ls_stateful [[label=\"ls_stateful_northd_handler\"]];\n@@ -189,7 +191,7 @@ digraph \"Incremental-Processing-Engine\" {\n \tSB_logical_dp_group -> lflow [[label=\"\"]];\n \tbfd_sync -> lflow [[label=\"\"]];\n \troute_policies -> lflow [[label=\"\"]];\n-\troutes -> lflow [[label=\"\"]];\n+\troutes -> lflow [[label=\"lflow_group_route_change_handler\"]];\n \tgroup_ecmp_route -> lflow [[label=\"lflow_group_ecmp_route_change_handler\"]];\n \tglobal_config -> lflow [[label=\"node_global_config_handler\"]];\n \tsampling_app -> lflow [[label=\"\"]];\ndiff --git a/tests/ovn-northd.at b/tests/ovn-northd.at\nindex 1d7bd6c28..eacccaf20 100644\n--- a/tests/ovn-northd.at\n+++ b/tests/ovn-northd.at\n@@ -4272,9 +4272,9 @@ check ovn-nbctl --bfd=$uuid lr-route-add r0 100.0.0.0/8 192.168.1.2\n wait_column down bfd status logical_port=r0-sw1\n AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.1.2 | grep -q bfd], [0], [], [ignore])\n \n-check_engine_stats northd recompute nocompute\n+check_engine_stats northd norecompute compute\n check_engine_stats bfd recompute nocompute\n-check_engine_stats routes recompute nocompute\n+check_engine_stats routes recompute incremental\n check_engine_stats lflow recompute nocompute\n check_engine_stats northd_output norecompute compute\n CHECK_NO_CHANGE_AFTER_RECOMPUTE\n@@ -4288,9 +4288,9 @@ check ovn-nbctl --bfd lr-route-add r0 240.0.0.0/8 192.168.5.2 r0-sw5\n wait_column down bfd status logical_port=r0-sw5\n AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.5.2 | grep -q bfd], [0], [], [ignore])\n \n-check_engine_stats northd recompute nocompute\n+check_engine_stats northd norecompute compute\n check_engine_stats bfd recompute nocompute\n-check_engine_stats routes recompute nocompute\n+check_engine_stats routes recompute incremental\n check_engine_stats lflow recompute nocompute\n check_engine_stats northd_output norecompute compute\n CHECK_NO_CHANGE_AFTER_RECOMPUTE\n@@ -4300,7 +4300,7 @@ check ovn-nbctl --bfd --policy=src-ip lr-route-add r0 192.168.6.1/32 192.168.10.\n wait_column down bfd status logical_port=r0-sw6\n AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.6.1 | grep -q bfd], [0], [], [ignore])\n \n-check_engine_stats northd recompute nocompute\n+check_engine_stats northd norecompute compute\n check_engine_stats bfd recompute nocompute\n check_engine_stats route_policies recompute nocompute\n check_engine_stats lflow recompute nocompute\n@@ -4335,10 +4335,10 @@ wait_column down bfd status logical_port=r0-sw8\n bfd_route_policy_uuid=$(fetch_column nb:bfd _uuid logical_port=r0-sw8)\n AT_CHECK([ovn-nbctl list logical_router_policy | grep -q $bfd_route_policy_uuid])\n \n-check_engine_stats northd recompute nocompute\n+check_engine_stats northd recompute incremental\n check_engine_stats bfd recompute nocompute\n-check_engine_stats routes recompute nocompute\n-check_engine_stats lflow recompute nocompute\n+check_engine_stats routes recompute incremental\n+check_engine_stats lflow recompute incremental\n check_engine_stats northd_output norecompute compute\n CHECK_NO_CHANGE_AFTER_RECOMPUTE\n check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats\n@@ -16437,12 +16437,12 @@ CHECK_NO_CHANGE_AFTER_RECOMPUTE\n \n check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats\n check ovn-nbctl --wait=sb lr-route-add lr0 192.168.0.0/24 10.0.0.10\n-check_engine_compute northd recompute\n-check_engine_compute routes recompute\n+check_engine_compute northd incremental\n+check_engine_compute routes incremental\n check_engine_compute advertised_route_sync recompute\n-check_engine_compute learned_route_sync recompute\n-check_engine_compute group_ecmp_route recompute\n-check_engine_compute lflow recompute\n+check_engine_compute learned_route_sync incremental\n+check_engine_compute group_ecmp_route incremental\n+check_engine_compute lflow incremental\n CHECK_NO_CHANGE_AFTER_RECOMPUTE\n \n check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats\n@@ -20672,3 +20672,75 @@ check_column \"$global_svc_mon_mac\" sb:Service_Monitor src_mac port=2\n OVN_CLEANUP_NORTHD\n AT_CLEANUP\n ])\n+\n+OVN_FOR_EACH_NORTHD_NO_HV([\n+AT_SETUP([Static Route incremental processing])\n+ovn_start\n+\n+check ovn-nbctl lr-add r0\n+\n+check ovn-nbctl --wait=sb lrp-add r0 r0-lrp1 00:00:00:00:00:01 192.168.1.1/24\n+\n+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats\n+check ovn-nbctl --wait=sb lr-route-add r0 10.0.0.0/24 192.168.1.2\n+\n+check_engine_compute northd incremental\n+check_engine_compute routes incremental\n+check_engine_compute group_ecmp_route incremental\n+check_engine_compute lflow incremental\n+\n+static_route_uuid=`ovn-nbctl --bare --columns _uuid find Logical_Router_Static_Route nexthop=192.168.1.2`\n+\n+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats\n+check ovn-nbctl --wait=sb set logical_router_static_route $static_route_uuid nexthop=192.168.1.3\n+check_engine_compute northd incremental\n+check_engine_compute routes incremental\n+check_engine_compute group_ecmp_route incremental\n+check_engine_compute lflow incremental\n+\n+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats\n+check ovn-nbctl --wait=sb set logical_router_static_route $static_route_uuid ip_prefix=10.0.1.0/24\n+check_engine_compute northd incremental\n+check_engine_compute routes incremental\n+check_engine_compute group_ecmp_route incremental\n+check_engine_compute lflow incremental\n+\n+check ovn-nbctl --wait=sb lrp-add r0 r0-lrp2 00:00:00:00:00:02 192.168.1.10/24\n+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats\n+check ovn-nbctl --wait=sb set logical_router_static_route $static_route_uuid output_port=r0-lrp2\n+check_engine_compute northd incremental\n+check_engine_compute routes incremental\n+check_engine_compute group_ecmp_route incremental\n+check_engine_compute lflow incremental\n+\n+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats\n+check ovn-nbctl --wait=sb set logical_router_static_route $static_route_uuid policy=src-ip\n+check_engine_compute northd incremental\n+check_engine_compute routes incremental\n+check_engine_compute group_ecmp_route incremental\n+check_engine_compute lflow incremental\n+\n+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats\n+check ovn-nbctl --wait=sb set logical_router_static_route $static_route_uuid selection_fields=\"ip_proto,ip_src,ip_dst\"\n+check_engine_compute northd incremental\n+check_engine_compute routes incremental\n+check_engine_compute group_ecmp_route incremental\n+check_engine_compute lflow incremental\n+\n+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats\n+check ovn-nbctl --wait=sb set logical_router_static_route $static_route_uuid options:ecmp_symmetric_reply=true\n+check_engine_compute northd incremental\n+check_engine_compute routes incremental\n+check_engine_compute group_ecmp_route incremental\n+check_engine_compute lflow incremental\n+\n+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats\n+check ovn-nbctl remove logical_router r0 static_routes $static_route_uuid\n+check_engine_compute northd incremental\n+check_engine_compute routes incremental\n+check_engine_compute group_ecmp_route incremental\n+check_engine_compute lflow incremental\n+\n+OVN_CLEANUP_NORTHD\n+AT_CLEANUP\n+])\n", "prefixes": [ "ovs-dev", "v2", "2/2" ] }