Commit da792b85c ("northd: Track router echo request/reply in the
router port ct zone.") assigned a CT zone to the switch-side patch
port of every router port on a local logical switch.  To keep that
assignment consistent it made ct_zones_runtime_data_handler() fall
back to a full recompute whenever any non-ACL patch port was tracked,
which is far more than the actual dependency: the zone of such a port
is needed if and only if the port is still present in the peer set of
a local logical switch.

Track the peer set instead.  The runtime_data engine node now
maintains a shash of tracked peer ports, keyed by logical port name,
which binding.c fills in whenever a peer relationship is added to or
removed from an already local datapath.  The ct_zones handler
processes those ports through the existing idempotent
ct_zone_handle_port_update(), and for non-ACL patch ports found in
tracked_dp_bindings it reads the need directly from the current peer
set of the port's datapath -- the same condition ct_zones_update()
applies on a full recompute -- so zones of ports that lost their peer
relationship (e.g. a router port that stops being an l3gateway port)
are still released.  Datapaths that become local keep the same
TRACKED_RESOURCE_NEW fallback, which is also required for the NAT
zones of newly local router datapaths.

Assisted-by: GLM 5.3, OpenCode
Signed-off-by: Jacob Tanenbaum <[email protected]>

diff --git a/controller/binding.c b/controller/binding.c
index 3e8114e7e..e0ebe90e9 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -743,7 +743,8 @@ remove_related_lport(const struct sbrec_port_binding *pb,
  */
 static void
 update_ld_peers(const struct sbrec_port_binding *pb,
-                 struct hmap *local_datapaths)
+                 struct hmap *local_datapaths,
+                 struct shash *tracked_peer_ports)
 {
     struct local_datapath *ld =
         get_local_datapath(local_datapaths, pb->datapath->tunnel_key);
@@ -760,7 +761,8 @@ update_ld_peers(const struct sbrec_port_binding *pb,
     enum en_lport_type type = get_lport_type(pb);
     size_t num_peers = vector_len(&ld->peer_ports);
     if (type != LP_PATCH) {
-        remove_local_datapath_peer_port(pb, ld, local_datapaths);
+        remove_local_datapath_peer_port(pb, ld, local_datapaths,
+                                        tracked_peer_ports);
         if (num_peers != vector_len(&ld->peer_ports)) {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
             VLOG_DBG_RL(&rl,
@@ -2039,7 +2041,8 @@ consider_nonvif_lport_(const struct sbrec_port_binding 
*pb,
                     b_ctx_in->sbrec_port_binding_by_datapath,
                     b_ctx_in->sbrec_port_binding_by_name,
                     ld, b_ctx_out->local_datapaths,
-                    b_ctx_out->tracked_dp_bindings);
+                    b_ctx_out->tracked_dp_bindings,
+                    b_ctx_out->tracked_peer_ports);
             }
         }
 
@@ -2073,7 +2076,8 @@ consider_nonvif_lport_(const struct sbrec_port_binding 
*pb,
 
         if (ld) {
             remove_local_datapath_peer_port(pb, ld,
-                                            b_ctx_out->local_datapaths);
+                                            b_ctx_out->local_datapaths,
+                                            b_ctx_out->tracked_peer_ports);
         }
     }
 
@@ -2460,7 +2464,8 @@ remove_pb_from_local_datapath(const struct 
sbrec_port_binding *pb,
     remove_related_lport(pb, b_ctx_out);
     if (!strcmp(pb->type, "patch") ||
         !strcmp(pb->type, "l3gateway")) {
-        remove_local_datapath_peer_port(pb, ld, b_ctx_out->local_datapaths);
+        remove_local_datapath_peer_port(pb, ld, b_ctx_out->local_datapaths,
+                                        b_ctx_out->tracked_peer_ports);
     } else if (!strcmp(pb->type, "localnet")) {
         if (ld->localnet_port && !strcmp(ld->localnet_port->logical_port,
                                          pb->logical_port)) {
@@ -3122,7 +3127,8 @@ consider_patch_port_for_local_datapaths(const struct 
sbrec_port_binding *pb,
                 b_ctx_in->sbrec_port_binding_by_datapath,
                 b_ctx_in->sbrec_port_binding_by_name,
                 ld, b_ctx_out->local_datapaths,
-                b_ctx_out->tracked_dp_bindings);
+                b_ctx_out->tracked_dp_bindings,
+                b_ctx_out->tracked_peer_ports);
         }
     }
 
@@ -3443,7 +3449,8 @@ delete_done:
         }
 
         if (sbrec_port_binding_is_updated(pb, SBREC_PORT_BINDING_COL_TYPE)) {
-            update_ld_peers(pb, b_ctx_out->local_datapaths);
+            update_ld_peers(pb, b_ctx_out->local_datapaths,
+                            b_ctx_out->tracked_peer_ports);
         }
 
         if (!handle_updated_port(b_ctx_in, b_ctx_out, pb)) {
diff --git a/controller/binding.h b/controller/binding.h
index 817c4e296..618506286 100644
--- a/controller/binding.h
+++ b/controller/binding.h
@@ -111,6 +111,11 @@ struct binding_ctx_out {
      * the changed datapaths and port bindings. */
     struct hmap *tracked_dp_bindings;
 
+    /* shash of 'struct tracked_peer_port' (keyed by logical port name)
+     * which the callee fills in when the peer relationship of a
+     * switch-side patch port changes. */
+    struct shash *tracked_peer_ports;
+
     struct if_status_mgr *if_mgr;
 
     struct sset *postponed_ports;
diff --git a/controller/local_data.c b/controller/local_data.c
index ad4b92f1f..38aa982f9 100644
--- a/controller/local_data.c
+++ b/controller/local_data.c
@@ -237,9 +237,13 @@ add_local_datapath_peer_port(
     struct ovsdb_idl_index *sbrec_port_binding_by_name,
     struct local_datapath *ld,
     struct hmap *local_datapaths,
-    struct hmap *tracked_datapaths)
+    struct hmap *tracked_datapaths,
+    struct shash *tracked_peer_ports)
 {
     local_datapath_peer_port_add(ld, pb, peer);
+    if (ld->is_switch) {
+        tracked_peer_port_add(pb, true, tracked_peer_ports);
+    }
 
     struct local_datapath *peer_ld =
         get_local_datapath(local_datapaths,
@@ -254,6 +258,9 @@ add_local_datapath_peer_port(
     }
 
     local_datapath_peer_port_add(peer_ld, peer, pb);
+    if (peer_ld->is_switch) {
+        tracked_peer_port_add(peer, true, tracked_peer_ports);
+    }
 }
 
 void
@@ -275,7 +282,8 @@ local_data_dump_peer_ports(struct hmap *local_datapaths, 
struct ds *peer_ports)
 void
 remove_local_datapath_peer_port(const struct sbrec_port_binding *pb,
                                 struct local_datapath *ld,
-                                struct hmap *local_datapaths)
+                                struct hmap *local_datapaths,
+                                struct shash *tracked_peer_ports)
 {
     size_t i = 0;
     const struct peer_ports *peers;
@@ -297,6 +305,10 @@ remove_local_datapath_peer_port(const struct 
sbrec_port_binding *pb,
         local_datapath_usage += vector_memory_usage(&ld->peer_ports);
     }
 
+    if (ld->is_switch) {
+        tracked_peer_port_add(pb, false, tracked_peer_ports);
+    }
+
     const struct sbrec_port_binding *peer = removed.remote;
     struct local_datapath *peer_ld =
         get_local_datapath(local_datapaths, peer->datapath->tunnel_key);
@@ -304,7 +316,8 @@ remove_local_datapath_peer_port(const struct 
sbrec_port_binding *pb,
         /* Remove the peer port from the peer datapath. The peer
          * datapath also tries to remove its peer lport, but that would
          * be no-op. */
-        remove_local_datapath_peer_port(peer, peer_ld, local_datapaths);
+        remove_local_datapath_peer_port(peer, peer_ld, local_datapaths,
+                                        tracked_peer_ports);
     }
 }
 
@@ -436,6 +449,30 @@ tracked_datapath_lport_add(const struct sbrec_port_binding 
*pb,
     lport->tracked_type = tracked_type;
 }
 
+/* Adds 'pb' to 'tracked_peer_ports' keyed by its logical port name,
+ * with the last write winning if the same logical port was already
+ * tracked (e.g. the peer relationship was added and removed within the
+ * same IDL iteration).  A NULL 'tracked_peer_ports' is allowed, in
+ * which case this is a no-op. */
+void
+tracked_peer_port_add(const struct sbrec_port_binding *pb,
+                      bool zone_needed,
+                      struct shash *tracked_peer_ports)
+{
+    if (!tracked_peer_ports) {
+        return;
+    }
+
+    struct tracked_peer_port *tpp =
+        shash_find_data(tracked_peer_ports, pb->logical_port);
+    if (!tpp) {
+        tpp = xmalloc(sizeof *tpp);
+        shash_add(tracked_peer_ports, pb->logical_port, tpp);
+    }
+    tpp->pb = pb;
+    tpp->zone_needed = zone_needed;
+}
+
 void
 tracked_datapaths_clear(struct hmap *tracked_datapaths)
 {
diff --git a/controller/local_data.h b/controller/local_data.h
index 130ae536a..06a73141c 100644
--- a/controller/local_data.h
+++ b/controller/local_data.h
@@ -101,11 +101,13 @@ void add_local_datapath_peer_port(
     struct ovsdb_idl_index *sbrec_port_binding_by_name,
     struct local_datapath *,
     struct hmap *local_datapaths,
-    struct hmap *tracked_datapaths);
+    struct hmap *tracked_datapaths,
+    struct shash *tracked_peer_ports);
 
 void remove_local_datapath_peer_port(const struct sbrec_port_binding *pb,
                                      struct local_datapath *ld,
-                                     struct hmap *local_datapaths);
+                                     struct hmap *local_datapaths,
+                                     struct shash *tracked_peer_ports);
 
 enum en_tracked_resource_type {
     TRACKED_RESOURCE_NEW,
@@ -138,6 +140,19 @@ void tracked_datapath_lport_add(const struct 
sbrec_port_binding *,
 void tracked_datapaths_clear(struct hmap *tracked_datapaths);
 void tracked_datapaths_destroy(struct hmap *tracked_datapaths);
 
+/* A switch-side router patch port whose CT zone assignment may have
+ * changed: 'zone_needed' is true when the connected logical switch is
+ * (still) a local datapath, false when the peer relationship was
+ * removed. */
+struct tracked_peer_port {
+    const struct sbrec_port_binding *pb;
+    bool zone_needed;
+};
+
+void tracked_peer_port_add(const struct sbrec_port_binding *pb,
+                           bool zone_needed,
+                           struct shash *tracked_peer_ports);
+
 /* Maps from a chassis to the OpenFlow port number of the tunnel that can be
  * used to reach that chassis. */
 struct chassis_tunnel {
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 56c917e64..0858312e2 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -1390,6 +1390,11 @@ struct ed_type_runtime_data {
     bool localnet_learn_fdb_changed;
     struct hmap tracked_dp_bindings;
 
+    /* Tracked peer (switch-side router) patch ports whose CT zone
+     * assignment may have changed.  Only meaningful when 'tracked' is
+     * true. */
+    struct shash tracked_peer_ports;
+
     struct shash local_active_ports_ipv6_pd;
     struct shash local_active_ports_ras;
 
@@ -1479,6 +1484,7 @@ en_runtime_data_clear_tracked_data(void *data_)
 
     tracked_datapaths_destroy(&data->tracked_dp_bindings);
     hmap_init(&data->tracked_dp_bindings);
+    shash_clear_free_data(&data->tracked_peer_ports);
     data->local_lports_changed = false;
     data->tracked = false;
 }
@@ -1503,6 +1509,7 @@ en_runtime_data_init(struct engine_node *node OVS_UNUSED,
 
     /* Init the tracked data. */
     hmap_init(&data->tracked_dp_bindings);
+    shash_init(&data->tracked_peer_ports);
 
     return data;
 }
@@ -1522,6 +1529,7 @@ en_runtime_data_cleanup(void *data)
     local_datapaths_destroy(&rt_data->local_datapaths);
     shash_destroy(&rt_data->local_active_ports_ipv6_pd);
     shash_destroy(&rt_data->local_active_ports_ras);
+    shash_destroy_free_data(&rt_data->tracked_peer_ports);
     local_binding_data_destroy(&rt_data->lbinding_data);
 }
 
@@ -1616,6 +1624,7 @@ init_binding_ctx(struct engine_node *node,
     b_ctx_out->local_iface_ids = &rt_data->local_iface_ids;
     b_ctx_out->postponed_ports = rt_data->postponed_ports;
     b_ctx_out->tracked_dp_bindings = NULL;
+    b_ctx_out->tracked_peer_ports = NULL;
     b_ctx_out->if_mgr = ctrl_ctx->if_mgr;
     b_ctx_out->localnet_learn_fdb = rt_data->localnet_learn_fdb;
     b_ctx_out->localnet_learn_fdb_changed = false;
@@ -1839,6 +1848,7 @@ runtime_data_ovs_interface_shadow_handler(struct 
engine_node *node, void *data)
     init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
     rt_data->tracked = true;
     b_ctx_out.tracked_dp_bindings = &rt_data->tracked_dp_bindings;
+    b_ctx_out.tracked_peer_ports = &rt_data->tracked_peer_ports;
 
     if (!binding_handle_ovs_interface_changes(&b_ctx_in, &b_ctx_out)) {
         return EN_UNHANDLED;
@@ -1991,6 +2001,7 @@ runtime_data_sb_port_binding_handler(struct engine_node 
*node, void *data)
 
     rt_data->tracked = true;
     b_ctx_out.tracked_dp_bindings = &rt_data->tracked_dp_bindings;
+    b_ctx_out.tracked_peer_ports = &rt_data->tracked_peer_ports;
 
     if (!binding_handle_port_binding_changes(&b_ctx_in, &b_ctx_out)) {
         return EN_UNHANDLED;
@@ -2003,7 +2014,8 @@ runtime_data_sb_port_binding_handler(struct engine_node 
*node, void *data)
             b_ctx_out.non_vif_ports_changed ||
             b_ctx_out.local_lports_changed ||
             b_ctx_out.localnet_learn_fdb_changed ||
-            !hmap_is_empty(b_ctx_out.tracked_dp_bindings)) {
+            !hmap_is_empty(b_ctx_out.tracked_dp_bindings) ||
+            !shash_is_empty(b_ctx_out.tracked_peer_ports)) {
         result = EN_HANDLED_UPDATED;
     }
 
@@ -2873,15 +2885,33 @@ ct_zones_runtime_data_handler(struct engine_node *node, 
void *data)
                 continue;
             }
 
-            /* The CT zone of a patch port without the ACL flag depends
-             * on the local datapaths of the connected switches, which is
-             * not tracked incrementally.  Fall back to a full recompute
-             * of ct_zones so that zones for router ports are assigned
-             * and released consistently with ct_zones_update(). */
+            /* The CT zone of a patch port without the ACL flag is needed
+             * if and only if the port is the switch-side peer of a router
+             * port on a local logical switch.  Read that condition
+             * directly from the peer set of the port's datapath, which is
+             * the same condition ct_zones_update() applies on a full
+             * recompute; ports whose peer relationship was removed are
+             * also tracked in rt_data->tracked_peer_ports below. */
             if (!strcmp(t_lport->pb->type, "patch") &&
                 !smap_get_bool(&t_lport->pb->options,
                                "enable_router_port_acl", false)) {
-                return EN_UNHANDLED;
+                bool zone_needed = false;
+                struct local_datapath *ld = get_local_datapath(
+                    &rt_data->local_datapaths,
+                    t_lport->pb->datapath->tunnel_key);
+                if (ld && ld->is_switch) {
+                    const struct peer_ports *peers;
+                    VECTOR_FOR_EACH_PTR (&ld->peer_ports, peers) {
+                        if (peers->local == t_lport->pb) {
+                            zone_needed = true;
+                            break;
+                        }
+                    }
+                }
+                updated |= ct_zone_handle_port_update(
+                    &ct_zones_data->ctx, t_lport->pb, zone_needed,
+                    &scan_start, min_ct_zone, max_ct_zone);
+                continue;
             }
             bool port_updated =
                     t_lport->tracked_type == TRACKED_RESOURCE_NEW ||
@@ -2893,6 +2923,15 @@ ct_zones_runtime_data_handler(struct engine_node *node, 
void *data)
         }
     }
 
+    struct shash_node *tpp_node;
+    SHASH_FOR_EACH (tpp_node, &rt_data->tracked_peer_ports) {
+        struct tracked_peer_port *tpp = tpp_node->data;
+        updated |= ct_zone_handle_port_update(&ct_zones_data->ctx,
+                                              tpp->pb, tpp->zone_needed,
+                                              &scan_start, min_ct_zone,
+                                              max_ct_zone);
+    }
+
     return updated ? EN_HANDLED_UPDATED : EN_HANDLED_UNCHANGED;
 }
 
diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
index d600ba2cd..d00780478 100644
--- a/tests/ovn-controller.at
+++ b/tests/ovn-controller.at
@@ -2870,6 +2870,85 @@ OVS_WAIT_UNTIL([check_ovsdb_zone lr0_snat $snat_zone])
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn-controller - I-P for CT zone patch ports])
+AT_KEYWORDS([ovn-ct-zone-patch-ip])
+
+ovn_start
+
+net_add n1
+sim_add hv1
+as hv1
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+
+# ls1 and ls2 are local through their VIFs and lr0 is local through
+# rp-ls1, so attaching lr0 to ls2 only changes the peer set of already
+# local datapaths.
+check ovn-nbctl lr-add lr0
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lrp-add lr0 rp-ls1 00:00:01:01:02:01 10.0.1.1/24
+check ovn-nbctl lsp-add-router-port ls1 ls1-rp rp-ls1
+check ovn-nbctl lsp-add ls1 lsp1 -- lsp-set-addresses lsp1 \
+"00:11:00:00:00:01 10.0.1.2"
+check ovn-nbctl ls-add ls2
+check ovn-nbctl lsp-add ls2 lsp2 -- lsp-set-addresses lsp2 \
+"00:11:00:00:00:02 10.0.2.2"
+
+as hv1
+check ovs-vsctl add-port br-int vif1 \
+    -- set Interface vif1 external_ids:iface-id=lsp1
+check ovs-vsctl add-port br-int vif2 \
+    -- set Interface vif2 external_ids:iface-id=lsp2
+
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+# Attaching lr0 to the already local ls2 assigns the CT zone of the
+# switch-side patch port incrementally.
+check as hv1 ovn-appctl -t ovn-controller inc-engine/clear-stats
+check ovn-nbctl --wait=hv lrp-add lr0 rp-ls2 00:00:01:01:02:02 10.0.2.1/24 \
+    -- lsp-add-router-port ls2 ls2-rp rp-ls2
+OVS_WAIT_UNTIL([ovs-vsctl get Bridge br-int external_ids:ct-zone-ls2-rp])
+check_controller_engine_stats hv1 runtime_data norecompute compute
+check_controller_engine_stats hv1 ct_zones norecompute compute
+
+# Detaching lr0 from ls2 releases the CT zone incrementally.
+check as hv1 ovn-appctl -t ovn-controller inc-engine/clear-stats
+check ovn-nbctl --wait=hv lsp-del ls2-rp -- lrp-del rp-ls2
+OVS_WAIT_WHILE([ovs-vsctl get Bridge br-int external_ids:ct-zone-ls2-rp])
+check_controller_engine_stats hv1 runtime_data norecompute compute
+check_controller_engine_stats hv1 ct_zones norecompute compute
+
+# Re-attaching with the same port names re-assigns the zone
+# incrementally.
+check as hv1 ovn-appctl -t ovn-controller inc-engine/clear-stats
+check ovn-nbctl --wait=hv lrp-add lr0 rp-ls2 00:00:01:01:02:02 10.0.2.1/24 \
+    -- lsp-add-router-port ls2 ls2-rp rp-ls2
+OVS_WAIT_UNTIL([ovs-vsctl get Bridge br-int external_ids:ct-zone-ls2-rp])
+check_controller_engine_stats hv1 ct_zones norecompute compute
+
+# ls3 is not local yet: attaching it to the already local lr0 makes the
+# datapath newly tracked, so a full ct_zones recompute is expected and
+# the zone is assigned by it.
+check ovn-nbctl ls-add ls3
+check as hv1 ovn-appctl -t ovn-controller inc-engine/clear-stats
+check ovn-nbctl --wait=hv lrp-add lr0 rp-ls3 00:00:01:01:02:03 10.0.3.1/24 \
+    -- lsp-add-router-port ls3 ls3-rp rp-ls3
+OVS_WAIT_UNTIL([ovs-vsctl get Bridge br-int external_ids:ct-zone-ls3-rp])
+check_controller_engine_stats hv1 ct_zones recompute nocompute
+
+# Toggling enable_router_port_acl on a local patch port is still handled
+# by the tracked lports path of the ct_zones handler.
+check as hv1 ovn-appctl -t ovn-controller inc-engine/clear-stats
+check ovn-nbctl --wait=hv lsp-set-options ls1-rp enable_router_port_acl=true
+OVS_WAIT_UNTIL([ovs-vsctl get Bridge br-int external_ids:ct-zone-ls1-rp])
+check_controller_engine_stats hv1 ct_zones norecompute compute
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([ovn-controller - Local Chassis_Template_Var updates])
 AT_KEYWORDS([templates])
-- 
2.56.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to