I also want to mention a logic change in the changelog:

Previously, if IC created a record in the target availability zone, and later 
northd in that same AZ tried to create a load balancer with the same backend 
along with a service monitor for it, northd wouldn’t create anything new—it 
would just reuse the status from the existing ic_learned record.

This seemed wrong to me. If the service monitor was initially deleted in the 
source AZ, the old logic would make ovn-ic delete the record in the target AZ 
too—even though it was a stable record (marked as ic_learned in the southbound 
DB and couldn’t be claimed by the northd). That record would get deleted, and 
northd would just recreate it, which felt unnecessary.

Now, in this situation:

  *   Northd will set ic_learned to false when it tries to recreate the same 
service monitor it originally learned from IC.

  *   This way, northd takes full ownership of the record, including updates 
and deletions.

  *   The service monitor in the other AZ won’t keep flapping unnecessarily.

On 15.08.2025 11:06, Alexandra Rukomoinikova wrote:

This commit introduces the ability to configure health checks for load balancer
backends located in remote availability zones through OVN Interconnect.

Key changes include:
1. Added new 'local' field to service monitor schema.
2. Extended ip_port_mappings syntax to support remote backends:
   - Added :<az-name> suffix for health check targets
3. Modified controller logic to skip non-local monitors.
4. Enhanced northd to handle remote health check configuration.

Signed-off-by: Alexandra Rukomoinikova 
<[email protected]><mailto:[email protected]>
---
 controller/pinctrl.c |  2 +-
 northd/lb.c          | 81 ++++++++++++++++++++++++++++++++++++--------
 northd/lb.h          | 10 ++++--
 northd/northd.c      | 42 +++++++++++++++++++----
 ovn-nb.xml           |  7 ++++
 ovn-sb.ovsschema     |  5 +--
 ovn-sb.xml           | 10 +++++-
 7 files changed, 130 insertions(+), 27 deletions(-)

diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 9071b2b58..400cb59e4 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -7034,7 +7034,7 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
         const struct sbrec_port_binding *pb
             = lport_lookup_by_name(sbrec_port_binding_by_name,
                                    sb_svc_mon->logical_port);
-        if (!pb) {
+        if (!pb || !sb_svc_mon->local) {
             continue;
         }

diff --git a/northd/lb.c b/northd/lb.c
index b11896cf1..30726cd27 100644
--- a/northd/lb.c
+++ b/northd/lb.c
@@ -100,6 +100,14 @@ void ovn_northd_lb_vip_init(struct ovn_northd_lb_vip 
*lb_vip_nb,
         ovn_lb_get_health_check(nbrec_lb, vip_port_str, template);
 }

+/*
+ * Initializes health check configuration for load balancer VIP
+ * backends. Parses the ip_port_mappings in the format :
+ * "ip:logical_port:src_ip[:az_name]".
+ * If az_name is present and non-empty, it indicates this is a
+ * remote service monitor (backend is in another availability zone),
+ * it should be propogated to another AZ by interconnection processing.
+ */
 static void
 ovn_lb_vip_backends_health_check_init(const struct ovn_northd_lb *lb,
                                       const struct ovn_lb_vip *lb_vip,
@@ -120,22 +128,64 @@ ovn_lb_vip_backends_health_check_init(const struct 
ovn_northd_lb *lb,
         }

         char *svc_mon_src_ip = NULL;
+        char *az_name = NULL;
+        bool is_remote = false;
         char *port_name = xstrdup(s);
-        char *p = strstr(port_name, ":");
-        if (p) {
-            *p = 0;
-            p++;
-            struct sockaddr_storage svc_mon_src_addr;
-            if (!inet_parse_address(p, &svc_mon_src_addr)) {
-                static struct vlog_rate_limit rl =
-                    VLOG_RATE_LIMIT_INIT(5, 1);
-                VLOG_WARN_RL(&rl, "Invalid svc mon src IP %s", p);
-            } else {
-                struct ds src_ip_s = DS_EMPTY_INITIALIZER;
-                ss_format_address_nobracks(&svc_mon_src_addr,
-                                            &src_ip_s);
-                svc_mon_src_ip = ds_steal_cstr(&src_ip_s);
+        char *src_ip = NULL;
+
+        char *first_colon = strchr(port_name, ':');
+        if (!first_colon) {
+            free(port_name);
+            continue;
+        }
+        *first_colon = '\0';
+
+        if (first_colon[1] == '[') {
+            /* IPv6 case - format: port:[ipv6]:az or port:[ipv6] */
+            char *ip_end = strchr(first_colon + 2, ']');
+            if (!ip_end) {
+                VLOG_WARN("Malformed IPv6 address in backend %s", s);
+                free(port_name);
+                continue;
             }
+
+            src_ip = first_colon + 2;
+            *ip_end = '\0';
+
+            if (ip_end[1] == ':') {
+                az_name = ip_end + 2;
+                if (!*az_name) {
+                    VLOG_WARN("Empty AZ name specified for backend %s",
+                              port_name);
+                    free(port_name);
+                    continue;
+                }
+                is_remote = true;
+            }
+        } else {
+            /* IPv4 case - format: port:ip:az or port:ip */
+            src_ip = first_colon + 1;
+            char *az_colon = strchr(src_ip, ':');
+            if (az_colon) {
+                *az_colon = '\0';
+                az_name = az_colon + 1;
+                if (!*az_name) {
+                    VLOG_WARN("Empty AZ name specified for backend %s",
+                              port_name);
+                    free(port_name);
+                    continue;
+                }
+            is_remote = true;
+            }
+        }
+
+        struct sockaddr_storage svc_mon_src_addr;
+        if (!src_ip || !inet_parse_address(src_ip, &svc_mon_src_addr)) {
+            VLOG_WARN("Invalid svc mon src IP %s", src_ip ? src_ip : "NULL");
+        } else {
+            struct ds src_ip_s = DS_EMPTY_INITIALIZER;
+            ss_format_address_nobracks(&svc_mon_src_addr, &src_ip_s);
+            svc_mon_src_ip = ds_steal_cstr(&src_ip_s);
         }

         if (svc_mon_src_ip) {
@@ -144,6 +194,8 @@ ovn_lb_vip_backends_health_check_init(const struct 
ovn_northd_lb *lb,
             backend_nb->health_check = true;
             backend_nb->logical_port = xstrdup(port_name);
             backend_nb->svc_mon_src_ip = svc_mon_src_ip;
+            backend_nb->az_name = is_remote ? xstrdup(az_name) : NULL;
+            backend_nb->local_backend = !is_remote;
         }
         free(port_name);
     }
@@ -158,6 +210,7 @@ void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip 
*vip)
     for (size_t i = 0; i < vip->n_backends; i++) {
         free(vip->backends_nb[i].logical_port);
         free(vip->backends_nb[i].svc_mon_src_ip);
+        free(vip->backends_nb[i].az_name);
     }
     free(vip->backends_nb);
 }
diff --git a/northd/lb.h b/northd/lb.h
index eb1942bd4..a0e560204 100644
--- a/northd/lb.h
+++ b/northd/lb.h
@@ -88,8 +88,14 @@ struct ovn_northd_lb_vip {

 struct ovn_northd_lb_backend {
     bool health_check;
-    char *logical_port; /* Logical port to which the ip belong to. */
-    char *svc_mon_src_ip; /* Source IP to use for monitoring. */
+     /* Set to true if port locates in local AZ. */
+    bool local_backend;
+    /* Logical port to which the ip belong to. */
+    char *logical_port;
+    /* Source IP address to be used for service monitoring. */
+    char *svc_mon_src_ip;
+    /* Target Availability Zone name for service monitoring. */
+    char *az_name;
 };

 struct ovn_northd_lb *ovn_northd_lb_create(const struct nbrec_load_balancer *);
diff --git a/northd/northd.c b/northd/northd.c
index 290fd2970..05636e598 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -2977,13 +2977,28 @@ get_service_mon(const struct hmap 
*local_svc_monitors_map,
     return NULL;
 }

+static void
+set_service_mon_options(const struct sbrec_service_monitor *sbrec_mon,
+                        const struct smap *nb_hc_options,
+                        const char *target_az_name)
+{
+    struct smap sb_svc_options = SMAP_INITIALIZER(&sb_svc_options);
+
+    smap_clone(&sb_svc_options, nb_hc_options);
+    if (target_az_name) {
+        smap_add(&sb_svc_options, "az-name", target_az_name);
+    }
+    sbrec_service_monitor_set_options(sbrec_mon, &sb_svc_options);
+    smap_destroy(&sb_svc_options);
+}
+
 static struct service_monitor_info *
 create_or_get_service_mon(struct ovsdb_idl_txn *ovnsb_txn,
                           struct hmap *local_svc_monitors_map,
                           struct hmap *ic_learned_svc_monitors_map,
                           const char *ip, const char *logical_port,
                           uint16_t service_port, const char *protocol,
-                          const char *chassis_name)
+                          const char *chassis_name, bool local_backend)
 {
     struct service_monitor_info *mon_info =
         get_service_mon(local_svc_monitors_map,
@@ -2996,6 +3011,14 @@ create_or_get_service_mon(struct ovsdb_idl_txn 
*ovnsb_txn,
             sbrec_service_monitor_set_chassis_name(mon_info->sbrec_mon,
                                                    chassis_name);
         }
+        /*
+         * if a similar record was created by the interconet database,
+         * then we transfer ownership rights to delete to northd:
+         * northd will create logical flows and delete the entry
+         * when the backend is no longer used locally.
+         */
+        sbrec_service_monitor_set_ic_learned(mon_info->sbrec_mon,
+                                             false);
         return mon_info;
     }

@@ -3010,6 +3033,7 @@ create_or_get_service_mon(struct ovsdb_idl_txn *ovnsb_txn,
     sbrec_service_monitor_set_port(sbrec_mon, service_port);
     sbrec_service_monitor_set_logical_port(sbrec_mon, logical_port);
     sbrec_service_monitor_set_protocol(sbrec_mon, protocol);
+    sbrec_service_monitor_set_local(sbrec_mon, local_backend);
     sbrec_service_monitor_set_ic_learned(sbrec_mon, false);
     if (chassis_name) {
         sbrec_service_monitor_set_chassis_name(sbrec_mon, chassis_name);
@@ -3052,7 +3076,8 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
             struct ovn_port *op = ovn_port_find(ls_ports,
                                                 backend_nb->logical_port);

-            if (!op || !lsp_is_enabled(op->nbsp)) {
+            if (backend_nb->local_backend &&
+                (!op || !lsp_is_enabled(op->nbsp))) {
                 continue;
             }

@@ -3062,7 +3087,7 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
             }

             const char *chassis_name = NULL;
-            if (op->sb->chassis) {
+            if (backend_nb->local_backend && op->sb->chassis) {
                 chassis_name = op->sb->chassis->name;
             }

@@ -3074,10 +3099,12 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
                                           backend_nb->logical_port,
                                           backend->port,
                                           protocol,
-                                          chassis_name);
+                                          chassis_name,
+                                          backend_nb->local_backend);
             ovs_assert(mon_info);
-            sbrec_service_monitor_set_options(
-                mon_info->sbrec_mon, &lb_vip_nb->lb_health_check->options);
+            set_service_mon_options(mon_info->sbrec_mon,
+                                    &lb_vip_nb->lb_health_check->options,
+                                    backend_nb->az_name);
             struct eth_addr ea;
             if (!mon_info->sbrec_mon->src_mac ||
                 !eth_addr_from_string(mon_info->sbrec_mon->src_mac, &ea) ||
@@ -3094,7 +3121,8 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
                     backend_nb->svc_mon_src_ip);
             }

-            if ((!op->sb->n_up || !op->sb->up[0])
+            if (backend_nb->local_backend &&
+                (!op->sb->n_up || !op->sb->up[0])
                 && mon_info->sbrec_mon->status
                 && !strcmp(mon_info->sbrec_mon->status, "online")) {
                 sbrec_service_monitor_set_status(mon_info->sbrec_mon,
diff --git a/ovn-nb.xml b/ovn-nb.xml
index cbe9c40bb..e518f7452 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -2231,6 +2231,9 @@
           Health checks are sent to this port with the specified source IP.
           For IPv6 square brackets must be used around IP address, e.g:
           <code><var>port_name</var>:<var>[sourc_ip]</var></code>
+          Remote endpoint:
+          Specify :target_zone_name at the end of the above syntax to create
+          remote health checks in a specific zone.
         </p>

         <p>
@@ -2238,11 +2241,15 @@
           defined as <code>10.0.0.4</code>=<code>sw0-p1:10.0.0.2</code> and
           <code>20.0.0.4</code>=<code>sw1-p1:20.0.0.2</code>, if the values
           given were suitable ports and IP addresses.
+          And remote endpoint:
+          <code>10.0.0.4</code>=<code>sw0-p1:10.0.0.2</code>:az1, where
+          <code>sw0-p1</code> - logical port in <code>az1</code>.
         </p>

         <p>
           For IPv6 IP to port mappings might be defined as
           <code>[2001::1]</code>=<code>sw0-p1:[2002::1]</code>.
+          Remote endpoint: same as for IP.
         </p>
       </column>
     </group>
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index 6efba45bd..f64cb99dd 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Southbound",
-    "version": "21.3.1",
-    "cksum": "3962801744 35179",
+    "version": "21.4.0",
+    "cksum": "812831561 35225",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -526,6 +526,7 @@
                              "enum": ["set", ["online", "offline", "error"]]},
                              "min": 0, "max": 1}},
                 "ic_learned": {"type": "boolean"},
+                "local": {"type": "boolean"},
                 "options": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}},
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 4d7960afa..4b563c5f1 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -4981,6 +4981,10 @@ tcp.flags = RST;
       The service can be an IPv4 TCP or UDP
       service. <code>ovn-controller</code> periodically sends out service
       monitor packets and updates the status of the service.
+      If the service monitor is not local, then ovn-ic creates a record about
+      it in the SBDB database, after which another OVN deployment creates a
+      record about the Service Monitor in its SBDB and the status is
+      propagated back to the initial record in the original availability zone.
     </p>

     <p>
@@ -4995,7 +4999,7 @@ tcp.flags = RST;
       </p>

       <column name="ip">
-        IP of the service to be monitored. Only IPv4 is supported.
+        IP of the service to be monitored.
       </column>

       <column name="protocol">
@@ -5024,6 +5028,10 @@ tcp.flags = RST;
         The name of the chassis where the logical port is bound.
       </column>

+      <column name="local">
+        Set to true if backend locates on local ovn deployment.
+      </column>
+
       <column name="ic_learned">
         Set to true if the service monitor was propagated from another
         OVN deployment via ovn-ic management.



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

Reply via email to