If two AZs connect ports with the same name to a transit switch,
ovn-ic retries a failing transaction forever. Port names are unique
in both the NB (Logical_Switch_Port) and the IC-SB (Port_Binding):
one AZ tries to create a remote LSP that clashes with its local one,
and the other AZ tries to bind a port name that is already bound.
The failed commit also blocks every other change in the same
transaction.
Check the name before inserting. On a conflict, skip the port and
log a rate-limited warning that names the port and AZs, so the
operator knows which port to rename.
Fixes: aa3e4436c232 ("ovn-ic: Interconnection port controller.")
Assisted-by: Claude Opus 5.5, Claude Code
Signed-off-by: Lucas Vargas Dias <[email protected]>
---
ic/en-port-binding.c | 75 +++++++++++++++++++++++++++++++++++++++-----
ic/ovn-ic.c | 5 +++
ic/ovn-ic.h | 1 +
tests/ovn-ic.at | 54 +++++++++++++++++++++++++++++++
4 files changed, 127 insertions(+), 8 deletions(-)
diff --git a/ic/en-port-binding.c b/ic/en-port-binding.c
index 5a2490b6e..638f819e0 100644
--- a/ic/en-port-binding.c
+++ b/ic/en-port-binding.c
@@ -465,6 +465,28 @@ create_nb_lsp(struct ic_context *ctx,
const struct icsbrec_port_binding *isb_pb,
const struct nbrec_logical_switch *ls)
{
+ /* The NB Logical_Switch_Port name is unique across the whole NB, but a
+ * remote AZ may bind a port whose name is already used here, e.g. by a
+ * local port with the same name on this transit switch or by a port of
+ * an unrelated logical switch. Inserting it would violate the name
+ * index, abort the whole NB transaction and retry it forever, so skip the
+ * remote port instead. A row inserted earlier in this same transaction
+ * (see lsp_create()) is also found here and is silently reused. */
+ const struct nbrec_logical_switch_port *existing =
+ get_lsp_by_ts_port_name(ctx, isb_pb->logical_port);
+ if (existing) {
+ if (!nbrec_logical_switch_port_is_new(existing)) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "Port %s of transit switch %s in availability "
+ "zone %s conflicts with an existing logical switch "
+ "port with the same name, skipping it.",
+ isb_pb->logical_port, isb_pb->transit_switch,
+ isb_pb->availability_zone
+ ? isb_pb->availability_zone->name : "<unknown>");
+ }
+ return;
+ }
+
const struct nbrec_logical_switch_port *lsp =
nbrec_logical_switch_port_insert(ctx->ovnnb_txn);
nbrec_logical_switch_port_set_name(lsp, isb_pb->logical_port);
@@ -521,11 +543,45 @@ find_transit_router_by_name(struct ic_context *ctx, const
char *name)
}
static const struct icsbrec_port_binding *
-create_isb_pb(struct ovsdb_idl_txn *isb_txn, const char *logical_port,
+find_isb_pb_by_name(struct ic_context *ctx, const char *logical_port)
+{
+ const struct icsbrec_port_binding *key =
+ icsbrec_port_binding_index_init_row(
+ ctx->icsbrec_port_binding_by_name);
+ icsbrec_port_binding_index_set_logical_port(key, logical_port);
+ const struct icsbrec_port_binding *isb_pb =
+ icsbrec_port_binding_index_find(ctx->icsbrec_port_binding_by_name,
+ key);
+ icsbrec_port_binding_index_destroy_row(key);
+ return isb_pb;
+}
+
+static const struct icsbrec_port_binding *
+create_isb_pb(struct ic_context *ctx, struct ovsdb_idl_txn *isb_txn,
+ const char *logical_port,
const struct icsbrec_availability_zone *az, const char *ts_name,
const struct uuid *nb_ic_uuid, const char *type,
struct hmap *pb_tnlids)
{
+ /* The IC-SB Port_Binding logical_port is unique across all AZs and
+ * transit switches/routers. If another AZ (or another transit
+ * switch/router) already binds a port with this name, inserting would
+ * violate the index, abort the whole IC-SB transaction and retry it
+ * forever, so leave this port unbound instead. */
+ const struct icsbrec_port_binding *existing =
+ find_isb_pb_by_name(ctx, logical_port);
+ if (existing) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "Port %s of %s in availability zone %s conflicts "
+ "with a port with the same name of %s in availability "
+ "zone %s, not binding it.",
+ logical_port, ts_name, az->name,
+ existing->transit_switch,
+ existing->availability_zone
+ ? existing->availability_zone->name : "<unknown>");
+ return NULL;
+ }
+
uint32_t pb_tnl_key = allocate_port_key(pb_tnlids);
if (!pb_tnl_key) {
return NULL;
@@ -720,7 +776,7 @@ port_binding_sync_ts(struct ic_context *ctx,
if (ctx->ovnisb_txn && is_az_leader(ctx->ovnisb_txn)) {
if (!isb_pb) {
isb_pb = create_isb_pb(
- ctx->ovnisb_txn, tsp->name, runned_az,
+ ctx, ctx->ovnisb_txn, tsp->name, runned_az,
ts->name, &ts->header_.uuid, "transit-switch-port",
&pb_tnlids);
}
@@ -730,8 +786,8 @@ port_binding_sync_ts(struct ic_context *ctx,
/* Create ISB port_binding as its chassis is local. */
isb_pb = shash_find_and_delete(&local_pbs, tsp->name);
if (!isb_pb) {
- isb_pb = create_isb_pb(ctx->ovnisb_unlocked_txn, tsp->name,
- runned_az,
+ isb_pb = create_isb_pb(ctx, ctx->ovnisb_unlocked_txn,
+ tsp->name, runned_az,
ts->name, &ts->header_.uuid,
"transit-switch-port", &pb_tnlids);
}
@@ -773,9 +829,12 @@ port_binding_sync_ts(struct ic_context *ctx,
isb_pb = shash_find_and_delete(&local_pbs, lsp->name);
if (!isb_pb) {
isb_pb = create_isb_pb(
- ctx->ovnisb_unlocked_txn, sb_pb->logical_port,
+ ctx, ctx->ovnisb_unlocked_txn, sb_pb->logical_port,
runned_az, ts->name, &ts->header_.uuid,
"transit-switch-port", &pb_tnlids);
+ if (!isb_pb) {
+ continue;
+ }
sync_ts_isb_pb(ctx, sb_pb, isb_pb);
} else {
sync_local_port(ctx, isb_pb, sb_pb, lsp);
@@ -899,7 +958,7 @@ port_binding_sync_tr(struct ic_context *ctx,
if (ctx->ovnisb_txn && is_az_leader(ctx->ovnisb_txn)) {
if (!isb_pb) {
- isb_pb = create_isb_pb(ctx->ovnisb_txn, trp->name,
+ isb_pb = create_isb_pb(ctx, ctx->ovnisb_txn, trp->name,
runned_az,
tr->name, &tr->header_.uuid,
"transit-router-port",
@@ -912,8 +971,8 @@ port_binding_sync_tr(struct ic_context *ctx,
} else {
isb_pb = shash_find_and_delete(&local_pbs, trp->name);
if (!isb_pb) {
- isb_pb = create_isb_pb(ctx->ovnisb_unlocked_txn, trp->name,
- runned_az,
+ isb_pb = create_isb_pb(ctx, ctx->ovnisb_unlocked_txn,
+ trp->name, runned_az,
tr->name, &tr->header_.uuid,
"transit-router-port", &pb_tnlids);
}
diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c
index e65256577..87a266b44 100644
--- a/ic/ovn-ic.c
+++ b/ic/ovn-ic.c
@@ -1191,6 +1191,10 @@ main(int argc, char *argv[])
= ovsdb_idl_index_create1(ovnisb_unlocked_idl_loop.idl,
&icsbrec_port_binding_col_availability_zone);
+ struct ovsdb_idl_index *icsbrec_port_binding_by_name
+ = ovsdb_idl_index_create1(ovnisb_unlocked_idl_loop.idl,
+ &icsbrec_port_binding_col_logical_port);
+
struct ovsdb_idl_index *icsbrec_port_binding_by_ts
= ovsdb_idl_index_create1(ovnisb_unlocked_idl_loop.idl,
&icsbrec_port_binding_col_transit_switch);
@@ -1408,6 +1412,7 @@ main(int argc, char *argv[])
.icnbrec_transit_router_by_name =
icnbrec_transit_router_by_name,
.icsbrec_port_binding_by_az = icsbrec_port_binding_by_az,
+ .icsbrec_port_binding_by_name = icsbrec_port_binding_by_name,
.icsbrec_port_binding_by_ts = icsbrec_port_binding_by_ts,
.icsbrec_port_binding_by_ts_az = icsbrec_port_binding_by_ts_az,
.icsbrec_route_by_az = icsbrec_route_by_az,
diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h
index 8adb17ad7..b1cfad08f 100644
--- a/ic/ovn-ic.h
+++ b/ic/ovn-ic.h
@@ -43,6 +43,7 @@ struct ic_context {
struct ovsdb_idl_index *icnbrec_transit_switch_by_name;
struct ovsdb_idl_index *icnbrec_transit_router_by_name;
struct ovsdb_idl_index *icsbrec_port_binding_by_az;
+ struct ovsdb_idl_index *icsbrec_port_binding_by_name;
struct ovsdb_idl_index *icsbrec_port_binding_by_ts;
struct ovsdb_idl_index *icsbrec_port_binding_by_ts_az;
struct ovsdb_idl_index *icsbrec_route_by_az;
diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at
index 646026817..4576c725e 100644
--- a/tests/ovn-ic.at
+++ b/tests/ovn-ic.at
@@ -1689,6 +1689,60 @@ OVN_CLEANUP_IC([az1], [az2])
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn-ic -- same port name in two AZs])
+
+ovn_init_ic_db
+ovn-ic-nbctl ts-add ts1
+ovn_start az1
+ovn_start az2
+check ovn-ic-nbctl --wait=sb sync
+
+dnl Each AZ connects a router to ts1 through a legacy port that has the
+dnl same name in both AZs. Pause ovn-ic so that neither AZ learns about the
+dnl other's port before creating its own.
+for az in az1 az2; do
+ as $az check ovn-appctl -t ic/ovn-ic pause
+done
+for az in az1 az2; do
+ ovn_as $az
+ check ovn-nbctl lr-add lr1
+ check ovn-nbctl lrp-add lr1 lrp-lr1-ts1 aa:aa:aa:aa:aa:01 169.254.100.1/24
+ check ovn-nbctl --wait=sb lsp-add-router-port ts1 lsp-dup lrp-lr1-ts1
+done
+for az in az1 az2; do
+ as $az check ovn-appctl -t ic/ovn-ic resume
+done
+
+dnl IC-SB port names are unique, so only one AZ gets to bind the port; the
+dnl other one must give up instead of retrying the insert forever.
+wait_row_count ic-sb:Port_Binding 1 logical_port=lsp-dup
+OVS_WAIT_UNTIL([grep -q "Port lsp-dup of ts1 .* conflicts with a port" \
+ az1/ic/ovn-ic.log az2/ic/ovn-ic.log])
+
+dnl And the AZ that got the binding must not try to create a remote NB port
+dnl over the other AZ's local port with the same name.
+OVS_WAIT_UNTIL([grep -q "Port lsp-dup of transit switch ts1 .* conflicts" \
+ az1/ic/ovn-ic.log az2/ic/ovn-ic.log])
+for az in az1 az2; do
+ ovn_as $az
+ check_row_count nb:Logical_Switch_Port 1 name=lsp-dup type=router
+done
+
+dnl ovn-ic must keep committing NB transactions: a port added in az1 shows
+dnl up as remote in az2.
+ovn_as az1
+check ovn-nbctl lrp-add lr1 lrp-lr1-ts1-2 aa:aa:aa:aa:aa:02 169.254.101.1/24
+check ovn-nbctl --wait=sb lsp-add-router-port ts1 lsp-az1-only lrp-lr1-ts1-2
+ovn_as az2
+wait_row_count nb:Logical_Switch_Port 1 name=lsp-az1-only type=remote
+
+AT_CHECK([grep -q "constraint violation" az1/ic/ovn-ic.log az2/ic/ovn-ic.log],
[1])
+
+OVN_CLEANUP_IC([az1], [az2])
+AT_CLEANUP
+])
+
OVN_FOR_EACH_NORTHD([
AT_SETUP([ovn-ic -- address-set sync])
--
2.43.0
--
_'Esta mensagem é direcionada apenas para os endereços constantes no
cabeçalho inicial. Se você não está listado nos endereços constantes no
cabeçalho, pedimos-lhe que desconsidere completamente o conteúdo dessa
mensagem e cuja cópia, encaminhamento e/ou execução das ações citadas estão
imediatamente anuladas e proibidas'._
* **'Apesar do Magazine Luiza tomar
todas as precauções razoáveis para assegurar que nenhum vírus esteja
presente nesse e-mail, a empresa não poderá aceitar a responsabilidade por
quaisquer perdas ou danos causados por esse e-mail ou por seus anexos'.*
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev