Today, for a load balancer on a router with a distributed gateway port,
the conntrack state lives on the chassis currently hosting the DGP: a
backend is selected for the first packet only, and the rest of the
connection follows the existing conntrack entry. That pins a connection
to a single node, so gateway nodes cannot be scaled horizontally.
onntrack state synchronization between gateways has been proposed
before, but looks rather dated. This patch takes the other direction:
keep no conntrack state on the gateway at all, select a backend
statelessly for every packet, and keep the conntrack state on the node
hosting the backend.
OVN already has 'use_stateless_nat', but it SNATs the reply traffic 1:1
to the VIP, which is only correct if a backend belongs to a single VIP.
So add a new 'deferred-nat' load balancer option, which defers the NAT
to the hypervisor hosting the backend:
1. A packet for the VIP arrives at the gateway.
2. A backend is picked with select() in lr_in_ip_routing (priority
1050), without touching conntrack.
3. The packet is routed straight to that backend: eth.dst becomes the
backend's MAC, ip.dst stays the VIP.
4. It goes through the ingress pipeline of the backend's switch and
over the tunnel to the node hosting the backend.
5. There the DNAT is done in the new ls_out_lb stage, with
ct_lb_mark() for that single backend, and conntrack state is
created.
6. The reply is unDNATed back to the VIP in the ingress pipeline of
the same switch, where the connection is known.
Since the NAT happens per backend on the backend's own node, a backend
may belong to several VIPs without any ambiguity on the reply path.
What remains is keeping established TCP sessions alive when the set of
backends changes, as select() maps to an OpenFlow group whose buckets
are recomputed on every change. The plan is to make ovn-controller
update such groups incrementally, inserting and removing individual
buckets instead of rebuilding the group, so that adding or removing a
backend does not reshuffle the connections already established on the
remaining backends. That is not implemented yet.
I-P is not supported for 'deferred-nat' load balancers either: northd
falls back to a full recompute when such a load balancer changes.
Signed-off-by: Alexandra Rukomoinikova <[email protected]>
Suggested-by: Vladislav Odintsov <[email protected]>
---
lib/ovn-util.c | 4 +-
lib/ovn-util.h | 2 +-
northd/en-lb-data.c | 13 +-
northd/en-lb-data.h | 2 +
northd/en-ls-stateful.c | 3 +
northd/en-ls-stateful.h | 9 +
northd/lb.c | 19 +-
northd/lb.h | 3 +
northd/northd.c | 445 +++++++++++++++++++----
northd/northd.h | 31 +-
ovn-nb.xml | 56 ++-
tests/ovn-northd.at | 785 +++++++++++++++++++++++++++++++++++++++-
tests/ovn.at | 26 +-
tests/system-ovn.at | 372 +++++++++++++++++++
14 files changed, 1672 insertions(+), 98 deletions(-)
diff --git a/lib/ovn-util.c b/lib/ovn-util.c
index d9b78fbb6..088e6c4b4 100644
--- a/lib/ovn-util.c
+++ b/lib/ovn-util.c
@@ -1007,8 +1007,8 @@ ip_address_and_port_from_lb_key(const char *key, char
**ip_address,
*
* NOTE: If OVN_NORTHD_PIPELINE_CSUM is updated make sure to double check
* whether an update of OVN_INTERNAL_MINOR_VER is required. */
-#define OVN_NORTHD_PIPELINE_CSUM "118971668 11318"
-#define OVN_INTERNAL_MINOR_VER 15
+#define OVN_NORTHD_PIPELINE_CSUM "1791078493 11397"
+#define OVN_INTERNAL_MINOR_VER 16
/* Returns the OVN version. The caller must free the returned value. */
char *
diff --git a/lib/ovn-util.h b/lib/ovn-util.h
index 3ffcf42c3..17079a29b 100644
--- a/lib/ovn-util.h
+++ b/lib/ovn-util.h
@@ -348,7 +348,7 @@ BUILD_ASSERT_DECL(
/* The number of tables for the ingress and egress pipelines. */
#define LOG_PIPELINE_INGRESS_LEN 35
-#define LOG_PIPELINE_EGRESS_LEN 16
+#define LOG_PIPELINE_EGRESS_LEN 17
static inline uint32_t
hash_add_in6_addr(uint32_t hash, const struct in6_addr *addr)
diff --git a/northd/en-lb-data.c b/northd/en-lb-data.c
index 3bc2e0443..e4fa06fbf 100644
--- a/northd/en-lb-data.c
+++ b/northd/en-lb-data.c
@@ -165,6 +165,7 @@ lb_data_load_balancer_handler(struct engine_node *node,
void *data)
lb->health_checks);
trk_lb_data->has_routable_lb |= lb->routable;
trk_lb_data->has_distributed_lb |= lb->is_distributed;
+ trk_lb_data->has_deferred_nat_lb |= lb->is_deferred_nat;
continue;
}
@@ -180,6 +181,7 @@ lb_data_load_balancer_handler(struct engine_node *node,
void *data)
lb->health_checks);
trk_lb_data->has_routable_lb |= lb->routable;
trk_lb_data->has_distributed_lb |= lb->is_distributed;
+ trk_lb_data->has_deferred_nat_lb |= lb->is_deferred_nat;
} else {
/* Load balancer updated. */
bool health_checks = lb->health_checks;
@@ -190,12 +192,14 @@ lb_data_load_balancer_handler(struct engine_node *node,
void *data)
enum lb_neighbor_responder_mode neigh_mode = lb->neigh_mode;
bool routable = lb->routable;
bool distributed_mode = lb->is_distributed;
+ bool deferred_nat = lb->is_deferred_nat;
ovn_northd_lb_reinit(lb, tracked_lb);
health_checks |= lb->health_checks;
struct crupdated_lb *clb = add_crupdated_lb_to_tracked_data(
lb, trk_lb_data, health_checks);
trk_lb_data->has_routable_lb |= lb->routable;
trk_lb_data->has_distributed_lb |= lb->is_distributed;
+ trk_lb_data->has_deferred_nat_lb |= lb->is_deferred_nat;
/* Determine the inserted and deleted vips and store them in
* the tracked data. */
@@ -232,6 +236,12 @@ lb_data_load_balancer_handler(struct engine_node *node,
void *data)
/* If distributed_mode is updated trigger a full recompute. */
return EN_UNHANDLED;
}
+ if (deferred_nat != lb->is_deferred_nat) {
+ /* If deferred_nat is updated trigger a full recompute:
+ * 'ovn_datapath.has_deferred_nat_lb' is only computed by a
+ * full run, and turning the option off has to clear it. */
+ return EN_UNHANDLED;
+ }
}
}
@@ -701,6 +711,7 @@ handle_od_lb_changes(struct nbrec_load_balancer **nbrec_lbs,
trk_lb_data->has_health_checks |= lb->health_checks;
trk_lb_data->has_routable_lb |= lb->routable;
trk_lb_data->has_distributed_lb |= lb->is_distributed;
+ trk_lb_data->has_deferred_nat_lb |= lb->is_deferred_nat;
}
if (unode) {
@@ -761,7 +772,7 @@ destroy_tracked_data(struct ed_type_lb_data *lb_data)
lb_data->tracked_lb_data.has_dissassoc_lbs_from_od = false;
lb_data->tracked_lb_data.has_dissassoc_lbgrps_from_od = false;
lb_data->tracked_lb_data.has_routable_lb = false;
- lb_data->tracked_lb_data.has_distributed_lb = false;
+ lb_data->tracked_lb_data.has_deferred_nat_lb = false;
struct hmapx_node *node;
HMAPX_FOR_EACH_SAFE (node, &lb_data->tracked_lb_data.deleted_lbs) {
diff --git a/northd/en-lb-data.h b/northd/en-lb-data.h
index 31d930995..4abfaa738 100644
--- a/northd/en-lb-data.h
+++ b/northd/en-lb-data.h
@@ -85,6 +85,8 @@ struct tracked_lb_data {
/* Indicates if any lb (in the tracked data) has 'distibuted' flag set. */
bool has_distributed_lb;
+
+ bool has_deferred_nat_lb;
};
/* Datapath (logical switch) to lb/lbgrp association data. */
diff --git a/northd/en-ls-stateful.c b/northd/en-ls-stateful.c
index 1127b7d50..2b517a381 100644
--- a/northd/en-ls-stateful.c
+++ b/northd/en-ls-stateful.c
@@ -369,6 +369,9 @@ ls_stateful_record_init(struct ls_stateful_record
*ls_stateful_rec,
const struct ls_port_group_table *ls_pgs)
{
ls_stateful_rec->has_lb_vip = ls_has_lb_vip(od);
+ /* For deferred-nat case, northd trigger full recompute,
+ * so we don't need to handle this option in the handlers. */
+ ls_stateful_rec->has_deferred_nat_lb = od->has_deferred_nat_lb;
ls_stateful_record_set_acls(ls_stateful_rec, od->nbs, ls_pgs);
}
diff --git a/northd/en-ls-stateful.h b/northd/en-ls-stateful.h
index ffc0f4269..2be886ab2 100644
--- a/northd/en-ls-stateful.h
+++ b/northd/en-ls-stateful.h
@@ -50,6 +50,7 @@ struct ls_stateful_record {
* by the northd engine node for each logical switch. */
size_t ls_index;
+ bool has_deferred_nat_lb;
bool has_stateful_acl;
bool has_lb_vip;
bool has_acls;
@@ -126,4 +127,12 @@ ls_stateful_has_tracked_data(struct
ls_stateful_tracked_data *trk_data) {
!hmapx_is_empty(&trk_data->deleted);
}
+static inline bool
+ls_stateful_rec_needs_lb_conntrack(
+ const struct ls_stateful_record *ls_stateful_rec)
+{
+ return ls_stateful_rec->has_lb_vip
+ || ls_stateful_rec->has_deferred_nat_lb;
+}
+
#endif /* EN_LS_STATEFUL_H */
diff --git a/northd/lb.c b/northd/lb.c
index 5ff9d1fad..538c75b60 100644
--- a/northd/lb.c
+++ b/northd/lb.c
@@ -109,8 +109,8 @@ ovn_northd_lb_vip_init(struct ovn_northd_lb_vip *lb_vip_nb,
/*
* Parses ip_port_mappings in the format :
* "ip:logical_port[:src_ip][:az_name]".
- * src_ip parameter is optional when distributed mode is enabled,
- * without health checks configured.
+ * src_ip parameter is optional when distributed/deferred_nat mode
+ * is enabled, without health checks configured.
* 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.
@@ -122,7 +122,7 @@ ovn_lb_vip_backends_ip_port_mappings_init(const struct
ovn_northd_lb *lb,
{
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
struct ds key = DS_EMPTY_INITIALIZER;
- bool allow_without_src_ip = lb->is_distributed
+ bool allow_without_src_ip = (lb->is_distributed || lb->is_deferred_nat)
&& !lb_vip_nb->lb_health_check;
for (size_t j = 0; j < vector_len(&lb_vip->backends); j++) {
@@ -218,6 +218,8 @@ init_backend:
backend_nb->remote_backend = is_remote;
backend_nb->svc_mon_lrp = NULL;
backend_nb->distributed_backend = lb->is_distributed;
+ backend_nb->deferred_nat_backend = lb->is_deferred_nat;
+
cleanup:
free(port_name);
}
@@ -340,6 +342,13 @@ validate_snap_ip_address(const char *snat_ip)
return ip_parse(snat_ip, &ip);
}
+static bool
+lb_vip_needs_port_mappings(const struct ovn_northd_lb *lb)
+{
+ return lb->health_checks || lb->is_distributed
+ || lb->is_deferred_nat;
+}
+
static void
ovn_northd_lb_init(struct ovn_northd_lb *lb,
const struct nbrec_load_balancer *nbrec_lb)
@@ -391,6 +400,8 @@ ovn_northd_lb_init(struct ovn_northd_lb *lb,
lb->is_distributed = smap_get_bool(&nbrec_lb->options, "distributed",
false);
+ lb->is_deferred_nat = smap_get_bool(&nbrec_lb->options, "deferred-nat",
+ false);
sset_init(&lb->ips_v4);
sset_init(&lb->ips_v6);
@@ -431,7 +442,7 @@ ovn_northd_lb_init(struct ovn_northd_lb *lb,
}
n_vips++;
- if (lb_vip_nb->lb_health_check || lb->is_distributed) {
+ if (lb_vip_needs_port_mappings(lb)) {
ovn_lb_vip_backends_ip_port_mappings_init(lb, lb_vip, lb_vip_nb);
}
}
diff --git a/northd/lb.h b/northd/lb.h
index 7a98c2f55..cd741e04d 100644
--- a/northd/lb.h
+++ b/northd/lb.h
@@ -79,6 +79,8 @@ struct ovn_northd_lb {
/* Indicates if distributed option is enabled for load balancer. */
bool is_distributed;
+ bool is_deferred_nat;
+
bool use_stateless_nat;
char *hairpin_snat_ip;
@@ -98,6 +100,7 @@ struct ovn_northd_lb_backend {
/* Set to true if port does not locate in local AZ. */
bool remote_backend;
bool distributed_backend;
+ bool deferred_nat_backend;
/* Logical port to which the ip belong to. */
char *logical_port;
/* Source IP address to be used for service monitoring. */
diff --git a/northd/northd.c b/northd/northd.c
index 9f33b50cf..d22f92fb6 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -397,6 +397,12 @@ static const char *reg_ct_state[] = {
#define ROUTE_PRIO_BASE_SHIFT ((MAX_PREFIX_LEN + 1) * \
ROUTE_PRIO_OFFSET_MULTIPLIER)
+/* Deferred-NAT load balancing picks the backend in lr_in_ip_routing and must
+ * win over any route. The highest priority a route can get is
+ * (128 * ROUTE_PRIO_OFFSET_MULTIPLIER) + 7 + 1 == 1032
+ */
+#define LB_DEFERRED_NAT_ROUTING_PRIO 1050
+
/* ovn_stages used by northd for logical switches and logical routers.
* The first three components are combined to form the constant stage's
* struct name, e.g. S_SWITCH_IN_PORT_SEC_L2, S_ROUTER_OUT_DELIVERY.
@@ -1373,7 +1379,21 @@ ovn_port_get_peer(const struct hmap *lr_ports, struct
ovn_port *op)
return ovn_port_find(lr_ports, peer_name);
}
+static struct ovn_port *
+get_directly_connected_switch_for_op(struct ovn_port *op,
+ struct ovn_datapath *lr)
+{
+ struct ovn_datapath *od = op->od;
+ struct ovn_port *lrp;
+ VECTOR_FOR_EACH (&od->router_ports, lrp) {
+ if (lrp->peer && lrp->peer->od == lr) {
+ return lrp->peer;
+ }
+ }
+
+ return NULL;
+}
/* Returns true if the given router port 'op' (assumed to be a distributed
* gateway port) is the relevant DGP where the NAT rule of the router needs to
@@ -3862,6 +3882,37 @@ ovn_lsp_svc_monitors_process_port(
}
}
+static void
+build_deferred_nat_backends(struct hmap *lb_dps_map,
+ const struct hmap *ls_ports,
+ struct sset *deferred_nat_lsps)
+{
+ struct ovn_lb_datapaths *lb_dps;
+ HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) {
+ const struct ovn_northd_lb *lb = lb_dps->lb;
+ if (!lb->is_deferred_nat) {
+ continue;
+ }
+ for (size_t i = 0; i < lb->n_vips; i++) {
+ const struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i];
+ for (size_t j = 0; j < lb_vip_nb->n_backends; j++) {
+ const char *lsp = lb_vip_nb->backends_nb[j].logical_port;
+ if (!lsp) {
+ continue;
+ }
+ sset_add(deferred_nat_lsps, lsp);
+
+ /* The switch of the backend does this load balancer's NAT,
+ * so mark it for ls_stateful. */
+ struct ovn_port *op = ovn_port_find(ls_ports, lsp);
+ if (op && op->od) {
+ op->od->has_deferred_nat_lb = true;
+ }
+ }
+ }
+ }
+}
+
static void
build_svc_monitors_data(
struct ovsdb_idl_txn *ovnsb_txn,
@@ -5032,6 +5083,9 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
if (!lsp_can_be_inc_processed(new_nbsp)) {
goto fail;
}
+ if (sset_contains(&nd->deferred_nat_lsps, new_nbsp->name)) {
+ goto fail;
+ }
op = ls_port_create(ovnsb_idl_txn, &nd->ls_ports,
new_nbsp->name, new_nbsp, od,
ni->sbrec_mirror_table,
@@ -5056,6 +5110,9 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
* by this change. Fallback to recompute. */
goto fail;
}
+ if (sset_contains(&nd->deferred_nat_lsps, new_nbsp->name)) {
+ goto fail;
+ }
if (!lsp_handle_mirror_rules_changes(op) ||
is_lsp_mirror_target_port(
ni->nbrec_mirror_by_type_and_sink, op)) {
@@ -5120,6 +5177,9 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
* resolved; fall back to recompute. */
goto fail;
}
+ if (sset_contains(&nd->deferred_nat_lsps, op->key)) {
+ goto fail;
+ }
add_op_to_northd_tracked_ports(&trk_lsps->deleted, op);
hmap_remove(&nd->ls_ports, &op->key_node);
hmap_remove(&od->ports, &op->dp_node);
@@ -5927,6 +5987,13 @@ northd_handle_lb_data_changes(struct tracked_lb_data
*trk_lb_data,
return false;
}
+ if (trk_lb_data->has_deferred_nat_lb) {
+ /* Fall back to recompute since the tracked load balancer has the
+ * deferred-nat option configured and I-P is not yet supported
+ * for such load balancers. */
+ return false;
+ }
+
/* Fall back to recompute if any load balancer was dissociated from
* a load balancer group (but not deleted). */
if (trk_lb_data->has_dissassoc_lbs_from_lbgrps) {
@@ -7140,7 +7207,7 @@ build_ls_stateful_rec_pre_lb(const struct
ls_stateful_record *ls_stateful_rec,
* ingress pipeline if a load balancer is configured. We can now
* add a lflow to drop ct.inv packets.
*/
- if (ls_stateful_rec->has_lb_vip) {
+ if (ls_stateful_rec_needs_lb_conntrack(ls_stateful_rec)) {
ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB,
100, "ip", REGBIT_CONNTRACK_NAT" = 1; next;",
lflow_ref);
@@ -8711,6 +8778,81 @@ build_qos(struct ovn_datapath *od, struct lflow_table
*lflows,
ds_destroy(&action);
}
+static void
+build_lb_pre_stateful_ct_flow(const struct ovn_northd_lb *lb,
+ const struct ovn_lb_vip *lb_vip,
+ struct ds *match, struct ds *action)
+{
+ bool ipv4 = lb_vip->address_family == AF_INET;
+ const char *ip_match = ipv4 ? "ip4" : "ip6";
+
+ ds_clear(match);
+ ds_clear(action);
+
+ ds_put_format(action, ipv4 ? REG_LB_IPV4 " = %s; " : REG_LB_IPV6 " = %s; ",
+ lb_vip->vip_str);
+ if (lb_vip->port_str) {
+ ds_put_format(action, REG_LB_PORT " = %s; ", lb_vip->port_str);
+ }
+ ds_put_cstr(action, "ct_lb_mark;");
+ ds_put_cstr(match, REGBIT_CONNTRACK_NAT" == 1 && ");
+ ds_put_format(match, "%s.dst == %s", ip_match, lb_vip->vip_str);
+ if (lb_vip->port_str) {
+ ds_put_format(match, " && %s.dst == %s", lb->proto, lb_vip->port_str);
+ }
+}
+
+static void
+build_ls_lb_with_stateless_acl_ingress_flow(struct ovn_datapath *od,
+ const struct ovn_northd_lb *lb,
+ const struct ovn_lb_vip *lb_vip,
+ struct lflow_table *lflows,
+ struct lflow_ref *lflow_ref,
+ struct ds *match,
+ struct ds *action)
+{
+ bool ipv4 = lb_vip->address_family == AF_INET;
+ const char *ip_match = ipv4 ? "ip4" : "ip6";
+
+ ds_clear(match);
+ ds_clear(action);
+
+ ds_put_format(match, "%s.dst == %s", ip_match, lb_vip->vip_str);
+
+ if (lb_vip->port_str) {
+ ds_put_format(match, " && %s.dst == %s", lb->proto,
+ lb_vip->port_str);
+ }
+
+ if (ipv4) {
+ ds_put_format(action, REG_LB_IPV4 " = %s; ", lb_vip->vip_str);
+ } else {
+ ds_put_format(action, REG_LB_IPV6 " = %s; ", lb_vip->vip_str);
+ }
+ if (lb_vip->port_str) {
+ ds_put_format(action, REG_LB_PORT " = %s; ", lb_vip->port_str);
+ }
+
+ ds_put_cstr(action, "ct_lb_mark;");
+
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 150,
+ ds_cstr(match), ds_cstr(action), lflow_ref);
+
+ if (lb->hairpin_snat_ip || lb_vip->port_str) {
+ ds_clear(action);
+ ds_clear(match);
+
+ ds_put_format(match, "%s && %s.dst == %s", lb->proto, ip_match,
+ lb->hairpin_snat_ip
+ ? lb->hairpin_snat_ip
+ : lb_vip->vip_str);
+ ds_put_cstr(action, "ct_lb_mark;");
+
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 105,
+ ds_cstr(match), ds_cstr(action), lflow_ref);
+ }
+}
+
static void
build_lb_rules_pre_stateful(struct lflow_table *lflows,
struct ovn_lb_datapaths *lb_dps,
@@ -8724,38 +8866,7 @@ build_lb_rules_pre_stateful(struct lflow_table *lflows,
const struct ovn_northd_lb *lb = lb_dps->lb;
for (size_t i = 0; i < lb->n_vips; i++) {
struct ovn_lb_vip *lb_vip = &lb->vips[i];
- ds_clear(action);
- ds_clear(match);
- const char *ip_match = NULL;
-
- /* Store the original destination IP to be used when generating
- * hairpin flows.
- */
- if (lb->vips[i].address_family == AF_INET) {
- ip_match = "ip4";
- ds_put_format(action, REG_LB_IPV4 " = %s; ",
- lb_vip->vip_str);
- } else {
- ip_match = "ip6";
- ds_put_format(action, REG_LB_IPV6 " = %s; ",
- lb_vip->vip_str);
- }
-
- if (lb_vip->port_str) {
- /* Store the original destination port to be used when generating
- * hairpin flows.
- */
- ds_put_format(action, REG_LB_PORT " = %s; ",
- lb_vip->port_str);
- }
- ds_put_cstr(action, "ct_lb_mark;");
-
- ds_put_format(match, REGBIT_CONNTRACK_NAT" == 1 && %s.dst == %s",
- ip_match, lb_vip->vip_str);
- if (lb_vip->port_str) {
- ds_put_format(match, " && %s.dst == %s", lb->proto,
- lb_vip->port_str);
- }
+ build_lb_pre_stateful_ct_flow(lb, lb_vip, match, action);
ovn_lflow_add_with_dp_group(lflows, lb_dps->nb_ls_map.map,
ods_size(ls_datapaths),
@@ -8764,7 +8875,6 @@ build_lb_rules_pre_stateful(struct lflow_table *lflows,
lb_dps->lflow_ref,
WITH_HINT(&lb->nlb->header_));
- struct lflow_ref *lflow_ref = lb_dps->lflow_ref;
struct hmapx_node *hmapx_node;
struct ovn_datapath *od;
HMAPX_FOR_EACH (hmapx_node, &lb_dps->ls_lb_with_stateless_mode) {
@@ -8772,41 +8882,9 @@ build_lb_rules_pre_stateful(struct lflow_table *lflows,
ds_clear(action);
ds_clear(match);
-
- ds_put_format(match, "%s.dst == %s", ip_match, lb_vip->vip_str);
-
- if (lb_vip->port_str) {
- ds_put_format(match, " && %s.dst == %s", lb->proto,
- lb_vip->port_str);
- }
-
- if (lb->vips[i].address_family == AF_INET) {
- ds_put_format(action, REG_LB_IPV4 " = %s; ", lb_vip->vip_str);
- } else {
- ds_put_format(action, REG_LB_IPV6 " = %s; ", lb_vip->vip_str);
- }
- if (lb_vip->port_str) {
- ds_put_format(action, REG_LB_PORT " = %s; ", lb_vip->port_str);
- }
-
- ds_put_cstr(action, "ct_lb_mark;");
-
- ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 150,
- ds_cstr(match), ds_cstr(action), lflow_ref);
-
- if (lb->hairpin_snat_ip || lb_vip->port_str) {
- ds_clear(action);
- ds_clear(match);
-
- ds_put_format(match, "%s && %s.dst == %s", lb->proto, ip_match,
- lb->hairpin_snat_ip
- ? lb->hairpin_snat_ip
- : lb_vip->vip_str);
- ds_put_cstr(action, "ct_lb_mark;");
-
- ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 105,
- ds_cstr(match), ds_cstr(action), lflow_ref);
- }
+ build_ls_lb_with_stateless_acl_ingress_flow(od, lb, lb_vip, lflows,
+ lb_dps->lflow_ref,
+ match, action);
}
}
}
@@ -11801,6 +11879,14 @@ build_lswitch_ip_unicast_lookup_for_nats(
}
}
+static void
+build_lswitch_default_egress_lb_flows(struct ovn_datapath *od,
+ struct lflow_table *lflows,
+ struct lflow_ref *lflow_ref)
+{
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_LB, 0, "1", "next;", lflow_ref);
+}
+
struct bfd_entry {
struct hmap_node hmap_node;
@@ -13616,6 +13702,203 @@ build_lrouter_flows_for_lb_stateless(struct
lrouter_nat_lb_flows_ctx *ctx,
ds_destroy(&snat_action);
}
+static void
+build_lb_rules_deferred_nat_ls_skip_flow(struct ovn_datapath *od,
+ struct ovn_lb_vip *lb_vip,
+ const struct ovn_northd_lb *lb,
+ struct lflow_table *lflows,
+ struct lflow_ref *lflow_ref)
+{
+ bool ipv4 = lb_vip->address_family == AF_INET;
+ const char *ip_match = ipv4 ? "ip4" : "ip6";
+ struct ds match = DS_EMPTY_INITIALIZER;
+
+ struct ovn_datapath *ls;
+ VECTOR_FOR_EACH (&od->ls_peers, ls) {
+ const struct ovn_port *sw_rp;
+ VECTOR_FOR_EACH (&ls->router_ports, sw_rp) {
+ ds_clear(&match);
+ ds_put_format(&match, "%s.dst == %s", ip_match, lb_vip->vip_str);
+ if (lb_vip->port_str) {
+ ds_put_format(&match, " && %s.dst == %s", lb->proto,
+ lb_vip->port_str);
+ }
+ ds_put_format(&match, " && inport == %s", sw_rp->json_key);
+ ovn_lflow_add(lflows, ls, S_SWITCH_IN_PRE_STATEFUL, 200,
+ ds_cstr(&match), "next;", lflow_ref,
+ WITH_HINT(&lb->nlb->header_));
+ }
+ }
+ ds_destroy(&match);
+}
+
+static void
+build_lb_rules_deferred_nat(struct lrouter_nat_lb_flows_ctx *ctx,
+ const struct ovn_northd_lb *lb,
+ struct ovn_lb_vip *lb_vip,
+ const struct ovn_northd_lb_vip *lb_vip_nb,
+ struct ovn_datapath *od,
+ struct lflow_ref *lflow_ref,
+ const struct hmap *ls_ports,
+ const struct svc_monitors_map_data *svc_mons_data)
+{
+ struct ds match = DS_EMPTY_INITIALIZER;
+ struct ds action = DS_EMPTY_INITIALIZER;
+ struct ds members = DS_EMPTY_INITIALIZER;
+ size_t n_members = 0;
+
+ bool ipv4 = lb_vip->address_family == AF_INET;
+ const char *ip_match = ipv4 ? "ip4" : "ip6";
+
+ const struct ovn_lb_backend *backend;
+ size_t i = 0;
+ VECTOR_FOR_EACH_PTR (&lb_vip->backends, backend) {
+ struct ovn_northd_lb_backend *backend_nb =
+ &lb_vip_nb->backends_nb[i++];
+
+ if (!backend_nb->deferred_nat_backend) {
+ continue;
+ }
+
+ struct ovn_port *op = ovn_port_find(ls_ports,
+ backend_nb->logical_port);
+ if (!op || !op->n_lsp_addrs || !lsp_is_enabled(op->nbsp)) {
+ continue;
+ }
+
+ struct ovn_port *lrp = get_directly_connected_switch_for_op(op, od);
+ if (!lrp) {
+ continue;
+ }
+
+ if (!lrp->lrp_networks.n_ipv4_addrs) {
+ continue;
+ }
+
+ if (backend_nb->health_check
+ && !backend_is_available(lb, backend, backend_nb, svc_mons_data)) {
+ continue;
+ }
+
+ ds_put_format(&members, "%s,", backend->ip_str);
+ n_members++;
+
+ ds_clear(&match);
+ ds_clear(&action);
+
+ ds_put_format(&match, "%s && %s.dst == %s && %s",
+ ip_match, ip_match, lb_vip->vip_str, lb->proto);
+ if (lb_vip->port_str) {
+ ds_put_format(&match, " && %s.dst == %s", lb->proto,
+ lb_vip->port_str);
+ }
+ ds_put_format(&match, " && "REG_LB_IPV4 " == %s", backend->ip_str);
+
+ ds_put_format(&action, "ip.ttl--; "
+ REG_ECMP_GROUP_ID " = 0; "
+ REG_NEXT_HOP_IPV4 " = %s; "
+ REG_SRC_IPV4 " = %s; "
+ "eth.src = %s; outport = %s; "
+ REGBIT_NEXTHOP_IS_IPV4 " = 1; "
+ "flags.loopback = 1; next;",
+ backend->ip_str, lrp->lrp_networks.ipv4_addrs[0].addr_s,
+ lrp->lrp_networks.ea_s, lrp->json_key);
+ ovn_lflow_add(ctx->lflows, od, S_ROUTER_IN_IP_ROUTING,
+ LB_DEFERRED_NAT_ROUTING_PRIO, ds_cstr(&match),
+ ds_cstr(&action), lflow_ref,
+ WITH_HINT(&lb->nlb->header_));
+
+ ds_clear(&match);
+ ds_clear(&action);
+ ds_put_format(&match, "eth.dst == %s && %s.dst == %s",
+ op->lsp_addrs->ea_s, ip_match, lb_vip->vip_str);
+ if (lb_vip->port_str) {
+ ds_put_format(&match, " && %s.dst == %s", lb->proto,
+ lb_vip->port_str);
+ }
+ ds_put_cstr(&action, "ct_lb_mark(backends=");
+ if (backend->port) {
+ ds_put_format(&action, ipv4 ? "%s:%"PRIu16 : "[%s]:%"PRIu16,
+ backend->ip_str, backend->port);
+ } else {
+ ds_put_cstr(&action, backend->ip_str);
+ }
+ ds_put_cstr(&action, ");");
+ ovn_lflow_add(ctx->lflows, op->od, S_SWITCH_OUT_LB, 130,
+ ds_cstr(&match), ds_cstr(&action), lflow_ref,
+ WITH_HINT(&lb->nlb->header_));
+
+ build_lb_pre_stateful_ct_flow(lb, lb_vip, &match, &action);
+ ovn_lflow_add(ctx->lflows, op->od, S_SWITCH_IN_PRE_STATEFUL, 120,
+ ds_cstr(&match), ds_cstr(&action), lflow_ref,
+ WITH_HINT(&lb->nlb->header_));
+
+ if (op->od->lb_with_stateless_mode) {
+ build_ls_lb_with_stateless_acl_ingress_flow(op->od, lb, lb_vip,
+ ctx->lflows, lflow_ref,
+ &match, &action);
+ }
+ }
+
+ build_lb_rules_deferred_nat_ls_skip_flow(od,lb_vip, lb,
+ ctx->lflows, lflow_ref);
+
+ ds_chomp(&members, ',');
+
+ ds_clear(&match);
+ ds_clear(&action);
+ ds_put_format(&match, "%s && %s.dst == %s && %s",
+ ip_match, ip_match, lb_vip->vip_str, lb->proto);
+ if (lb_vip->port_str) {
+ ds_put_format(&match, " && %s.dst == %s", lb->proto,
+ lb_vip->port_str);
+ }
+
+ if (!n_members) {
+ ds_put_cstr(&action, "drop;");
+ ovn_lflow_add(ctx->lflows, od, S_ROUTER_IN_CT_EXTRACT, 130,
+ ds_cstr(&match), ds_cstr(&action), lflow_ref,
+ WITH_HINT(&lb->nlb->header_));
+ goto out;
+ }
+
+ if (n_members > 1) {
+ ds_put_format(&action, REG_LB_IPV4 " = select(values=(%s)",
+ ds_cstr(&members));
+ ds_put_format(&action, "; group_key=\"%s:%s:%s:%d\"",
+ ovn_datapath_name(od->sdp->sb_dp),
+ lb->nlb->name, lb_vip->vip_str, lb_vip->vip_port);
+ if (lb->selection_fields) {
+ ds_put_format(&action, "; hash_fields=\"%s\"",
+ lb->selection_fields);
+ }
+ ds_put_cstr(&action, ");");
+ } else {
+ ds_put_format(&action, REG_LB_IPV4 " = %s; next;", ds_cstr(&members));
+ }
+ ovn_lflow_add(ctx->lflows, od, S_ROUTER_IN_CT_EXTRACT, 130,
+ ds_cstr(&match), ds_cstr(&action), lflow_ref,
+ WITH_HINT(&lb->nlb->header_));
+
+ ds_clear(&match);
+ ds_clear(&action);
+ ds_put_format(&match, "%s && %s.dst == %s && %s",
+ ip_match, ip_match, lb_vip->vip_str, lb->proto);
+ if (lb_vip->port_str) {
+ ds_put_format(&match, " && %s.dst == %s", lb->proto,
+ lb_vip->port_str);
+ }
+ ds_put_cstr(&action, "next;");
+ ovn_lflow_add(ctx->lflows, od, S_ROUTER_IN_DNAT, 130,
+ ds_cstr(&match), ds_cstr(&action), lflow_ref,
+ WITH_HINT(&lb->nlb->header_));
+
+out:
+ ds_destroy(&action);
+ ds_destroy(&match);
+ ds_destroy(&members);
+}
+
static void
build_distr_lrouter_nat_flows_for_lb(struct lrouter_nat_lb_flows_ctx *ctx,
enum lrouter_nat_lb_flow_type type,
@@ -13766,7 +14049,8 @@ build_lrouter_nat_flows_for_lb(
struct lflow_table *lflows,
struct ds *match, struct ds *action,
const struct shash *meter_groups,
- const struct svc_monitors_map_data *svc_mons_data)
+ const struct svc_monitors_map_data *svc_mons_data,
+ const struct hmap *ls_ports)
{
const struct ovn_northd_lb *lb = lb_dps->lb;
bool ipv4 = lb_vip->address_family == AF_INET;
@@ -13885,8 +14169,13 @@ build_lrouter_nat_flows_for_lb(
type = LROUTER_NAT_LB_FLOW_NORMAL;
}
+ bool deferred_nat = lb->is_deferred_nat;
if (vector_is_empty(&od->l3dgw_ports)) {
bitmap_set1(gw_dp_bitmap[type], index);
+ } else if (deferred_nat) {
+ build_lb_rules_deferred_nat(&ctx, lb, lb_vip,
+ vips_nb, od, lb_dps->lflow_ref,
+ ls_ports, svc_mons_data);
} else {
/* Create stateless LB NAT rules when using multiple DGPs and
* use_stateless_nat is true.
@@ -14111,6 +14400,7 @@ build_lrouter_flows_for_lb(struct ovn_lb_datapaths
*lb_dps,
const struct ovn_datapaths *lr_datapaths,
const struct lr_stateful_table *lr_stateful_table,
const struct svc_monitors_map_data *svc_mons_data,
+ const struct hmap *ls_ports,
struct ds *match, struct ds *action)
{
size_t index;
@@ -14126,7 +14416,7 @@ build_lrouter_flows_for_lb(struct ovn_lb_datapaths
*lb_dps,
build_lrouter_nat_flows_for_lb(lb_vip, lb_dps, &lb->vips_nb[i],
lr_datapaths, lr_stateful_table, lflows,
match, action, meter_groups,
- svc_mons_data);
+ svc_mons_data, ls_ports);
build_lrouter_allow_vip_traffic_template(lflows, lb_dps, lb_vip, lb,
lr_datapaths);
@@ -19427,7 +19717,7 @@ build_ls_stateful_flows(const struct ls_stateful_record
*ls_stateful_rec,
/* Build CT extraction flows - only needed if this datapath has load
* balancers. */
- if (ls_stateful_rec->has_lb_vip) {
+ if (ls_stateful_rec_needs_lb_conntrack(ls_stateful_rec)) {
ovn_lflow_add(lflows, od, S_SWITCH_IN_CT_EXTRACT, 100,
"ct.new && ip",
REG_CT_PROTO " = ct_proto(); "
@@ -20276,6 +20566,7 @@ build_lswitch_and_lrouter_iterate_by_ls(struct
ovn_datapath *od,
build_lswitch_lflows_l2_unknown(od, lsi->lflows, NULL);
}
build_mcast_flood_lswitch(od, lsi->lflows, &lsi->actions, NULL);
+ build_lswitch_default_egress_lb_flows(od, lsi->lflows, NULL);
}
/* Helper function to combine all lflow generation which is iterated by
@@ -20526,7 +20817,7 @@ build_lflows_thread(void *arg)
lsi->meter_groups,
lsi->lr_datapaths,
lsi->lr_stateful_table,
- &svc_mons_data,
+ &svc_mons_data, lsi->ls_ports,
&lsi->match, &lsi->actions);
build_lswitch_flows_for_lb(lb_dps, lsi->lflows,
lsi->meter_groups,
@@ -20769,7 +21060,7 @@ build_lswitch_and_lrouter_flows(
lsi.lr_datapaths, &lsi.match);
build_lrouter_flows_for_lb(lb_dps, lsi.lflows, lsi.meter_groups,
lsi.lr_datapaths, lsi.lr_stateful_table,
- svc_mons_data,
+ svc_mons_data, lsi.ls_ports,
&lsi.match, &lsi.actions);
build_lswitch_flows_for_lb(lb_dps, lsi.lflows, lsi.meter_groups,
lsi.ls_datapaths,
@@ -21180,7 +21471,7 @@ lflow_handle_northd_lb_changes(struct ovsdb_idl_txn
*ovnsb_txn,
lflow_input->meter_groups,
lflow_input->lr_datapaths,
lflow_input->lr_stateful_table,
- &svc_mons_data,
+ &svc_mons_data, lflow_input->ls_ports,
&match, &actions);
build_lswitch_flows_for_lb(lb_dps, lflows,
lflow_input->meter_groups,
@@ -21746,6 +22037,7 @@ northd_init(struct northd_data *data)
hmap_init(&data->lb_datapaths_map);
hmap_init(&data->lb_group_datapaths_map);
sset_init(&data->svc_monitor_lsps);
+ sset_init(&data->deferred_nat_lsps);
hmap_init(&data->local_svc_monitors_map);
hmapx_init(&data->monitored_ports_map);
init_northd_tracked_data(data);
@@ -21836,6 +22128,7 @@ northd_destroy(struct northd_data *data)
&data->ls_ports, &data->lr_ports);
sset_destroy(&data->svc_monitor_lsps);
+ sset_destroy(&data->deferred_nat_lsps);
hmapx_destroy(&data->monitored_ports_map);
destroy_northd_tracked_data(data);
}
@@ -21973,6 +22266,8 @@ ovnnb_db_run(struct northd_input *input_data,
&data->svc_monitor_lsps, &data->local_svc_monitors_map,
input_data->ic_learned_svc_monitors_map,
&data->monitored_ports_map);
+ build_deferred_nat_backends(&data->lb_datapaths_map, &data->ls_ports,
+ &data->deferred_nat_lsps);
build_lb_count_dps(&data->lb_datapaths_map);
build_network_function_active(
input_data->nbrec_network_function_group_table,
diff --git a/northd/northd.h b/northd/northd.h
index 2e3a9e00d..7f12c28ad 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -203,6 +203,7 @@ struct northd_data {
struct hmap lb_datapaths_map;
struct hmap lb_group_datapaths_map;
struct sset svc_monitor_lsps;
+ struct sset deferred_nat_lsps;
struct hmap local_svc_monitors_map;
struct hmapx monitored_ports_map;
@@ -437,6 +438,13 @@ struct ovn_datapath {
* on the logical switch. */
bool lb_with_stateless_mode;
+ /* Set to true if the logical switch hosts a backend of a load balancer
+ * with the 'deferred-nat' option set. Such a switch does the SNAT/unSNAT
+ * of that load balancer even though it does not carry it in its own
+ * 'load_balancer' column, so it needs the same conntrack handling as a
+ * switch that does. */
+ bool has_deferred_nat_lb;
+
/* IPAM data. */
struct ipam_info ipam_info;
bool ipam_info_initialized;
@@ -575,19 +583,20 @@ ls_has_localnet_port(const struct ovn_datapath *od)
PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 2, "ls_out_pre_acl") \
PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 3, "ls_out_pre_lb") \
PIPELINE_STAGE(SWITCH, OUT, PRE_STATEFUL, 4, "ls_out_pre_stateful") \
- PIPELINE_STAGE(SWITCH, OUT, ACL_HINT, 5, "ls_out_acl_hint") \
- PIPELINE_STAGE(SWITCH, OUT, ACL_EVAL, 6, "ls_out_acl_eval") \
- PIPELINE_STAGE(SWITCH, OUT, ACL_SAMPLE, 7, "ls_out_acl_sample") \
- PIPELINE_STAGE(SWITCH, OUT, ACL_ACTION, 8, "ls_out_acl_action") \
- PIPELINE_STAGE(SWITCH, OUT, MIRROR, 9, "ls_out_mirror") \
- PIPELINE_STAGE(SWITCH, OUT, QOS, 10, "ls_out_qos") \
- PIPELINE_STAGE(SWITCH, OUT, PRE_NF, 11, \
+ PIPELINE_STAGE(SWITCH, OUT, ACL_HINT, 6, "ls_out_acl_hint") \
+ PIPELINE_STAGE(SWITCH, OUT, ACL_EVAL, 7, "ls_out_acl_eval") \
+ PIPELINE_STAGE(SWITCH, OUT, LB, 5, "ls_out_lb") \
+ PIPELINE_STAGE(SWITCH, OUT, ACL_SAMPLE, 8, "ls_out_acl_sample") \
+ PIPELINE_STAGE(SWITCH, OUT, ACL_ACTION, 9, "ls_out_acl_action") \
+ PIPELINE_STAGE(SWITCH, OUT, MIRROR, 10, "ls_out_mirror") \
+ PIPELINE_STAGE(SWITCH, OUT, QOS, 11, "ls_out_qos") \
+ PIPELINE_STAGE(SWITCH, OUT, PRE_NF, 12, \
"ls_out_pre_network_function") \
- PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 12, "ls_out_stateful") \
- PIPELINE_STAGE(SWITCH, OUT, NF, 13, \
+ PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 13, "ls_out_stateful") \
+ PIPELINE_STAGE(SWITCH, OUT, NF, 14, \
"ls_out_network_function") \
- PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC, 14, "ls_out_check_port_sec") \
- PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 15, "ls_out_apply_port_sec")
+ PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC, 15, "ls_out_check_port_sec") \
+ PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 16, "ls_out_apply_port_sec")
/* Logical router ingress stages. */
#define ROUTER_IN_PIPELINE_STAGES \
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 8a9d19fa9..4605b240f 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -2613,8 +2613,10 @@
Remote endpoint:
Specify :target_zone_name at the end of the above syntax to create
remote health checks in a specific zone.
- For distributed load balancers - ip_port_mappings is required.
- In the absence of health checks - source_ip is optional.
+ For load balancers with the <code>deferred-nat</code> or
+ <code>distributed</code> options enabled - ip_port_mappings
+ is required.
+ In absence of health checks - source_ip is optional.
</p>
<p>
@@ -2832,6 +2834,56 @@ or
router.
</column>
+ <column name="options" key="deferred-nat">
+ <p>
+ If set to <code>true</code>, the load balancer performs the backend
+ selection in a stateless manner and defers the NAT itself to the
+ hypervisor that hosts the selected backend. No connection tracking
+ state is created on the gateway chassis, so gateway nodes can be
+ scaled horizontally. This option is set to <code>false</code> by
+ default.
+ </p>
+
+ <p>
+ A packet destined to the VIP is not NATed on the router. Instead,
+ a backend is selected for every packet in the routing stage and the
+ packet is sent directly to that backend: the destination MAC becomes
+ the backend's MAC while the destination IP stays the VIP. The DNAT
+ to the backend address is then performed on the node hosting the
+ backend, where the connection tracking state is created.
+ Because the translation is done per backend on the backend's own
+ node, a backend may be a member of several VIPs.
+ </p>
+
+ <p>
+ Since the packet is delivered to the backend by its MAC address,
+ traffic to a <code>deferred-nat</code> load balancer is no longer
+ routed: every backend must belong to a subnet of the logical router
+ that references this load balancer, that is, it must reside on a
+ logical switch that is directly connected to that router. Backends
+ that are reachable only through a next hop are not supported and are
+ ignored.
+ </p>
+
+ <p>
+ Required configuration: <ref column="ip_port_mappings"/>, since the
+ logical port of each backend must be known to resolve its MAC
+ address and the switch it belongs to.
+ </p>
+
+ <p>
+ Because the backend is selected per packet, the OpenFlow group that
+ implements the selection is updated incrementally: when a backend is
+ added or removed, only its own bucket is inserted into or deleted
+ from the group, instead of the whole group being rebuilt. For that
+ to actually preserve the TCP sessions established on the remaining
+ backends, the <code>hash</code> selection method must be used rather
+ than <code>dp_hash</code>, which reassigns the traffic across all
+ the buckets whenever their number changes. The hash method is chosen
+ by setting selection_fields on this load balancer.
+ </p>
+ </column>
+
<column name="options" key="event" type='{"type": "boolean"}'>
If set to <code>true</code> and the load balancer has no
active backends, whenever a packet matches a VIP on this
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 8c8d7852e..a1f227ce1 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -5161,7 +5161,9 @@ check_stateful_flows() {
table=??(ls_out_pre_stateful), priority=110 , match=(reg0[[2]] == 1),
action=(ct_lb_mark;)
])
- AT_CHECK([grep "ls_out_lb" sw0flows | sort], [0], [])
+ AT_CHECK([grep "ls_out_lb" sw0flows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_out_lb ), priority=0 , match=(1), action=(next;)
+])
AT_CHECK([grep "ls_out_stateful" sw0flows | ovn_strip_lflows], [0], [dnl
table=??(ls_out_stateful ), priority=0 , match=(1), action=(next;)
@@ -23647,3 +23649,784 @@ AT_CHECK([as northd ovn-appctl -t ovn-northd
inc-engine/enable-stopwatch nonexis
OVN_CLEANUP_NORTHD
AT_CLEANUP
])
+
+# Common topology for the "deferred-nat" tests below:
+#
+# lport1 (00:00:00:00:00:01, 172.16.0.101)
+# lr1 ------------ ls1 - lport2 (00:00:00:00:00:02, 172.16.0.102)
+# (lr1-ls1, DGP) lport3 (00:00:00:00:00:03, 172.16.0.103)
+#
+# lb1: 30.0.0.1:80 -> 172.16.0.10{1,2,3}:8080, attached to lr1.
+#
+# 'options:deferred-nat' is deliberately left unset here, every test enables
+# it on its own so that the transition itself is covered as well.
+#
+# The macro also defines three shell helpers that dump only the logical flows
+# the deferred-nat path is responsible for:
+# deferred_lr_flows LR VIP
+# deferred_ls_flows LS VIP
+# deferred_ls_lb_flows LS VIP
+# The last one covers the ordinary switch load balancing tables, which the
+# backend switches get even though they do not carry the load balancer in
+# their own 'load_balancer' column; it is kept apart so that the tests which
+# only care about the deferred NAT flows proper stay readable.
+# All of them always exit 0 and print nothing when there is no such flow, so
+# an empty expectation means "the feature emitted nothing".
+m4_define([DEFERRED_NAT_SETUP], [
+deferred_lr_flows() {
+ ovn-sbctl lflow-list $[]1 | grep -E \
+
"lr_in_ct_extract|lr_in_dnat.*priority=130|lr_in_ip_routing.*priority=1050" \
+ | grep "$[]2" | ovn_strip_lflows
+}
+
+deferred_ls_flows() {
+ ovn-sbctl lflow-list $[]1 | grep -E \
+ "ls_out_lb.*priority=130|ls_in_pre_stateful.*priority=200" \
+ | grep "$[]2" | ovn_strip_lflows
+}
+
+deferred_ls_lb_flows() {
+ ovn-sbctl lflow-list $[]1 | grep -E \
+
"ls_in_pre_stateful.*priority=(120|150|105)|ls_in_lb.*priority=1[[12]]0" \
+ | grep "$[]2" | ovn_strip_lflows
+}
+
+prelb_flows() {
+ ovn-sbctl lflow-list $1 | grep -E "ls_in_pre_lb" | \
+ grep "priority=100" | ovn_strip_lflows
+}
+
+ct_extract_flows() {
+ ovn-sbctl lflow-list $1 | grep "ls_in_ct_extract" | grep "priority=100" | \
+ ovn_strip_lflows
+}
+
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:01:02:03:04 172.16.0.1/24
+check ovn-nbctl lrp-set-gateway-chassis lr1-ls1 hv1
+check ovn-nbctl lsp-add-router-port ls1 ls1-lr1 lr1-ls1
+
+check ovn-nbctl lsp-add ls1 lport1 -- lsp-set-addresses lport1
"00:00:00:00:00:01 172.16.0.101"
+check ovn-nbctl lsp-add ls1 lport2 -- lsp-set-addresses lport2
"00:00:00:00:00:02 172.16.0.102"
+check ovn-nbctl lsp-add ls1 lport3 -- lsp-set-addresses lport3
"00:00:00:00:00:03 172.16.0.103"
+
+check ovn-nbctl lb-add lb1 30.0.0.1:80 \
+ 172.16.0.101:8080,172.16.0.102:8080,172.16.0.103:8080
+check ovn-nbctl set load_balancer lb1
ip_port_mappings:172.16.0.101=lport1:192.168.0.99
+check ovn-nbctl set load_balancer lb1
ip_port_mappings:172.16.0.102=lport2:192.168.0.99
+check ovn-nbctl set load_balancer lb1
ip_port_mappings:172.16.0.103=lport3:192.168.0.99
+check ovn-nbctl --wait=sb lr-lb-add lr1 lb1
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([deferred-nat -- option toggling])
+ovn_start
+DEFERRED_NAT_SETUP
+
+AS_BOX([Nothing is emitted while the option is unset])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1], [0], [])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1], [0], [])
+
+AS_BOX([Change options:deferred-nat=true])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 options:deferred-nat=true
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 =
select(values=(172.16.0.101,172.16.0.102,172.16.0.103);
group_key="lr1:lb1:30.0.0.1:80");)
+ table=??(lr_in_dnat ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(next;)
+ table=??(lr_in_ip_routing ), priority=1050 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80 && reg4 == 172.16.0.101), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = 172.16.0.101; reg5 = 172.16.0.1; eth.src =
00:00:01:02:03:04; outport = "lr1-ls1"; reg9[[9]] = 1; flags.loopback = 1;
next;)
+ table=??(lr_in_ip_routing ), priority=1050 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80 && reg4 == 172.16.0.102), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = 172.16.0.102; reg5 = 172.16.0.1; eth.src =
00:00:01:02:03:04; outport = "lr1-ls1"; reg9[[9]] = 1; flags.loopback = 1;
next;)
+ table=??(lr_in_ip_routing ), priority=1050 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80 && reg4 == 172.16.0.103), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = 172.16.0.103; reg5 = 172.16.0.1; eth.src =
00:00:01:02:03:04; outport = "lr1-ls1"; reg9[[9]] = 1; flags.loopback = 1;
next;)
+])
+
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=200 , match=(ip4.dst == 30.0.0.1 &&
tcp.dst == 80 && inport == "ls1-lr1"), action=(next;)
+ table=??(ls_out_lb ), priority=130 , match=(eth.dst ==
00:00:00:00:00:01 && ip4.dst == 30.0.0.1 && tcp.dst == 80),
action=(ct_lb_mark(backends=172.16.0.101:8080);)
+ table=??(ls_out_lb ), priority=130 , match=(eth.dst ==
00:00:00:00:00:02 && ip4.dst == 30.0.0.1 && tcp.dst == 80),
action=(ct_lb_mark(backends=172.16.0.102:8080);)
+ table=??(ls_out_lb ), priority=130 , match=(eth.dst ==
00:00:00:00:00:03 && ip4.dst == 30.0.0.1 && tcp.dst == 80),
action=(ct_lb_mark(backends=172.16.0.103:8080);)
+])
+
+dnl ls1 does not carry lb1 in its own 'load_balancer' column, but it hosts the
+dnl backends, so requests that originate inside 172.16.0.0/24 have to be load
+dnl balanced here rather than on lr1.
+AT_CHECK([deferred_ls_lb_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 &&
ip4.dst == 30.0.0.1 && tcp.dst == 80), action=(reg4 = 30.0.0.1; reg2[[0..15]] =
80; ct_lb_mark;)
+])
+
+# Check switch is now statefull datapath.
+AT_CHECK([prelb_flows ls1], [0], [dnl
+ table=??(ls_in_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]]
= 1; next;)
+])
+AT_CHECK([ct_extract_flows ls1], [0], [dnl
+ table=??(ls_in_ct_extract ), priority=100 , match=(ct.new && ip),
action=(reg1[[16..23]] = ct_proto(); reg1[[0..15]] = ct_tp_dst(); next;)
+])
+
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([enable-stateless-acl-with-lb on the backend switch])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set logical_switch ls1 \
+ other_config:enable-stateless-acl-with-lb=true
+
+AT_CHECK([deferred_ls_lb_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=105 , match=(tcp && ip4.dst ==
30.0.0.1), action=(ct_lb_mark;)
+ table=??(ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 &&
ip4.dst == 30.0.0.1 && tcp.dst == 80), action=(reg4 = 30.0.0.1; reg2[[0..15]] =
80; ct_lb_mark;)
+ table=??(ls_in_pre_stateful ), priority=150 , match=(ip4.dst == 30.0.0.1 &&
tcp.dst == 80), action=(reg4 = 30.0.0.1; reg2[[0..15]] = 80; ct_lb_mark;)
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+check ovn-nbctl --wait=sb remove logical_switch ls1 other_config \
+ enable-stateless-acl-with-lb
+
+AT_CHECK([deferred_ls_lb_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 &&
ip4.dst == 30.0.0.1 && tcp.dst == 80), action=(reg4 = 30.0.0.1; reg2[[0..15]] =
80; ct_lb_mark;)
+])
+AT_CHECK([prelb_flows ls1], [0], [dnl
+ table=??(ls_in_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]]
= 1; next;)
+])
+AT_CHECK([ct_extract_flows ls1], [0], [dnl
+ table=??(ls_in_ct_extract ), priority=100 , match=(ct.new && ip),
action=(reg1[[16..23]] = ct_proto(); reg1[[0..15]] = ct_tp_dst(); next;)
+])
+
+AS_BOX([Remove the option -- every deferred-nat flow has to go away])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb remove load_balancer lb1 options deferred-nat
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1], [0], [])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1], [0], [])
+AT_CHECK([deferred_ls_lb_flows ls1 30.0.0.1], [0], [])
+AT_CHECK([prelb_flows ls1], [0], [])
+AT_CHECK([ct_extract_flows ls1], [0], [])
+
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Turn it back on])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 options:deferred-nat=true
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [3
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [3
+])
+AT_CHECK([deferred_ls_lb_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 &&
ip4.dst == 30.0.0.1 && tcp.dst == 80), action=(reg4 = 30.0.0.1; reg2[[0..15]] =
80; ct_lb_mark;)
+])
+AT_CHECK([prelb_flows ls1], [0], [dnl
+ table=??(ls_in_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]]
= 1; next;)
+])
+AT_CHECK([ct_extract_flows ls1], [0], [dnl
+ table=??(ls_in_ct_extract ), priority=100 , match=(ct.new && ip),
action=(reg1[[16..23]] = ct_proto(); reg1[[0..15]] = ct_tp_dst(); next;)
+])
+
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl set load_balancer lb1 options:deferred-nat=false
+check ovn-nbctl --wait=sb set load_balancer lb1 options:deferred-nat=true
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [3
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [3
+])
+AT_CHECK([deferred_ls_lb_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 &&
ip4.dst == 30.0.0.1 && tcp.dst == 80), action=(reg4 = 30.0.0.1; reg2[[0..15]] =
80; ct_lb_mark;)
+])
+AT_CHECK([prelb_flows ls1], [0], [dnl
+ table=??(ls_in_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]]
= 1; next;)
+])
+AT_CHECK([ct_extract_flows ls1], [0], [dnl
+ table=??(ls_in_ct_extract ), priority=100 , match=(ct.new && ip),
action=(reg1[[16..23]] = ct_proto(); reg1[[0..15]] = ct_tp_dst(); next;)
+])
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+check ovn-nbctl set load_balancer lb1 options:deferred-nat=false
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl set load_balancer lb1 options:deferred-nat=true
+check ovn-nbctl --wait=sb set load_balancer lb1 options:deferred-nat=false
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1], [0], [])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1], [0], [])
+AT_CHECK([deferred_ls_lb_flows ls1 30.0.0.1], [0], [])
+AT_CHECK([prelb_flows ls1], [0], [])
+AT_CHECK([ct_extract_flows ls1], [0], [])
+
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([deferred-nat -- vips and backends])
+ovn_start
+DEFERRED_NAT_SETUP
+
+check ovn-nbctl --wait=sb set load_balancer lb1 options:deferred-nat=true
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [3
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [3
+])
+
+AS_BOX([Drop one backend -- 3 members left of 2])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 \
+ vips:'"30.0.0.1:80"'='"172.16.0.101:8080,172.16.0.102:8080"'
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 =
select(values=(172.16.0.101,172.16.0.102); group_key="lr1:lb1:30.0.0.1:80");)
+])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [2
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [2
+])
+
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([A single backend is named directly -- "select" needs two])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 \
+ vips:'"30.0.0.1:80"'='"172.16.0.102:8080"'
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 = 172.16.0.102; next;)
+])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [1
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=200 , match=(ip4.dst == 30.0.0.1 &&
tcp.dst == 80 && inport == "ls1-lr1"), action=(next;)
+ table=??(ls_out_lb ), priority=130 , match=(eth.dst ==
00:00:00:00:00:02 && ip4.dst == 30.0.0.1 && tcp.dst == 80),
action=(ct_lb_mark(backends=172.16.0.102:8080);)
+])
+AT_CHECK([deferred_ls_lb_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 &&
ip4.dst == 30.0.0.1 && tcp.dst == 80), action=(reg4 = 30.0.0.1; reg2[[0..15]] =
80; ct_lb_mark;)
+])
+
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([No backend at all])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"30.0.0.1:80"'='""'
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(drop;)
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=200 , match=(ip4.dst == 30.0.0.1 &&
tcp.dst == 80 && inport == "ls1-lr1"), action=(next;)
+])
+dnl There is no backend left to host the switch side either.
+AT_CHECK([deferred_ls_lb_flows ls1 30.0.0.1], [0], [])
+
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 \
+
vips:'"30.0.0.1:80"'='"172.16.0.103:8080,172.16.0.101:8080,172.16.0.102:8080"'
+
+AS_BOX([A second vip gets its own group_key])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 \
+ vips:'"30.0.0.2:80"'='"172.16.0.101:8080,172.16.0.102:8080"'
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.2 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.2 && tcp && tcp.dst == 80), action=(reg4 =
select(values=(172.16.0.101,172.16.0.102); group_key="lr1:lb1:30.0.0.2:80");)
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.2 | grep -c "ls_out_lb"], [0], [2
+])
+AT_CHECK([deferred_ls_lb_flows ls1 30.0.0.2], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 &&
ip4.dst == 30.0.0.2 && tcp.dst == 80), action=(reg4 = 30.0.0.2; reg2[[0..15]] =
80; ct_lb_mark;)
+])
+AT_CHECK([prelb_flows ls1], [0], [dnl
+ table=??(ls_in_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]]
= 1; next;)
+])
+AT_CHECK([ct_extract_flows ls1], [0], [dnl
+ table=??(ls_in_ct_extract ), priority=100 , match=(ct.new && ip),
action=(reg1[[16..23]] = ct_proto(); reg1[[0..15]] = ct_tp_dst(); next;)
+])
+
+# The first vip is untouched.
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [3
+])
+
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([selection_fields does not disturb group_key])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 selection_fields=ip_src,ip_dst
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 =
select(values=(172.16.0.103,172.16.0.101,172.16.0.102);
group_key="lr1:lb1:30.0.0.1:80"; hash_fields="ip_dst,ip_src");)
+])
+
+dnl The switch side doensn't pick hash fields up
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=200 , match=(ip4.dst == 30.0.0.1 &&
tcp.dst == 80 && inport == "ls1-lr1"), action=(next;)
+ table=??(ls_out_lb ), priority=130 , match=(eth.dst ==
00:00:00:00:00:01 && ip4.dst == 30.0.0.1 && tcp.dst == 80),
action=(ct_lb_mark(backends=172.16.0.101:8080);)
+ table=??(ls_out_lb ), priority=130 , match=(eth.dst ==
00:00:00:00:00:02 && ip4.dst == 30.0.0.1 && tcp.dst == 80),
action=(ct_lb_mark(backends=172.16.0.102:8080);)
+ table=??(ls_out_lb ), priority=130 , match=(eth.dst ==
00:00:00:00:00:03 && ip4.dst == 30.0.0.1 && tcp.dst == 80),
action=(ct_lb_mark(backends=172.16.0.103:8080);)
+])
+
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Renaming the lb moves the group_key])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+lb1_uuid=$(fetch_column nb:load_balancer _uuid name=lb1)
+check ovn-nbctl --wait=sb set load_balancer $lb1_uuid name=lb-renamed
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c
'group_key="lr1:lb-renamed:30.0.0.1:80"'], [0], [1
+])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep
'group_key="lr1:lb1:30.0.0.1:80"'], [1], [])
+
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([deferred-nat -- ip_port_mappings])
+AT_KEYWORDS([lb deferred-nat])
+ovn_start
+DEFERRED_NAT_SETUP
+
+check ovn-nbctl --wait=sb set load_balancer lb1 options:deferred-nat=true
+
+AS_BOX([A backend without a mapping is not steered])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb remove load_balancer lb1 ip_port_mappings
172.16.0.103
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 =
select(values=(172.16.0.101,172.16.0.102); group_key="lr1:lb1:30.0.0.1:80");)
+])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [2
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [2
+])
+
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Adding the mapping back brings the backend back])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 \
+ ip_port_mappings:172.16.0.103=lport3:192.168.0.99
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [3
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [3
+])
+
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Clearing all the mappings removes every deferred-nat flow])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb clear load_balancer lb1 ip_port_mappings
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(drop;)
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=200 , match=(ip4.dst == 30.0.0.1 &&
tcp.dst == 80 && inport == "ls1-lr1"), action=(next;)
+])
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([A mapping without a source IP is supported])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1
ip_port_mappings:172.16.0.101=lport1
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 = 172.16.0.101; next;)
+])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [1
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [1
+])
+
+check ovn-nbctl clear load_balancer lb1 ip_port_mappings
+AS_BOX([A mapping to a port that does not exist is ignored])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 \
+ ip_port_mappings:172.16.0.101=nosuchport:192.168.0.99
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(drop;)
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=200 , match=(ip4.dst == 30.0.0.1 &&
tcp.dst == 80 && inport == "ls1-lr1"), action=(next;)
+])
+
+check_engine_compute lflow recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([deferred-nat -- logical switch port changes])
+AT_KEYWORDS([lb deferred-nat])
+ovn_start
+DEFERRED_NAT_SETUP
+
+check ovn-nbctl --wait=sb set load_balancer lb1 options:deferred-nat=true
+
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep "172.16.0.101:8080"], [0], [dnl
+ table=??(ls_out_lb ), priority=130 , match=(eth.dst ==
00:00:00:00:00:01 && ip4.dst == 30.0.0.1 && tcp.dst == 80),
action=(ct_lb_mark(backends=172.16.0.101:8080);)
+])
+
+AS_BOX([Change the mac of a backend port])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-set-addresses lport1 "00:00:00:00:00:aa
172.16.0.101"
+
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep "172.16.0.101:8080"], [0], [dnl
+ table=??(ls_out_lb ), priority=130 , match=(eth.dst ==
00:00:00:00:00:aa && ip4.dst == 30.0.0.1 && tcp.dst == 80),
action=(ct_lb_mark(backends=172.16.0.101:8080);)
+])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [3
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [3
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Delete a backend port])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-del lport3
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 =
select(values=(172.16.0.101,172.16.0.102); group_key="lr1:lb1:30.0.0.1:80");)
+])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [2
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [2
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Re-create the port with a different mac])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl lsp-add ls1 lport3
+check ovn-nbctl --wait=sb lsp-set-addresses lport3 "00:00:00:00:00:cc
172.16.0.103"
+
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep "172.16.0.103:8080"], [0], [dnl
+ table=??(ls_out_lb ), priority=130 , match=(eth.dst ==
00:00:00:00:00:cc && ip4.dst == 30.0.0.1 && tcp.dst == 80),
action=(ct_lb_mark(backends=172.16.0.103:8080);)
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([A backend whose port shows up only later])
+check ovn-nbctl --wait=sb set load_balancer lb1 \
+ vips:'"30.0.0.1:80"'='"172.16.0.101:8080,172.16.0.104:8080"' \
+ ip_port_mappings:172.16.0.104=lport4:192.168.0.99
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 = 172.16.0.101; next;)
+])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [1
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [1
+])
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl lsp-add ls1 lport4
+check ovn-nbctl --wait=sb lsp-set-addresses lport4 "00:00:00:00:00:04
172.16.0.104"
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 =
select(values=(172.16.0.101,172.16.0.104); group_key="lr1:lb1:30.0.0.1:80");)
+])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [2
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep "172.16.0.104:8080"], [0], [dnl
+ table=??(ls_out_lb ), priority=130 , match=(eth.dst ==
00:00:00:00:00:04 && ip4.dst == 30.0.0.1 && tcp.dst == 80),
action=(ct_lb_mark(backends=172.16.0.104:8080);)
+])
+
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Disabling a backend port])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-set-enabled lport4 disabled
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 = 172.16.0.101; next;)
+])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [1
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep "172.16.0.101:8080"], [0], [dnl
+ table=??(ls_out_lb ), priority=130 , match=(eth.dst ==
00:00:00:00:00:aa && ip4.dst == 30.0.0.1 && tcp.dst == 80),
action=(ct_lb_mark(backends=172.16.0.101:8080);)
+])
+
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([A backend port with no address at all])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb clear logical_switch_port lport4 addresses
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ct_extract"], [0], [1
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([deferred-nat -- subnets and router ports])
+AT_KEYWORDS([lb deferred-nat])
+ovn_start
+DEFERRED_NAT_SETUP
+
+check ovn-nbctl --wait=sb set load_balancer lb1 options:deferred-nat=true
+
+AS_BOX([New subnet first, backend afterwards])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl ls-add ls2
+check ovn-nbctl lrp-add lr1 lr1-ls2 00:00:01:02:03:05 172.17.0.1/24
+check ovn-nbctl lsp-add-router-port ls2 ls2-lr1 lr1-ls2
+check ovn-nbctl lsp-add ls2 lport5 -- lsp-set-addresses lport5
"00:00:00:00:00:05 172.17.0.101"
+check ovn-nbctl --wait=sb set load_balancer lb1 \
+ vips:'"30.0.0.1:80"'='"172.16.0.101:8080,172.17.0.101:8080"' \
+ ip_port_mappings:172.17.0.101=lport5:192.168.0.99
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 =
select(values=(172.16.0.101,172.17.0.101); group_key="lr1:lb1:30.0.0.1:80");)
+ table=??(lr_in_dnat ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(next;)
+ table=??(lr_in_ip_routing ), priority=1050 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80 && reg4 == 172.16.0.101), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = 172.16.0.101; reg5 = 172.16.0.1; eth.src =
00:00:01:02:03:04; outport = "lr1-ls1"; reg9[[9]] = 1; flags.loopback = 1;
next;)
+ table=??(lr_in_ip_routing ), priority=1050 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80 && reg4 == 172.17.0.101), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = 172.17.0.101; reg5 = 172.17.0.1; eth.src =
00:00:01:02:03:05; outport = "lr1-ls2"; reg9[[9]] = 1; flags.loopback = 1;
next;)
+])
+AT_CHECK([deferred_ls_flows ls2 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=200 , match=(ip4.dst == 30.0.0.1 &&
tcp.dst == 80 && inport == "ls2-lr1"), action=(next;)
+ table=??(ls_out_lb ), priority=130 , match=(eth.dst ==
00:00:00:00:00:05 && ip4.dst == 30.0.0.1 && tcp.dst == 80),
action=(ct_lb_mark(backends=172.17.0.101:8080);)
+])
+
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Backend first, subnet afterwards])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 \
+
vips:'"30.0.0.1:80"'='"172.16.0.101:8080,172.17.0.101:8080,172.18.0.101:8080"' \
+ ip_port_mappings:172.18.0.101=lport6:192.168.0.99
+
+# Neither ls3 nor lport6 exist yet.
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 =
select(values=(172.16.0.101,172.17.0.101); group_key="lr1:lb1:30.0.0.1:80");)
+ table=??(lr_in_dnat ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(next;)
+ table=??(lr_in_ip_routing ), priority=1050 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80 && reg4 == 172.16.0.101), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = 172.16.0.101; reg5 = 172.16.0.1; eth.src =
00:00:01:02:03:04; outport = "lr1-ls1"; reg9[[9]] = 1; flags.loopback = 1;
next;)
+ table=??(lr_in_ip_routing ), priority=1050 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80 && reg4 == 172.17.0.101), action=(ip.ttl--;
reg8[[0..15]] = 0; reg0 = 172.17.0.101; reg5 = 172.17.0.1; eth.src =
00:00:01:02:03:05; outport = "lr1-ls2"; reg9[[9]] = 1; flags.loopback = 1;
next;)
+])
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl ls-add ls3
+check ovn-nbctl lrp-add lr1 lr1-ls3 00:00:01:02:03:06 172.18.0.1/24
+check ovn-nbctl lsp-add-router-port ls3 ls3-lr1 lr1-ls3
+check ovn-nbctl lsp-add ls3 lport6 -- lsp-set-addresses lport6
"00:00:00:00:00:06 172.18.0.101"
+check ovn-nbctl --wait=sb sync
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [3
+])
+AT_CHECK([deferred_ls_flows ls3 30.0.0.1 | grep -c "ls_out_lb"], [0], [1
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Drop a subnet])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lrp-del lr1-ls3
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [2
+])
+AT_CHECK([deferred_ls_flows ls3 30.0.0.1 | grep "ls_in_pre_stateful"], [1], [])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([A second router port on the backend switch])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl lr-add lr2
+check ovn-nbctl lrp-add lr2 lr2-ls1 00:00:02:02:03:04 172.16.1.1/24
+check ovn-nbctl --wait=sb lsp-add-router-port ls1 ls1-lr2 lr2-ls1
+
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep "ls_in_pre_stateful"], [0],
[dnl
+ table=??(ls_in_pre_stateful ), priority=200 , match=(ip4.dst == 30.0.0.1 &&
tcp.dst == 80 && inport == "ls1-lr1"), action=(next;)
+ table=??(ls_in_pre_stateful ), priority=200 , match=(ip4.dst == 30.0.0.1 &&
tcp.dst == 80 && inport == "ls1-lr2"), action=(next;)
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Remove the second router port again])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-del ls1-lr2
+
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_in_pre_stateful"], [0],
[1
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([deferred-nat -- health checks])
+AT_KEYWORDS([lb deferred-nat])
+ovn_start
+DEFERRED_NAT_SETUP
+
+check ovn-nbctl --wait=sb set load_balancer lb1 options:deferred-nat=true
+
+AS_BOX([Adding a health check -- backends start out offline])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+AT_CHECK([ovn-nbctl --wait=sb \
+ -- --id=@hc create Load_Balancer_Health_Check vip=30.0.0.1\\:80 \
+ -- add Load_Balancer lb1 health_check @hc | uuidfilt], [0], [<0>
+])
+
+check_engine_compute northd recompute
+wait_row_count Service_Monitor 3 status=offline
+check ovn-nbctl --wait=sb sync
+
+OVS_WAIT_FOR_OUTPUT(
+ [deferred_lr_flows lr1 30.0.0.1], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(drop;)
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=200 , match=(ip4.dst == 30.0.0.1 &&
tcp.dst == 80 && inport == "ls1-lr1"), action=(next;)
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Backends come online])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-sbctl set port_binding lport1 up=true
+check ovn-sbctl set port_binding lport2 up=true
+check ovn-sbctl set port_binding lport3 up=true
+check ovn-sbctl set service_monitor lport1 status=online
+check ovn-sbctl set service_monitor lport2 status=online
+check ovn-sbctl set service_monitor lport3 status=online
+ovn-sbctl list service_monitor
+
+wait_row_count Service_Monitor 3 status=online
+check ovn-nbctl --wait=sb sync
+
+OVS_WAIT_FOR_OUTPUT(
+ [deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 =
select(values=(172.16.0.101,172.16.0.102,172.16.0.103);
group_key="lr1:lb1:30.0.0.1:80");)
+])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [3
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [3
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([One backend goes offline -- it drops out of the select, the routing
and egress flows are unaffected])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-sbctl set service_monitor lport2 status=offline
+wait_row_count Service_Monitor 1 logical_port=lport2 status=offline
+check ovn-nbctl --wait=sb sync
+
+OVS_WAIT_FOR_OUTPUT(
+ [deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 =
select(values=(172.16.0.101,172.16.0.103); group_key="lr1:lb1:30.0.0.1:80");)
+])
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [2
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [2
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Every backend goes offline])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-sbctl set service_monitor lport1 status=offline
+check ovn-sbctl set service_monitor lport3 status=offline
+wait_row_count Service_Monitor 3 status=offline
+check ovn-nbctl --wait=sb sync
+
+# Back to the all-offline shape: the select gives way to a drop, and the
+# egress side disappears again, while the per-backend routing/dnat flows
+# stay untouched.
+OVS_WAIT_FOR_OUTPUT(
+ [deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(drop;)
+])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ip_routing"], [1], [])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1], [0], [dnl
+ table=??(ls_in_pre_stateful ), priority=200 , match=(ip4.dst == 30.0.0.1 &&
tcp.dst == 80 && inport == "ls1-lr1"), action=(next;)
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Backends come back])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-sbctl set service_monitor lport1 status=online
+check ovn-sbctl set service_monitor lport2 status=online
+check ovn-sbctl set service_monitor lport3 status=online
+wait_row_count Service_Monitor 3 status=online
+check ovn-nbctl --wait=sb sync
+
+OVS_WAIT_FOR_OUTPUT(
+ [deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 =
select(values=(172.16.0.101,172.16.0.102,172.16.0.103);
group_key="lr1:lb1:30.0.0.1:80");)
+])
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [3
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [3
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Removing the health check])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb clear load_balancer lb1 health_check
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep -c "lr_in_ip_routing"], [0], [3
+])
+AT_CHECK([deferred_ls_flows ls1 30.0.0.1 | grep -c "ls_out_lb"], [0], [3
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([deferred-nat -- backend outside of the router])
+AT_KEYWORDS([lb deferred-nat])
+ovn_start
+DEFERRED_NAT_SETUP
+
+check ovn-nbctl --wait=sb set load_balancer lb1 options:deferred-nat=true
+
+AS_BOX([A backend on a switch that lr1 cannot reach])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl ls-add ls9
+check ovn-nbctl lr-add lr9
+check ovn-nbctl lrp-add lr9 lr9-ls9 00:00:09:02:03:04 172.19.0.1/24
+check ovn-nbctl lsp-add-router-port ls9 ls9-lr9 lr9-ls9
+check ovn-nbctl lsp-add ls9 lport9 -- lsp-set-addresses lport9
"00:00:00:00:00:09 172.19.0.101"
+check ovn-nbctl --wait=sb set load_balancer lb1 \
+ vips:'"30.0.0.1:80"'='"172.16.0.101:8080,172.19.0.101:8080"' \
+ ip_port_mappings:172.19.0.101=lport9:192.168.0.99
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 = 172.16.0.101; next;)
+])
+AT_CHECK([deferred_ls_flows ls9 30.0.0.1], [0], [])
+AT_CHECK([deferred_ls_lb_flows ls9 30.0.0.1], [0], [])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AS_BOX([Connect that switch to lr1 as well])
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl lrp-add lr1 lr1-ls9 00:00:01:02:03:09 172.19.0.2/24
+check ovn-nbctl --wait=sb lsp-add-router-port ls9 ls9-lr1 lr1-ls9
+
+AT_CHECK([deferred_lr_flows lr1 30.0.0.1 | grep "lr_in_ct_extract"], [0], [dnl
+ table=??(lr_in_ct_extract ), priority=130 , match=(ip4 && ip4.dst ==
30.0.0.1 && tcp && tcp.dst == 80), action=(reg4 =
select(values=(172.16.0.101,172.19.0.101); group_key="lr1:lb1:30.0.0.1:80");)
+])
+AT_CHECK([deferred_ls_flows ls9 30.0.0.1 | grep -c "ls_in_pre_stateful"], [0],
[2
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
diff --git a/tests/ovn.at b/tests/ovn.at
index 9c4698d76..51cd10948 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -2425,6 +2425,30 @@ ct_lb_mark_local(backends="lsp1":[[fd0f::2]]:80);
uses group: id(23),
name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=[[fd0f::2]]:80),commit,table=oflow_in_table,zone=NXM_NX_REG13[[0..15]],exec(set_field:2/2->ct_mark)))
has prereqs ip
+# "select" takes a group_key, and accepts it as the only modifier.
+reg0 = select(values=(1, 2); group_key="lb0:30.0.0.1:80");
+ formats as reg0 = select(values=(1=100, 2=100);
group_key="lb0:30.0.0.1:80");
+ encodes as group:30
+ uses group: id(30),
name(lb0:30.0.0.1:80|type=select,selection_method=dp_hash|table=oflow_in_table|xxreg0[[96..127]])
+
+reg0 = select(values=(1, 2); group_key="lb1:30.0.0.2:80";
hash_fields="ip_dst,ip_src");
+ formats as reg0 = select(values=(1=100, 2=100);
group_key="lb1:30.0.0.2:80"; hash_fields="ip_dst,ip_src");
+ encodes as group:31
+ uses group: id(31),
name(lb1:30.0.0.2:80|type=select,selection_method=hash,fields(ip_dst,ip_src)|table=oflow_in_table|xxreg0[[96..127]])
+
+reg0 = select(values=(1, 2); group_key=);
+ Syntax error at `)' invalid group_key.
+
+reg0 = select(values=(42.42.42.3, 42.42.42.4); group_key="lb3:30.0.0.4:80");
+ formats as reg0 = select(values=(42.42.42.3=100, 42.42.42.4=100);
group_key="lb3:30.0.0.4:80");
+ encodes as group:32
+ uses group: id(32),
name(lb3:30.0.0.4:80|type=select,selection_method=dp_hash|table=oflow_in_table|xxreg0[[96..127]])
+
+reg0 = select(1, 2; group_key="lb2:30.0.0.3:80");
+ formats as reg0 = select(values=(1=100, 2=100);
group_key="lb2:30.0.0.3:80");
+ encodes as group:33
+ uses group: id(33),
name(lb2:30.0.0.3:80|type=select,selection_method=dp_hash|table=oflow_in_table|xxreg0[[96..127]])
+
# Miscellaneous negative tests.
;
Syntax error at `;'.
@@ -38194,7 +38218,7 @@ check_default_flows() {
OFTABLE_CT_STATE_SAVE)
continue ;;
esac
- AT_CHECK([grep -qe "table=$table.* priority=0\(,metadata=0x\w*\)\?
actions" oflows], [0], [ignore], [ignore], [echo "Table $table does not contain
a default action"])
+ AT_CHECK([grep -qe "table=$table,.*
priority=[[0-9]]\+\(,metadata=0x\w*\)\? actions" oflows], [0], [ignore],
[ignore], [echo "Table $table does not contain a default action"])
done
}
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index d4f2ce69e..4b04c68bc 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -23946,3 +23946,375 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port
patch-.*/d
AT_CLEANUP
])
+
+m4_define([DEFERRED_NAT_GROUP_MARK], [NXM_NX_XXREG1])
+m4_define([DEFERRED_NAT_GROUP_HELPERS], [
+# The deferred-nat group as one line, or nothing if it is not installed.
+lb_group_line() {
+ ovs-ofctl dump-groups br-int | grep "type=select" | grep
"DEFERRED_NAT_GROUP_MARK"
+}
+
+# One bucket per line, sorted.
+lb_group_buckets() {
+ lb_group_line | sed 's/,bucket=/@/g' | tr '@' '\n' |
+ grep "^bucket_id:" | sort
+}
+
+lb_group_id() {
+ lb_group_line | sed -n 's/^ *group_id=\([[0-9]]*\),.*/\1/p'
+}
+
+# bucket_id_of HEX -- id of the bucket that loads HEX into the result field,
+# i.e. the bucket of one backend. Prints nothing if there is no such bucket.
+bucket_id_of() {
+ lb_group_buckets | grep "$[]1" | sed -n 's/^bucket_id:\([[0-9]]*\),.*/\1/p'
+}
+
+n_buckets() {
+ lb_group_buckets | wc -l | tr -d ' '
+}
+
+# Deferred-nat groups only -- the ct_lb_mark ones are not ours to count.
+n_select_groups() {
+ lb_group_line | grep -c "group_id="
+}
+])
+
+# DEFERRED_NAT_SYSTEM_SETUP
+# hv1 with br-int, and:
+#
+# client 42.42.42.2 -- ls1 -- lr1 (lr1-ls1 42.42.42.1/24, gateway chassis
hv1)
+# |
+# +---- backend3..6 (42.42.42.3 .. 42.42.42.6)
+#
+# lb0: 30.0.0.1:80 with 'deferred-nat' set. Only 'client' is backed by a real
+# veth -- that is enough to make ls1 and lr1 local to hv1 so that the group is
+# installed; the backends only need to exist in the NB.
+#
+# The backend addresses are chosen so that the value each bucket loads is easy
+# to spot in the group dump: 42.42.42.3 is 0x2a2a2a03 and so on.
+m4_define([DEFERRED_NAT_SYSTEM_SETUP], [
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+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_daemon ovn-controller
+
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 42.42.42.1/24
+check ovn-nbctl lrp-set-gateway-chassis lr1-ls1 hv1
+
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lsp-add-router-port ls1 ls1-lr1 lr1-ls1
+
+ADD_NAMESPACES(client)
+ADD_VETH(client, client, br-int, "42.42.42.2/24", "f0:00:00:01:02:02", \
+ "42.42.42.1")
+check ovn-nbctl lsp-add ls1 client \
+ -- lsp-set-addresses client "f0:00:00:01:02:02 42.42.42.2"
+
+for i in 3 4 5 6; do
+ check ovn-nbctl lsp-add ls1 backend$i \
+ -- lsp-set-addresses backend$i "f0:00:00:01:02:0$i 42.42.42.$i"
+done
+
+check ovn-nbctl lb-add lb0 30.0.0.1:80 \
+ 42.42.42.3:10880,42.42.42.4:10880,42.42.42.5:10880
+check ovn-nbctl set load_balancer lb0 options:deferred-nat=true
+for i in 3 4 5 6; do
+ check ovn-nbctl set load_balancer lb0 \
+ ip_port_mappings:42.42.42.$i=backend$i:42.42.42.2
+done
+check ovn-nbctl lr-lb-add lr1 lb0
+check ovn-nbctl --wait=hv sync
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([deferred-nat load balancing -- incremental group buckets])
+AT_KEYWORDS([ovnlb deferred-nat])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+DEFERRED_NAT_GROUP_HELPERS
+DEFERRED_NAT_SYSTEM_SETUP
+
+AS_BOX([Three backends -- one bucket each])
+
+$(n_buckets)
+OVS_WAIT_UNTIL([test $(n_buckets) = 3])
+AT_CHECK([test $(n_select_groups) = 1])
+AT_CHECK([lb_group_buckets | grep -c 0x2a2a2a03], [0], [1
+])
+AT_CHECK([lb_group_buckets | grep -c 0x2a2a2a04], [0], [1
+])
+AT_CHECK([lb_group_buckets | grep -c 0x2a2a2a05], [0], [1
+])
+
+gid=$(lb_group_id)
+b3=$(bucket_id_of 0x2a2a2a03)
+b4=$(bucket_id_of 0x2a2a2a04)
+b5=$(bucket_id_of 0x2a2a2a05)
+
+AT_CHECK([test -n "$gid" && test -n "$b3" && test -n "$b4" && test -n "$b5"])
+echo "group_id=$gid buckets: .3=$b3 .4=$b4 .5=$b5"
+
+AS_BOX([Drop a backend -- the group is not rebuilt])
+check ovn-nbctl --wait=hv set load_balancer lb0 \
+ vips:'"30.0.0.1:80"'='"42.42.42.3:10880,42.42.42.4:10880"'
+OVS_WAIT_UNTIL([test $(n_buckets) = 2])
+
+# Same group, same ids for the backends that stayed: the group was updated a
+# bucket at a time rather than deleted and recreated.
+AT_CHECK([test "$(lb_group_id)" = "$gid"])
+AT_CHECK([test "$(bucket_id_of 0x2a2a2a03)" = "$b3"])
+AT_CHECK([test "$(bucket_id_of 0x2a2a2a04)" = "$b4"])
+AT_CHECK([lb_group_buckets | grep -c 0x2a2a2a05], [1], [0
+])
+
+AS_BOX([Add a different backend])
+check ovn-nbctl --wait=hv set load_balancer lb0 \
+ vips:'"30.0.0.1:80"'='"42.42.42.3:10880,42.42.42.4:10880,42.42.42.6:10880"'
+OVS_WAIT_UNTIL([test $(n_buckets) = 3])
+
+AT_CHECK([test "$(lb_group_id)" = "$gid"])
+AT_CHECK([test "$(bucket_id_of 0x2a2a2a03)" = "$b3"])
+AT_CHECK([test "$(bucket_id_of 0x2a2a2a04)" = "$b4"])
+AT_CHECK([lb_group_buckets | grep -c 0x2a2a2a06], [0], [1
+])
+b6=$(bucket_id_of 0x2a2a2a06)
+
+AS_BOX([A backend that comes back keeps its old identity])
+check ovn-nbctl --wait=hv set load_balancer lb0 \
+
vips:'"30.0.0.1:80"'='"42.42.42.3:10880,42.42.42.4:10880,42.42.42.5:10880,42.42.42.6:10880"'
+OVS_WAIT_UNTIL([test $(n_buckets) = 4])
+
+AT_CHECK([test "$(lb_group_id)" = "$gid"])
+AT_CHECK([test "$(bucket_id_of 0x2a2a2a03)" = "$b3"])
+AT_CHECK([test "$(bucket_id_of 0x2a2a2a04)" = "$b4"])
+AT_CHECK([test "$(bucket_id_of 0x2a2a2a06)" = "$b6"])
+# The bucket id is derived from the backend itself, so .5 lands back on the
+# id it had before it was removed.
+AT_CHECK([test "$(bucket_id_of 0x2a2a2a05)" = "$b5"])
+
+AS_BOX([Reordering the backends changes nothing])
+check ovn-nbctl --wait=hv set load_balancer lb0 \
+
vips:'"30.0.0.1:80"'='"42.42.42.6:10880,42.42.42.5:10880,42.42.42.4:10880,42.42.42.3:10880"'
+check ovn-nbctl --wait=hv sync
+OVS_WAIT_UNTIL([test $(n_buckets) = 4])
+
+AT_CHECK([test "$(lb_group_id)" = "$gid"])
+AT_CHECK([test "$(bucket_id_of 0x2a2a2a03)" = "$b3"])
+AT_CHECK([test "$(bucket_id_of 0x2a2a2a04)" = "$b4"])
+AT_CHECK([test "$(bucket_id_of 0x2a2a2a05)" = "$b5"])
+AT_CHECK([test "$(bucket_id_of 0x2a2a2a06)" = "$b6"])
+
+AS_BOX([A controller recompute produces the same group])
+buckets_before=$(lb_group_buckets)
+check ovn-appctl -t ovn-controller inc-engine/recompute
+check ovn-nbctl --wait=hv sync
+AT_CHECK([test $(n_select_groups) = 1])
+AT_CHECK([test "$(lb_group_id)" = "$gid"])
+AT_CHECK([test "$(lb_group_buckets)" = "$buckets_before"])
+
+AS_BOX([The group survives a controller restart])
+TMPPID=$(cat $OVS_RUNDIR/ovn-controller.pid)
+check ovs-appctl -t ovn-controller exit --restart
+OVS_WAIT_WHILE([kill -0 $TMPPID 2>/dev/null])
+start_daemon ovn-controller
+check ovn-nbctl --wait=hv sync
+
+# The group id may be picked anew here, but there must be exactly one group,
+# with one bucket per backend and no duplicates.
+OVS_WAIT_UNTIL([test $(n_buckets) = 4])
+AT_CHECK([test $(n_select_groups) = 1])
+AT_CHECK([lb_group_buckets | grep -c 0x2a2a2a03], [0], [1
+])
+AT_CHECK([lb_group_buckets | grep -c 0x2a2a2a04], [0], [1
+])
+AT_CHECK([lb_group_buckets | grep -c 0x2a2a2a05], [0], [1
+])
+AT_CHECK([lb_group_buckets | grep -c 0x2a2a2a06], [0], [1
+])
+
+# None of the bucket updates above may have failed to parse or been rejected
+# by the switch -- ofctrl.c logs those at ERR. This is what catches a bridge
+# that never negotiated OpenFlow 1.5, since INSERT_BUCKET/REMOVE_BUCKET need
+# it.
+AT_CHECK([grep -cE "(insert|remove) bucket" ovn-controller.log], [1], [0
+])
+
+OVN_CLEANUP_CONTROLLER([hv1])
+OVN_CLEANUP_NORTHD
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([deferred-nat load balancing -- group lifecycle])
+AT_KEYWORDS([ovnlb deferred-nat])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+DEFERRED_NAT_GROUP_HELPERS
+DEFERRED_NAT_SYSTEM_SETUP
+
+OVS_WAIT_UNTIL([test $(n_buckets) = 3])
+
+AS_BOX([A single backend needs no group at all])
+# northd emits a plain assignment instead of a select, so the group has to be
+# removed from br-int rather than left behind.
+check ovn-nbctl --wait=hv set load_balancer lb0 \
+ vips:'"30.0.0.1:80"'='"42.42.42.3:10880"'
+OVS_WAIT_UNTIL([test $(n_select_groups) = 0])
+
+AS_BOX([Two backends again -- exactly one group])
+check ovn-nbctl --wait=hv set load_balancer lb0 \
+ vips:'"30.0.0.1:80"'='"42.42.42.3:10880,42.42.42.4:10880"'
+OVS_WAIT_UNTIL([test $(n_buckets) = 2])
+AT_CHECK([test $(n_select_groups) = 1])
+
+AS_BOX([No backends at all])
+check ovn-nbctl --wait=hv clear load_balancer lb0 ip_port_mappings
+OVS_WAIT_UNTIL([test $(n_select_groups) = 0])
+
+check ovn-nbctl set load_balancer lb0 \
+ ip_port_mappings:42.42.42.3=backend3:42.42.42.2
+check ovn-nbctl --wait=hv set load_balancer lb0 \
+ ip_port_mappings:42.42.42.4=backend4:42.42.42.2
+OVS_WAIT_UNTIL([test $(n_buckets) = 2])
+
+AS_BOX([Turning the option off removes the group])
+check ovn-nbctl --wait=hv remove load_balancer lb0 options deferred-nat
+OVS_WAIT_UNTIL([test $(n_select_groups) = 0])
+
+check ovn-nbctl --wait=hv set load_balancer lb0 options:deferred-nat=true
+OVS_WAIT_UNTIL([test $(n_buckets) = 2])
+AT_CHECK([test $(n_select_groups) = 1])
+
+AS_BOX([Detaching the lb from the router removes the group])
+check ovn-nbctl --wait=hv lr-lb-del lr1 lb0
+OVS_WAIT_UNTIL([test $(n_select_groups) = 0])
+
+check ovn-nbctl --wait=hv lr-lb-add lr1 lb0
+OVS_WAIT_UNTIL([test $(n_buckets) = 2])
+
+AS_BOX([Deleting the lb removes the group])
+check ovn-nbctl --wait=hv lb-del lb0
+OVS_WAIT_UNTIL([test $(n_select_groups) = 0])
+
+# Nothing must be left behind on the bridge.
+AT_CHECK([ovs-ofctl -O OpenFlow15 dump-groups br-int | grep -c "group_id="],
[1], [0
+])
+
+OVN_CLEANUP_CONTROLLER([hv1])
+OVN_CLEANUP_NORTHD
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([deferred-nat load balancing -- connectivity across backend removal])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+DEFERRED_NAT_GROUP_HELPERS
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+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_daemon ovn-controller
+
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 42.42.42.1/24
+check ovn-nbctl lrp-set-gateway-chassis lr1-ls1 hv1
+check ovn-nbctl lrp-add lr1 lr1-ls2 00:00:00:00:00:02 10.0.0.1/24
+
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lsp-add-router-port ls1 ls1-lr1 lr1-ls1
+check ovn-nbctl ls-add ls2
+check ovn-nbctl lsp-add-router-port ls2 ls2-lr1 lr1-ls2
+
+ADD_NAMESPACES(client)
+ADD_VETH(client, client, br-int, "10.0.0.2/24", "f0:00:00:01:02:02", \
+ "10.0.0.1")
+check ovn-nbctl lsp-add ls2 client \
+ -- lsp-set-addresses client "f0:00:00:01:02:02 10.0.0.2"
+
+ADD_NAMESPACES(backend3)
+ADD_VETH(backend3, backend3, br-int, "42.42.42.3/24", "f0:00:00:01:02:03", \
+ "42.42.42.1")
+ADD_NAMESPACES(backend4)
+ADD_VETH(backend4, backend4, br-int, "42.42.42.4/24", "f0:00:00:01:02:04", \
+ "42.42.42.1")
+ADD_NAMESPACES(backend5)
+ADD_VETH(backend5, backend5, br-int, "42.42.42.5/24", "f0:00:00:01:02:05", \
+ "42.42.42.1")
+
+for i in 3 4 5; do
+ check ovn-nbctl lsp-add ls1 backend$i \
+ -- lsp-set-addresses backend$i "f0:00:00:01:02:0$i 42.42.42.$i"
+done
+
+check ovn-nbctl lb-add lb0 30.0.0.1:80 \
+ 42.42.42.3:80,42.42.42.4:80,42.42.42.5:80
+check ovn-nbctl set load_balancer lb0 options:deferred-nat=true
+for i in 3 4 5; do
+ check ovn-nbctl set load_balancer lb0 \
+ ip_port_mappings:42.42.42.$i=backend$i:42.42.42.1
+done
+check ovn-nbctl lr-lb-add lr1 lb0
+check ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([test $(n_buckets) = 3])
+
+NETNS_DAEMONIZE([backend3], [nc -l -k 42.42.42.3 80 > backend3.log],
+ [backend3.pid])
+NETNS_DAEMONIZE([backend4], [nc -l -k 42.42.42.4 80 > backend4.log],
+ [backend4.pid])
+NETNS_DAEMONIZE([backend5], [nc -l -k 42.42.42.5 80 > backend5.log],
+ [backend5.pid])
+for i in 3 4 5; do
+ OVS_WAIT_UNTIL([NS_EXEC([backend$i],
+ [netstat -ln | grep -q "42.42.42.$i:80"])])
+done
+
+AS_BOX([The vip accepts connections through the gateway])
+NS_CHECK_EXEC([client], [nc -z -w 1 30.0.0.1 80], [0], [ignore], [ignore])
+
+AS_BOX([Every connection to the vip is delivered to some backend])
+for i in $(seq 1 20); do
+ NS_CHECK_EXEC([client], [sh -c "echo conn$i | nc -w 1 30.0.0.1 80"],
+ [0], [ignore], [ignore])
+done
+OVS_WAIT_UNTIL([test $(cat backend3.log backend4.log backend5.log | \
+ grep -c '^conn') = 20])
+
+OVN_CLEANUP_CONTROLLER([hv1])
+OVN_CLEANUP_NORTHD
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+AT_CLEANUP
+])
--
2.48.1
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev