Load Balancer Health Checks require specifying "source IP". This IP
has to be from the subnet of the monitored LB backend and should not
be used by any other port in the LSP. If the "source IP" is also used
by some other port, the host with the LB backend won't be able to communicate
with this port due to the ARP responder rule installed by the controller.
This limitation forces CMSes to reserve an IP per LSP with LB backend, which
is not always practical or possible.
This change allows usage of LRP IPs as the source of LB health check
probes. It introduces following changes for such scenario:
* service_monitor (SBDB) will use MAC address of the LRP
* ARP responder rule is not necessary in this case
* Destination IP, "inport" and source port will be used to determine that a
packet is a response to a health check probe. These packets will be
redirected to the controller.
Behavior is unchanged for the LB health checks that use reserved/unused
"source IPs".
Signed-off-by: Martin Kalcok <[email protected]>
---
RFC -> v2
* Reduce number of iterations when searching for monitor source IP in ports
by searching only in lrp_networks of LRPs, using `lrp_find_member_ip`.
* Narrowed down match for health check replies by matching
"inport == <monitored_lb_backend_port>"
* ICMP unreachable messages are also forwarded to the controller to allow
for monitoring UDP ports and detecting when they are down.
* Flows that send monitor replies to the controller are still inserted into
LS datapath, but the logic is tied to the LR. i.e.: Whether to add the
flows is evaluated when LB is attached to LR.
It removes the necessity to add LB to the LS as well. It is enough to add
it to the LR.
* IPv6 support added
* System and Unit tests added
* existing LB and Service Monitor checks were already quite involved, so
I opted to add new set of tests that focus specifically on a setup
with LRP IPs as a source for service checks.
* Documentation of "ip_port_mappings" field in "Load_Balancer" was extended
to explicitly state requirements for the source_ip.
v2 -> v3
* rebased on `main`
* `vector_get` replaced with `sparse_array_get`
* `ovn_lflow_metered` replace with WITH_CTRL_METER macro
* Addressed review comments
* NEWS entry added
* Formatting and comments fixed
* Formatting of "match" rules in `build_lb_health_check_response_lflows`
split for ipv4 and ipv6 cases.
* [tests] added `check` to ovs-vsctl calls
* [tests] dropped unnecessary --wait=sb
* Bug found: Function `build_lb_health_check_response_lflows` was, on each
call, iterating over backends of the same `ovn_northd_lb_vip` instance
(see in v2: `&lb->vips_nb->backends_nb[j++];`). This caused occasional
segfaults and was replaced by passing "current" `ovn_northd_lb_vip` instance
as an argument to the function.
NEWS | 5 +
northd/northd.c | 143 +++++++++++++++-
ovn-nb.xml | 5 +-
tests/ovn.at | 325 +++++++++++++++++++++++++++++++++++
tests/system-ovn.at | 400 ++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 875 insertions(+), 3 deletions(-)
diff --git a/NEWS b/NEWS
index b7268c835..2a2b5e12d 100644
--- a/NEWS
+++ b/NEWS
@@ -93,6 +93,11 @@ Post v25.09.0
other_config column.
- Introduce the capability to specify multiple ips for ovn-evpn-local-ip
option.
+ - Load balancer health checks can now use Logical Router Port IPs as the
+ source IP for health check probes. Previously, health checks required
+ reserving an unused IP from the backend's subnet. This change allows
+ using LRP IPs directly, eliminating the need to reserve additional IPs
+ per backend port.
OVN v25.09.0 - xxx xx xxxx
--------------------------
diff --git a/northd/northd.c b/northd/northd.c
index adaa94e85..1e4e24216 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -3260,6 +3260,28 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
continue;
}
+ /* If the service monitor is backed by a real port, use its MAC
+ * address instead of the default service check MAC. */
+ const char *source_mac = svc_monitor_mac;
+ const struct eth_addr *source_mac_ea = svc_monitor_mac_ea;
+ if (op) {
+ struct ovn_port *svc_mon_op;
+ VECTOR_FOR_EACH (&op->od->router_ports, svc_mon_op) {
+ if (!svc_mon_op->peer) {
+ continue;
+ }
+ const char *lrp_ip = lrp_find_member_ip(
+ svc_mon_op->peer,
+ backend_nb->svc_mon_src_ip);
+ if (lrp_ip && !strcmp(lrp_ip,
+ backend_nb->svc_mon_src_ip)) {
+ source_mac = svc_mon_op->peer->lrp_networks.ea_s;
+ source_mac_ea = &svc_mon_op->peer->lrp_networks.ea;
+ break;
+ }
+ }
+ }
+
const char *protocol = lb->nlb->protocol;
if (!protocol || !protocol[0]) {
protocol = "tcp";
@@ -3289,9 +3311,9 @@ ovn_lb_svc_create(struct ovsdb_idl_txn *ovnsb_txn,
struct eth_addr ea;
if (!mon_info->sbrec_mon->src_mac ||
!eth_addr_from_string(mon_info->sbrec_mon->src_mac, &ea) ||
- !eth_addr_equals(ea, *svc_monitor_mac_ea)) {
+ !eth_addr_equals(ea, *source_mac_ea)) {
sbrec_service_monitor_set_src_mac(mon_info->sbrec_mon,
- svc_monitor_mac);
+ source_mac);
}
if (!mon_info->sbrec_mon->src_ip ||
@@ -8499,6 +8521,99 @@ build_lb_rules_for_stateless_acl(struct lflow_table
*lflows,
}
}
+static void
+build_lb_health_check_response_lflows(
+ struct lflow_table *lflows,
+ const struct ovn_northd_lb *lb,
+ const struct ovn_lb_vip *lb_vip,
+ const struct ovn_northd_lb_vip *lb_vip_nb,
+ const struct ovn_lb_datapaths *lb_dps,
+ const struct ovn_datapaths *lr_datapaths,
+ const struct shash *meter_groups,
+ struct ds *match,
+ struct ds *action)
+{
+ /* For each LB backend that is monitored by a source_ip belonging
+ * to a real LRP, install rule that punts service check replies to the
+ * controller. */
+ size_t j = 0;
+ const struct ovn_lb_backend *backend;
+ VECTOR_FOR_EACH_PTR (&lb_vip->backends, backend) {
+ struct ovn_northd_lb_backend *backend_nb =
+ &lb_vip_nb->backends_nb[j++];
+
+ if (!backend_nb->health_check) {
+ continue;
+ }
+
+ const char *protocol = lb->nlb->protocol;
+ if (!protocol || !protocol[0]) {
+ protocol = "tcp";
+ }
+
+ size_t index;
+ DYNAMIC_BITMAP_FOR_EACH_1 (index, &lb_dps->nb_lr_map) {
+ struct ovn_datapath *od = sparse_array_get(&lr_datapaths->dps,
+ index);
+ /* Only install the rule if the datapath has a port with
+ * monitor source IP. */
+ struct ovn_datapath *peer_switch_od = NULL;
+ struct ovn_port *svc_mon_op;
+
+ HMAP_FOR_EACH (svc_mon_op, dp_node, &od->ports) {
+ if (!svc_mon_op->peer) {
+ continue;
+ }
+ const char *lrp_ip = lrp_find_member_ip(
+ svc_mon_op,
+ backend_nb->svc_mon_src_ip);
+ if (lrp_ip &&
+ !strcmp(lrp_ip, backend_nb->svc_mon_src_ip)) {
+ peer_switch_od = svc_mon_op->peer->od;
+ break;
+ }
+ }
+ if (!peer_switch_od) {
+ continue;
+ }
+
+ ds_clear(match);
+ ds_clear(action);
+
+ /* icmp6 type 1 and icmp4 type 3 are included as well, because
+ * the controller is using them to detect unreachable ports. */
+ if (addr_is_ipv6(backend_nb->svc_mon_src_ip)) {
+ ds_put_format(match,
+ "inport == \"%s\" && ip6.dst == %s && "
+ "(%s.src == %s || icmp6.type == 1)",
+ backend_nb->logical_port,
+ backend_nb->svc_mon_src_ip,
+ protocol,
+ backend->port_str);
+ } else {
+ ds_put_format(match,
+ "inport == \"%s\" && ip4.dst == %s && "
+ "(%s.src == %s || icmp4.type == 3)",
+ backend_nb->logical_port,
+ backend_nb->svc_mon_src_ip,
+ protocol,
+ backend->port_str);
+ }
+
+ /* ovn-controller expects health check responses from the LS
+ * datapath in which the backend is located. That's why we
+ * install the response lflow into the peer's datapath. */
+ const char *meter = copp_meter_get(COPP_SVC_MONITOR,
+ peer_switch_od->nbs->copp,
+ meter_groups);
+ ovn_lflow_add(lflows, peer_switch_od, S_SWITCH_IN_LB, 160,
+ ds_cstr(match), "handle_svc_check(inport);",
+ lb_dps->lflow_ref,
+ WITH_CTRL_METER(meter));
+ }
+ }
+}
+
static void
build_lb_rules(struct lflow_table *lflows, struct ovn_lb_datapaths *lb_dps,
const struct ovn_datapaths *ls_datapaths,
@@ -10349,6 +10464,26 @@ build_lswitch_arp_nd_local_svc_mon(const struct
ovn_lb_datapaths *lb_dps,
continue;
}
+ /* ARP responder is necessary only if the service check is not
+ * backed by a real port and an IP. */
+ struct ovn_port *svc_mon_op;
+ bool port_found = false;
+ VECTOR_FOR_EACH (&op->od->router_ports, svc_mon_op) {
+ if (!svc_mon_op->peer) {
+ continue;
+ }
+ const char *lrp_ip = lrp_find_member_ip(
+ svc_mon_op->peer,
+ backend_nb->svc_mon_src_ip);
+ if (lrp_ip && !strcmp(lrp_ip, backend_nb->svc_mon_src_ip)) {
+ port_found = true;
+ break;
+ }
+ }
+ if (port_found) {
+ continue;
+ }
+
ds_clear(match);
ds_clear(actions);
@@ -13136,6 +13271,10 @@ build_lrouter_flows_for_lb(struct ovn_lb_datapaths
*lb_dps,
build_lrouter_allow_vip_traffic_template(lflows, lb_dps, lb_vip, lb,
lr_datapaths);
+ build_lb_health_check_response_lflows(
+ lflows, lb, lb_vip, &lb->vips_nb[i], lb_dps, lr_datapaths,
+ meter_groups, match, action);
+
if (!build_empty_lb_event_flow(lb_vip, lb, match, action)) {
continue;
}
diff --git a/ovn-nb.xml b/ovn-nb.xml
index e9ca27413..2ddc3bc20 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -2402,7 +2402,10 @@
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>
+ <code><var>port_name</var>:<var>[sourc_ip]</var></code>. The source
+ IP must be from the subnet of the monitored endpoint. It can be
+ either an unused IP from the subnet, or an IP of one of the Logical
+ Router Ports connected to the same switch.
Remote endpoint:
Specify :target_zone_name at the end of the above syntax to create
remote health checks in a specific zone.
diff --git a/tests/ovn.at b/tests/ovn.at
index 4d15d4a62..782fd0256 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -26972,6 +26972,331 @@
ignored_tables=OFTABLE_CHK_LB_HAIRPIN,OFTABLE_CT_SNAT_HAIRPIN])
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Load balancer health checks - LRP source IP - IPv4])
+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 10.0.0.3"
+check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3"
+
+check ovn-nbctl lsp-add sw0 sw0-p2
+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4"
+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4"
+
+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 20.0.0.3"
+check ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3"
+
+# 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 10.0.0.1/24
+check ovn-nbctl lsp-add-router-port sw0 sw0-lr0 lr0-sw0
+
+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
+check ovn-nbctl lsp-add-router-port sw1 sw1-lr0 lr0-sw1
+
+# Create TCP LB and configure health checks using router port IPs as source.
+check ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80
+check ovn-nbctl set load_balancer lb1 ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.1
+check ovn-nbctl set load_balancer lb1 ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.1
+
+AT_CHECK([ovn-nbctl --wait=sb \
+ -- --id=@hc create Load_Balancer_Health_Check vip="10.0.0.10\:80" \
+ -- add Load_Balancer lb1 health_check @hc | uuidfilt], [0], [<0>
+])
+
+# Create UDP LB with health check using router port IP.
+check ovn-nbctl lb-add lb2 10.0.0.11:53 10.0.0.4:53 udp
+check ovn-nbctl set load_balancer lb2 ip_port_mappings:10.0.0.4=sw0-p2:10.0.0.1
+
+AT_CHECK([ovn-nbctl --wait=sb \
+ -- --id=@hc create Load_Balancer_Health_Check vip="10.0.0.11\:53" \
+ -- add Load_Balancer lb2 health_check @hc | uuidfilt], [0], [<0>
+])
+
+check ovn-nbctl --wait=sb lr-lb-add lr0 lb1
+check ovn-nbctl --wait=sb lr-lb-add lr0 lb2
+
+OVN_POPULATE_ARP
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+wait_row_count Service_Monitor 3
+
+# Verify that Service_Monitor uses LRP MAC address, not svc_monitor_mac.
+lrp_sw0_mac=$(ovn-nbctl get Logical_Router_Port lr0-sw0 mac | sed 's/"//g')
+lrp_sw1_mac=$(ovn-nbctl get Logical_Router_Port lr0-sw1 mac | sed 's/"//g')
+
+svc_mon_sw0_p1_mac=$(ovn-sbctl --bare --columns src_mac find Service_Monitor
logical_port=sw0-p1)
+svc_mon_sw0_p2_mac=$(ovn-sbctl --bare --columns src_mac find Service_Monitor
logical_port=sw0-p2)
+svc_mon_sw1_mac=$(ovn-sbctl --bare --columns src_mac find Service_Monitor
logical_port=sw1-p1)
+
+AT_CHECK([test "$svc_mon_sw0_p1_mac" = "$lrp_sw0_mac"])
+AT_CHECK([test "$svc_mon_sw0_p2_mac" = "$lrp_sw0_mac"])
+AT_CHECK([test "$svc_mon_sw1_mac" = "$lrp_sw1_mac"])
+
+# Verify priority 160 flows exist for punting health check replies to
controller.
+AT_CAPTURE_FILE([sbflows1])
+ovn-sbctl dump-flows sw0 > sbflows1
+AT_CAPTURE_FILE([sbflows2])
+ovn-sbctl dump-flows sw1 > sbflows2
+
+## TCP Load balancer health check flows.
+# TCP backend - verify flow matches on inport, destination IP, and TCP source
port.
+AT_CHECK([grep "priority=160" sbflows1 | grep "handle_svc_check" | \
+ grep 'inport == "sw0-p1"' | grep "ip4.dst == 10.0.0.1" | \
+ grep -q "tcp.src == 80" ])
+
+AT_CHECK([grep "priority=160" sbflows2 | grep "handle_svc_check" | \
+ grep 'inport == "sw1-p1"' | grep "ip4.dst == 20.0.0.1" | \
+ grep -q "tcp.src == 80" ])
+
+
+# Verify flow also matches ICMP unreachable (type 3 for IPv4).
+AT_CHECK([grep "priority=160" sbflows1 | grep "handle_svc_check" | \
+ grep 'inport == "sw0-p1"' | grep "ip4.dst == 10.0.0.1" | \
+ grep -q "icmp4.type == 3"])
+
+AT_CHECK([grep "priority=160" sbflows2 | grep "handle_svc_check" | \
+ grep 'inport == "sw1-p1"' | grep "ip4.dst == 20.0.0.1" | \
+ grep -q "icmp4.type == 3"])
+
+## UDP Load balancer health check flows.
+# UDP backend - verify flow uses UDP instead of TCP.
+AT_CHECK([grep "priority=160" sbflows1 | grep "handle_svc_check" | \
+ grep 'inport == "sw0-p2"' | grep "ip4.dst == 10.0.0.1" |\
+ grep -q "udp.src == 53"])
+
+# Verify NO TCP match in the UDP backend flow.
+AT_CHECK([grep "priority=160" sbflows1 | grep "handle_svc_check" | \
+ grep 'inport == "sw0-p2"' | grep "tcp.src"], [1])
+
+# Verify NO ARP responder flows using svc_monitor_mac for the router port IPs.
+# The router port IPs should use the LRP MAC, not the global svc_monitor_mac.
+svc_monitor_mac=$(ovn-nbctl get NB_Global . options:svc_monitor_mac | sed
's/"//g')
+AT_CHECK([test -n "$svc_monitor_mac"])
+
+AT_CHECK([grep "arp.tpa == 10.0.0.1" sbflows1 | \
+ grep "arp.sha = $svc_monitor_mac"], [1])
+AT_CHECK([grep "arp.tpa == 20.0.0.1" sbflows2 | \
+ grep "arp.sha = $svc_monitor_mac"], [1])
+
+# Verify health check packets are sent with LRP MAC as source.
+lrp_sw0_mac_hex=$(echo $lrp_sw0_mac | sed 's/://g')
+lrp_sw1_mac_hex=$(echo $lrp_sw1_mac | sed 's/://g')
+
+OVS_WAIT_UNTIL(
+ [test 1 -le `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap
| \
+ grep "505400000003${lrp_sw0_mac_hex}" | wc -l`]
+)
+
+OVS_WAIT_UNTIL(
+ [test 1 -le `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap
| \
+ grep "505400000004${lrp_sw0_mac_hex}" | wc -l`]
+)
+
+OVS_WAIT_UNTIL(
+ [test 1 -le `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/vif1-tx.pcap
| \
+ grep "405400000003${lrp_sw1_mac_hex}" | wc -l`]
+)
+
+OVN_CLEANUP([hv1], [hv2])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Load balancer health checks - LRP source IP - 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"
+
+check ovn-nbctl lsp-add sw0 sw0-p2
+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 2001::4"
+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 2001::4"
+
+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 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-router-port sw0 sw0-lr0 lr0-sw0
+
+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 2002::1/64
+check ovn-nbctl lsp-add-router-port sw1 sw1-lr0 lr0-sw1
+
+# Create TCP LB and configure health checks using router port IPs as source.
+check ovn-nbctl lb-add lb1 [[2001::a]]:80 [[2001::3]]:80,[[2002::3]]:80
+check ovn-nbctl set load_balancer lb1
ip_port_mappings:\"[[2001::3]]\"=\"sw0-p1:[[2001::1]]\"
+check ovn-nbctl set load_balancer lb1
ip_port_mappings:\"[[2002::3]]\"=\"sw1-p1:[[2002::1]]\"
+
+AT_CHECK([ovn-nbctl --wait=sb \
+ -- --id=@hc create Load_Balancer_Health_Check
vip="\[\[2001\:\:a\]\]\:80" \
+ -- add Load_Balancer lb1 health_check @hc | uuidfilt], [0], [<0>
+])
+
+# Create UDP LB with health check using router port IP.
+check ovn-nbctl lb-add lb2 [[2001::b]]:53 [[2001::4]]:53 udp
+check ovn-nbctl set load_balancer lb2
ip_port_mappings:\"[[2001::4]]\"=\"sw0-p2:[[2001::1]]\"
+
+AT_CHECK([ovn-nbctl --wait=sb \
+ -- --id=@hc create Load_Balancer_Health_Check
vip="\[\[2001\:\:b\]\]\:53" \
+ -- add Load_Balancer lb2 health_check @hc | uuidfilt], [0], [<0>
+])
+
+check ovn-nbctl --wait=sb lr-lb-add lr0 lb1
+check ovn-nbctl --wait=sb lr-lb-add lr0 lb2
+
+OVN_POPULATE_ARP
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+wait_row_count Service_Monitor 3
+
+# Verify that Service_Monitor uses LRP MAC address.
+lrp_sw0_mac=$(ovn-nbctl get Logical_Router_Port lr0-sw0 mac | sed 's/"//g')
+lrp_sw1_mac=$(ovn-nbctl get Logical_Router_Port lr0-sw1 mac | sed 's/"//g')
+
+svc_mon_sw0_p1_mac=$(ovn-sbctl --bare --columns src_mac find Service_Monitor
logical_port=sw0-p1)
+svc_mon_sw0_p2_mac=$(ovn-sbctl --bare --columns src_mac find Service_Monitor
logical_port=sw0-p2)
+svc_mon_sw1_mac=$(ovn-sbctl --bare --columns src_mac find Service_Monitor
logical_port=sw1-p1)
+
+AT_CHECK([test "$svc_mon_sw0_p1_mac" = "$lrp_sw0_mac"])
+AT_CHECK([test "$svc_mon_sw0_p2_mac" = "$lrp_sw0_mac"])
+AT_CHECK([test "$svc_mon_sw1_mac" = "$lrp_sw1_mac"])
+
+# Verify priority 160 flows exist for punting health check replies to
controller.
+AT_CAPTURE_FILE([sbflows1])
+ovn-sbctl dump-flows sw0 > sbflows1
+AT_CAPTURE_FILE([sbflows2])
+ovn-sbctl dump-flows sw1 > sbflows2
+
+## TCP Load balancer health check flows.
+# TCP backend - verify flow matches on inport, destination IP, and TCP source
port.
+AT_CHECK([grep "priority=160" sbflows1 | grep "handle_svc_check" | \
+ grep 'inport == "sw0-p1"' | grep "ip6.dst == 2001::1" | \
+ grep -q "tcp.src == 80" ])
+
+AT_CHECK([grep "priority=160" sbflows2 | grep "handle_svc_check" | \
+ grep 'inport == "sw1-p1"' | grep "ip6.dst == 2002::1" | \
+ grep -q "tcp.src == 80" ])
+
+# Verify flow also matches ICMP unreachable (type 1 for IPv6).
+AT_CHECK([grep "priority=160" sbflows1 | grep "handle_svc_check" | \
+ grep 'inport == "sw0-p1"' | grep "ip6.dst == 2001::1" | \
+ grep -q "icmp6.type == 1"])
+
+AT_CHECK([grep "priority=160" sbflows2 | grep "handle_svc_check" | \
+ grep 'inport == "sw1-p1"' | grep "ip6.dst == 2002::1" | \
+ grep -q "icmp6.type == 1"])
+
+## UDP Load balancer health check flows.
+# UDP backend - verify flow uses UDP instead of TCP.
+AT_CHECK([grep "priority=160" sbflows1 | grep "handle_svc_check" | \
+ grep 'inport == "sw0-p2"' | grep "ip6.dst == 2001::1" | \
+ grep -q "udp.src == 53"])
+
+# Verify NO TCP match in the UDP backend flow.
+AT_CHECK([grep "priority=160" sbflows1 | grep "handle_svc_check" | \
+ grep 'inport == "sw0-p2"' | grep "tcp.src"], [1])
+
+# Verify NO IPv6 ND responder flows using svc_monitor_mac for the router port
IPs.
+# The router port IPs should use the LRP MAC, not the global svc_monitor_mac.
+svc_monitor_mac=$(ovn-nbctl get NB_Global . options:svc_monitor_mac | sed
's/"//g')
+AT_CHECK([test -n "$svc_monitor_mac"])
+
+AT_CHECK([ovn-sbctl dump-flows sw0 | grep "nd.target == 2001::1" | \
+ grep "nd.tll = $svc_monitor_mac"], [1])
+AT_CHECK([ovn-sbctl dump-flows sw1 | grep "nd.target == 2002::1" | \
+ grep "nd.tll = $svc_monitor_mac"], [1])
+
+# Verify health check packets are sent with LRP MAC as source.
+lrp_sw0_mac_hex=$(echo $lrp_sw0_mac | sed 's/://g')
+lrp_sw1_mac_hex=$(echo $lrp_sw1_mac | sed 's/://g')
+
+OVS_WAIT_UNTIL(
+ [test 1 -le `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap
| \
+ grep "505400000003${lrp_sw0_mac_hex}" | wc -l`]
+)
+
+OVS_WAIT_UNTIL(
+ [test 1 -le `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap
| \
+ grep "505400000004${lrp_sw0_mac_hex}" | wc -l`]
+)
+
+OVS_WAIT_UNTIL(
+ [test 1 -le `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/vif1-tx.pcap
| \
+ grep "405400000003${lrp_sw1_mac_hex}" | wc -l`]
+)
+
+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 6eaf38a4c..a372d3676 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -4422,6 +4422,406 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port
patch-.*/d
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Load balancer health checks with LRP IP - IPv4])
+AT_KEYWORDS([lb])
+
+# This is a variant of a 'Load balancer health checks - IPv4' test
+# that uses LRP IPs as a source IP for the health checks. As such, it
+# doesn't go so deep into testing and focuses only on "health checks"
+# having proper status and the backend services being reachable.
+#
+# Topology:
+#
+# LB VIP: 172.16.0.42
+# Backends: 10.0.0.3 10.0.0.4
+#
+# .3
+--------------+
+# +------------+ +------ |
sw0-p1 |
+# .42 | | |
+--------------+
+# +--------------+ .2 .1 | | .1 |
+# | sw-ext-p1 | ------- sw-ext ------- | lr0 | ------- sw0
10.0.0.0/24
+# +--------------+ | | |
+# 172.16.0.0/24 | | | .4
+--------------+
+# +------------+ +------ |
sw0-p2 |
+#
+--------------+
+#
+ovn_start
+
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+# Set external-ids in br-int needed for ovn-controller.
+check 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
+
+# Create internal network with LB backends.
+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 10.0.0.3"
+
+check ovn-nbctl lsp-add sw0 sw0-p2
+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4"
+
+# Create external network with LB VIP.
+check ovn-nbctl ls-add sw-ext
+
+check ovn-nbctl lsp-add sw-ext sw-ext-p1
+check ovn-nbctl lsp-set-addresses sw-ext-p1 "60:64:00:00:00:02 172.16.0.2"
+
+# Create a logical router and attach both logical switches.
+check ovn-nbctl lr-add lr0
+check ovn-nbctl set logical_router lr0 options:chassis=hv1
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+check ovn-nbctl lrp-add lr0 lr0-sw-ext 00:00:00:00:ee:01 172.16.0.1/24
+check ovn-nbctl lsp-add-router-port sw0 sw0-lr0 lr0-sw0
+check ovn-nbctl lsp-add-router-port sw-ext sw-ext-lr0 lr0-sw-ext
+
+check ovn-nbctl lb-add lb1 172.16.0.42:80 10.0.0.3:80,10.0.0.4:80
+
+# Load balancer health check will be using LR IP as a source of the health
check.
+check ovn-nbctl set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.1
+check ovn-nbctl set load_balancer . ip_port_mappings:10.0.0.4=sw0-p2:10.0.0.1
+
+check_uuid ovn-nbctl --id=@hc create \
+Load_Balancer_Health_Check vip="172.16.0.42\:80" -- add Load_Balancer . \
+health_check @hc
+
+# LB is bound only to the LR. It shouldn't be necessary to bind it to the
+# LS as well.
+check ovn-nbctl lr-lb-add lr0 lb1
+
+OVN_POPULATE_ARP
+check ovn-nbctl --wait=hv sync
+
+ADD_NAMESPACES(sw0-p1)
+ADD_VETH(sw0-p1, sw0-p1, br-int, "10.0.0.3/24", "50:54:00:00:00:03", \
+ "10.0.0.1")
+
+ADD_NAMESPACES(sw0-p2)
+ADD_VETH(sw0-p2, sw0-p2, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \
+ "10.0.0.1")
+
+ADD_NAMESPACES(sw-ext-p1)
+ADD_VETH(sw-ext-p1, sw-ext-p1, br-int, "172.16.0.2/24", "60:64:00:00:00:02", \
+ "172.16.0.1")
+
+wait_row_count Service_Monitor 2 status=offline
+
+# Start webservers in 'sw0-p1' and 'sw1-p1'.
+OVS_START_L7([sw0-p1], [http])
+sw0_p1_pid_file=`cat l7_pid_file`
+OVS_START_L7([sw0-p2], [http])
+
+wait_row_count Service_Monitor 2 status=online
+
+for i in `seq 1 10`; do
+ NS_CHECK_EXEC([sw-ext-p1], [curl --connect-timeout 5 http://172.16.0.42 >
curl$i.log 2>&1 ])
+done
+
+# Stop webserver in sw0-p1.
+kill `cat $sw0_p1_pid_file`
+
+wait_row_count Service_Monitor 1 logical_port=sw0-p1 status=offline
+wait_row_count Service_Monitor 1 logical_port=sw0-p2 status=online
+
+for i in `seq 11 20`; do
+ NS_CHECK_EXEC([sw-ext-p1], [curl --connect-timeout 5 http://172.16.0.42 >
curl$i.log 2>&1 ])
+done
+
+# Trigger port binding release and check if status changed to offline.
+check ovs-vsctl remove interface ovs-sw0-p2 external_ids iface-id
+wait_row_count Service_Monitor 2
+wait_row_count Service_Monitor 2 status=offline
+
+check ovs-vsctl set interface ovs-sw0-p2 external_ids:iface-id=sw0-p2
+wait_row_count Service_Monitor 2
+wait_row_count Service_Monitor 1 status=online
+
+# Remove TCP Load Balancer.
+check ovn-nbctl lb-del lb1
+
+# Create UDP load balancer.
+check ovn-nbctl lb-add lb2 172.16.0.42:53 10.0.0.3:53,10.0.0.4:53 udp
+lb_udp=`ovn-nbctl lb-list | grep udp | awk '{print $1}'`
+
+check ovn-nbctl set load_balancer $lb_udp
ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.1
+check ovn-nbctl set load_balancer $lb_udp
ip_port_mappings:10.0.0.4=sw0-p2:10.0.0.1
+
+check_uuid ovn-nbctl --id=@hc create \
+Load_Balancer_Health_Check vip="172.16.0.42\:53" -- add Load_Balancer $lb_udp \
+health_check @hc
+
+check ovn-nbctl --wait=hv lr-lb-add lr0 lb2
+
+# Wait until udp service_monitor are set to offline.
+wait_row_count Service_Monitor 2 status=offline protocol=udp
+
+# We need to use "-k" argument because we keep receiving health check probes.
+NETNS_DAEMONIZE([sw0-p1], [nc -4 -k -l -u -p 53 -d 0.1 -c '/bin/cat'],
[udp1.pid])
+NETNS_DAEMONIZE([sw0-p2], [nc -4 -k -l -u -p 53 -d 0.1 -c '/bin/cat'],
[udp2.pid])
+
+# UDP nc with "-k" is bit tricky to cleanup, especially on test failures, due
to its
+# child processes keeping the listening port open. If they are not cleaned up
properly,
+# the whole test cleanup hangs.
+udp1_pid=$(cat udp1.pid)
+udp2_pid=$(cat udp2.pid)
+echo "pkill -P $udp1_pid && kill $udp1_pid" >> cleanup
+echo "pkill -P $udp2_pid && kill $udp2_pid" >> cleanup
+:> udp1.pid
+:> udp2.pid
+
+wait_row_count Service_Monitor 2 status=online
+
+NETNS_DAEMONIZE(sw-ext-p1, [nc -4 -u 172.16.0.42 53 -d 0.1 -c 'for i in $(seq
1 5); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data_1; done'],
[udp1_client.pid])
+
+OVS_WAIT_FOR_OUTPUT([cat ./udp_data_1], [0], [dnl
+udp_data 1
+udp_data 2
+udp_data 3
+udp_data 4
+udp_data 5
+])
+
+# Stop UDP service in sw0-p1.
+# Due to the way UDP nc with "-k" forks, we need to kill all
+# children processes and then then the parent.
+NS_CHECK_EXEC([sw0-p1], [pkill -P $udp1_pid && kill $udp1_pid])
+
+wait_row_count Service_Monitor 1 logical_port=sw0-p1 status=offline
+wait_row_count Service_Monitor 1 logical_port=sw0-p2 status=online
+
+NETNS_DAEMONIZE(sw-ext-p1, [nc -4 -u 172.16.0.42 53 -d 0.1 -c 'for i in $(seq
1 5); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data_2; done'],
[udp2_client.pid])
+
+OVS_WAIT_FOR_OUTPUT([cat ./udp_data_2], [0], [dnl
+udp_data 1
+udp_data 2
+udp_data 3
+udp_data 4
+udp_data 5
+])
+
+OVN_CLEANUP_CONTROLLER([hv1])
+
+OVN_CLEANUP_NORTHD
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d
+/Service monitor not found.*/d
+/handle service check: Unsupported protocol*/d"])
+
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Load balancer health checks with LRP IP - IPv6])
+AT_KEYWORDS([lb])
+
+# This is a variant of a 'Load balancer health checks - IPv6' test
+# that uses LRP IPs as a source IP for the health checks. As such, it
+# doesn't go so deep into testing and focuses only on "health checks"
+# having proper status and the backend services being reachable.
+#
+# Topology:
+#
+# LB VIP: fd20::42
+# Backends: fd11::3 fd11::4
+#
+# ::3
+--------------+
+# +------------+ +------ |
sw0-p1 |
+# ::42 | | |
+--------------+
+# +--------------+ ::2 ::1 | | ::1 |
+# | sw-ext-p1 | ------- sw-ext ------- | lr0 | ------- sw0
fd11::/64
+# +--------------+ | | |
+# fd20::/64 | | | ::4
+--------------+
+# +------------+ +------ |
sw0-p2 |
+#
+--------------+
+#
+ovn_start
+
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+# Set external-ids in br-int needed for ovn-controller.
+check 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
+
+# Create internal network with LB backends.
+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 fd11::3"
+
+check ovn-nbctl lsp-add sw0 sw0-p2
+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 fd11::4"
+
+# Create external network with LB VIP.
+check ovn-nbctl ls-add sw-ext
+
+check ovn-nbctl lsp-add sw-ext sw-ext-p1
+check ovn-nbctl lsp-set-addresses sw-ext-p1 "60:64:00:00:00:02 fd20::2"
+
+# Create a logical router and attach both logical switches.
+check ovn-nbctl lr-add lr0
+check ovn-nbctl set logical_router lr0 options:chassis=hv1
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 fd11::1/64
+check ovn-nbctl lrp-add lr0 lr0-sw-ext 00:00:00:00:ee:01 fd20::1/64
+check ovn-nbctl lsp-add-router-port sw0 sw0-lr0 lr0-sw0
+check ovn-nbctl lsp-add-router-port sw-ext sw-ext-lr0 lr0-sw-ext
+
+check ovn-nbctl lb-add lb1 [[fd20::42]]:80 [[fd11::3]]:80,[[fd11::4]]:80
+
+# Load balancer health check will be using LR IP as a source of the health
check.
+check ovn-nbctl set load_balancer .
ip_port_mappings:\"[[fd11::3]]\"=\"sw0-p1:[[fd11::1]]\"
+check ovn-nbctl set load_balancer .
ip_port_mappings:\"[[fd11::4]]\"=\"sw0-p2:[[fd11::1]]\"
+
+check_uuid ovn-nbctl --id=@hc create \
+Load_Balancer_Health_Check vip="\[\[fd20\:\:42\]\]\:80" -- add Load_Balancer .
\
+health_check @hc
+
+# LB is bound only to the LR. It shouldn't be necessary to bind it to the
+# LS as well.
+check ovn-nbctl lr-lb-add lr0 lb1
+
+OVN_POPULATE_ARP
+check ovn-nbctl --wait=hv sync
+
+ADD_NAMESPACES(sw0-p1)
+ADD_VETH(sw0-p1, sw0-p1, br-int, "fd11::3/64", "50:54:00:00:00:03", \
+ "fd11::1")
+
+ADD_NAMESPACES(sw0-p2)
+ADD_VETH(sw0-p2, sw0-p2, br-int, "fd11::4/64", "50:54:00:00:00:04", \
+ "fd11::1")
+
+ADD_NAMESPACES(sw-ext-p1)
+ADD_VETH(sw-ext-p1, sw-ext-p1, br-int, "fd20::2/64", "60:64:00:00:00:02", \
+ "fd20::1")
+
+wait_row_count Service_Monitor 2 status=offline
+
+# Start webservers in 'sw0-p1' and 'sw0-p2'.
+OVS_START_L7([sw0-p1], [http6])
+sw0_p1_pid_file=`cat l7_pid_file`
+OVS_START_L7([sw0-p2], [http6])
+
+wait_row_count Service_Monitor 2 status=online
+
+for i in `seq 1 10`; do
+ NS_CHECK_EXEC([sw-ext-p1], [curl --connect-timeout 5 http://[[fd20::42]] >
curl$i.log 2>&1 ])
+done
+
+# Stop webserver in sw0-p1.
+kill `cat $sw0_p1_pid_file`
+
+wait_row_count Service_Monitor 1 logical_port=sw0-p1 status=offline
+wait_row_count Service_Monitor 1 logical_port=sw0-p2 status=online
+
+for i in `seq 11 20`; do
+ NS_CHECK_EXEC([sw-ext-p1], [curl --connect-timeout 5 http://[[fd20::42]] >
curl$i.log 2>&1 ])
+done
+
+# Trigger port binding release and check if status changed to offline.
+check ovs-vsctl remove interface ovs-sw0-p2 external_ids iface-id
+wait_row_count Service_Monitor 2
+wait_row_count Service_Monitor 2 status=offline
+
+check ovs-vsctl set interface ovs-sw0-p2 external_ids:iface-id=sw0-p2
+wait_row_count Service_Monitor 2
+wait_row_count Service_Monitor 1 status=online
+
+# Remove TCP Load Balancer.
+check ovn-nbctl lb-del lb1
+
+# Create udp load balancer.
+check ovn-nbctl lb-add lb2 [[fd20::42]]:53 [[fd11::3]]:53,[[fd11::4]]:53 udp
+lb_udp=`ovn-nbctl lb-list | grep udp | awk '{print $1}'`
+
+check ovn-nbctl set load_balancer $lb_udp
ip_port_mappings:\"[[fd11::3]]\"=\"sw0-p1:[[fd11::1]]\"
+check ovn-nbctl set load_balancer $lb_udp
ip_port_mappings:\"[[fd11::4]]\"=\"sw0-p2:[[fd11::1]]\"
+
+check_uuid ovn-nbctl --id=@hc create \
+Load_Balancer_Health_Check vip="\[\[fd20\:\:42\]\]\:53" -- add Load_Balancer
$lb_udp \
+health_check @hc
+
+check ovn-nbctl --wait=hv lr-lb-add lr0 lb2
+
+# Wait until udp service_monitor are set to offline.
+wait_row_count Service_Monitor 2 status=offline protocol=udp
+
+# We need to use "-k" argument because we keep receiving health check probes.
+NETNS_DAEMONIZE([sw0-p1], [nc -6 -k -l -u -p 53 -d 0.1 -c '/bin/cat'],
[udp1.pid])
+NETNS_DAEMONIZE([sw0-p2], [nc -6 -k -l -u -p 53 -d 0.1 -c '/bin/cat'],
[udp2.pid])
+
+# UDP nc with "-k" is bit tricky to cleanup, especially on test failures, due
to its
+# child processes keeping the listening port open. If they are not cleaned up
properly,
+# the whole test cleanup hangs.
+udp1_pid=$(cat udp1.pid)
+udp2_pid=$(cat udp2.pid)
+echo "pkill -P $udp1_pid && kill $udp1_pid" >> cleanup
+echo "pkill -P $udp2_pid && kill $udp2_pid" >> cleanup
+:> udp1.pid
+:> udp2.pid
+
+wait_row_count Service_Monitor 2 status=online
+
+NETNS_DAEMONIZE(sw-ext-p1, [nc -6 -u fd20::42 53 -d 0.1 -c 'for i in $(seq 1
5); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data_1; done'],
[udp1_client.pid])
+
+OVS_WAIT_FOR_OUTPUT([cat ./udp_data_1], [0], [dnl
+udp_data 1
+udp_data 2
+udp_data 3
+udp_data 4
+udp_data 5
+])
+
+# Stop UDP service in sw0-p1.
+# Due to the way UDP nc with "-k" forks, we need to kill all
+# children processes and then then the parent.
+NS_CHECK_EXEC([sw0-p1], [pkill -P $udp1_pid && kill $udp1_pid])
+
+wait_row_count Service_Monitor 1 logical_port=sw0-p1 status=offline
+wait_row_count Service_Monitor 1 logical_port=sw0-p2 status=online
+
+NETNS_DAEMONIZE(sw-ext-p1, [nc -6 -u fd20::42 53 -d 0.1 -c 'for i in $(seq 1
5); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data_2; done'],
[udp2_client.pid])
+
+OVS_WAIT_FOR_OUTPUT([cat ./udp_data_2], [0], [dnl
+udp_data 1
+udp_data 2
+udp_data 3
+udp_data 4
+udp_data 5
+])
+
+OVN_CLEANUP_CONTROLLER([hv1])
+
+OVN_CLEANUP_NORTHD
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d
+/Service monitor not found.*/d
+/handle service check: Unsupported protocol*/d"])
+
+AT_CLEANUP
+])
+
OVN_FOR_EACH_NORTHD([
AT_SETUP([Load balancer health checks - IPv4])
AT_KEYWORDS([lb])
--
2.51.0
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev