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
