Add Similar to IPv4 counterpart, introduce IPv6 load-balancer health
check support.

Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2136094
Signed-off-by: Lorenzo Bianconi <lorenzo.bianc...@redhat.com>
---
Changes since v1:
- fix potential crash in ovn-northd
- improve documentation
---
 controller/pinctrl.c    | 213 ++++++++++++++++++++++++-------------
 northd/northd.c         |  79 ++++++++++----
 northd/ovn-northd.8.xml |  17 +++
 ovn-nb.xml              |  21 ++--
 tests/ovn.at            | 201 ++++++++++++++++++++++++++++++++++-
 tests/system-ovn.at     | 230 +++++++++++++++++++++++++++++++++++++++-
 6 files changed, 658 insertions(+), 103 deletions(-)

diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index f1176a2f2..d7dbdab0e 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -6740,7 +6740,7 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
         ovs_be32 ip4;
         if (ip_parse(sb_svc_mon->ip, &ip4)) {
             ip_addr = in6_addr_mapped_ipv4(ip4);
-        } else {
+        } else if (!ipv6_parse(sb_svc_mon->ip, &ip_addr)) {
             continue;
         }
 
@@ -6753,16 +6753,27 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
                 continue;
             }
 
-            for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
-                if (ip4 == laddrs.ipv4_addrs[j].addr) {
-                    ea = laddrs.ea;
-                    mac_found = true;
-                    break;
+            if (IN6_IS_ADDR_V4MAPPED(&ip_addr)) {
+                for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
+                    if (ip4 == laddrs.ipv4_addrs[j].addr) {
+                        ea = laddrs.ea;
+                        mac_found = true;
+                        break;
+                    }
+                }
+            } else {
+                for (size_t j = 0; j < laddrs.n_ipv6_addrs; j++) {
+                    if (IN6_ARE_ADDR_EQUAL(&ip_addr,
+                                           &laddrs.ipv6_addrs[j].addr)) {
+                        ea = laddrs.ea;
+                        mac_found = true;
+                        break;
+                    }
                 }
             }
 
