Add the incremental change handlers for the en_port_binding node and scope its processing per transit switch / transit router: an IC-NB, IC-SB Port_Binding, NB Logical_Switch/Router (or port), SB Port_Binding or SB Chassis change reconciles only the affected entities instead of forcing a full recompute.
Supporting changes folded in: - Extract per-entity port-binding sync functions and resolve a transit port -> switch/router via a prebuilt map (Fix F), so scoped reconciles are O(scope). - Allocate transit-port tunnel keys per transit switch/router, tolerate duplicate IC-SB port bindings, and make lrp_create()/lsp_create() idempotent (reuse an NB port already inserted uncommitted this run). - Gate the node on the AZ identity, add the ic-recompute-threshold-abs safeguard, and reconcile each transit switch/router at most once per engine run (synced_ts/synced_tr + clear_tracked_data). Add a tests/ovn-ic.at test for the port-binding handler scoping. Assisted-by: Claude Opus 4.8, Claude Code Signed-off-by: Paulo Guilherme Silva <[email protected]> --- ic/en-port-binding.c | 1504 +++++++++++++++++++++++++++++++++++++++++- ic/en-port-binding.h | 25 + ic/inc-proc-ic.c | 46 +- ic/ovn-ic.c | 848 ++---------------------- ic/ovn-ic.h | 25 +- tests/ovn-ic.at | 90 +++ 6 files changed, 1712 insertions(+), 826 deletions(-) diff --git a/ic/en-port-binding.c b/ic/en-port-binding.c index 0b8b9c5b2..5a1c78160 100644 --- a/ic/en-port-binding.c +++ b/ic/en-port-binding.c @@ -16,11 +16,1114 @@ #include "en-port-binding.h" #include "lib/inc-proc-eng.h" +#include "lib/ovn-ic-nb-idl.h" +#include "lib/ovn-ic-sb-idl.h" +#include "lib/ovn-nb-idl.h" +#include "lib/ovn-sb-idl.h" #include "openvswitch/vlog.h" #include "ovn-ic.h" +#include "ovsdb-idl.h" +#include "smap.h" +#include "sset.h" VLOG_DEFINE_THIS_MODULE(en_ic_port_binding); +static const struct nbrec_logical_router * +find_tr_in_nb(struct ic_context *ctx, char *tr_name) +{ + const struct nbrec_logical_router *key = + nbrec_logical_router_index_init_row(ctx->nbrec_lr_by_name); + nbrec_logical_router_index_set_name(key, tr_name); + + const struct nbrec_logical_router *lr; + bool found = false; + NBREC_LOGICAL_ROUTER_FOR_EACH_EQUAL (lr, key, ctx->nbrec_lr_by_name) { + if (smap_get(&lr->options, "interconn-tr")) { + found = true; + break; + } + } + + nbrec_logical_router_index_destroy_row(key); + if (found) { + return lr; + } + + return NULL; +} + +static const struct sbrec_port_binding * +find_peer_port(struct ic_context *ctx, + const struct sbrec_port_binding *sb_pb) +{ + const char *peer_name = smap_get(&sb_pb->options, "peer"); + if (!peer_name) { + return NULL; + } + + return find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, peer_name); +} + +static const struct sbrec_port_binding * +find_crp_from_lrp(struct ic_context *ctx, + const struct sbrec_port_binding *lrp_pb) +{ + char *crp_name = ovn_chassis_redirect_name(lrp_pb->logical_port); + + const struct sbrec_port_binding *pb = + find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, crp_name); + + free(crp_name); + return pb; +} + +static const struct sbrec_port_binding * +find_crp_for_sb_pb(struct ic_context *ctx, + const struct sbrec_port_binding *sb_pb) +{ + const struct sbrec_port_binding *peer = find_peer_port(ctx, sb_pb); + if (!peer) { + return NULL; + } + + return find_crp_from_lrp(ctx, peer); +} + +static const char * +get_lp_address_for_sb_pb(struct ic_context *ctx, + const struct sbrec_port_binding *sb_pb) +{ + const struct nbrec_logical_switch_port *nb_lsp; + + nb_lsp = get_lsp_by_ts_port_name(ctx, sb_pb->logical_port); + if (!strcmp(nb_lsp->type, "switch")) { + /* Switches always have implicit "unknown" address, and IC-SB port + * binding can only have one address specified. */ + return "unknown"; + } + + const struct sbrec_port_binding *peer = find_peer_port(ctx, sb_pb); + if (!peer) { + return NULL; + } + + return peer->n_mac ? *peer->mac : NULL; +} + +static void +sync_lsp_tnl_key(const struct nbrec_logical_switch_port *lsp, + int64_t isb_tnl_key) +{ + int64_t tnl_key = smap_get_int(&lsp->options, "requested-tnl-key", 0); + if (tnl_key != isb_tnl_key) { + VLOG_DBG("Set options:requested-tnl-key %"PRId64 + " for lsp %s in NB.", isb_tnl_key, lsp->name); + char *tnl_key_str = xasprintf("%"PRId64, isb_tnl_key); + nbrec_logical_switch_port_update_options_setkey(lsp, + "requested-tnl-key", + tnl_key_str); + free(tnl_key_str); + } + +} + +static inline void +sync_lrp_tnl_key(const struct nbrec_logical_router_port *lrp, + int64_t isb_tnl_key) +{ + int64_t tnl_key = smap_get_int(&lrp->options, "requested-tnl-key", 0); + if (tnl_key != isb_tnl_key) { + VLOG_DBG("Set options:requested-tnl-key %" PRId64 " for lrp %s in NB.", + isb_tnl_key, lrp->name); + char *tnl_key_str = xasprintf("%"PRId64, isb_tnl_key); + nbrec_logical_router_port_update_options_setkey( + lrp, "requested-tnl-key", tnl_key_str); + free(tnl_key_str); + } +} + +static bool +get_router_uuid_by_sb_pb(struct ic_context *ctx, + const struct sbrec_port_binding *sb_pb, + struct uuid *router_uuid) +{ + const struct sbrec_port_binding *router_pb = find_peer_port(ctx, sb_pb); + if (!router_pb || !router_pb->datapath) { + return NULL; + } + + return datapath_get_nb_uuid(router_pb->datapath, router_uuid); +} + +static void +update_isb_pb_external_ids(struct ic_context *ctx, + const struct sbrec_port_binding *sb_pb, + const struct icsbrec_port_binding *isb_pb) +{ + struct uuid lr_uuid; + if (!get_router_uuid_by_sb_pb(ctx, sb_pb, &lr_uuid)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Can't get router uuid for transit switch port %s.", + isb_pb->logical_port); + return; + } + + struct uuid current_lr_uuid; + if (smap_get_uuid(&isb_pb->external_ids, "router-id", ¤t_lr_uuid) && + uuid_equals(&lr_uuid, ¤t_lr_uuid)) { + return; + } + + char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&lr_uuid)); + icsbrec_port_binding_update_external_ids_setkey(isb_pb, "router-id", + uuid_s); + free(uuid_s); +} + +/* Sync a local port's fields from TSP INB->ISB. */ +static void +sync_tsp_pb(const struct icnbrec_transit_switch_port *tsp, + const struct icsbrec_port_binding *isb_pb) +{ + if (!isb_pb) { + return; + } + + /* Sync address to ISB. */ + if (!tsp->n_addresses && isb_pb->address[0]) { + icsbrec_port_binding_set_address(isb_pb, ""); + } else if (tsp->n_addresses && + strcmp(tsp->addresses[0], isb_pb->address)) { + icsbrec_port_binding_set_address(isb_pb, tsp->addresses[0]); + } + + /* Sync gateway to ISB. */ + if (strcmp(tsp->chassis, isb_pb->gateway)) { + icsbrec_port_binding_set_gateway(isb_pb, tsp->chassis); + } +} + +/* For each local port: + * - Sync from NB to ISB. + * - Sync gateway from SB to ISB. + * - Sync tunnel key from ISB to NB. + */ +static void +sync_local_port(struct ic_context *ctx, + const struct icsbrec_port_binding *isb_pb, + const struct sbrec_port_binding *sb_pb, + const struct nbrec_logical_switch_port *lsp) +{ + /* Sync address from NB to ISB */ + const char *address = get_lp_address_for_sb_pb(ctx, sb_pb); + if (!address) { + VLOG_DBG("Can't get router/switch port address for logical" + " switch port %s", sb_pb->logical_port); + if (isb_pb->address[0]) { + icsbrec_port_binding_set_address(isb_pb, ""); + } + } else { + if (strcmp(address, isb_pb->address)) { + icsbrec_port_binding_set_address(isb_pb, address); + } + } + + /* Sync gateway from SB to ISB */ + const struct sbrec_port_binding *crp = find_crp_for_sb_pb(ctx, sb_pb); + if (crp && crp->chassis) { + if (strcmp(crp->chassis->name, isb_pb->gateway)) { + icsbrec_port_binding_set_gateway(isb_pb, crp->chassis->name); + } + } else if (!strcmp(lsp->type, "switch") && sb_pb->chassis) { + if (strcmp(sb_pb->chassis->name, isb_pb->gateway)) { + icsbrec_port_binding_set_gateway(isb_pb, sb_pb->chassis->name); + } + } else { + if (isb_pb->gateway[0]) { + icsbrec_port_binding_set_gateway(isb_pb, ""); + } + } + + /* Sync external_ids:router-id to ISB */ + update_isb_pb_external_ids(ctx, sb_pb, isb_pb); + + /* Sync back tunnel key from ISB to NB */ + sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); +} + +static void +sync_tsp_router_peer(const struct icnbrec_transit_switch_port *tsp, + const struct nbrec_logical_switch_port *lsp) +{ + /* Clear the peer for router port type. */ + if (lsp->peer && lsp->peer[0]) { + nbrec_logical_switch_port_set_peer(lsp, NULL); + } + + if (tsp->peer[0]) { + const char *rp = smap_get(&lsp->options, "router-port"); + if (!rp || strcmp(rp, tsp->peer)) { + nbrec_logical_switch_port_update_options_setkey( + lsp, "router-port", tsp->peer); + } + } else if (smap_get(&lsp->options, "router-port")) { + nbrec_logical_switch_port_update_options_delkey(lsp, "router-port"); + } +} + +static void +sync_tsp_non_router_peer(const struct icnbrec_transit_switch_port *tsp, + const struct nbrec_logical_switch_port *lsp) +{ + /* Clear router-port setting for non-routers. */ + if (smap_get(&lsp->options, "router-port")) { + nbrec_logical_switch_port_update_options_delkey(lsp, "router-port"); + } + + if (tsp->peer[0]) { + if (!lsp->peer || strcmp(lsp->peer, tsp->peer)) { + nbrec_logical_switch_port_set_peer(lsp, tsp->peer); + } + } else if (lsp->peer && lsp->peer[0]) { + nbrec_logical_switch_port_set_peer(lsp, NULL); + } +} + +static void +sync_tsp_common(const struct icnbrec_transit_switch_port *tsp, + const struct icsbrec_port_binding *isb_pb, + const struct nbrec_logical_switch_port *lsp) +{ + /* Sync addresses from TSP to NB. */ + nbrec_logical_switch_port_set_addresses( + lsp, (const char **) tsp->addresses, tsp->n_addresses); + + if (tsp->chassis[0]) { + const char *current = smap_get(&lsp->options, "requested-chassis"); + if (!current || strcmp(current, tsp->chassis)) { + nbrec_logical_switch_port_update_options_setkey( + lsp, "requested-chassis", tsp->chassis); + } + } else if (smap_get(&lsp->options, "requested-chassis")) { + nbrec_logical_switch_port_update_options_delkey( + lsp, "requested-chassis"); + } + + /* Sync tunnel key from ISB to NB. */ + sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); + + if (!strcmp(tsp->type, "router")) { + sync_tsp_router_peer(tsp, lsp); + } else { + sync_tsp_non_router_peer(tsp, lsp); + } +} + +static void +sync_tsp_local(const struct icnbrec_transit_switch_port *tsp, + const struct icsbrec_port_binding *isb_pb, + const struct nbrec_logical_switch_port *lsp) +{ + if (strcmp(lsp->type, tsp->type)) { + nbrec_logical_switch_port_set_type(lsp, tsp->type); + } + sync_tsp_common(tsp, isb_pb, lsp); +} + +static void +sync_tsp_remote(const struct icnbrec_transit_switch_port *tsp, + const struct icsbrec_port_binding *isb_pb, + const struct nbrec_logical_switch_port *lsp) +{ + if (strcmp(lsp->type, "remote")) { + nbrec_logical_switch_port_set_type(lsp, "remote"); + } + sync_tsp_common(tsp, isb_pb, lsp); +} + +/* For each remote port: + * - Sync from ISB to NB + * - Sync gateway from ISB to SB + */ +static void +sync_remote_port(struct ic_context *ctx, + const struct icsbrec_port_binding *isb_pb, + const struct nbrec_logical_switch_port *lsp, + const struct sbrec_port_binding *sb_pb) +{ + /* Sync address from ISB to NB */ + if (isb_pb->address[0]) { + if (lsp->n_addresses != 1 || + strcmp(isb_pb->address, lsp->addresses[0])) { + nbrec_logical_switch_port_set_addresses( + lsp, (const char **)&isb_pb->address, 1); + } + } else { + if (lsp->n_addresses != 0) { + nbrec_logical_switch_port_set_addresses(lsp, NULL, 0); + } + } + + /* Sync tunnel key from ISB to NB */ + sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); + + /* Skip port binding if it is already requested by the CMS. */ + if (smap_get(&lsp->options, "requested-chassis")) { + return; + } + + /* Sync gateway from ISB to SB */ + if (isb_pb->gateway[0]) { + if (!sb_pb->chassis || strcmp(sb_pb->chassis->name, isb_pb->gateway)) { + const struct sbrec_chassis *chassis = + find_sb_chassis(ctx, isb_pb->gateway); + if (!chassis) { + VLOG_DBG("Chassis %s is not found in SB, syncing from ISB " + "to SB skipped for logical port %s.", + isb_pb->gateway, lsp->name); + return; + } + sbrec_port_binding_set_chassis(sb_pb, chassis); + } + } else { + if (sb_pb->chassis) { + sbrec_port_binding_set_chassis(sb_pb, NULL); + } + } +} + +/* For each remote port: + * - Sync from ISB to NB + */ +static void +sync_router_port(const struct icsbrec_port_binding *isb_pb, + const struct icnbrec_transit_router_port *trp, + const struct nbrec_logical_router_port *lrp) +{ + /* Sync from ICNB to NB */ + if (trp->chassis[0]) { + const char *chassis_name = + smap_get_def(&lrp->options, "requested-chassis", ""); + if (strcmp(trp->chassis, chassis_name)) { + nbrec_logical_router_port_update_options_setkey( + lrp, "requested-chassis", trp->chassis); + } + } else { + nbrec_logical_router_port_update_options_delkey( + lrp, "requested-chassis"); + } + + if (strcmp(trp->mac, lrp->mac)) { + nbrec_logical_router_port_set_mac(lrp, trp->mac); + } + + bool sync_networks = false; + if (trp->n_networks != lrp->n_networks) { + sync_networks = true; + } else { + for (size_t i = 0; i < trp->n_networks; i++) { + if (strcmp(trp->networks[i], lrp->networks[i])) { + sync_networks |= true; + break; + } + } + } + + if (sync_networks) { + nbrec_logical_router_port_set_networks( + lrp, (const char **) trp->networks, trp->n_networks); + } + + /* Sync tunnel key from ISB to NB */ + sync_lrp_tnl_key(lrp, isb_pb->tunnel_key); +} + +static void +create_nb_lsp(struct ic_context *ctx, + const struct icsbrec_port_binding *isb_pb, + const struct nbrec_logical_switch *ls) +{ + 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); + nbrec_logical_switch_port_set_type(lsp, "remote"); + + bool up = true; + nbrec_logical_switch_port_set_up(lsp, &up, 1); + + if (isb_pb->address[0]) { + nbrec_logical_switch_port_set_addresses( + lsp, (const char **)&isb_pb->address, 1); + } + sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); + nbrec_logical_switch_update_ports_addvalue(ls, lsp); +} + +static uint32_t +allocate_port_key(struct hmap *pb_tnlids) +{ + static uint32_t hint; + return ovn_allocate_tnlid(pb_tnlids, "transit port", + 1, (1u << 15) - 1, &hint); +} + +/* Fix A: look up a transit switch / router by name through an index instead of + * scanning every IC-NB row, so a scoped reconcile is O(scope) rather than + * O(all transit switches/routers). */ +static const struct icnbrec_transit_switch * +find_transit_switch_by_name(struct ic_context *ctx, const char *name) +{ + const struct icnbrec_transit_switch *key = + icnbrec_transit_switch_index_init_row( + ctx->icnbrec_transit_switch_by_name); + icnbrec_transit_switch_index_set_name(key, name); + const struct icnbrec_transit_switch *ts = + icnbrec_transit_switch_index_find(ctx->icnbrec_transit_switch_by_name, + key); + icnbrec_transit_switch_index_destroy_row(key); + return ts; +} + +static const struct icnbrec_transit_router * +find_transit_router_by_name(struct ic_context *ctx, const char *name) +{ + const struct icnbrec_transit_router *key = + icnbrec_transit_router_index_init_row( + ctx->icnbrec_transit_router_by_name); + icnbrec_transit_router_index_set_name(key, name); + const struct icnbrec_transit_router *tr = + icnbrec_transit_router_index_find(ctx->icnbrec_transit_router_by_name, + key); + icnbrec_transit_router_index_destroy_row(key); + return tr; +} + +static const struct icsbrec_port_binding * +create_isb_pb(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) +{ + uint32_t pb_tnl_key = allocate_port_key(pb_tnlids); + if (!pb_tnl_key) { + return NULL; + } + + const struct icsbrec_port_binding *isb_pb = + icsbrec_port_binding_insert(isb_txn); + icsbrec_port_binding_set_availability_zone(isb_pb, az); + icsbrec_port_binding_set_transit_switch(isb_pb, ts_name); + icsbrec_port_binding_set_logical_port(isb_pb, logical_port); + icsbrec_port_binding_set_tunnel_key(isb_pb, pb_tnl_key); + icsbrec_port_binding_set_nb_ic_uuid(isb_pb, nb_ic_uuid, 1); + icsbrec_port_binding_set_type(isb_pb, type); + return isb_pb; +} + +static bool +chassis_is_remote(struct ic_context *ctx, const char *chassis_name) +{ + if (chassis_name) { + const struct sbrec_chassis *chassis = + find_sb_chassis(ctx, chassis_name); + if (chassis) { + return smap_get_bool(&chassis->other_config, "is-remote", false); + } else { + return true; + } + } + + return false; +} + +static struct nbrec_logical_router_port * +lrp_create(struct ic_context *ctx, const struct nbrec_logical_router *lr, + const struct icnbrec_transit_router_port *trp) +{ + /* An LRP with this name may already exist as an uncommitted insert from an + * earlier change handler in this same engine iteration: the engine can run + * change handlers and then recompute the node in one iteration (a later + * input handler returning EN_UNHANDLED triggers run() without rolling back + * the earlier handlers' NB writes - see inc-proc-eng engine_recompute()). + * That row is invisible to the caller's lr->ports scan (the pending + * addvalue is not reflected until commit) but is present in the by-name + * index, so reuse it instead of inserting a duplicate, which would violate + * the Logical_Router_Port name uniqueness index and abort the txn. */ + const struct nbrec_logical_router_port *existing = + get_lrp_by_lrp_name(ctx, trp->name); + if (existing) { + return CONST_CAST(struct nbrec_logical_router_port *, existing); + } + + struct nbrec_logical_router_port *lrp = + nbrec_logical_router_port_insert(ctx->ovnnb_txn); + nbrec_logical_router_port_set_name(lrp, trp->name); + + nbrec_logical_router_port_update_options_setkey(lrp, "interconn-tr", + trp->name); + nbrec_logical_router_update_ports_addvalue(lr, lrp); + return lrp; +} + +static void +sync_ts_isb_pb(struct ic_context *ctx, const struct sbrec_port_binding *sb_pb, + const struct icsbrec_port_binding *isb_pb) +{ + const char *address = get_lp_address_for_sb_pb(ctx, sb_pb); + if (address) { + icsbrec_port_binding_set_address(isb_pb, address); + } + + const struct sbrec_port_binding *crp = find_crp_for_sb_pb(ctx, sb_pb); + if (crp && crp->chassis) { + icsbrec_port_binding_set_gateway(isb_pb, crp->chassis->name); + } + + update_isb_pb_external_ids(ctx, sb_pb, isb_pb); + + /* Sync encap so that multiple encaps can be used for the same + * gateway. However, it is not needed for now, since we don't yet + * support specifying encap type/ip for gateway chassis or ha-chassis + * for logical router port in NB DB, and now encap should always be + * empty. The sync can be added if we add such support for gateway + * chassis/ha-chassis in NB DB. */ +} + +static struct nbrec_logical_switch_port * +lsp_create(struct ic_context *ctx, const struct nbrec_logical_switch *ls, + const struct icnbrec_transit_switch_port *tsp) +{ + /* Reuse an LSP already inserted (uncommitted) by an earlier change handler + * in this same engine iteration: the engine can run change handlers and + * then recompute the node in one iteration, and the pending row is + * invisible to the caller's ls->ports scan but present in the by-name + * index. Inserting a duplicate would violate the name uniqueness index. + * See the note in lrp_create(). */ + const struct nbrec_logical_switch_port *existing = + get_lsp_by_ts_port_name(ctx, tsp->name); + if (existing) { + return CONST_CAST(struct nbrec_logical_switch_port *, existing); + } + + struct nbrec_logical_switch_port *lsp = + nbrec_logical_switch_port_insert(ctx->ovnnb_txn); + nbrec_logical_switch_port_set_name(lsp, tsp->name); + + nbrec_logical_switch_port_update_options_setkey(lsp, "interconn-tsp", + tsp->name); + nbrec_logical_switch_update_ports_addvalue(ls, lsp); + return lsp; +} + +static const struct sbrec_port_binding * +find_lsp_in_sb(struct ic_context *ctx, + const struct nbrec_logical_switch_port *lsp) +{ + return find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, lsp->name); +} + +/* Reconcile the IC-SB and NB port bindings of a single transit switch 'ts' + * (against its NB logical-switch mirror). Local IC-SB ports that are matched + * to an existing transit-switch port are removed from 'switch_all_local_pbs' + * so the caller can delete the leftovers as orphans. + * + * Fix E: the tunnel-key pool is built here, per transit switch, from every + * binding on this switch -- both this AZ's (local) and the other AZs' + * (remote). The IC-SB uniqueness constraint is on (transit_switch, + * tunnel_key) across all AZs, so a per-AZ pool could hand out a key already + * used by a remote AZ on the same switch and the commit would fail. Seeding + * from the per-switch 'by_ts' scan (which this function already performs) + * makes allocations unique per switch as the constraint requires, and lets + * keys be reused across switches so the 15-bit space is not exhausted. */ +static void +port_binding_sync_ts(struct ic_context *ctx, + const struct icnbrec_transit_switch *ts, + struct shash *switch_all_local_pbs) +{ + const struct nbrec_logical_switch *ls = find_ts_in_nb(ctx, ts->name); + if (!ls) { + VLOG_DBG("Transit switch %s not found in NB.", ts->name); + return; + } + + struct shash nb_ports = SHASH_INITIALIZER(&nb_ports); + struct shash old_nb_ports = SHASH_INITIALIZER(&old_nb_ports); + struct shash local_pbs = SHASH_INITIALIZER(&local_pbs); + struct shash remote_pbs = SHASH_INITIALIZER(&remote_pbs); + struct hmap pb_tnlids = HMAP_INITIALIZER(&pb_tnlids); + const struct icsbrec_port_binding *isb_pb; + struct shash_node *node; + + for (size_t i = 0; i < ls->n_ports; i++) { + const struct nbrec_logical_switch_port *lsp = ls->ports[i]; + if (smap_get(&lsp->options, "interconn-tsp")) { + shash_add(&nb_ports, lsp->name, lsp); + } else { + shash_add(&old_nb_ports, lsp->name, lsp); + } + } + + const struct icsbrec_port_binding *isb_pb_key = + icsbrec_port_binding_index_init_row(ctx->icsbrec_port_binding_by_ts); + icsbrec_port_binding_index_set_transit_switch(isb_pb_key, ts->name); + + ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, + ctx->icsbrec_port_binding_by_ts) { + if (isb_pb->availability_zone == ctx->runned_az) { + shash_add(&local_pbs, isb_pb->logical_port, isb_pb); + shash_find_and_delete(switch_all_local_pbs, isb_pb->logical_port); + } else { + /* Fix G: the IC-SB only enforces unique (transit_switch, + * tunnel_key), not (transit_switch, logical_port), so a remote AZ + * may have left more than one binding with the same logical_port + * (e.g. orphans from an earlier tunnel-key churn). Deduplicate by + * logical_port: creating one NB Logical_Switch_Port per name keeps + * the transaction valid (the NB port name is unique). Without + * this, the two inserts collide and the commit fails on every + * run, blocking convergence for this transit switch. */ + shash_add_once(&remote_pbs, isb_pb->logical_port, isb_pb); + } + /* Reserve every key in use on this switch (local and remote). */ + ovn_add_tnlid(&pb_tnlids, isb_pb->tunnel_key); + } + icsbrec_port_binding_index_destroy_row(isb_pb_key); + + for (size_t i = 0; i < ts->n_ports; i++) { + struct icnbrec_transit_switch_port *tsp = ts->ports[i]; + + if (!tsp->chassis[0]) { + isb_pb = shash_find_and_delete(&local_pbs, tsp->name); + if (!isb_pb) { + isb_pb = shash_find_and_delete(&remote_pbs, tsp->name); + } + + if (ctx->ovnisb_txn && is_az_leader(ctx->ovnisb_txn)) { + if (!isb_pb) { + isb_pb = create_isb_pb( + ctx->ovnisb_txn, tsp->name, ctx->runned_az, + ts->name, &ts->header_.uuid, "transit-switch-port", + &pb_tnlids); + } + sync_tsp_pb(tsp, isb_pb); + } + } else if (!chassis_is_remote(ctx, tsp->chassis)) { + /* 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, + ctx->runned_az, + ts->name, &ts->header_.uuid, + "transit-switch-port", &pb_tnlids); + } + sync_tsp_pb(tsp, isb_pb); + } else { + isb_pb = shash_find_and_delete(&remote_pbs, tsp->name); + } + + if (!isb_pb) { + continue; + } + + const struct nbrec_logical_switch_port *lsp = + shash_find_and_delete(&nb_ports, tsp->name); + if (!lsp) { + lsp = lsp_create(ctx, ls, tsp); + } + + if (!tsp->chassis[0] || !chassis_is_remote(ctx, tsp->chassis)) { + sync_tsp_local(tsp, isb_pb, lsp); + } else { + sync_tsp_remote(tsp, isb_pb, lsp); + } + } + + /* Support legacy way of adding transit switch ports. */ + const struct sbrec_port_binding *sb_pb; + const struct nbrec_logical_switch_port *lsp; + SHASH_FOR_EACH (node, &old_nb_ports) { + lsp = node->data; + + if (!strcmp(lsp->type, "router") + || !strcmp(lsp->type, "switch")) { + /* The port is local. */ + sb_pb = find_lsp_in_sb(ctx, lsp); + if (!sb_pb) { + continue; + } + 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->runned_az, ts->name, &ts->header_.uuid, + "transit-switch-port", &pb_tnlids); + sync_ts_isb_pb(ctx, sb_pb, isb_pb); + } else { + sync_local_port(ctx, isb_pb, sb_pb, lsp); + } + + if (isb_pb->type) { + icsbrec_port_binding_set_type(isb_pb, "transit-switch-port"); + } + + if (isb_pb->nb_ic_uuid) { + icsbrec_port_binding_set_nb_ic_uuid(isb_pb, + &ts->header_.uuid, 1); + } + } else if (!strcmp(lsp->type, "remote")) { + /* The port is remote. */ + isb_pb = shash_find_and_delete(&remote_pbs, lsp->name); + if (!isb_pb) { + nbrec_logical_switch_update_ports_delvalue(ls, lsp); + } else { + sb_pb = find_lsp_in_sb(ctx, lsp); + if (!sb_pb) { + continue; + } + sync_remote_port(ctx, isb_pb, lsp, sb_pb); + } + } else { + VLOG_DBG("Ignore lsp %s on ts %s with type %s.", + lsp->name, ts->name, lsp->type); + } + } + + SHASH_FOR_EACH (node, &nb_ports) { + nbrec_logical_switch_port_delete(node->data); + nbrec_logical_switch_update_ports_delvalue(ls, node->data); + } + + /* Delete extra port-binding from ISB */ + SHASH_FOR_EACH (node, &local_pbs) { + icsbrec_port_binding_delete(node->data); + } + + /* Create lsp in NB for remote ports */ + SHASH_FOR_EACH (node, &remote_pbs) { + create_nb_lsp(ctx, node->data, ls); + } + + ovn_destroy_tnlids(&pb_tnlids); + shash_destroy(&nb_ports); + shash_destroy(&local_pbs); + shash_destroy(&remote_pbs); + shash_destroy(&old_nb_ports); +} + +/* Reconcile the IC-SB and NB port bindings of a single transit router 'tr' + * (against its NB logical-router mirror). Local IC-SB ports that are matched + * to an existing transit-router port are removed from 'router_all_local_pbs' + * so the caller can delete the leftovers as orphans. New port tunnel keys are + * allocated from a per-router pool seeded from every binding on this router + * (local and remote) -- see the note on port_binding_sync_ts() (Fix E). */ +static void +port_binding_sync_tr(struct ic_context *ctx, + const struct icnbrec_transit_router *tr, + struct shash *router_all_local_pbs) +{ + const struct nbrec_logical_router *lr = find_tr_in_nb(ctx, tr->name); + if (!lr) { + VLOG_DBG("Transit router %s not found in NB.", tr->name); + return; + } + + struct shash nb_ports = SHASH_INITIALIZER(&nb_ports); + struct shash local_pbs = SHASH_INITIALIZER(&local_pbs); + struct shash remote_pbs = SHASH_INITIALIZER(&remote_pbs); + struct hmap pb_tnlids = HMAP_INITIALIZER(&pb_tnlids); + const struct icsbrec_port_binding *isb_pb; + struct shash_node *node; + + for (size_t i = 0; i < lr->n_ports; i++) { + const struct nbrec_logical_router_port *lrp = lr->ports[i]; + if (smap_get_def(&lrp->options, "interconn-tr", NULL)) { + shash_add(&nb_ports, lrp->name, lrp); + } + } + + const struct icsbrec_port_binding *isb_pb_key = + icsbrec_port_binding_index_init_row(ctx->icsbrec_port_binding_by_ts); + icsbrec_port_binding_index_set_transit_switch(isb_pb_key, tr->name); + + ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, + ctx->icsbrec_port_binding_by_ts) { + if (isb_pb->availability_zone == ctx->runned_az) { + shash_add(&local_pbs, isb_pb->logical_port, isb_pb); + shash_find_and_delete(router_all_local_pbs, isb_pb->logical_port); + } else { + shash_add(&remote_pbs, isb_pb->logical_port, isb_pb); + } + /* Reserve every key in use on this router (local and remote). */ + ovn_add_tnlid(&pb_tnlids, isb_pb->tunnel_key); + } + icsbrec_port_binding_index_destroy_row(isb_pb_key); + + for (size_t i = 0; i < tr->n_ports; i++) { + const struct icnbrec_transit_router_port *trp = tr->ports[i]; + + if (chassis_is_remote(ctx, trp->chassis)) { + isb_pb = shash_find_and_delete(&remote_pbs, trp->name); + } 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, + ctx->runned_az, + tr->name, &tr->header_.uuid, + "transit-router-port", &pb_tnlids); + icsbrec_port_binding_set_address(isb_pb, trp->mac); + } + } + + /* Don't allow remote ports to create NB LRP until ICSB entry is + * created in the appropriate AZ. */ + if (isb_pb) { + const struct nbrec_logical_router_port *lrp = + shash_find_and_delete(&nb_ports, trp->name); + if (!lrp) { + lrp = lrp_create(ctx, lr, trp); + } + + sync_router_port(isb_pb, trp, lrp); + } + } + + SHASH_FOR_EACH (node, &nb_ports) { + nbrec_logical_router_port_delete(node->data); + nbrec_logical_router_update_ports_delvalue(lr, node->data); + } + + ovn_destroy_tnlids(&pb_tnlids); + shash_destroy(&nb_ports); + shash_destroy(&local_pbs); + shash_destroy(&remote_pbs); +} + +/* Adds to 'gc' (keyed by logical_port) every local IC-SB port binding whose + * transit_switch is one of the names in 'names'. Used to build a transit- + * switch/router-scoped garbage-collection set: the per-entity sync claims the + * still-valid bindings and the caller deletes whatever remains. */ +static void +collect_local_pbs_by_names(struct ic_context *ctx, const struct sset *names, + struct shash *gc) +{ + const char *name; + SSET_FOR_EACH (name, names) { + const struct icsbrec_port_binding *isb_pb; + const struct icsbrec_port_binding *key = + icsbrec_port_binding_index_init_row( + ctx->icsbrec_port_binding_by_ts); + icsbrec_port_binding_index_set_transit_switch(key, name); + ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, key, + ctx->icsbrec_port_binding_by_ts) { + if (isb_pb->availability_zone == ctx->runned_az && + !shash_find(gc, isb_pb->logical_port)) { + shash_add(gc, isb_pb->logical_port, isb_pb); + } + } + icsbrec_port_binding_index_destroy_row(key); + } +} + +/* Synchronizes cross-AZ port bindings. When both 'ts_scope' and 'tr_scope' + * are NULL every transit switch and transit router in the AZ is reconciled + * (full recompute). Otherwise only the transit switches named in 'ts_scope' + * and the transit routers named in 'tr_scope' are reconciled (each scope may + * be a non-NULL empty set, meaning "no entity of that kind"). A name no + * longer present in IC-NB (a deleted entity, or one whose NB mirror is gone) + * is still honoured: its local bindings end up as garbage-collection leftovers + * and are deleted, matching the full-recompute behaviour. */ +static void +port_binding_sync_scope(struct ic_context *ctx, const struct sset *ts_scope, + const struct sset *tr_scope) +{ + if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { + return; + } + + bool full = !ts_scope && !tr_scope; + + struct shash switch_all_local_pbs = + SHASH_INITIALIZER(&switch_all_local_pbs); + struct shash router_all_local_pbs = + SHASH_INITIALIZER(&router_all_local_pbs); + struct shash_node *node; + + /* Seed the garbage-collection sets with this AZ's local bindings. Tunnel + * keys are no longer pooled here: port_binding_sync_ts()/_tr() build a + * per-entity key pool from every binding on that switch/router (Fix E). */ + if (full) { + const struct icsbrec_port_binding *isb_pb; + const struct icsbrec_port_binding *isb_pb_key = + icsbrec_port_binding_index_init_row( + ctx->icsbrec_port_binding_by_az); + icsbrec_port_binding_index_set_availability_zone(isb_pb_key, + ctx->runned_az); + ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, + ctx->icsbrec_port_binding_by_az) { + struct shash *gc = ic_pb_get_type(isb_pb) == IC_ROUTER_PORT + ? &router_all_local_pbs : &switch_all_local_pbs; + shash_add(gc, isb_pb->logical_port, isb_pb); + } + icsbrec_port_binding_index_destroy_row(isb_pb_key); + } else { + if (ts_scope) { + collect_local_pbs_by_names(ctx, ts_scope, &switch_all_local_pbs); + } + if (tr_scope) { + collect_local_pbs_by_names(ctx, tr_scope, &router_all_local_pbs); + } + } + + /* Fix A: reconcile exactly the in-scope transit switches via an index + * lookup, instead of scanning every transit switch on every call. */ + if (full) { + const struct icnbrec_transit_switch *ts; + ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) { + port_binding_sync_ts(ctx, ts, &switch_all_local_pbs); + } + } else if (ts_scope) { + const char *name; + SSET_FOR_EACH (name, ts_scope) { + const struct icnbrec_transit_switch *ts = + find_transit_switch_by_name(ctx, name); + if (ts) { + port_binding_sync_ts(ctx, ts, &switch_all_local_pbs); + } + } + } + + SHASH_FOR_EACH (node, &switch_all_local_pbs) { + icsbrec_port_binding_delete(node->data); + } + shash_destroy(&switch_all_local_pbs); + + /* Fix A: same scope-driven reconcile for transit routers. */ + if (full) { + const struct icnbrec_transit_router *tr; + ICNBREC_TRANSIT_ROUTER_FOR_EACH (tr, ctx->ovninb_idl) { + port_binding_sync_tr(ctx, tr, &router_all_local_pbs); + } + } else if (tr_scope) { + const char *name; + SSET_FOR_EACH (name, tr_scope) { + const struct icnbrec_transit_router *tr = + find_transit_router_by_name(ctx, name); + if (tr) { + port_binding_sync_tr(ctx, tr, &router_all_local_pbs); + } + } + } + + SHASH_FOR_EACH (node, &router_all_local_pbs) { + icsbrec_port_binding_delete(node->data); + } + + shash_destroy(&router_all_local_pbs); +} + +static void +port_binding_run(struct ic_context *ctx) +{ + port_binding_sync_scope(ctx, NULL, NULL); +} + +/* Build once per handler invocation, a map from every NB transit-router-mirror + * port name to its transit router name (the mirror logical router's name). + * See port_binding_lsp_ts_map_init(). */ +static void +port_binding_lrp_tr_map_init(struct ic_context *ctx, struct shash *map) +{ + shash_init(map); + const struct nbrec_logical_router *lr; + NBREC_LOGICAL_ROUTER_FOR_EACH (lr, ctx->ovnnb_idl) { + if (!smap_get(&lr->options, "interconn-tr")) { + continue; + } + for (size_t i = 0; i < lr->n_ports; i++) { + shash_replace(map, lr->ports[i]->name, CONST_CAST(char *, + lr->name)); + } + } +} + +/* If 'lrp' is an interconnection port of a transit-router mirror, adds that + * transit router's name (looked up in 'lrp_tr_map') to 'tr_scope'. */ +static void +port_binding_collect_lrp_tr(const struct shash *lrp_tr_map, + const struct nbrec_logical_router_port *lrp, + struct sset *tr_scope) +{ + const char *tr_name = shash_find_data(lrp_tr_map, lrp->name); + if (tr_name) { + sset_add(tr_scope, tr_name); + } +} + +/* Builds 'map': a mapping from every SB port-binding name whose state a + * transit-switch sync reads, to the owning transit switch's name. For each + * TS-mirror port this covers the port itself and, for router-typed ports, the + * peer logical_router_port (its MAC/datapath) and that port's chassisredirect + * (the gateway chassis) - i.e. exactly the SB rows read by sync_local_port() / + * sync_ts_isb_pb(). Transit-router syncs do not read SB port bindings, so + * only transit switches contribute. Values are IC-NB-owned strings; destroy + * with + * port_binding_sb_name_ts_map_destroy(). */ +static void +port_binding_sb_name_ts_map_init(struct ic_context *ctx, struct shash *map) +{ + shash_init(map); + + const struct icnbrec_transit_switch *ts; + ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) { + const struct nbrec_logical_switch *ls = find_ts_in_nb(ctx, ts->name); + if (!ls) { + continue; + } + for (size_t i = 0; i < ls->n_ports; i++) { + const struct nbrec_logical_switch_port *lsp = ls->ports[i]; + if (!shash_find(map, lsp->name)) { + shash_add(map, lsp->name, ts->name); + } + if (strcmp(lsp->type, "router")) { + continue; + } + const char *rport = smap_get(&lsp->options, "router-port"); + if (!rport) { + continue; + } + if (!shash_find(map, rport)) { + shash_add(map, rport, ts->name); + } + char *crp = ovn_chassis_redirect_name(rport); + if (!shash_find(map, crp)) { + shash_add(map, crp, ts->name); + } + free(crp); + } + } +} + +static void +port_binding_sb_name_ts_map_destroy(struct shash *map) +{ + shash_destroy(map); +} + + +/* en_port_binding tracked data. 'synced_ts'/'synced_tr' hold the transit + * switches / routers already reconciled during the current engine run. Two + * different input change handlers of this node can map their changes to the + * same entity (e.g. an IC-SB port_binding change and the NB Logical_Switch + * mirror change both point at the same transit switch), and reconciling it a + * second time in the same NB transaction re-creates its NB ports: pending row + * inserts are not visible on the IDL until the transaction commits, so the + * second pass does not see the ports created by the first. These sets make + * each entity reconciled at most once per run. Cleared between runs by + * en_port_binding_clear_tracked_data(). */ +struct ed_type_port_binding { + struct sset synced_ts; + struct sset synced_tr; +}; + enum engine_node_state en_port_binding_run(struct engine_node *node OVS_UNUSED, void *data OVS_UNUSED) { @@ -38,14 +1141,411 @@ en_port_binding_run(struct engine_node *node OVS_UNUSED, void *data OVS_UNUSED) return EN_UPDATED; } +/* Returns the ic_context, or NULL when there is no availability zone yet (in + * which case the caller should report EN_HANDLED_UNCHANGED). */ +static struct ic_context * +port_binding_handler_ctx(void) +{ + struct ic_context *ctx = engine_get_context()->client_ctx; + return ctx->runned_az ? ctx : NULL; +} + +/* The change handlers translate a changed input row into the set of transit + * switches ('ts_scope') and transit routers ('tr_scope') that must be + * reconciled, then call port_binding_sync_scope() on those sets instead of + * forcing a full recompute. port_binding_sync_ts()/_tr() are already scoped + * to a single entity, so reconciling only the affected ones is sufficient. */ + +/* Runs the scoped sync for the collected scopes and maps it to an engine + * result. When there is work to do but the transactions sync needs are not + * available yet, returns EN_UNHANDLED so the change is retried. */ +static enum engine_input_handler_result +port_binding_scope_finish(struct ic_context *ctx, struct sset *ts_scope, + struct sset *tr_scope, void *node_data) +{ + struct ed_type_port_binding *pb = node_data; + + /* Drop transit switches / routers already reconciled earlier in this + * engine run: port_binding_sync_ts()/_tr() fully reconcile an entity, so a + * repeat sync in the same NB transaction is redundant and, because pending + * row inserts are not visible until commit, would re-create its NB ports + * (duplicate insert -> constraint violation -> recompute -> abort). + * See 'synced_ts'/'synced_tr'. */ + const char *name; + SSET_FOR_EACH_SAFE (name, ts_scope) { + if (sset_contains(&pb->synced_ts, name)) { + sset_find_and_delete(ts_scope, name); + } + } + SSET_FOR_EACH_SAFE (name, tr_scope) { + if (sset_contains(&pb->synced_tr, name)) { + sset_find_and_delete(tr_scope, name); + } + } + + if (sset_is_empty(ts_scope) && sset_is_empty(tr_scope)) { + return EN_HANDLED_UNCHANGED; + } + if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { + return EN_UNHANDLED; + } + SSET_FOR_EACH (name, ts_scope) { + sset_add(&pb->synced_ts, name); + } + SSET_FOR_EACH (name, tr_scope) { + sset_add(&pb->synced_tr, name); + } + port_binding_sync_scope(ctx, ts_scope, tr_scope); + return EN_HANDLED_UPDATED; +} + +/* IC-NB Transit_Switch: a new/deleted/renamed transit switch must have its + * port bindings reconciled (a deletion is honoured via the scoped GC). */ +enum engine_input_handler_result +port_binding_icnb_transit_switch_handler(struct engine_node *node, + void *data) +{ + struct ic_context *ctx = port_binding_handler_ctx(); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icnbrec_transit_switch_table *tbl = + EN_OVSDB_GET(engine_get_input("ICNB_transit_switch", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct icnbrec_transit_switch *ts; + ICNBREC_TRANSIT_SWITCH_TABLE_FOR_EACH_TRACKED (ts, tbl) { + sset_add(&ts_scope, ts->name); + } + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* IC-NB Transit_Router: analogous to the transit-switch handler. */ +enum engine_input_handler_result +port_binding_icnb_transit_router_handler(struct engine_node *node, + void *data) +{ + struct ic_context *ctx = port_binding_handler_ctx(); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icnbrec_transit_router_table *tbl = + EN_OVSDB_GET(engine_get_input("ICNB_transit_router", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct icnbrec_transit_router *tr; + ICNBREC_TRANSIT_ROUTER_TABLE_FOR_EACH_TRACKED (tr, tbl) { + sset_add(&tr_scope, tr->name); + } + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* IC-NB Transit_Router_Port: a column change on an existing port reconciles + * its owning transit router (resolved via tr_uuid). Port creation/deletion + * also updates the transit router's 'ports' column and is handled by the + * transit-router handler; handling it here too would reconcile the same router + * twice in one iteration and double-delete rows, so new/deleted ports are + * skipped. */ +enum engine_input_handler_result +port_binding_icnb_transit_router_port_handler(struct engine_node *node, + void *data) +{ + struct ic_context *ctx = port_binding_handler_ctx(); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icnbrec_transit_router_port_table *tbl = + EN_OVSDB_GET(engine_get_input("ICNB_transit_router_port", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct icnbrec_transit_router_port *trp; + ICNBREC_TRANSIT_ROUTER_PORT_TABLE_FOR_EACH_TRACKED (trp, tbl) { + if (icnbrec_transit_router_port_is_new(trp) || + icnbrec_transit_router_port_is_deleted(trp)) { + continue; + } + const struct icnbrec_transit_router *tr = + icnbrec_transit_router_get_for_uuid(ctx->ovninb_idl, + &trp->tr_uuid); + if (tr) { + sset_add(&tr_scope, tr->name); + } + } + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* IC-SB Port_Binding: the binding's transit_switch column names its entity; + * ic_pb_get_type() tells whether it is a transit-switch or transit-router + * port. The address/gateway/external_ids write-backs by the sync are + * idempotent (guarded by value comparisons), so they converge without a + * feedback loop. */ +enum engine_input_handler_result +port_binding_icsb_port_binding_handler(struct engine_node *node, + void *data) +{ + struct ic_context *ctx = port_binding_handler_ctx(); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct icsbrec_port_binding_table *tbl = + EN_OVSDB_GET(engine_get_input("ICSB_port_binding", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct icsbrec_port_binding *isb_pb; + ICSBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (isb_pb, tbl) { + if (ic_pb_get_type(isb_pb) == IC_ROUTER_PORT) { + sset_add(&tr_scope, isb_pb->transit_switch); + } else { + sset_add(&ts_scope, isb_pb->transit_switch); + } + } + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* NB Logical_Switch: only transit-switch mirrors (other_config:interconn-ts) + * are read by the sync. A change to such a mirror (its ports, name, or its + * deletion) reconciles the transit switch; tenant switches are irrelevant. */ +enum engine_input_handler_result +port_binding_nb_logical_switch_handler(struct engine_node *node, + void *data) +{ + struct ic_context *ctx = port_binding_handler_ctx(); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_switch_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_switch", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct nbrec_logical_switch *ls; + NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH_TRACKED (ls, tbl) { + if (smap_get(&ls->other_config, "interconn-ts")) { + sset_add(&ts_scope, ls->name); + } + } + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* NB Logical_Switch_Port: a column change on an existing transit-switch port + * reconciles its transit switch. Port creation/deletion changes the mirror's + * 'ports' column and is handled by the NB logical_switch handler. */ +enum engine_input_handler_result +port_binding_nb_logical_switch_port_handler(struct engine_node *node, + void *data) +{ + struct ic_context *ctx = port_binding_handler_ctx(); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_switch_port_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_switch_port", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + struct shash lsp_ts_map; + port_binding_lsp_ts_map_init(ctx, &lsp_ts_map); + const struct nbrec_logical_switch_port *lsp; + NBREC_LOGICAL_SWITCH_PORT_TABLE_FOR_EACH_TRACKED (lsp, tbl) { + if (nbrec_logical_switch_port_is_new(lsp) || + nbrec_logical_switch_port_is_deleted(lsp)) { + continue; + } + port_binding_collect_lsp_ts(&lsp_ts_map, lsp, &ts_scope); + } + shash_destroy(&lsp_ts_map); + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* NB Logical_Router: only transit-router mirrors (options:interconn-tr) are + * read by the sync. */ +enum engine_input_handler_result +port_binding_nb_logical_router_handler(struct engine_node *node, + void *data) +{ + struct ic_context *ctx = port_binding_handler_ctx(); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_router_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_router", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + const struct nbrec_logical_router *lr; + NBREC_LOGICAL_ROUTER_TABLE_FOR_EACH_TRACKED (lr, tbl) { + if (smap_get(&lr->options, "interconn-tr")) { + sset_add(&tr_scope, lr->name); + } + } + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* NB Logical_Router_Port: a column change on an existing transit-router port + * reconciles its transit router. Port creation/deletion changes the mirror's + * 'ports' column and is handled by the NB logical_router handler. */ +enum engine_input_handler_result +port_binding_nb_logical_router_port_handler(struct engine_node *node, + void *data) +{ + struct ic_context *ctx = port_binding_handler_ctx(); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct nbrec_logical_router_port_table *tbl = + EN_OVSDB_GET(engine_get_input("NB_logical_router_port", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + struct shash lrp_tr_map; + port_binding_lrp_tr_map_init(ctx, &lrp_tr_map); + const struct nbrec_logical_router_port *lrp; + NBREC_LOGICAL_ROUTER_PORT_TABLE_FOR_EACH_TRACKED (lrp, tbl) { + if (nbrec_logical_router_port_is_new(lrp) || + nbrec_logical_router_port_is_deleted(lrp)) { + continue; + } + port_binding_collect_lrp_tr(&lrp_tr_map, lrp, &tr_scope); + } + shash_destroy(&lrp_tr_map); + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* SB Port_Binding: a transit-switch sync reads SB state of its ports (the port + * itself, the peer router port's MAC/datapath, and the chassisredirect's + * gateway chassis). Map each changed SB port binding back to its transit + * switch via the name map; SB bindings of no IC port are irrelevant. Transit- + * router syncs do not read SB port bindings. */ +enum engine_input_handler_result +port_binding_sb_port_binding_handler(struct engine_node *node, + void *data) +{ + struct ic_context *ctx = port_binding_handler_ctx(); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct sbrec_port_binding_table *tbl = + EN_OVSDB_GET(engine_get_input("SB_port_binding", node)); + struct sset ts_scope = SSET_INITIALIZER(&ts_scope); + struct sset tr_scope = SSET_INITIALIZER(&tr_scope); + + struct shash name_map; + port_binding_sb_name_ts_map_init(ctx, &name_map); + + const struct sbrec_port_binding *sb_pb; + SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sb_pb, tbl) { + const char *ts_name = shash_find_data(&name_map, sb_pb->logical_port); + if (ts_name) { + sset_add(&ts_scope, ts_name); + } + } + port_binding_sb_name_ts_map_destroy(&name_map); + + enum engine_input_handler_result ret = + port_binding_scope_finish(ctx, &ts_scope, &tr_scope, data); + sset_destroy(&ts_scope); + sset_destroy(&tr_scope); + return ret; +} + +/* SB Chassis (Fix D): the sync only reads a chassis' existence and its + * other_config:is-remote flag (via trp_is_remote()), plus its (immutable) + * name for the gateway field. A chassis being created or deleted, or its + * other_config changing, can therefore change the output; any other column + * change (the frequent heartbeat-style updates: nb_cfg, encaps, external_ids, + * hostname, ...) cannot. So recompute only on insert/delete or an + * other_config change, and ignore the rest instead of forcing a full + * recompute on every chassis update. */ +enum engine_input_handler_result +port_binding_sb_chassis_handler(struct engine_node *node, + void *data OVS_UNUSED) +{ + struct ic_context *ctx = port_binding_handler_ctx(); + if (!ctx) { + return EN_HANDLED_UNCHANGED; + } + + const struct sbrec_chassis_table *tbl = + EN_OVSDB_GET(engine_get_input("SB_chassis", node)); + const struct sbrec_chassis *chassis; + SBREC_CHASSIS_TABLE_FOR_EACH_TRACKED (chassis, tbl) { + if (sbrec_chassis_is_new(chassis) + || sbrec_chassis_is_deleted(chassis) + || sbrec_chassis_is_updated(chassis, + SBREC_CHASSIS_COL_OTHER_CONFIG)) { + return EN_UNHANDLED; + } + } + return EN_HANDLED_UNCHANGED; +} + void * en_port_binding_init(struct engine_node *node OVS_UNUSED, struct engine_arg *arg OVS_UNUSED) { - return NULL; + struct ed_type_port_binding *data = xzalloc(sizeof *data); + sset_init(&data->synced_ts); + sset_init(&data->synced_tr); + return data; +} + +void +en_port_binding_clear_tracked_data(void *data_) +{ + struct ed_type_port_binding *data = data_; + sset_clear(&data->synced_ts); + sset_clear(&data->synced_tr); } void -en_port_binding_cleanup(void *data OVS_UNUSED) +en_port_binding_cleanup(void *data_) { + struct ed_type_port_binding *data = data_; + sset_destroy(&data->synced_ts); + sset_destroy(&data->synced_tr); } diff --git a/ic/en-port-binding.h b/ic/en-port-binding.h index 1cf71eb9a..4f5a9235d 100644 --- a/ic/en-port-binding.h +++ b/ic/en-port-binding.h @@ -8,6 +8,31 @@ enum engine_node_state en_port_binding_run(struct engine_node *node, void *data); void *en_port_binding_init(struct engine_node *node, struct engine_arg *arg); +void en_port_binding_clear_tracked_data(void *data); void en_port_binding_cleanup(void *data); +enum engine_input_handler_result +port_binding_icnb_transit_switch_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +port_binding_icnb_transit_router_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +port_binding_icnb_transit_router_port_handler(struct engine_node *node, + void *data); +enum engine_input_handler_result +port_binding_icsb_port_binding_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +port_binding_nb_logical_switch_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +port_binding_nb_logical_switch_port_handler(struct engine_node *node, + void *data); +enum engine_input_handler_result +port_binding_nb_logical_router_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +port_binding_nb_logical_router_port_handler(struct engine_node *node, + void *data); +enum engine_input_handler_result +port_binding_sb_port_binding_handler(struct engine_node *node, void *data); +enum engine_input_handler_result +port_binding_sb_chassis_handler(struct engine_node *node, void *data); + #endif /* EN_IC_PORT_BINDING_H */ diff --git a/ic/inc-proc-ic.c b/ic/inc-proc-ic.c index 5a193872f..8df80802e 100644 --- a/ic/inc-proc-ic.c +++ b/ic/inc-proc-ic.c @@ -179,7 +179,7 @@ static ENGINE_NODE(dp_enum); static ENGINE_NODE(gateway); static ENGINE_NODE(ts); static ENGINE_NODE(tr); -static ENGINE_NODE(port_binding); +static ENGINE_NODE(port_binding, CLEAR_TRACKED_DATA); static ENGINE_NODE(route); static ENGINE_NODE(service_monitor); static ENGINE_NODE(address_set); @@ -297,19 +297,39 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_tr, &en_nb_logical_router, en_tr_nb_logical_router_handler); - /* en_port_binding: sync cross-AZ port bindings. */ + /* en_port_binding: sync cross-AZ port bindings. + * + * Like en_gateway, this node uses only the AZ identity (ctx->runned_az, + * provided by en_az, plus the by-AZ port-binding index) and does not read + * the Availability_Zone table itself, so it does not depend on + * en_icsb_availability_zone and is not churned by the nb_ic_cfg sequence + * number bumped there on every change. */ engine_add_input(&en_port_binding, &en_az, NULL); - engine_add_input(&en_port_binding, &en_icsb_availability_zone, NULL); - engine_add_input(&en_port_binding, &en_icsb_port_binding, NULL); - engine_add_input(&en_port_binding, &en_icnb_transit_switch, NULL); - engine_add_input(&en_port_binding, &en_icnb_transit_router, NULL); - engine_add_input(&en_port_binding, &en_icnb_transit_router_port, NULL); - engine_add_input(&en_port_binding, &en_nb_logical_switch, NULL); - engine_add_input(&en_port_binding, &en_nb_logical_switch_port, NULL); - engine_add_input(&en_port_binding, &en_nb_logical_router, NULL); - engine_add_input(&en_port_binding, &en_nb_logical_router_port, NULL); - engine_add_input(&en_port_binding, &en_sb_port_binding, NULL); - engine_add_input(&en_port_binding, &en_sb_chassis, NULL); + engine_add_input(&en_port_binding, &en_icsb_port_binding, + port_binding_icsb_port_binding_handler); + engine_add_input(&en_port_binding, &en_icnb_transit_switch, + port_binding_icnb_transit_switch_handler); + engine_add_input(&en_port_binding, &en_icnb_transit_router, + port_binding_icnb_transit_router_handler); + engine_add_input(&en_port_binding, &en_icnb_transit_router_port, + port_binding_icnb_transit_router_port_handler); + engine_add_input(&en_port_binding, &en_nb_logical_switch, + port_binding_nb_logical_switch_handler); + engine_add_input(&en_port_binding, &en_nb_logical_switch_port, + port_binding_nb_logical_switch_port_handler); + engine_add_input(&en_port_binding, &en_nb_logical_router, + port_binding_nb_logical_router_handler); + engine_add_input(&en_port_binding, &en_nb_logical_router_port, + port_binding_nb_logical_router_port_handler); + engine_add_input(&en_port_binding, &en_sb_port_binding, + port_binding_sb_port_binding_handler); + /* SB chassis affects gateways and trp_is_remote across many ports. The + * reverse mapping chassis -> affected ports is impractical, but the sync + * only reads a chassis' existence and its other_config (is-remote); so the + * handler recomputes only on chassis insert/delete or an other_config + * change and ignores the frequent heartbeat-style updates (Fix D). */ + engine_add_input(&en_port_binding, &en_sb_chassis, + port_binding_sb_chassis_handler); /* en_route: advertise/learn cross-AZ routes. */ engine_add_input(&en_route, &en_az, NULL); diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c index e096a818e..983c6e3b8 100644 --- a/ic/ovn-ic.c +++ b/ic/ovn-ic.c @@ -68,11 +68,6 @@ static const char *ssl_private_key_file; static const char *ssl_certificate_file; static const char *ssl_ca_cert_file; -static const struct sbrec_port_binding * find_sb_pb_by_name( - struct ovsdb_idl_index *sbrec_port_binding_by_name, const char *name); -static const struct nbrec_logical_switch * find_ts_in_nb( - struct ic_context *ctx, char *ts_name); - static void usage(void) @@ -121,7 +116,7 @@ ic_dp_get_type(const struct icsbrec_datapath_binding *isb_dp) return IC_SWITCH; } -static enum ic_port_binding_type +enum ic_port_binding_type ic_pb_get_type(const struct icsbrec_port_binding *isb_pb) { if (isb_pb->type && !strcmp(isb_pb->type, "transit-router-port")) { @@ -552,7 +547,7 @@ address_set_run(struct ic_context *ctx) shash_destroy(&ic_remote_as); } -static const struct nbrec_logical_switch * +const struct nbrec_logical_switch * find_ts_in_nb(struct ic_context *ctx, char *ts_name) { const struct nbrec_logical_switch *key = @@ -576,31 +571,7 @@ find_ts_in_nb(struct ic_context *ctx, char *ts_name) return NULL; } -static const struct nbrec_logical_router * -find_tr_in_nb(struct ic_context *ctx, char *tr_name) -{ - const struct nbrec_logical_router *key = - nbrec_logical_router_index_init_row(ctx->nbrec_lr_by_name); - nbrec_logical_router_index_set_name(key, tr_name); - - const struct nbrec_logical_router *lr; - bool found = false; - NBREC_LOGICAL_ROUTER_FOR_EACH_EQUAL (lr, key, ctx->nbrec_lr_by_name) { - if (smap_get(&lr->options, "interconn-tr")) { - found = true; - break; - } - } - - nbrec_logical_router_index_destroy_row(key); - if (found) { - return lr; - } - - return NULL; -} - -static const struct sbrec_port_binding * +const struct sbrec_port_binding * find_sb_pb_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name, const char *name) { @@ -631,44 +602,7 @@ find_sb_dp_by_nb_uuid(struct ovsdb_idl_index *sbrec_datapath_binding, return dp; } -static const struct sbrec_port_binding * -find_peer_port(struct ic_context *ctx, - const struct sbrec_port_binding *sb_pb) -{ - const char *peer_name = smap_get(&sb_pb->options, "peer"); - if (!peer_name) { - return NULL; - } - - return find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, peer_name); -} - -static const struct sbrec_port_binding * -find_crp_from_lrp(struct ic_context *ctx, - const struct sbrec_port_binding *lrp_pb) -{ - char *crp_name = ovn_chassis_redirect_name(lrp_pb->logical_port); - - const struct sbrec_port_binding *pb = - find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, crp_name); - - free(crp_name); - return pb; -} - -static const struct sbrec_port_binding * -find_crp_for_sb_pb(struct ic_context *ctx, - const struct sbrec_port_binding *sb_pb) -{ - const struct sbrec_port_binding *peer = find_peer_port(ctx, sb_pb); - if (!peer) { - return NULL; - } - - return find_crp_from_lrp(ctx, peer); -} - -static const struct nbrec_logical_switch_port * +const struct nbrec_logical_switch_port * get_lsp_by_ts_port_name(struct ic_context *ctx, const char *ts_port_name) { const struct nbrec_logical_switch_port *lsp, *key; @@ -681,27 +615,6 @@ get_lsp_by_ts_port_name(struct ic_context *ctx, const char *ts_port_name) return lsp; } -static const char * -get_lp_address_for_sb_pb(struct ic_context *ctx, - const struct sbrec_port_binding *sb_pb) -{ - const struct nbrec_logical_switch_port *nb_lsp; - - nb_lsp = get_lsp_by_ts_port_name(ctx, sb_pb->logical_port); - if (!strcmp(nb_lsp->type, "switch")) { - /* Switches always have implicit "unknown" address, and IC-SB port - * binding can only have one address specified. */ - return "unknown"; - } - - const struct sbrec_port_binding *peer = find_peer_port(ctx, sb_pb); - if (!peer) { - return NULL; - } - - return peer->n_mac ? *peer->mac : NULL; -} - const struct sbrec_chassis * find_sb_chassis(struct ic_context *ctx, const char *name) { @@ -716,386 +629,7 @@ find_sb_chassis(struct ic_context *ctx, const char *name) return chassis; } -static void -sync_lsp_tnl_key(const struct nbrec_logical_switch_port *lsp, - int64_t isb_tnl_key) -{ - int64_t tnl_key = smap_get_int(&lsp->options, "requested-tnl-key", 0); - if (tnl_key != isb_tnl_key) { - VLOG_DBG("Set options:requested-tnl-key %"PRId64 - " for lsp %s in NB.", isb_tnl_key, lsp->name); - char *tnl_key_str = xasprintf("%"PRId64, isb_tnl_key); - nbrec_logical_switch_port_update_options_setkey(lsp, - "requested-tnl-key", - tnl_key_str); - free(tnl_key_str); - } - -} - -static inline void -sync_lrp_tnl_key(const struct nbrec_logical_router_port *lrp, - int64_t isb_tnl_key) -{ - int64_t tnl_key = smap_get_int(&lrp->options, "requested-tnl-key", 0); - if (tnl_key != isb_tnl_key) { - VLOG_DBG("Set options:requested-tnl-key %" PRId64 " for lrp %s in NB.", - isb_tnl_key, lrp->name); - char *tnl_key_str = xasprintf("%"PRId64, isb_tnl_key); - nbrec_logical_router_port_update_options_setkey( - lrp, "requested-tnl-key", tnl_key_str); - free(tnl_key_str); - } -} - -static bool -get_router_uuid_by_sb_pb(struct ic_context *ctx, - const struct sbrec_port_binding *sb_pb, - struct uuid *router_uuid) -{ - const struct sbrec_port_binding *router_pb = find_peer_port(ctx, sb_pb); - if (!router_pb || !router_pb->datapath) { - return NULL; - } - - return datapath_get_nb_uuid(router_pb->datapath, router_uuid); -} - -static void -update_isb_pb_external_ids(struct ic_context *ctx, - const struct sbrec_port_binding *sb_pb, - const struct icsbrec_port_binding *isb_pb) -{ - struct uuid lr_uuid; - if (!get_router_uuid_by_sb_pb(ctx, sb_pb, &lr_uuid)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Can't get router uuid for transit switch port %s.", - isb_pb->logical_port); - return; - } - - struct uuid current_lr_uuid; - if (smap_get_uuid(&isb_pb->external_ids, "router-id", ¤t_lr_uuid) && - uuid_equals(&lr_uuid, ¤t_lr_uuid)) { - return; - } - - char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&lr_uuid)); - icsbrec_port_binding_update_external_ids_setkey(isb_pb, "router-id", - uuid_s); - free(uuid_s); -} - -/* Sync a local port's fields from TSP INB->ISB. */ -static void -sync_tsp_pb(const struct icnbrec_transit_switch_port *tsp, - const struct icsbrec_port_binding *isb_pb) -{ - if (!isb_pb) { - return; - } - - /* Sync address to ISB. */ - if (!tsp->n_addresses && isb_pb->address[0]) { - icsbrec_port_binding_set_address(isb_pb, ""); - } else if (tsp->n_addresses && - strcmp(tsp->addresses[0], isb_pb->address)) { - icsbrec_port_binding_set_address(isb_pb, tsp->addresses[0]); - } - - /* Sync gateway to ISB. */ - if (strcmp(tsp->chassis, isb_pb->gateway)) { - icsbrec_port_binding_set_gateway(isb_pb, tsp->chassis); - } -} - -/* For each local port: - * - Sync from NB to ISB. - * - Sync gateway from SB to ISB. - * - Sync tunnel key from ISB to NB. - */ -static void -sync_local_port(struct ic_context *ctx, - const struct icsbrec_port_binding *isb_pb, - const struct sbrec_port_binding *sb_pb, - const struct nbrec_logical_switch_port *lsp) -{ - /* Sync address from NB to ISB */ - const char *address = get_lp_address_for_sb_pb(ctx, sb_pb); - if (!address) { - VLOG_DBG("Can't get router/switch port address for logical" - " switch port %s", sb_pb->logical_port); - if (isb_pb->address[0]) { - icsbrec_port_binding_set_address(isb_pb, ""); - } - } else { - if (strcmp(address, isb_pb->address)) { - icsbrec_port_binding_set_address(isb_pb, address); - } - } - - /* Sync gateway from SB to ISB */ - const struct sbrec_port_binding *crp = find_crp_for_sb_pb(ctx, sb_pb); - if (crp && crp->chassis) { - if (strcmp(crp->chassis->name, isb_pb->gateway)) { - icsbrec_port_binding_set_gateway(isb_pb, crp->chassis->name); - } - } else if (!strcmp(lsp->type, "switch") && sb_pb->chassis) { - if (strcmp(sb_pb->chassis->name, isb_pb->gateway)) { - icsbrec_port_binding_set_gateway(isb_pb, sb_pb->chassis->name); - } - } else { - if (isb_pb->gateway[0]) { - icsbrec_port_binding_set_gateway(isb_pb, ""); - } - } - - /* Sync external_ids:router-id to ISB */ - update_isb_pb_external_ids(ctx, sb_pb, isb_pb); - - /* Sync back tunnel key from ISB to NB */ - sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); -} - -static void -sync_tsp_router_peer(const struct icnbrec_transit_switch_port *tsp, - const struct nbrec_logical_switch_port *lsp) -{ - /* Clear the peer for router port type. */ - if (lsp->peer && lsp->peer[0]) { - nbrec_logical_switch_port_set_peer(lsp, NULL); - } - - if (tsp->peer[0]) { - const char *rp = smap_get(&lsp->options, "router-port"); - if (!rp || strcmp(rp, tsp->peer)) { - nbrec_logical_switch_port_update_options_setkey( - lsp, "router-port", tsp->peer); - } - } else if (smap_get(&lsp->options, "router-port")) { - nbrec_logical_switch_port_update_options_delkey(lsp, "router-port"); - } -} - -static void -sync_tsp_non_router_peer(const struct icnbrec_transit_switch_port *tsp, - const struct nbrec_logical_switch_port *lsp) -{ - /* Clear router-port setting for non-routers. */ - if (smap_get(&lsp->options, "router-port")) { - nbrec_logical_switch_port_update_options_delkey(lsp, "router-port"); - } - - if (tsp->peer[0]) { - if (!lsp->peer || strcmp(lsp->peer, tsp->peer)) { - nbrec_logical_switch_port_set_peer(lsp, tsp->peer); - } - } else if (lsp->peer && lsp->peer[0]) { - nbrec_logical_switch_port_set_peer(lsp, NULL); - } -} - -static void -sync_tsp_common(const struct icnbrec_transit_switch_port *tsp, - const struct icsbrec_port_binding *isb_pb, - const struct nbrec_logical_switch_port *lsp) -{ - /* Sync addresses from TSP to NB. */ - nbrec_logical_switch_port_set_addresses( - lsp, (const char **) tsp->addresses, tsp->n_addresses); - - if (tsp->chassis[0]) { - const char *current = smap_get(&lsp->options, "requested-chassis"); - if (!current || strcmp(current, tsp->chassis)) { - nbrec_logical_switch_port_update_options_setkey( - lsp, "requested-chassis", tsp->chassis); - } - } else if (smap_get(&lsp->options, "requested-chassis")) { - nbrec_logical_switch_port_update_options_delkey( - lsp, "requested-chassis"); - } - - /* Sync tunnel key from ISB to NB. */ - sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); - - if (!strcmp(tsp->type, "router")) { - sync_tsp_router_peer(tsp, lsp); - } else { - sync_tsp_non_router_peer(tsp, lsp); - } -} - -static void -sync_tsp_local(const struct icnbrec_transit_switch_port *tsp, - const struct icsbrec_port_binding *isb_pb, - const struct nbrec_logical_switch_port *lsp) -{ - if (strcmp(lsp->type, tsp->type)) { - nbrec_logical_switch_port_set_type(lsp, tsp->type); - } - sync_tsp_common(tsp, isb_pb, lsp); -} - -static void -sync_tsp_remote(const struct icnbrec_transit_switch_port *tsp, - const struct icsbrec_port_binding *isb_pb, - const struct nbrec_logical_switch_port *lsp) -{ - if (strcmp(lsp->type, "remote")) { - nbrec_logical_switch_port_set_type(lsp, "remote"); - } - sync_tsp_common(tsp, isb_pb, lsp); -} - -/* For each remote port: - * - Sync from ISB to NB - * - Sync gateway from ISB to SB - */ -static void -sync_remote_port(struct ic_context *ctx, - const struct icsbrec_port_binding *isb_pb, - const struct nbrec_logical_switch_port *lsp, - const struct sbrec_port_binding *sb_pb) -{ - /* Sync address from ISB to NB */ - if (isb_pb->address[0]) { - if (lsp->n_addresses != 1 || - strcmp(isb_pb->address, lsp->addresses[0])) { - nbrec_logical_switch_port_set_addresses( - lsp, (const char **)&isb_pb->address, 1); - } - } else { - if (lsp->n_addresses != 0) { - nbrec_logical_switch_port_set_addresses(lsp, NULL, 0); - } - } - - /* Sync tunnel key from ISB to NB */ - sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); - - /* Skip port binding if it is already requested by the CMS. */ - if (smap_get(&lsp->options, "requested-chassis")) { - return; - } - - /* Sync gateway from ISB to SB */ - if (isb_pb->gateway[0]) { - if (!sb_pb->chassis || strcmp(sb_pb->chassis->name, isb_pb->gateway)) { - const struct sbrec_chassis *chassis = - find_sb_chassis(ctx, isb_pb->gateway); - if (!chassis) { - VLOG_DBG("Chassis %s is not found in SB, syncing from ISB " - "to SB skipped for logical port %s.", - isb_pb->gateway, lsp->name); - return; - } - sbrec_port_binding_set_chassis(sb_pb, chassis); - } - } else { - if (sb_pb->chassis) { - sbrec_port_binding_set_chassis(sb_pb, NULL); - } - } -} - -/* For each remote port: - * - Sync from ISB to NB - */ -static void -sync_router_port(const struct icsbrec_port_binding *isb_pb, - const struct icnbrec_transit_router_port *trp, - const struct nbrec_logical_router_port *lrp) -{ - /* Sync from ICNB to NB */ - if (trp->chassis[0]) { - const char *chassis_name = - smap_get_def(&lrp->options, "requested-chassis", ""); - if (strcmp(trp->chassis, chassis_name)) { - nbrec_logical_router_port_update_options_setkey( - lrp, "requested-chassis", trp->chassis); - } - } else { - nbrec_logical_router_port_update_options_delkey( - lrp, "requested-chassis"); - } - - if (strcmp(trp->mac, lrp->mac)) { - nbrec_logical_router_port_set_mac(lrp, trp->mac); - } - - bool sync_networks = false; - if (trp->n_networks != lrp->n_networks) { - sync_networks = true; - } else { - for (size_t i = 0; i < trp->n_networks; i++) { - if (strcmp(trp->networks[i], lrp->networks[i])) { - sync_networks |= true; - break; - } - } - } - - if (sync_networks) { - nbrec_logical_router_port_set_networks( - lrp, (const char **) trp->networks, trp->n_networks); - } - - /* Sync tunnel key from ISB to NB */ - sync_lrp_tnl_key(lrp, isb_pb->tunnel_key); -} - -static void -create_nb_lsp(struct ic_context *ctx, - const struct icsbrec_port_binding *isb_pb, - const struct nbrec_logical_switch *ls) -{ - 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); - nbrec_logical_switch_port_set_type(lsp, "remote"); - - bool up = true; - nbrec_logical_switch_port_set_up(lsp, &up, 1); - - if (isb_pb->address[0]) { - nbrec_logical_switch_port_set_addresses( - lsp, (const char **)&isb_pb->address, 1); - } - sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); - nbrec_logical_switch_update_ports_addvalue(ls, lsp); -} - -static uint32_t -allocate_port_key(struct hmap *pb_tnlids) -{ - static uint32_t hint; - return ovn_allocate_tnlid(pb_tnlids, "transit port", - 1, (1u << 15) - 1, &hint); -} - -static const struct icsbrec_port_binding * -create_isb_pb(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) -{ - uint32_t pb_tnl_key = allocate_port_key(pb_tnlids); - if (!pb_tnl_key) { - return NULL; - } - - const struct icsbrec_port_binding *isb_pb = - icsbrec_port_binding_insert(isb_txn); - icsbrec_port_binding_set_availability_zone(isb_pb, az); - icsbrec_port_binding_set_transit_switch(isb_pb, ts_name); - icsbrec_port_binding_set_logical_port(isb_pb, logical_port); - icsbrec_port_binding_set_tunnel_key(isb_pb, pb_tnl_key); - icsbrec_port_binding_set_nb_ic_uuid(isb_pb, nb_ic_uuid, 1); - icsbrec_port_binding_set_type(isb_pb, type); - return isb_pb; -} - -static const struct nbrec_logical_router_port * +const struct nbrec_logical_router_port * get_lrp_by_lrp_name(struct ic_context *ctx, const char *lrp_name) { const struct nbrec_logical_router_port *lrp; @@ -1109,354 +643,42 @@ get_lrp_by_lrp_name(struct ic_context *ctx, const char *lrp_name) return lrp; } -static bool -chassis_is_remote(struct ic_context *ctx, const char *chassis_name) -{ - if (chassis_name) { - const struct sbrec_chassis *chassis = - find_sb_chassis(ctx, chassis_name); - if (chassis) { - return smap_get_bool(&chassis->other_config, "is-remote", false); - } else { - return true; - } - } - - return false; -} - -static struct nbrec_logical_router_port * -lrp_create(struct ic_context *ctx, const struct nbrec_logical_router *lr, - const struct icnbrec_transit_router_port *trp) -{ - struct nbrec_logical_router_port *lrp = - nbrec_logical_router_port_insert(ctx->ovnnb_txn); - nbrec_logical_router_port_set_name(lrp, trp->name); - - nbrec_logical_router_port_update_options_setkey(lrp, "interconn-tr", - trp->name); - nbrec_logical_router_update_ports_addvalue(lr, lrp); - return lrp; -} - -static void -sync_ts_isb_pb(struct ic_context *ctx, const struct sbrec_port_binding *sb_pb, - const struct icsbrec_port_binding *isb_pb) -{ - const char *address = get_lp_address_for_sb_pb(ctx, sb_pb); - if (address) { - icsbrec_port_binding_set_address(isb_pb, address); - } - - const struct sbrec_port_binding *crp = find_crp_for_sb_pb(ctx, sb_pb); - if (crp && crp->chassis) { - icsbrec_port_binding_set_gateway(isb_pb, crp->chassis->name); - } - - update_isb_pb_external_ids(ctx, sb_pb, isb_pb); - - /* XXX: Sync encap so that multiple encaps can be used for the same - * gateway. However, it is not needed for now, since we don't yet - * support specifying encap type/ip for gateway chassis or ha-chassis - * for logical router port in NB DB, and now encap should always be - * empty. The sync can be added if we add such support for gateway - * chassis/ha-chassis in NB DB. */ -} - -static struct nbrec_logical_switch_port * -lsp_create(struct ic_context *ctx, const struct nbrec_logical_switch *ls, - const struct icnbrec_transit_switch_port *tsp) -{ - struct nbrec_logical_switch_port *lsp = - nbrec_logical_switch_port_insert(ctx->ovnnb_txn); - nbrec_logical_switch_port_set_name(lsp, tsp->name); - - nbrec_logical_switch_port_update_options_setkey(lsp, "interconn-tsp", - tsp->name); - nbrec_logical_switch_update_ports_addvalue(ls, lsp); - return lsp; -} - -static const struct sbrec_port_binding * -find_lsp_in_sb(struct ic_context *ctx, - const struct nbrec_logical_switch_port *lsp) -{ - return find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, lsp->name); -} - +/* Fix F: build once per handler invocation, a map from every NB + * transit-switch-mirror port name to its transit switch name, by walking the + * logical switches directly. This replaces the previous reverse lookup that + * scanned every transit switch (with a per-switch index lookup) for each + * changed port -- an O(changed-ports x transit-switches) cost that dominated + * the profile (json_string / skiplist / compare_3way) and blocked the main + * loop for minutes at large fan-out. The values point into IDL-owned smaps + * and are valid for the lifetime of the map; destroy with shash_destroy(). */ void -port_binding_run(struct ic_context *ctx) +port_binding_lsp_ts_map_init(struct ic_context *ctx, struct shash *map) { - if (!ctx->ovnisb_unlocked_txn || !ctx->ovnnb_txn || !ctx->ovnsb_txn) { - return; - } - - struct shash switch_all_local_pbs = - SHASH_INITIALIZER(&switch_all_local_pbs); - struct shash router_all_local_pbs = - SHASH_INITIALIZER(&router_all_local_pbs); - struct hmap pb_tnlids = HMAP_INITIALIZER(&pb_tnlids); - struct shash_node *node; - - const struct icsbrec_port_binding *isb_pb; - const struct icsbrec_port_binding *isb_pb_key = - icsbrec_port_binding_index_init_row(ctx->icsbrec_port_binding_by_az); - icsbrec_port_binding_index_set_availability_zone(isb_pb_key, - ctx->runned_az); - - ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, - ctx->icsbrec_port_binding_by_az) { - ic_pb_get_type(isb_pb) != IC_ROUTER_PORT - ? shash_add(&switch_all_local_pbs, isb_pb->logical_port, isb_pb) - : shash_add(&router_all_local_pbs, isb_pb->logical_port, isb_pb); - - ovn_add_tnlid(&pb_tnlids, isb_pb->tunnel_key); - } - icsbrec_port_binding_index_destroy_row(isb_pb_key); - - const struct icnbrec_transit_switch *ts; - ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) { - const struct nbrec_logical_switch *ls = find_ts_in_nb(ctx, ts->name); - if (!ls) { - VLOG_DBG("Transit switch %s not found in NB.", ts->name); + shash_init(map); + const struct nbrec_logical_switch *ls; + NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->ovnnb_idl) { + const char *ts_name = smap_get(&ls->other_config, "interconn-ts"); + if (!ts_name) { continue; } - struct shash nb_ports = SHASH_INITIALIZER(&nb_ports); - struct shash old_nb_ports = SHASH_INITIALIZER(&old_nb_ports); - struct shash local_pbs = SHASH_INITIALIZER(&local_pbs); - struct shash remote_pbs = SHASH_INITIALIZER(&remote_pbs); - for (size_t i = 0; i < ls->n_ports; i++) { - const struct nbrec_logical_switch_port *lsp = ls->ports[i]; - if (smap_get(&lsp->options, "interconn-tsp")) { - shash_add(&nb_ports, lsp->name, lsp); - } else { - shash_add(&old_nb_ports, lsp->name, lsp); - } - } - - isb_pb_key = icsbrec_port_binding_index_init_row( - ctx->icsbrec_port_binding_by_ts); - icsbrec_port_binding_index_set_transit_switch(isb_pb_key, ts->name); - - ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, - ctx->icsbrec_port_binding_by_ts) { - if (isb_pb->availability_zone == ctx->runned_az) { - shash_add(&local_pbs, isb_pb->logical_port, isb_pb); - shash_find_and_delete(&switch_all_local_pbs, - isb_pb->logical_port); - } else { - shash_add(&remote_pbs, isb_pb->logical_port, isb_pb); - } - } - icsbrec_port_binding_index_destroy_row(isb_pb_key); - - for (size_t i = 0; i < ts->n_ports; i++) { - struct icnbrec_transit_switch_port *tsp = ts->ports[i]; - - if (!tsp->chassis[0]) { - isb_pb = shash_find_and_delete(&local_pbs, tsp->name); - if (!isb_pb) { - isb_pb = shash_find_and_delete(&remote_pbs, tsp->name); - } - - if (ctx->ovnisb_txn && is_az_leader(ctx->ovnisb_txn)) { - if (!isb_pb) { - isb_pb = create_isb_pb( - ctx->ovnisb_txn, tsp->name, ctx->runned_az, - ts->name, &ts->header_.uuid, "transit-switch-port", - &pb_tnlids); - } - sync_tsp_pb(tsp, isb_pb); - } - } else if (!chassis_is_remote(ctx, tsp->chassis)) { - /* 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, - ctx->runned_az, - ts->name, &ts->header_.uuid, - "transit-switch-port", &pb_tnlids); - } - sync_tsp_pb(tsp, isb_pb); - } else { - isb_pb = shash_find_and_delete(&remote_pbs, tsp->name); - } - - if (!isb_pb) { - continue; - } - - const struct nbrec_logical_switch_port *lsp = - shash_find_and_delete(&nb_ports, tsp->name); - if (!lsp) { - lsp = lsp_create(ctx, ls, tsp); - } - - if (!tsp->chassis[0] || !chassis_is_remote(ctx, tsp->chassis)) { - sync_tsp_local(tsp, isb_pb, lsp); - } else { - sync_tsp_remote(tsp, isb_pb, lsp); - } - } - - /* Support legacy way of adding transit switch ports. */ - const struct sbrec_port_binding *sb_pb; - const struct nbrec_logical_switch_port *lsp; - SHASH_FOR_EACH (node, &old_nb_ports) { - lsp = node->data; - - if (!strcmp(lsp->type, "router") - || !strcmp(lsp->type, "switch")) { - /* The port is local. */ - sb_pb = find_lsp_in_sb(ctx, lsp); - if (!sb_pb) { - continue; - } - 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->runned_az, ts->name, &ts->header_.uuid, - "transit-switch-port", &pb_tnlids); - sync_ts_isb_pb(ctx, sb_pb, isb_pb); - } else { - sync_local_port(ctx, isb_pb, sb_pb, lsp); - } - - if (isb_pb->type) { - icsbrec_port_binding_set_type(isb_pb, - "transit-switch-port"); - } - - if (isb_pb->nb_ic_uuid) { - icsbrec_port_binding_set_nb_ic_uuid(isb_pb, - &ts->header_.uuid, 1); - } - } else if (!strcmp(lsp->type, "remote")) { - /* The port is remote. */ - isb_pb = shash_find_and_delete(&remote_pbs, lsp->name); - if (!isb_pb) { - nbrec_logical_switch_update_ports_delvalue(ls, lsp); - } else { - sb_pb = find_lsp_in_sb(ctx, lsp); - if (!sb_pb) { - continue; - } - sync_remote_port(ctx, isb_pb, lsp, sb_pb); - } - } else { - VLOG_DBG("Ignore lsp %s on ts %s with type %s.", - lsp->name, ts->name, lsp->type); - } - } - - SHASH_FOR_EACH (node, &nb_ports) { - nbrec_logical_switch_port_delete(node->data); - nbrec_logical_switch_update_ports_delvalue(ls, node->data); + shash_replace(map, ls->ports[i]->name, CONST_CAST(char *, + ts_name)); } - - /* Delete extra port-binding from ISB */ - SHASH_FOR_EACH (node, &local_pbs) { - icsbrec_port_binding_delete(node->data); - } - - /* Create lsp in NB for remote ports */ - SHASH_FOR_EACH (node, &remote_pbs) { - create_nb_lsp(ctx, node->data, ls); - } - - shash_destroy(&nb_ports); - shash_destroy(&local_pbs); - shash_destroy(&remote_pbs); - shash_destroy(&old_nb_ports); - } - - SHASH_FOR_EACH (node, &switch_all_local_pbs) { - icsbrec_port_binding_delete(node->data); - } - shash_destroy(&switch_all_local_pbs); - - const struct icnbrec_transit_router *tr; - ICNBREC_TRANSIT_ROUTER_FOR_EACH (tr, ctx->ovninb_idl) { - const struct nbrec_logical_router *lr = find_tr_in_nb(ctx, tr->name); - if (!lr) { - VLOG_DBG("Transit router %s not found in NB.", tr->name); - continue; - } - - struct shash nb_ports = SHASH_INITIALIZER(&nb_ports); - struct shash local_pbs = SHASH_INITIALIZER(&local_pbs); - struct shash remote_pbs = SHASH_INITIALIZER(&remote_pbs); - - for (size_t i = 0; i < lr->n_ports; i++) { - const struct nbrec_logical_router_port *lrp = lr->ports[i]; - if (smap_get_def(&lrp->options, "interconn-tr", NULL)) { - shash_add(&nb_ports, lrp->name, lrp); - } - } - - isb_pb_key = icsbrec_port_binding_index_init_row( - ctx->icsbrec_port_binding_by_ts); - icsbrec_port_binding_index_set_transit_switch(isb_pb_key, tr->name); - - ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key, - ctx->icsbrec_port_binding_by_ts) { - if (isb_pb->availability_zone == ctx->runned_az) { - shash_add(&local_pbs, isb_pb->logical_port, isb_pb); - shash_find_and_delete(&router_all_local_pbs, - isb_pb->logical_port); - } else { - shash_add(&remote_pbs, isb_pb->logical_port, isb_pb); - } - } - icsbrec_port_binding_index_destroy_row(isb_pb_key); - - for (size_t i = 0; i < tr->n_ports; i++) { - const struct icnbrec_transit_router_port *trp = tr->ports[i]; - - if (chassis_is_remote(ctx, trp->chassis)) { - isb_pb = shash_find_and_delete(&remote_pbs, trp->name); - } 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, - ctx->runned_az, - tr->name, &tr->header_.uuid, - "transit-router-port", &pb_tnlids); - icsbrec_port_binding_set_address(isb_pb, trp->mac); - } - } - - /* Don't allow remote ports to create NB LRP until ICSB entry is - * created in the appropriate AZ. */ - if (isb_pb) { - const struct nbrec_logical_router_port *lrp = - shash_find_and_delete(&nb_ports, trp->name); - if (!lrp) { - lrp = lrp_create(ctx, lr, trp); - } - - sync_router_port(isb_pb, trp, lrp); - } - } - - SHASH_FOR_EACH(node, &nb_ports) { - nbrec_logical_router_port_delete(node->data); - nbrec_logical_router_update_ports_delvalue(lr, node->data); - } - - shash_destroy(&nb_ports); - shash_destroy(&local_pbs); - shash_destroy(&remote_pbs); } +} - SHASH_FOR_EACH (node, &router_all_local_pbs) { - icsbrec_port_binding_delete(node->data); +/* If 'lsp' is an interconnection port of a transit-switch mirror, adds that + * transit switch's name (looked up in 'lsp_ts_map') to 'ts_scope'. */ +void +port_binding_collect_lsp_ts(const struct shash *lsp_ts_map, + const struct nbrec_logical_switch_port *lsp, + struct sset *ts_scope) +{ + const char *ts_name = shash_find_data(lsp_ts_map, lsp->name); + if (ts_name) { + sset_add(ts_scope, ts_name); } - - ovn_destroy_tnlids(&pb_tnlids); - shash_destroy(&router_all_local_pbs); } struct ic_router_info { @@ -3961,6 +3183,10 @@ main(int argc, char *argv[]) = ovsdb_idl_index_create1(ovninb_idl_loop.idl, &icnbrec_transit_switch_col_name); + struct ovsdb_idl_index *icnbrec_transit_router_by_name + = ovsdb_idl_index_create1(ovninb_idl_loop.idl, + &icnbrec_transit_router_col_name); + struct ovsdb_idl_index *icsbrec_port_binding_by_az = ovsdb_idl_index_create1(ovnisb_unlocked_idl_loop.idl, &icsbrec_port_binding_col_availability_zone); @@ -4170,6 +3396,8 @@ main(int argc, char *argv[]) sbrec_service_monitor_by_remote_type_logical_port, .icnbrec_transit_switch_by_name = icnbrec_transit_switch_by_name, + .icnbrec_transit_router_by_name = + icnbrec_transit_router_by_name, .icsbrec_port_binding_by_az = icsbrec_port_binding_by_az, .icsbrec_port_binding_by_ts = icsbrec_port_binding_by_ts, .icsbrec_port_binding_by_ts_az = icsbrec_port_binding_by_ts_az, diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h index 8bca1340a..ab8541064 100644 --- a/ic/ovn-ic.h +++ b/ic/ovn-ic.h @@ -41,6 +41,7 @@ struct ic_context { struct ovsdb_idl_index *sbrec_service_monitor_by_ic_learned; struct ovsdb_idl_index *sbrec_service_monitor_by_remote_type_logical_port; 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_ts; struct ovsdb_idl_index *icsbrec_port_binding_by_ts_az; @@ -66,12 +67,16 @@ struct shash; struct icsbrec_datapath_binding; struct sbrec_chassis_table; struct icsbrec_gateway_table; +struct nbrec_logical_switch_port; +struct nbrec_logical_router_port; enum ic_datapath_type ic_dp_get_type( const struct icsbrec_datapath_binding *isb_dp); void address_set_run(struct ic_context *ctx); -void port_binding_run(struct ic_context *ctx); +struct icsbrec_port_binding; +enum ic_port_binding_type ic_pb_get_type( + const struct icsbrec_port_binding *isb_pb); struct sset; @@ -82,6 +87,15 @@ struct sset; */ void ts_sync_scope(struct ic_context *ctx, struct hmap *dp_tnlids, struct shash *isb_ts_dps, const struct sset *ts_scope); + +/* Scope-resolution helpers for the en_port_binding change handlers. Build the + * reverse maps once per handler invocation, then resolve each changed port + * with the *_collect_* helpers (Fix F). */ +void port_binding_lsp_ts_map_init(struct ic_context *ctx, struct shash *map); +void port_binding_collect_lsp_ts(const struct shash *lsp_ts_map, + const struct nbrec_logical_switch_port *lsp, + struct sset *ts_scope); + void route_run(struct ic_context *ctx); void sync_service_monitor(struct ic_context *ctx); @@ -91,6 +105,15 @@ uint32_t allocate_dp_key(struct hmap *dp_tnlids, bool vxlan_mode, const char *name); const struct sbrec_chassis * find_sb_chassis(struct ic_context *ctx, const char *name); +const struct sbrec_port_binding * +find_sb_pb_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name, + const char *name); +const struct nbrec_logical_switch * +find_ts_in_nb(struct ic_context *ctx, char *ts_name); +const struct nbrec_logical_router_port * +get_lrp_by_lrp_name(struct ic_context *ctx, const char *lrp_name); +const struct nbrec_logical_switch_port * +get_lsp_by_ts_port_name(struct ic_context *ctx, const char *ts_port_name); bool is_az_leader(struct ovsdb_idl_txn *txn); diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at index 5ed04aeb1..f38d48b5d 100644 --- a/tests/ovn-ic.at +++ b/tests/ovn-ic.at @@ -996,6 +996,96 @@ OVN_CLEANUP_IC([az1]) AT_CLEANUP ]) +AT_SETUP([ovn-ic -- incremental processing - port binding handler scoping]) + +# Exercises the per-transit-switch/router scoping of the en_port_binding change +# handlers on a single AZ. Helpers quiesce the engine, reset the stats, apply +# one change, and assert whether the port_binding node was fully recomputed. + +# CHECK_PB_RECOMPUTE(CHANGE_CMD, EXPECT) - run CHANGE_CMD and assert the +# port_binding node's recompute count is "0" (EXPECT=norecompute) or non-zero +# (EXPECT=recompute) afterwards. +m4_define([CHECK_PB_RECOMPUTE], [ + check ovn-ic-nbctl --wait=sb sync + check ovn-appctl -t ic/ovn-ic inc-engine/recompute + check ovn-ic-nbctl --wait=sb sync + check ovn-appctl -t ic/ovn-ic inc-engine/clear-stats + $1 + check ovn-ic-nbctl --wait=sb sync + rc=$(ovn-appctl -t ic/ovn-ic inc-engine/show-stats port_binding recompute) + echo "port_binding recompute count after '$1': $rc (expect $2)" + if test "$2" = norecompute; then + AT_CHECK([test "$rc" = 0]) + else + AT_CHECK([test "$rc" -gt 0]) + fi +]) + +ovn_init_ic_db +ovn-ic-nbctl ts-add ts1 +ovn-ic-nbctl ts-add ts2 +ovn_start az1 +as az1 +check ovn-ic-nbctl --wait=sb sync + +# One logical router with a port on each transit switch. +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 lsp-add-router-port ts1 lsp-ts1-lr1 lrp-lr1-ts1 +check ovn-nbctl lrp-add lr1 lrp-lr1-ts2 aa:aa:aa:aa:aa:02 169.254.101.1/24 +check ovn-nbctl --wait=sb lsp-add-router-port ts2 lsp-ts2-lr1 lrp-lr1-ts2 + +# Wait until both transit-switch ports are mirrored to IC-SB. +OVS_WAIT_UNTIL([test "$(ovn-ic-sbctl --bare --columns=logical_port find \ + port_binding | grep -c lsp-ts)" = 2]) + +# --- Changes handled incrementally (no full recompute of port_binding) --- + +# IC-NB Transit_Switch handler: a new transit switch is scoped to itself. +CHECK_PB_RECOMPUTE([check ovn-ic-nbctl --wait=sb ts-add ts3], [norecompute]) + +# NB Logical_Switch_Port handler: a column change on an existing transit-switch +# port is scoped to its transit switch. +CHECK_PB_RECOMPUTE( + [check ovn-nbctl set logical_switch_port lsp-ts1-lr1 options:foo=bar], + [norecompute]) + +# NB Logical_Switch handler: a tenant switch (no other_config:interconn-ts) is +# irrelevant. +CHECK_PB_RECOMPUTE([check ovn-nbctl --wait=sb ls-add sw0], [norecompute]) + +# --- Fallback: an SB chassis change still triggers a full recompute --- +CHECK_PB_RECOMPUTE([check ovn-sbctl chassis-add ch-test geneve 192.0.2.10], + [recompute]) + +# --- Cross-transit-switch isolation --- + +# A change scoped to ts1 must leave ts2's port binding untouched. +ovn-ic-sbctl --bare --columns=logical_port,tunnel_key,address find \ + port_binding transit_switch=ts2 | sort > ts2_before +CHECK_PB_RECOMPUTE( + [check ovn-nbctl set logical_switch_port lsp-ts1-lr1 options:bar=baz], + [norecompute]) +ovn-ic-sbctl --bare --columns=logical_port,tunnel_key,address find \ + port_binding transit_switch=ts2 | sort > ts2_after +AT_CHECK([diff ts2_before ts2_after]) + +# --- Recompute equivalence: scoped syncs produced the same state a full +# recompute would. --- +check ovn-ic-nbctl --wait=sb sync +m4_define([_PB_DUMP], [ + ovn-ic-sbctl --columns=logical_port,transit_switch,type,address \ + list port_binding | sort > $1 +]) +_PB_DUMP([pb_before]) +check ovn-appctl -t ic/ovn-ic inc-engine/recompute +check ovn-ic-nbctl --wait=sb sync +_PB_DUMP([pb_after]) +AT_CHECK([diff pb_before pb_after]) + +OVN_CLEANUP_IC([az1]) +AT_CLEANUP + OVN_FOR_EACH_NORTHD([ AT_SETUP([ovn-ic -- port sync]) -- 2.34.1 -- _'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