-            if (!mac_found && !laddrs.n_ipv4_addrs) {
-                /* IPv4 address(es) are not configured. Use the first mac. */
+            if (!mac_found && !laddrs.n_ipv4_addrs && !laddrs.n_ipv6_addrs) {
+                /* IP address(es) are not configured. Use the first mac. */
                 ea = laddrs.ea;
                 mac_found = true;
             }
@@ -6796,7 +6807,7 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
             svc_mon->port_key = port_key;
             svc_mon->proto_port = sb_svc_mon->port;
             svc_mon->ip = ip_addr;
-            svc_mon->is_ip6 = false;
+            svc_mon->is_ip6 = !IN6_IS_ADDR_V4MAPPED(&ip_addr);
             svc_mon->state = SVC_MON_S_INIT;
             svc_mon->status = SVC_MON_ST_UNKNOWN;
             svc_mon->protocol = protocol;
@@ -7564,26 +7575,30 @@ svc_monitor_send_tcp_health_check__(struct rconn 
*swconn,
                                     ovs_be32 tcp_ack,
                                     ovs_be16 tcp_src)
 {
-    if (svc_mon->is_ip6) {
-        return;
-    }
-
     /* Compose a TCP-SYN packet. */
     uint64_t packet_stub[128 / 8];
     struct dp_packet packet;
+    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
 
     struct eth_addr eth_src;
     eth_addr_from_string(svc_mon->sb_svc_mon->src_mac, &eth_src);
-    ovs_be32 ip4_src;
-    ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
-
-    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-    pinctrl_compose_ipv4(&packet, eth_src, svc_mon->ea,
-                         ip4_src, in6_addr_get_mapped_ipv4(&svc_mon->ip),
-                         IPPROTO_TCP, 63, TCP_HEADER_LEN);
+    if (svc_mon->is_ip6) {
+        struct in6_addr ip6_src;
+        ipv6_parse(svc_mon->sb_svc_mon->src_ip, &ip6_src);
+        pinctrl_compose_ipv6(&packet, eth_src, svc_mon->ea,
+                             &ip6_src, &svc_mon->ip, IPPROTO_TCP,
+                             63, TCP_HEADER_LEN);
+    } else {
+        ovs_be32 ip4_src;
+        ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
+        pinctrl_compose_ipv4(&packet, eth_src, svc_mon->ea,
+                             ip4_src, in6_addr_get_mapped_ipv4(&svc_mon->ip),
+                             IPPROTO_TCP, 63, TCP_HEADER_LEN);
+    }
 
     struct tcp_header *th = dp_packet_l4(&packet);
     dp_packet_set_l4(&packet, th);
+    th->tcp_csum = 0;
     th->tcp_dst = htons(svc_mon->proto_port);
     th->tcp_src = tcp_src;
 
@@ -7594,7 +7609,11 @@ svc_monitor_send_tcp_health_check__(struct rconn *swconn,
     th->tcp_winsz = htons(65160);
 
     uint32_t csum;
-    csum = packet_csum_pseudoheader(dp_packet_l3(&packet));
+    if (svc_mon->is_ip6) {
+        csum = packet_csum_pseudoheader6(dp_packet_l3(&packet));
+    } else {
+        csum = packet_csum_pseudoheader(dp_packet_l3(&packet));
+    }
     csum = csum_continue(csum, th, dp_packet_size(&packet) -
                          ((const unsigned char *)th -
                          (const unsigned char *)dp_packet_eth(&packet)));
@@ -7629,21 +7648,26 @@ svc_monitor_send_udp_health_check(struct rconn *swconn,
                                   struct svc_monitor *svc_mon,
                                   ovs_be16 udp_src)
 {
-    if (svc_mon->is_ip6) {
-        return;
-    }
-
     struct eth_addr eth_src;
     eth_addr_from_string(svc_mon->sb_svc_mon->src_mac, &eth_src);
-    ovs_be32 ip4_src;
-    ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
 
     uint64_t packet_stub[128 / 8];
     struct dp_packet packet;
     dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-    pinctrl_compose_ipv4(&packet, eth_src, svc_mon->ea,
-                         ip4_src, in6_addr_get_mapped_ipv4(&svc_mon->ip),
-                         IPPROTO_UDP, 63, UDP_HEADER_LEN + 8);
+
+    if (svc_mon->is_ip6) {
+        struct in6_addr ip6_src;
+        ipv6_parse(svc_mon->sb_svc_mon->src_ip, &ip6_src);
+        pinctrl_compose_ipv6(&packet, eth_src, svc_mon->ea,
+                             &ip6_src, &svc_mon->ip, IPPROTO_UDP,
+                             63, UDP_HEADER_LEN + 8);
+    } else {
+        ovs_be32 ip4_src;
+        ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
+        pinctrl_compose_ipv4(&packet, eth_src, svc_mon->ea,
+                             ip4_src, in6_addr_get_mapped_ipv4(&svc_mon->ip),
+                             IPPROTO_UDP, 63, UDP_HEADER_LEN + 8);
+    }
 
     struct udp_header *uh = dp_packet_l4(&packet);
     dp_packet_set_l4(&packet, uh);
@@ -7651,6 +7675,16 @@ svc_monitor_send_udp_health_check(struct rconn *swconn,
     uh->udp_src = udp_src;
     uh->udp_len = htons(UDP_HEADER_LEN + 8);
     uh->udp_csum = 0;
+    if (svc_mon->is_ip6) {
+        uint32_t csum = packet_csum_pseudoheader6(dp_packet_l3(&packet));
+        csum = csum_continue(csum, uh, dp_packet_size(&packet) -
+                             ((const unsigned char *) uh -
+                              (const unsigned char *) dp_packet_eth(&packet)));
+        uh->udp_csum = csum_finish(csum);
+        if (!uh->udp_csum) {
+            uh->udp_csum = htons(0xffff);
+        }
+    }
 
     uint64_t ofpacts_stub[4096 / 8];
     struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
@@ -7713,6 +7747,7 @@ svc_monitors_run(struct rconn *swconn,
         long long int current_time = time_msec();
         long long int next_run_time = LLONG_MAX;
         enum svc_monitor_status old_status = svc_mon->status;
+
         switch (svc_mon->state) {
         case SVC_MON_S_INIT:
             svc_monitor_send_health_check(swconn, svc_mon);
@@ -7843,32 +7878,38 @@ pinctrl_handle_svc_check(struct rconn *swconn, const 
struct flow *ip_flow,
     uint32_t port_key = md->flow.regs[MFF_LOG_INPORT - MFF_REG0];
     struct in6_addr ip_addr;
     struct eth_header *in_eth = dp_packet_data(pkt_in);
-    struct ip_header *in_ip = dp_packet_l3(pkt_in);
+    uint8_t ip_proto;
 
-    if (in_ip->ip_proto != IPPROTO_TCP && in_ip->ip_proto != IPPROTO_ICMP) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl,
-                     "handle service check: Unsupported protocol - [%x]",
-                     in_ip->ip_proto);
-        return;
+    if (in_eth->eth_type == htons(ETH_TYPE_IP)) {
+        struct ip_header *in_ip = dp_packet_l3(pkt_in);
+        uint16_t in_ip_len = ntohs(in_ip->ip_tot_len);
+        if (in_ip_len < IP_HEADER_LEN) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_WARN_RL(&rl,
+                         "IP packet with invalid length (%u)",
+                         in_ip_len);
+            return;
+        }
+
+        ip_addr = in6_addr_mapped_ipv4(ip_flow->nw_src);
+        ip_proto = in_ip->ip_proto;
+    } else {
+        struct ovs_16aligned_ip6_hdr *in_ip = dp_packet_l3(pkt_in);
+        ip_addr = ip_flow->ipv6_src;
+        ip_proto = in_ip->ip6_nxt;
     }
 
-    uint16_t in_ip_len = ntohs(in_ip->ip_tot_len);
-    if (in_ip_len < IP_HEADER_LEN) {
+    if (ip_proto != IPPROTO_TCP && ip_proto != IPPROTO_ICMP &&
+        ip_proto != IPPROTO_ICMPV6) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
         VLOG_WARN_RL(&rl,
-                     "IP packet with invalid length (%u)",
-                     in_ip_len);
+                     "handle service check: Unsupported protocol - [%x]",
+                     ip_proto);
         return;
     }
 
-    if (in_eth->eth_type == htons(ETH_TYPE_IP)) {
-        ip_addr = in6_addr_mapped_ipv4(ip_flow->nw_src);
-    } else {
-        ip_addr = ip_flow->ipv6_dst;
-    }
 
-    if (in_ip->ip_proto == IPPROTO_TCP) {
+    if (ip_proto == IPPROTO_TCP) {
         uint32_t hash =
             hash_bytes(&ip_addr, sizeof ip_addr,
                        hash_3words(dp_key, port_key, ntohs(ip_flow->tp_src)));
@@ -7885,44 +7926,68 @@ pinctrl_handle_svc_check(struct rconn *swconn, const 
struct flow *ip_flow,
         }
         pinctrl_handle_tcp_svc_check(swconn, pkt_in, svc_mon);
     } else {
-        /* It's ICMP packet. */
-        struct icmp_header *ih = dp_packet_l4(pkt_in);
-        if (!ih) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-            VLOG_WARN_RL(&rl, "ICMPv4 packet with invalid header");
-            return;
-        }
-
-        if (ih->icmp_type != ICMP4_DST_UNREACH || ih->icmp_code != 3) {
-            return;
-        }
-
+        struct udp_header *orig_uh;
         const char *end =
             (char *)dp_packet_l4(pkt_in) + dp_packet_l4_size(pkt_in);
 
-        const struct ip_header *orig_ip_hr =
-            dp_packet_get_icmp_payload(pkt_in);
-        if (!orig_ip_hr) {
+        void *l4h = dp_packet_l4(pkt_in);
+        if (!l4h) {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-            VLOG_WARN_RL(&rl, "Original IP datagram not present in "
-                         "ICMP packet");
+            VLOG_WARN_RL(&rl, "ICMP packet with invalid header");
             return;
         }
 
-        if (ntohs(orig_ip_hr->ip_tot_len) !=
-            (IP_HEADER_LEN + UDP_HEADER_LEN + 8)) {
+        const void *in_ip = dp_packet_get_icmp_payload(pkt_in);
+        if (!in_ip) {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-            VLOG_WARN_RL(&rl, "Invalid original IP datagram length present "
-                         "in ICMP packet");
+            VLOG_WARN_RL(&rl, "Original IP datagram not present in "
+                         "ICMP packet");
             return;
         }
 
-        struct udp_header *orig_uh = (struct udp_header *) (orig_ip_hr + 1);
-        if ((char *)orig_uh >= end) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-            VLOG_WARN_RL(&rl, "Invalid UDP header in the original "
-                         "IP datagram");
-            return;
+        if (in_eth->eth_type == htons(ETH_TYPE_IP)) {
+            struct icmp_header *ih = l4h;
+            /* It's ICMP packet. */
+            if (ih->icmp_type != ICMP4_DST_UNREACH || ih->icmp_code != 3) {
+                return;
+            }
+
+            const struct ip_header *orig_ip_hr = in_ip;
+            if (ntohs(orig_ip_hr->ip_tot_len) !=
+                (IP_HEADER_LEN + UDP_HEADER_LEN + 8)) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+                VLOG_WARN_RL(&rl, "Invalid original IP datagram length "
+                             "present in ICMP packet");
+                return;
+            }
+
+            orig_uh = (struct udp_header *) (orig_ip_hr + 1);
+            if ((char *) orig_uh >= end) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+                VLOG_WARN_RL(&rl, "Invalid UDP header in the original "
+                             "IP datagram");
+                return;
+            }
+        } else {
+            struct icmp6_header *ih6 = l4h;
+            if (ih6->icmp6_type != 1 || ih6->icmp6_code != 4) {
+                return;
+            }
+
+            const struct ovs_16aligned_ip6_hdr *ip6_hdr = in_ip;
+            if (ntohs(ip6_hdr->ip6_plen) != UDP_HEADER_LEN + 8) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+                VLOG_WARN_RL(&rl, "Invalid original IP datagram length "
+                             "present in ICMP packet");
+            }
+
+            orig_uh = (struct udp_header *) (ip6_hdr + 1);
+            if ((char *) orig_uh >= end) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+                VLOG_WARN_RL(&rl, "Invalid UDP header in the original "
+                             "IP datagram");
+                return;
+            }
         }
 
         uint32_t hash =
diff --git a/northd/northd.c b/northd/northd.c
index 4751feab4..a7115c55c 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -3788,8 +3788,15 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn, 
struct ovn_northd_lb *lb,
 
             struct ovn_port *op = NULL;
             char *svc_mon_src_ip = NULL;
+
+            bool ipv6 = !IN6_IS_ADDR_V4MAPPED(&lb_vip->vip);
+            struct ds key = DS_EMPTY_INITIALIZER;
+            ds_put_format(&key, "%s%s%s",
+                          ipv6 ? "[" : "", backend->ip_str,
+                          ipv6 ? "]" : "");
+
             const char *s = smap_get(&lb->nlb->ip_port_mappings,
-                                     backend->ip_str);
+                                     ds_cstr(&key));
             if (s) {
                 char *port_name = xstrdup(s);
                 char *p = strstr(port_name, ":");
@@ -3797,10 +3804,21 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn, 
struct ovn_northd_lb *lb,
                     *p = 0;
                     p++;
                     op = ovn_port_find(ports, port_name);
-                    svc_mon_src_ip = xstrdup(p);
+                    if (ipv6) {
+                        char *t, *q = strstr(p, "[");
+                        p = NULL;
+                        if (q && (t = strstr(q + 1, "]"))) {
+                            p = q + 1;
+                            *t = 0;
+                        }
+                    }
+                    if (p) {
+                        svc_mon_src_ip = xstrdup(p);
+                    }
                 }
                 free(port_name);
             }
+            ds_destroy(&key);
 
             backend_nb->op = op;
             backend_nb->svc_mon_src_ip = svc_mon_src_ip;
@@ -3878,8 +3896,10 @@ build_lb_vip_actions(struct ovn_lb_vip *lb_vip,
             }
 
             n_active_backends++;
-            ds_put_format(action, "%s:%"PRIu16",",
-                          backend->ip_str, backend->port);
+            bool ipv6 = !IN6_IS_ADDR_V4MAPPED(&backend->ip);
+            ds_put_format(action, "%s%s%s:%"PRIu16",",
+                          ipv6 ? "[" : "", backend->ip_str,
+                          ipv6 ? "]" : "", backend->port);
         }
 
         if (!n_active_backends) {
@@ -8709,6 +8729,7 @@ build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb 
*lb,
             continue;
         }
 
+        struct ovn_lb_vip *lb_vip = &lb->vips[i];
         for (size_t j = 0; j < lb_vip_nb->n_backends; j++) {
             struct ovn_northd_lb_backend *backend_nb =
                 &lb_vip_nb->backends_nb[j];
@@ -8717,22 +8738,42 @@ build_lswitch_arp_nd_service_monitor(struct 
ovn_northd_lb *lb,
             }
 
             ds_clear(match);
-            ds_put_format(match, "arp.tpa == %s && arp.op == 1",
-                          backend_nb->svc_mon_src_ip);
             ds_clear(actions);
-            ds_put_format(actions,
-                "eth.dst = eth.src; "
-                "eth.src = %s; "
-                "arp.op = 2; /* ARP reply */ "
-                "arp.tha = arp.sha; "
-                "arp.sha = %s; "
-                "arp.tpa = arp.spa; "
-                "arp.spa = %s; "
-                "outport = inport; "
-                "flags.loopback = 1; "
-                "output;",
-                svc_monitor_mac, svc_monitor_mac,
-                backend_nb->svc_mon_src_ip);
+            if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
+                ds_put_format(match, "arp.tpa == %s && arp.op == 1",
+                              backend_nb->svc_mon_src_ip);
+                ds_put_format(actions,
+                    "eth.dst = eth.src; "
+                    "eth.src = %s; "
+                    "arp.op = 2; /* ARP reply */ "
+                    "arp.tha = arp.sha; "
+                    "arp.sha = %s; "
+                    "arp.tpa = arp.spa; "
+                    "arp.spa = %s; "
+                    "outport = inport; "
+                    "flags.loopback = 1; "
+                    "output;",
+                    svc_monitor_mac, svc_monitor_mac,
+                    backend_nb->svc_mon_src_ip);
+            } else {
+                ds_put_format(match, "nd_ns && nd.target == %s",
+                              backend_nb->svc_mon_src_ip);
+                ds_put_format(actions,
+                        "nd_na { "
+                        "eth.dst = eth.src; "
+                        "eth.src = %s; "
+                        "ip6.src = %s; "
+                        "nd.target = %s; "
+                        "nd.tll = %s; "
+                        "outport = inport; "
+                        "flags.loopback = 1; "
+                        "output; "
+                        "};",
+                        svc_monitor_mac,
+                        backend_nb->svc_mon_src_ip,
+                        backend_nb->svc_mon_src_ip,
+                        svc_monitor_mac);
+            }
             ovn_lflow_add_with_hint(lflows,
                                     backend_nb->op->od,
                                     S_SWITCH_IN_ARP_ND_RSP, 110,
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 25a742c90..9c18d2aba 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -1459,6 +1459,23 @@ output;
           These flows are required if an ARP request is sent for the IP
           <var>SVC_MON_SRC_IP</var>.
         </p>
+
+        <p>
+          For IPv6 the similar flow is added with the following action
+        </p>
+
+        <pre>
+nd_na {
+    eth.dst = eth.src;
+    eth.src = <var>E</var>;
+    ip6.src = <var>A</var>;
+    nd.target = <var>A</var>;
+    nd.tll = <var>E</var>;
+    outport = inport;
+    flags.loopback = 1;
+    output;
+};
+        </pre>
       </li>
 
       <li>
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 21c0f6197..0df83bec4 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1826,9 +1826,8 @@
 
     <group title="Health Checks">
       <p>
-        OVN supports health checks for load balancer endpoints, for IPv4 load
-        balancers only.  When health checks are enabled, the load balancer uses
-        only healthy endpoints.
+        OVN supports health checks for load balancer endpoints. When health
+        checks are enabled, the load balancer uses only healthy endpoints.
       </p>
 
       <p>
@@ -1840,7 +1839,7 @@
         column="health_check"/> a reference to a <ref
         table="Load_Balancer_Health_Check"/> row whose <ref
         table="Load_Balancer_Health_Check" column="vip"/> is set to
-        <code>10.0.0.10</code>.
+        <code>10.0.0.10</code>. The same approach can be used for IPv6 as well.
       </p>
 
       <column name="health_check">
@@ -1851,8 +1850,10 @@
         <p>
           Maps from endpoint IP to a colon-separated pair of logical port name
           and source IP,
-          e.g. <code><var>port_name</var>:<var>sourc_ip</var></code>.  Health
-          checks are sent to this port with the specified source IP.
+          e.g. <code><var>port_name</var>:<var>sourc_ip</var></code> for IPv4.
+          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>
         </p>
 
         <p>
@@ -1861,6 +1862,11 @@
           <code>20.0.0.4</code>=<code>sw1-p1:20.0.0.2</code>, if the values
           given were suitable ports and IP addresses.
         </p>
+
+        <p>
+          For IPv6 IP to port mappings might be defined as
+          <code>[2001::1]</code>=<code>sw0-p1:[2002::1]</code>.
+        </p>
       </column>
     </group>
 
@@ -2034,8 +2040,7 @@ or
 
   <table name="Load_Balancer_Health_Check" title="load balancer">
     <p>
-      Each row represents one load balancer health check. Health checks
-      are supported for IPv4 load balancers only.
+      Each row represents one load balancer health check.
     </p>
 
     <column name="vip">
diff --git a/tests/ovn.at b/tests/ovn.at
index d2163d87d..4c8fd896c 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -24228,7 +24228,7 @@ AT_CLEANUP
 ])
 
 OVN_FOR_EACH_NORTHD([
-AT_SETUP([Load balancer health checks])
+AT_SETUP([Load balancer health checks - IPv4])
 AT_KEYWORDS([lb])
 ovn_start
 
@@ -24426,6 +24426,205 @@ OVN_CLEANUP([hv1], [hv2])
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Load balancer health checks - IPv6])
+AT_KEYWORDS([lb])
+ovn_start
+
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=1
+check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+    set interface hv1-vif2 external-ids:iface-id=sw0-p2 \
+    options:tx_pcap=hv1/vif2-tx.pcap \
+    options:rxq_pcap=hv1/vif2-rx.pcap \
+    ofport-request=2
+
+sim_add hv2
+as hv2
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+    set interface hv2-vif1 external-ids:iface-id=sw1-p1 \
+    options:tx_pcap=hv2/vif1-tx.pcap \
+    options:rxq_pcap=hv2/vif1-rx.pcap \
+    ofport-request=1
+
+check ovn-nbctl ls-add sw0
+
+check ovn-nbctl lsp-add sw0 sw0-p1
+check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 2001::3"
+check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 2001::3"
+
+# Create port group and ACLs for sw0 ports.
+check ovn-nbctl pg-add pg0_drop sw0-p1
+check ovn-nbctl acl-add pg0_drop from-lport 1001 "inport == @pg0_drop && ip" 
drop
+check ovn-nbctl acl-add pg0_drop to-lport 1001 "outport == @pg0_drop && ip" 
drop
+
+# Create the second logical switch with one port
+check ovn-nbctl ls-add sw1
+check ovn-nbctl lsp-add sw1 sw1-p1
+check ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 2002::3"
+check ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 2002::3"
+
+# Create port group and ACLs for sw1 ports.
+check ovn-nbctl pg-add pg1_drop sw1-p1
+check ovn-nbctl acl-add pg1_drop from-lport 1001 "inport == @pg1_drop && ip" 
drop
+check ovn-nbctl acl-add pg1_drop to-lport 1001 "outport == @pg1_drop && ip" 
drop
+
+check ovn-nbctl pg-add pg1 sw1-p1
+check ovn-nbctl acl-add pg1 from-lport 1002 "inport == @pg1 && ip6" 
allow-related
+check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src 
== ::/0 && icmp6" allow-related
+check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src 
== ::/0 && tcp && tcp.dst == 80" allow-related
+check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src 
== ::/0 && udp && udp.dst == 80" allow-related
+
+# Create a logical router and attach both logical switches
+check ovn-nbctl lr-add lr0
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 2001::1/64
+check ovn-nbctl lsp-add sw0 sw0-lr0
+check ovn-nbctl lsp-set-type sw0-lr0 router
+check ovn-nbctl lsp-set-addresses sw0-lr0 router
+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 2001::a/64
+check ovn-nbctl lsp-add sw1 sw1-lr0
+check ovn-nbctl lsp-set-type sw1-lr0 router
+check ovn-nbctl lsp-set-addresses sw1-lr0 router
+check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+check ovn-nbctl lb-add lb1 [[2001::a]]:80 [[2001::3]]:80,[[2002::3]]:80
+OVN_LB_ID=$(ovn-nbctl --bare --column _uuid find load_balancer name=lb1)
+check ovn-nbctl set load_balancer ${OVN_LB_ID} 
selection_fields="ip_dst,ip_src,tp_dst,tp_src"
+#
+check ovn-nbctl --wait=sb set load_balancer . 
ip_port_mappings:\"[[2001::3]]\"=\"sw0-p1:[[2001::2]]\"
+check ovn-nbctl --wait=sb set load_balancer . 
ip_port_mappings:\"[[2002::3]]\"=\"sw1-p1:[[2002::2]]\"
+
+AT_CHECK([ovn-nbctl --wait=sb \
+          -- --id=@hc create Load_Balancer_Health_Check 
vip="\[\[2001\:\:a\]\]\:80" \
+             options:failure_count=100 \
+          -- add Load_Balancer . health_check @hc | uuidfilt], [0], [<0>
+])
+
+check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
+check ovn-nbctl --wait=sb ls-lb-add sw1 lb1
+check ovn-nbctl --wait=sb lr-lb-add lr0 lb1
+
+check ovn-nbctl ls-add public
+check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 2003::1/64
+check ovn-nbctl lsp-add public public-lr0
+check ovn-nbctl lsp-set-type public-lr0 router
+check ovn-nbctl lsp-set-addresses public-lr0 router
+check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
+
+# localnet port
+check ovn-nbctl lsp-add public ln-public
+check ovn-nbctl lsp-set-type ln-public localnet
+check ovn-nbctl lsp-set-addresses ln-public unknown
+check ovn-nbctl lsp-set-options ln-public network_name=public
+
+# schedule the gw router port to a chassis. Change the name of the chassis
+check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20
+
+OVN_POPULATE_ARP
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+wait_row_count Service_Monitor 2
+
+AT_CAPTURE_FILE([sbflows])
+OVS_WAIT_FOR_OUTPUT(
+  [ovn-sbctl dump-flows > sbflows
+   ovn-sbctl dump-flows sw0 | grep ct_lb_mark | grep priority=120 | sed 
's/table=..//'], 0,
+  [dnl
+  (ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && ip6.dst == 
2001::a && tcp.dst == 80), action=(xxreg1 = 2001::a; reg2[[0..15]] = 80; 
ct_lb_mark;)
+  (ls_in_lb           ), priority=120  , match=(ct.new && ip6.dst == 2001::a 
&& tcp.dst == 80), action=(reg0[[1]] = 0; 
ct_lb_mark(backends=[[2001::3]]:80,[[2002::3]]:80; 
hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
+])
+
+AT_CAPTURE_FILE([sbflows2])
+OVS_WAIT_FOR_OUTPUT(
+  [ovn-sbctl dump-flows > sbflows2
+   ovn-sbctl dump-flows lr0 | grep ct_lb_mark | grep priority=120 | sed 
's/table=..//'], 0,
+  [  (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip6 && 
xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && 
is_chassis_resident("cr-lr0-public")), 
action=(ct_lb_mark(backends=[[2001::3]]:80,[[2002::3]]:80; 
hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
+])
+
+# get the svc monitor mac.
+svc_mon_src_mac=`ovn-nbctl get NB_Global . options:svc_monitor_mac | \
+sed s/":"//g | sed s/\"//g`
+
+OVS_WAIT_UNTIL(
+    [test 1 = `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | \
+grep "505400000003${svc_mon_src_mac}" | wc -l`]
+)
+
+OVS_WAIT_UNTIL(
+    [test 1 = `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/vif1-tx.pcap | \
+grep "405400000003${svc_mon_src_mac}" | wc -l`]
+)
+
+check ovn-nbctl set load_balancer_health_check [[2001::a]]:80 
options:failure_count=1
+wait_row_count Service_Monitor 2 status=offline
+
+OVS_WAIT_UNTIL(
+    [test 2 = `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | \
+grep "505400000003${svc_mon_src_mac}" | wc -l`]
+)
+
+OVS_WAIT_UNTIL(
+    [test 2 = `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/vif1-tx.pcap | \
+grep "405400000003${svc_mon_src_mac}" | wc -l`]
+)
+
+AT_CAPTURE_FILE([sbflows3])
+ovn-sbctl dump-flows sw0 > sbflows3
+AT_CHECK(
+  [grep "ip6.dst == 2001::a && tcp.dst == 80" sbflows3 | grep priority=120 |\
+   sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && 
ip6.dst == 2001::a && tcp.dst == 80), action=(xxreg1 = 2001::a; reg2[[0..15]] = 
80; ct_lb_mark;)
+  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip6.dst == 
2001::a && tcp.dst == 80), action=(drop;)
+])
+
+AT_CAPTURE_FILE([sbflows4])
+ovn-sbctl dump-flows lr0 > sbflows4
+AT_CHECK([grep lr_in_dnat sbflows4 | grep priority=120 | sed 's/table=..//' | 
sort], [0], [dnl
+  (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip6 && 
xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1 && 
is_chassis_resident("cr-lr0-public")), action=(next;)
+  (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip6 && 
xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && 
is_chassis_resident("cr-lr0-public")), action=(drop;)
+])
+
+# Delete sw0-p1
+check ovn-nbctl lsp-del sw0-p1
+
+wait_row_count Service_Monitor 1
+
+# Add back sw0-p1 but without any IP address.
+check ovn-nbctl lsp-add sw0 sw0-p1
+check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03" -- \
+    lsp-set-port-security sw0-p1 "50:54:00:00:00:03"
+
+wait_row_count Service_Monitor 2 status=offline
+
+check ovn-nbctl lsp-del sw0-p1
+check ovn-nbctl lsp-del sw1-p1
+wait_row_count Service_Monitor 0
+
+# Add back sw0-p1 but without any address set.
+check ovn-nbctl lsp-add sw0 sw0-p1
+
+wait_row_count Service_Monitor 1
+wait_row_count Service_Monitor 0 status=offline
+wait_row_count Service_Monitor 0 status=online
+
+OVN_CLEANUP([hv1], [hv2])
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([SCTP Load balancer health checks])
 AT_KEYWORDS([lb sctp])
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 710cf22a2..1578f81a5 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -4392,7 +4392,7 @@ AT_CLEANUP
 ])
 
 OVN_FOR_EACH_NORTHD([
-AT_SETUP([Load balancer health checks])
+AT_SETUP([Load balancer health checks - IPv4])
 AT_KEYWORDS([lb])
 ovn_start
 
@@ -4619,6 +4619,234 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port 
patch-.*/d
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Load balancer health checks - IPv6])
+AT_KEYWORDS([lb])
+ovn_start
+
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . 
external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+ovn-nbctl ls-add sw0
+
+ovn-nbctl lsp-add sw0 sw0-p1
+ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 2001::3"
+ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 2001::3"
+
+ovn-nbctl lsp-add sw0 sw0-p2
+ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 2001::4"
+ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 2001::4"
+
+# Create port group and ACLs for sw0 ports.
+ovn-nbctl pg-add pg0_drop sw0-p1 sw0-p2
+ovn-nbctl acl-add pg0_drop from-lport 1001 "inport == @pg0_drop && ip" drop
+ovn-nbctl acl-add pg0_drop to-lport 1001 "outport == @pg0_drop && ip" drop
+
+ovn-nbctl pg-add pg0 sw0-p1 sw0-p2
+ovn-nbctl acl-add pg0 from-lport 1002 "inport == @pg0 && ip6" allow-related
+ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip6 && ip6.src == ::/0 
&& icmp6" allow-related
+ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip6 && ip6.src == ::/0 
&& tcp && tcp.dst == 80" allow-related
+ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip6 && ip6.src == ::/0 
&& udp && udp.dst == 80" allow-related
+
+# Create the second logical switch with one port
+ovn-nbctl ls-add sw1
+ovn-nbctl lsp-add sw1 sw1-p1
+ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 2002::3"
+ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 2002::3"
+
+# Create port group and ACLs for sw1 ports.
+ovn-nbctl pg-add pg1_drop sw1-p1
+ovn-nbctl acl-add pg1_drop from-lport 1001 "inport == @pg1_drop && ip" drop
+ovn-nbctl acl-add pg1_drop to-lport 1001 "outport == @pg1_drop && ip" drop
+
+ovn-nbctl pg-add pg1 sw1-p1
+ovn-nbctl acl-add pg1 from-lport 1002 "inport == @pg1 && ip6" allow-related
+ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 
&& icmp6" allow-related
+ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 
&& tcp && tcp.dst == 80" allow-related
+ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip6 && ip6.src == ::/0 
&& udp && udp.dst == 80" allow-related
+
+# Create a logical router and attach both logical switches
+ovn-nbctl lr-add lr0
+ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 2001::1/64
+ovn-nbctl lsp-add sw0 sw0-lr0
+ovn-nbctl lsp-set-type sw0-lr0 router
+ovn-nbctl lsp-set-addresses sw0-lr0 router
+ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 2002::1/64
+ovn-nbctl lsp-add sw1 sw1-lr0
+ovn-nbctl lsp-set-type sw1-lr0 router
+ovn-nbctl lsp-set-addresses sw1-lr0 router
+ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+ovn-nbctl --reject lb-add lb1 [[2001::a]]:80 [[2001::3]]:80,[[2002::3]]:80
+
+check ovn-nbctl --wait=sb set load_balancer . 
ip_port_mappings:\"[[2001::3]]\"=\"sw0-p1:[[2001::2]]\"
+check ovn-nbctl --wait=sb set load_balancer . 
ip_port_mappings:\"[[2002::3]]\"=\"sw1-p1:[[2002::2]]\"
+
+ovn-nbctl --wait=sb -- --id=@hc create \
+Load_Balancer_Health_Check vip="\[\[2001\:\:a\]\]\:80" -- add Load_Balancer . \
+health_check @hc
+
+ovn-nbctl --wait=sb ls-lb-add sw0 lb1
+ovn-nbctl --wait=sb ls-lb-add sw1 lb1
+ovn-nbctl --wait=sb lr-lb-add lr0 lb1
+
+OVN_POPULATE_ARP
+ovn-nbctl --wait=hv sync
+
+ADD_NAMESPACES(sw0-p1)
+ADD_VETH(sw0-p1, sw0-p1, br-int, "2001::3/64", "50:54:00:00:00:03", \
+         "2001::1")
+
+ADD_NAMESPACES(sw1-p1)
+ADD_VETH(sw1-p1, sw1-p1, br-int, "2002::3/64", "40:54:00:00:00:03", \
+         "2002::1")
+
+ADD_NAMESPACES(sw0-p2)
+ADD_VETH(sw0-p2, sw0-p2, br-int, "2001::4/64", "50:54:00:00:00:04", \
+         "2001::1")
+
+# Wait until all the services are set to offline.
+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
+service_monitor | sed '/^$/d' | grep offline | wc -l`])
+
+# Start webservers in 'sw0-p1' and 'sw1-p1'.
+OVS_START_L7([sw0-p1], [http6])
+sw0_p1_pid_file=$(cat l7_pid_file)
+OVS_START_L7([sw1-p1], [http6])
+
+# Wait until the services are set to online.
+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
+service_monitor | sed '/^$/d' | grep online | wc -l`])
+
+OVS_WAIT_UNTIL(
+    [ovn-sbctl dump-flows sw0 | grep ct_lb_mark | grep priority=120 | grep 
"ip6.dst == 2001::a" > lflows.txt
+     test 1 = `cat lflows.txt | grep 
"ct_lb_mark(backends=[\[2001::3\]]:80,[\[2002::3\]]:80)" | wc -l`]
+)
+
+# From sw0-p2 send traffic to vip - 2001::a
+for i in `seq 1 20`; do
+    echo Request $i
+    ovn-sbctl list service_monitor
+    NS_CHECK_EXEC([sw0-p2], [wget http://[[2001::a]] -t 5 -T 1 
--retry-connrefused -v -o wget$i.log])
+done
+
+dnl Each server should have at least one connection.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(2001::a) | grep -v fe80 
| \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+tcp,orig=(src=2001::4,dst=2001::a,sport=<cleared>,dport=<cleared>),reply=(src=2001::3,dst=2001::4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+tcp,orig=(src=2001::4,dst=2001::a,sport=<cleared>,dport=<cleared>),reply=(src=2002::3,dst=2001::4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+])
+
+# Stop webserver in sw0-p1
+kill `cat $sw0_p1_pid_file`
+
+# Wait until service_monitor for sw0-p1 is set to offline
+OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns status find \
+service_monitor logical_port=sw0-p1 | sed '/^$/d' | grep offline | wc -l`])
+
+OVS_WAIT_UNTIL(
+    [ovn-sbctl dump-flows sw0 | grep ct_lb_mark | grep priority=120 | grep 
"ip6.dst == 2001::a" > lflows.txt
+     test 1 = `cat lflows.txt | grep "ct_lb_mark(backends=[\[2002::3\]]:80)" | 
wc -l`]
+)
+
+ovs-appctl dpctl/flush-conntrack
+# From sw0-p2 send traffic to vip - 2001::a
+for i in `seq 1 20`; do
+    echo Request $i
+    NS_CHECK_EXEC([sw0-p2], [wget http://[[2001::a]] -t 5 -T 1 
--retry-connrefused -v -o wget$i.log])
+done
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(2001::a) | grep -v fe80 
| \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+tcp,orig=(src=2001::4,dst=2001::a,sport=<cleared>,dport=<cleared>),reply=(src=2002::3,dst=2001::4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+])
+
+# trigger port binding release and check if status changed to offline
+ovs-vsctl remove interface ovs-sw1-p1 external_ids iface-id
+wait_row_count Service_Monitor 2
+wait_row_count Service_Monitor 2 status=offline
+
+ovs-vsctl set interface ovs-sw1-p1 external_ids:iface-id=sw1-p1
+wait_row_count Service_Monitor 2
+wait_row_count Service_Monitor 1 status=online
+
+# Create udp load balancer.
+#ovn-nbctl lb-add lb2 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 udp
+#lb_udp=`ovn-nbctl lb-list | grep udp | awk '{print $1}'`
+#
+#echo "lb udp uuid = $lb_udp"
+#
+#ovn-nbctl list load_balancer
+#
+#ovn-nbctl --wait=sb set load_balancer $lb_udp 
ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2
+#ovn-nbctl --wait=sb set load_balancer $lb_udp 
ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2
+#
+#ovn-nbctl --wait=sb -- --id=@hc create \
+#Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer $lb_udp \
+#health_check @hc
+#
+#ovn-nbctl --wait=sb ls-lb-add sw0 lb2
+#ovn-nbctl --wait=sb ls-lb-add sw1 lb2
+#ovn-nbctl --wait=sb lr-lb-add lr0 lb2
+#
+#sleep 10
+#
+#ovn-nbctl list load_balancer
+#echo "*******Next is health check*******"
+#ovn-nbctl list Load_Balancer_Health_Check
+#echo "********************"
+#ovn-sbctl list service_monitor
+#
+## Wait until udp service_monitor are set to offline
+#OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
+#service_monitor protocol=udp | sed '/^$/d' | grep offline | wc -l`])
+#
+## Stop webserver in sw1-p1
+#pid_file=$(cat l7_pid_file)
+#NS_CHECK_EXEC([sw1-p1], [kill $(cat $pid_file)])
+#
+#NS_CHECK_EXEC([sw0-p2], [tcpdump -c 1 -neei sw0-p2 ip[[33:1]]=0x14 > rst.pcap 
&])
+#OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
+#service_monitor protocol=tcp | sed '/^$/d' | grep offline | wc -l`])
+#NS_CHECK_EXEC([sw0-p2], [wget 10.0.0.10 -v -o wget$i.log],[4])
+#
+#OVS_WAIT_UNTIL([
+#    n_reset=$(cat rst.pcap | wc -l)
+#    test "${n_reset}" = "1"
+#])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d
+/Service monitor not found.*/d"])
+
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([Load Balancer LS hairpin IPv4])
 AT_SKIP_IF([test $HAVE_NC = no])
-- 
2.38.1


_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to