On Tue, Jun 16, 2026 at 10:26:31AM +0200, Ales Musil wrote: > On Mon, Jun 15, 2026 at 5:46 PM Mairtin O'Loingsigh <[email protected]> > wrote: > > > Enable the creation and management of transit switch ports, when > > created, these ports are available across multiple AZs and may share > > port binding depending on configuration. This patch also includes tests > > and a multinode test. > > > > Commands to add, set address and delete port > > ovn-ic-nbctl tsp-add ts0 ts0-p0 chassis=chassis > > ovn-ic-nbctl tsp-set-addr ts0-p0 "00:11:22:11:22:34 > > 192.168.10.11/24" > > ovn-ic-nbctl tsp-del ts0-p0 > > > > Reported-at: https://issues.redhat.com/browse/FDP-2878 > > Signed-off-by: Mairtin O'Loingsigh <[email protected]> > > --- > > > > Hi Mairtin, > > thank you for the v5. There are still a few things that need to be > addressed. See down below. > > > > > > Changes since v4: > > - Fix router port type address sync. > > - Sync requested chassis, type, peer, tunnel. > > - Fix ICSB race by adding a leader check. > > > > Changes since v2: > > - Remove function which was not needed. > > - Dont overwrite router-id. > > - Unify support functions. > > - Remove left over debug code from tests. > > - Add ovn-ic-nbctl.8.xml updates. > > - Add tsp-del test. > > > > Changes since v1: > > - Merge patches. > > - Bump OVNIC NB version number. > > - Add NEWS entry. > > - Remove option column from TSP. > > - Remove nb_uuid from TSP. > > - Clean up coding standard violations. > > - Fix missing port issue. > > > > NEWS | 2 + > > ic/ovn-ic.c | 389 +++++++++++++++++++++++++++-------- > > ic/ovn-ic.h | 1 + > > lib/ovn-util.c | 44 ++++ > > lib/ovn-util.h | 4 + > > ovn-ic-nb.ovsschema | 26 ++- > > ovn-ic-nb.xml | 42 ++++ > > tests/multinode.at | 44 +--- > > tests/ovn-ic-nbctl.at | 47 +++++ > > tests/ovn-ic.at | 315 ++++++++++++++++++++++++++++ > > utilities/ovn-ic-nbctl.8.xml | 56 +++++ > > utilities/ovn-ic-nbctl.c | 248 +++++++++++++++++++++- > > utilities/ovn-nbctl.c | 52 +---- > > 13 files changed, 1098 insertions(+), 172 deletions(-) > > > > diff --git a/NEWS b/NEWS > > index 748ae30eb..82d2999f9 100644 > > --- a/NEWS > > +++ b/NEWS > > @@ -1,5 +1,7 @@ > > Post v26.03.0 > > ------------- > > + - Added Transit Switch port support with new ovn-ic-nbctl 'tsp-add', > > + 'tsp-del' and 'tsp-set-addr' commands. > > - Added ability to set any "ipsec_*" NB_Global option to configure the > > IPsec backend. > > - Documented missing ovn-nbctl commands: "mirror-rule-add", > > diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c > > index 0a3f2336b..8efb083db 100644 > > --- a/ic/ovn-ic.c > > +++ b/ic/ovn-ic.c > > @@ -733,6 +733,17 @@ get_lp_address_for_sb_pb(struct ic_context *ctx, > > return peer->n_mac ? *peer->mac : NULL; > > } > > > > +static const char * > > +get_lp_address_for_ts_pb(struct ic_context *ctx, const char *peer_name) > > +{ > > + const struct sbrec_port_binding *peer = > > + find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, peer_name); > > + if (peer && peer->n_mac) { > > + return *peer->mac; > > + } > > + return NULL; > > +} > > + > > static const struct sbrec_chassis * > > find_sb_chassis(struct ic_context *ctx, const char *name) > > { > > @@ -817,52 +828,157 @@ update_isb_pb_external_ids(struct ic_context *ctx, > > free(uuid_s); > > } > > > > -/* For each local port: > > - * - Sync from NB to ISB. > > - * - Sync gateway from SB to ISB. > > - * - Sync tunnel key from ISB to NB. > > - */ > > +static const char * > > +get_ts_port_address(struct ic_context *ctx, > > + const struct icnbrec_transit_switch_port *tsp, > > + const struct sbrec_port_binding *sb_pb) > > +{ > > + if (tsp) { > > + if (!strcmp(tsp->type, "router") && tsp->peer[0]) { > > + return get_lp_address_for_ts_pb(ctx, tsp->peer); > > + } > > + return tsp->n_addresses ? tsp->addresses[0] : NULL; > > + } > > + > > + return sb_pb ? get_lp_address_for_sb_pb(ctx, sb_pb) : NULL; > > +} > > + > > +/* Sync a local port's fields from SB/TSP towards ISB and NB. > > + * sb_pb may be NULL when the NB LSP was just created and northd > > + * hasn't processed it yet; in that case gateway is set from > > + * tsp->chassis and external_ids:router-id is skipped. */ > > static void > > sync_local_port(struct ic_context *ctx, > > + const struct icnbrec_transit_switch_port *tsp, > > const struct icsbrec_port_binding *isb_pb, > > - const struct sbrec_port_binding *sb_pb, > > - const struct nbrec_logical_switch_port *lsp) > > + const struct nbrec_logical_switch_port *lsp, > > + const struct sbrec_port_binding *sb_pb) > > { > > - /* Sync address from NB to ISB */ > > - const char *address = get_lp_address_for_sb_pb(ctx, sb_pb); > > + const char *address = get_ts_port_address(ctx, tsp, sb_pb); > > + /* Sync address to ISB. */ > > 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); > > + } else if (strcmp(address, isb_pb->address)) { > > + icsbrec_port_binding_set_address(isb_pb, address); > > + } > > + > > + /* Sync gateway to ISB. */ > > + if (sb_pb) { > > + 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] && !tsp) { > > + icsbrec_port_binding_set_gateway(isb_pb, ""); > > + } > > + } else if (tsp && tsp->chassis[0]) { > > + if (strcmp(tsp->chassis, isb_pb->gateway)) { > > + icsbrec_port_binding_set_gateway(isb_pb, tsp->chassis); > > } > > } > > > > - /* 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); > > + /* Sync external_ids:router-id to ISB (requires sb_pb). */ > > + if (sb_pb) { > > + update_isb_pb_external_ids(ctx, sb_pb, isb_pb); > > + } > > + > > + sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); > > +} > > + > > +static bool > > +trp_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 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); > > + return true; > > + } > > + > > + return false; > > +} > > + > > +static void > > +sync_tsp(struct ic_context *ctx, const struct icnbrec_transit_switch_port > > *tsp, > > + const struct icsbrec_port_binding *isb_pb, > > + const struct nbrec_logical_switch_port *lsp) > > +{ > > + /* Sync type and peer from TSP to NB. */ > > + if (!tsp->chassis[0] || !trp_is_remote(ctx, tsp->chassis)) { > > + if (strcmp(lsp->type, tsp->type)) { > > + nbrec_logical_switch_port_set_type(lsp, tsp->type); > > + if (!strcmp(tsp->type, "router")) { > > + if (tsp->peer[0]) { > > + nbrec_logical_switch_port_update_options_setkey( > > + lsp, "router-port", tsp->peer); > > + } > > + nbrec_logical_switch_port_set_addresses( > > + lsp, (const char *[]) {"router"}, 1); > > > > This is a bit confusing setting the address even though this block is > taking care of the type and peer. It would make more sense to move > it down below. > ACK, Ill move the address set below. > + } else { > > + nbrec_logical_switch_port_update_options_delkey( > > + lsp, "router-port"); > > + } > > } > > } else { > > - if (isb_pb->gateway[0]) { > > - icsbrec_port_binding_set_gateway(isb_pb, ""); > > + if (strcmp(lsp->type, "remote")) { > > + nbrec_logical_switch_port_set_type(lsp, "remote"); > > + } > > + } > > + > > + /* Sync address to NB (skip for router type, which > > + * uses "router" as its NB address). */ > > + const char *address = get_ts_port_address(ctx, tsp, NULL); > > > > We don't need the helper right? The address is set only if the type is not > router, and in that case we can copy directly what is in the IC-NB. > Which we would end up doing anyway. Arguably the TSP of type router > should actually have address "router". We could just copy it regardless > of the type WDYT? Agreed, ill replace the helper with TSP->addresses. > > > > + if (strcmp(tsp->type, "router")) { > > + if (!address) { > > + if (lsp->n_addresses) { > > + nbrec_logical_switch_port_set_addresses(lsp, NULL, 0); > > + } > > + } else if (!lsp->n_addresses || strcmp(address, > > lsp->addresses[0])) { > > + nbrec_logical_switch_port_set_addresses( > > + lsp, (const char **) &address, 1); > > } > > } > > > > - /* Sync external_ids:router-id to ISB */ > > - update_isb_pb_external_ids(ctx, sb_pb, isb_pb); > > + 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 back tunnel key from ISB to NB */ > > + /* Sync tunnel key from ISB to NB. */ > > sync_lsp_tnl_key(lsp, isb_pb->tunnel_key); > > + > > + if (tsp->peer[0]) { > > + if (!lsp->peer || strcmp(lsp->peer, tsp->peer)) { > > + nbrec_logical_switch_port_set_peer(lsp, tsp->peer); > > + } > > + if (!strcmp(lsp->type, "router")) { > > + const char *current = smap_get(&lsp->options, "router-port"); > > + if (!current || strcmp(current, tsp->peer)) { > > + nbrec_logical_switch_port_update_options_setkey( > > + lsp, "router-port", tsp->peer); > > + } > > + } > > + } else if (lsp->peer && lsp->peer[0]) { > > + nbrec_logical_switch_port_set_peer(lsp, ""); > > + } > > } > > > > /* For each remote port: > > @@ -992,7 +1108,7 @@ allocate_port_key(struct hmap *pb_tnlids) > > } > > > > static const struct icsbrec_port_binding * > > -create_isb_pb(struct ic_context *ctx, const char *logical_port, > > +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) > > @@ -1003,7 +1119,7 @@ create_isb_pb(struct ic_context *ctx, const char > > *logical_port, > > } > > > > const struct icsbrec_port_binding *isb_pb = > > - icsbrec_port_binding_insert(ctx->ovnisb_unlocked_txn); > > + 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); > > @@ -1027,22 +1143,6 @@ get_lrp_by_lrp_name(struct ic_context *ctx, const > > char *lrp_name) > > return lrp; > > } > > > > -static bool > > -trp_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) > > @@ -1057,28 +1157,38 @@ lrp_create(struct ic_context *ctx, const struct > > nbrec_logical_router *lr, > > 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) > > +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) > > { > > - const char *address = get_lp_address_for_sb_pb(ctx, sb_pb); > > - if (address) { > > - icsbrec_port_binding_set_address(isb_pb, address); > > - } > > + bool router_port = !strcmp(tsp->type, "router"); > > + > > + struct nbrec_logical_switch_port *lsp = > > + nbrec_logical_switch_port_insert(ctx->ovnnb_txn); > > + nbrec_logical_switch_port_set_name(lsp, tsp->name); > > > > - 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); > > + nbrec_logical_switch_port_update_options_setkey(lsp, "interconn-ts", > > + tsp->name); > > + if (tsp->peer[0]) { > > + nbrec_logical_switch_port_set_peer(lsp, tsp->peer); > > } > > > > - update_isb_pb_external_ids(ctx, sb_pb, isb_pb); > > + if (router_port) { > > + if (tsp->peer[0]) { > > + nbrec_logical_switch_port_update_options_setkey( > > + lsp, "router-port", tsp->peer); > > + } > > + > > + nbrec_logical_switch_port_set_addresses( > > + lsp, (const char *[]) {"router"}, 1); > > + } else { > > + nbrec_logical_switch_port_set_addresses(lsp, > > + (const char **) > > tsp->addresses, > > + tsp->n_addresses); > > + } > > > > - /* 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. */ > > + nbrec_logical_switch_update_ports_addvalue(ls, lsp); > > + return lsp; > > } > > > > static const struct sbrec_port_binding * > > @@ -1118,7 +1228,6 @@ port_binding_run(struct ic_context *ctx) > > } > > icsbrec_port_binding_index_destroy_row(isb_pb_key); > > > > - const struct sbrec_port_binding *sb_pb; > > 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); > > @@ -1126,9 +1235,20 @@ port_binding_run(struct ic_context *ctx) > > VLOG_DBG("Transit switch %s not found in NB.", 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-ts")) { > > + 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); > > @@ -1145,9 +1265,70 @@ port_binding_run(struct ic_context *ctx) > > } > > 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]; > > + bool is_owner = false; > > + > > + if (!tsp->chassis[0]) { > > + const struct icsbrec_port_binding *isb_pb_key_name = > > + icsbrec_port_binding_index_init_row( > > + ctx->icsbrec_port_binding_by_name); > > + > > icsbrec_port_binding_index_set_logical_port(isb_pb_key_name, > > + tsp->name); > > + isb_pb = icsbrec_port_binding_index_find( > > + ctx->icsbrec_port_binding_by_name, isb_pb_key_name); > > + icsbrec_port_binding_index_destroy_row(isb_pb_key_name); > > + if (isb_pb) { > > + shash_find_and_delete(&local_pbs, tsp->name); > > + shash_find_and_delete(&remote_pbs, tsp->name); > > + } > > > > I'm still slightly confused why do we need the lookup? The isb_pb > should end up in one of those shashes. So maybe doing a lookup in > both? If it's not in either we need to create a new one anyway. Agreed, second lookup is not needed, ill remote it. > > + > > + 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); > > + } > > + is_owner = true; > > + } > > + } else if (!trp_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); > > + } > > + is_owner = true; > > + } 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); > > + } > > + > > + const struct sbrec_port_binding *sb_pb = find_lsp_in_sb(ctx, > > lsp); > > + if (is_owner) { > > + sync_local_port(ctx, tsp, isb_pb, lsp, sb_pb); > > + } > > + > > + sync_tsp(ctx, 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; > > - for (int i = 0; i < ls->n_ports; i++) { > > - lsp = ls->ports[i]; > > + SHASH_FOR_EACH (node, &old_nb_ports) { > > > > I think that we should leave the legacy way completely intact. > There are some similarities which could be handled by common > helpers, but I'm afraid this will create confusion. I would suggest > to focus only on the tsp sync, that is IC-NB -> IC-SB sync. > > The part for syncing into IC-SB -> NB seems to fine with some minor > comments. Ack, Ill remove legacy path changes and keep the sync code separate. > > + lsp = node->data; > > > > if (!strcmp(lsp->type, "router") > > || !strcmp(lsp->type, "switch")) { > > @@ -1158,23 +1339,18 @@ port_binding_run(struct ic_context *ctx) > > } > > isb_pb = shash_find_and_delete(&local_pbs, lsp->name); > > if (!isb_pb) { > > - isb_pb = create_isb_pb( > > - ctx, 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); > > + 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); > > + if (!isb_pb) { > > + VLOG_DBG("Tunnel ID allocation failed for %s", > > + sb_pb->logical_port); > > + continue; > > + } > > } > > + sync_local_port(ctx, NULL, isb_pb, lsp, sb_pb); > > } else if (!strcmp(lsp->type, "remote")) { > > /* The port is remote. */ > > isb_pb = shash_find_and_delete(&remote_pbs, lsp->name); > > @@ -1186,13 +1362,28 @@ port_binding_run(struct ic_context *ctx) > > continue; > > } > > sync_remote_port(ctx, isb_pb, lsp, sb_pb); > > + if (!isb_pb->type[0]) { > > + icsbrec_port_binding_set_type(isb_pb, > > + > > "transit-switch-port"); > > + } > > + > > + if (!isb_pb->n_nb_ic_uuid) { > > + icsbrec_port_binding_set_nb_ic_uuid( > > + isb_pb, &ts->header_.uuid, 1); > > + } > > } > > + > > } 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); > > @@ -1203,8 +1394,10 @@ port_binding_run(struct ic_context *ctx) > > 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) { > > @@ -1255,7 +1448,8 @@ port_binding_run(struct ic_context *ctx) > > } else { > > isb_pb = shash_find_and_delete(&local_pbs, trp->name); > > if (!isb_pb) { > > - isb_pb = create_isb_pb(ctx, trp->name, ctx->runned_az, > > + 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); > > @@ -1275,7 +1469,7 @@ port_binding_run(struct ic_context *ctx) > > } > > } > > > > - SHASH_FOR_EACH(node, &nb_ports) { > > + SHASH_FOR_EACH (node, &nb_ports) { > > nbrec_logical_router_port_delete(node->data); > > nbrec_logical_router_update_ports_delvalue(lr, node->data); > > } > > @@ -2629,16 +2823,30 @@ route_run(struct ic_context *ctx) > > const struct nbrec_logical_switch_port *nb_lsp; > > > > nb_lsp = get_lsp_by_ts_port_name(ctx, isb_pb->logical_port); > > - if (!strcmp(nb_lsp->type, "switch")) { > > - VLOG_DBG("IC-SB Port_Binding '%s' on ts '%s' corresponds to a > > " > > - "switch port, not considering for route collection.", > > - isb_pb->logical_port, isb_pb->transit_switch); > > + if (!nb_lsp) { > > + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); > > + VLOG_DBG_RL(&rl, > > + "IC-SB Port_Binding '%s' on ts '%s': " > > + "NB LSP not found, skipping route collection.", > > + isb_pb->logical_port, isb_pb->transit_switch); > > + continue; > > + } > > + > > + if (!strcmp(nb_lsp->type, "switch") || !strcmp(nb_lsp->type, "")) > > { > > + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); > > + VLOG_DBG_RL(&rl, > > + "IC-SB Port_Binding '%s' on ts '%s' corresponds > > to a " > > + "switch port, not considering for route > > collection.", > > + isb_pb->logical_port, isb_pb->transit_switch); > > continue; > > } > > > > const char *ts_lrp_name = > > get_lrp_name_by_ts_port_name(ctx, isb_pb->logical_port); > > if (!ts_lrp_name) { > > + if (!strcmp(isb_pb->type, "transit-switch-port")) { > > + continue; > > + } > > static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); > > VLOG_WARN_RL(&rl, "Route sync ignores port %s on ts %s > > because " > > "logical router port is not found in NB. > > Deleting it", > > @@ -3575,6 +3783,8 @@ main(int argc, char *argv[]) > > &nbrec_logical_switch_port_col_enabled); > > ovsdb_idl_track_add_column(ovnnb_idl_loop.idl, > > > > &nbrec_logical_switch_port_col_external_ids); > > + ovsdb_idl_track_add_column(ovnnb_idl_loop.idl, > > + &nbrec_logical_switch_port_col_peer); > > > > ovsdb_idl_add_table(ovnnb_idl_loop.idl, &nbrec_table_load_balancer); > > ovsdb_idl_track_add_column(ovnnb_idl_loop.idl, > > @@ -3720,6 +3930,10 @@ main(int argc, char *argv[]) > > = ovsdb_idl_index_create1(ovnisb_unlocked_idl_loop.idl, > > > > &icsbrec_port_binding_col_transit_switch); > > > > + 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_az > > = ovsdb_idl_index_create2(ovnisb_unlocked_idl_loop.idl, > > > > &icsbrec_port_binding_col_transit_switch, > > @@ -3912,6 +4126,7 @@ main(int argc, char *argv[]) > > icnbrec_transit_switch_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_name = > > icsbrec_port_binding_by_name, > > .icsbrec_port_binding_by_ts_az = > > icsbrec_port_binding_by_ts_az, > > .icsbrec_route_by_az = icsbrec_route_by_az, > > .icsbrec_route_by_ts = icsbrec_route_by_ts, > > diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h > > index 9f52bb0f9..b41c214e0 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 *icsbrec_port_binding_by_az; > > struct ovsdb_idl_index *icsbrec_port_binding_by_ts; > > + struct ovsdb_idl_index *icsbrec_port_binding_by_name; > > struct ovsdb_idl_index *icsbrec_port_binding_by_ts_az; > > struct ovsdb_idl_index *icsbrec_route_by_az; > > struct ovsdb_idl_index *icsbrec_route_by_ts; > > diff --git a/lib/ovn-util.c b/lib/ovn-util.c > > index e6143d7a9..8ec22f81a 100644 > > --- a/lib/ovn-util.c > > +++ b/lib/ovn-util.c > > @@ -1850,3 +1850,47 @@ eth_addr_parse_masked(const char *s, struct > > eth_addr *ea, unsigned int *plen) > > *ea = eth_addr_zero; > > return false; > > } > > + > > +bool > > +port_contains_duplicate_ip(struct lport_addresses *laddrs1, > > + struct lport_addresses *laddrs2, > > + const char *port_name, > > + char **error_str) > > +{ > > + for (size_t i = 0; i < laddrs1->n_ipv4_addrs; i++) { > > + for (size_t j = 0; j < laddrs2->n_ipv4_addrs; j++) { > > + if (laddrs1->ipv4_addrs[i].addr == > > laddrs2->ipv4_addrs[j].addr) { > > + if (error_str) { > > + *error_str = xasprintf("duplicate IPv4 address '%s' " > > + "found on logical switch " > > + "port '%s'", > > + laddrs1->ipv4_addrs[i].addr_s, > > + port_name); > > + } > > + return true; > > + } > > + } > > + } > > + > > + for (size_t i = 0; i < laddrs1->n_ipv6_addrs; i++) { > > + for (size_t j = 0; j < laddrs2->n_ipv6_addrs; j++) { > > + if (IN6_ARE_ADDR_EQUAL(&laddrs1->ipv6_addrs[i].addr, > > + &laddrs2->ipv6_addrs[j].addr)) { > > + if (error_str) { > > + *error_str = xasprintf("duplicate IPv6 address " > > + "'%s' found on logical " > > + "switch port '%s'", > > + laddrs1->ipv6_addrs[i].addr_s, > > + port_name); > > + } > > + return true; > > + } > > + } > > + } > > + > > + if (error_str) { > > + *error_str = NULL; > > + } > > + > > + return false; > > +} > > diff --git a/lib/ovn-util.h b/lib/ovn-util.h > > index bfca178e4..eb88f8549 100644 > > --- a/lib/ovn-util.h > > +++ b/lib/ovn-util.h > > @@ -794,6 +794,10 @@ char *normalize_ipv6_addr_str(const char *orig_addr); > > > > char *normalize_addr_str(const char *orig_addr); > > > > +bool port_contains_duplicate_ip(struct lport_addresses *laddrs1, > > + struct lport_addresses *laddrs2, > > + const char *name, char **error_str); > > + > > #define NEIGH_REDISTRIBUTE_MODES \ > > NEIGH_REDISTRIBUTE_MODE(FDB, 0) \ > > NEIGH_REDISTRIBUTE_MODE(IP, 1) > > diff --git a/ovn-ic-nb.ovsschema b/ovn-ic-nb.ovsschema > > index ca67a2fa9..9628e5824 100644 > > --- a/ovn-ic-nb.ovsschema > > +++ b/ovn-ic-nb.ovsschema > > @@ -1,7 +1,7 @@ > > { > > "name": "OVN_IC_Northbound", > > - "version": "1.3.0", > > - "cksum": "1918565391 5082", > > + "version": "1.4.0", > > + "cksum": "518084103 6175", > > "tables": { > > "IC_NB_Global": { > > "columns": { > > @@ -24,9 +24,31 @@ > > "min": 0, "max": "unlimited"}}}, > > "maxRows": 1, > > "isRoot": true}, > > + "Transit_Switch_Port": { > > + "columns": { > > + "name": {"type": "string"}, > > + "other_config": { > > + "type": {"key": "string", "value": "string", > > + "min": 0, "max": "unlimited"}}, > > + "type": {"type": "string"}, > > + "chassis": {"type": "string"}, > > + "peer": {"type": "string"}, > > + "addresses": {"type": {"key": "string", > > + "min": 0, > > + "max": "unlimited"}}, > > + "external_ids": { > > + "type": {"key": "string", "value": "string", > > + "min": 0, "max": "unlimited"}}}, > > + "isRoot": false, > > + "indexes": [["name"]]}, > > "Transit_Switch": { > > "columns": { > > "name": {"type": "string"}, > > + "ports": {"type": {"key": {"type": "uuid", > > + "refTable": > > "Transit_Switch_Port", > > + "refType": "strong"}, > > + "min": 0, > > + "max": "unlimited"}}, > > "other_config": { > > "type": {"key": "string", "value": "string", > > "min": 0, "max": "unlimited"}}, > > diff --git a/ovn-ic-nb.xml b/ovn-ic-nb.xml > > index a3a35baf2..669f9f7aa 100644 > > --- a/ovn-ic-nb.xml > > +++ b/ovn-ic-nb.xml > > @@ -112,6 +112,10 @@ > > </column> > > </group> > > > > + <column name="ports"> > > + The transit switch's <ref table="Transit_Switch_Port"/> ports. > > + </column> > > + > > <group title="Common Columns"> > > <column name="other_config"/> > > <column name="external_ids"> > > @@ -190,6 +194,44 @@ > > </group> > > </table> > > > > + <table name="Transit_Switch_Port" title="Transit logical switch port"> > > + <p> > > + Each row represents one transit logical switch port for > > interconnection > > + between different OVN deployments (availability zones). > > + </p> > > + > > + <group title="Naming"> > > + <column name="name"> > > + A name that uniquely identifies the transit logical switch port. > > + </column> > > + </group> > > + > > + <column name="type"> > > + The type of the port. Set to <code>router</code> for a port > > + connected to a logical router, or leave empty for a VIF port. > > + </column> > > + > > + <column name="chassis"> > > + The chassis this switch port should be bound to. If empty, > > + the port is local to all availability zones. > > + </column> > > + > > + <column name="peer"> > > + The name of the peer logical router port, used when > > + <ref column="type"/> is <code>router</code>. > > + </column> > > + > > + <column name="addresses"> > > + The addresses associated with this port. Used when > > + <ref column="type"/> is not <code>router</code>. > > + </column> > > + > > + <group title="Common Columns"> > > + <column name="other_config"/> > > + <column name="external_ids"/> > > + </group> > > + </table> > > + > > <table name="SSL"> > > SSL/TLS configuration for ovn-nb database access. > > > > diff --git a/tests/multinode.at b/tests/multinode.at > > index 069f2a677..1ce75f4d8 100644 > > --- a/tests/multinode.at > > +++ b/tests/multinode.at > > @@ -2354,7 +2354,7 @@ fi > > > > AT_CLEANUP > > > > -AT_SETUP([ovn multinode - Transit Router]) > > +AT_SETUP([ovn multinode - Transit Router + Transit Switch]) > > > > # Check that ovn-fake-multinode setup is up and running > > check_fake_multinode_setup > > @@ -2424,7 +2424,7 @@ for i in 1 2; do > > check m_as $chassis ovs-vsctl set open . > > external_ids:ovn-is-interconn=true > > done > > > > -# Do the ovn-ic setup. > > +# Add TR and TS > > check m_central_as ovn-ic-nbctl tr-add tr > > check m_central_as ovn-ic-nbctl trp-add tr tr-gw1 \ > > 00:00:00:00:30:02 100.65.0.2/30 100:65::2/126 \ > > @@ -2432,6 +2432,14 @@ check m_central_as ovn-ic-nbctl trp-add tr tr-gw1 \ > > check m_central_as ovn-ic-nbctl trp-add tr tr-gw2 \ > > 00:00:00:00:30:06 100.65.0.6/30 100:65::6/126 \ > > chassis=ovn-chassis-2 > > +check m_central_as ovn-ic-nbctl ts-add ts > > +check m_central_as ovn-ic-nbctl tsp-add ts ts-tr type=router peer=tr-ts > > +check m_central_as ovn-ic-nbctl tsp-add ts pod10 chassis=ovn-chassis-1 > > +check m_central_as ovn-ic-nbctl tsp-set-addr pod10 "00:00:00:00:10:10 > > 10.100.200.10 10:200::10" > > +check m_central_as ovn-ic-nbctl tsp-add ts pod20 chassis=ovn-chassis-2 > > +check m_central_as ovn-ic-nbctl tsp-set-addr pod20 "00:00:00:00:10:20 > > 10.100.200.20 10:200::20" > > +check m_central_as ovn-ic-nbctl tsp-add ts mgmt > > +check m_central_as ovn-ic-nbctl tsp-set-addr mgmt "00:00:00:00:10:11 > > 10.100.200.11 10:200::11" > > check m_central_as ovn-ic-nbctl --wait=sb sync > > > > for i in 1 2; do > > @@ -2446,29 +2454,9 @@ for i in 1 2; do > > > > check m_as $chassis ovn-nbctl set logical_router gw > > options:chassis=$chassis > > > > - # Add TR and set the same tunnel key for both chassis > > - check m_as $chassis ovn-nbctl ls-add ts > > - check m_as $chassis ovn-nbctl set logical_switch ts > > other_config:requested-tnl-key=10 > > - > > - check m_as $chassis ovn-nbctl lsp-add-router-port ts ts-tr tr-ts > > - > > check m_as $chassis ovn-nbctl lrp-add tr tr-ts 00:00:00:00:10:00 > > 10.100.200.1/24 10:200::1/64 > > - check m_as $chassis ovn-nbctl set logical_router tr > > options:requested-tnl-key=20 > > check m_as $chassis ovn-nbctl lrp-set-gateway-chassis tr-ts $chassis > > > > - # Add TS pods, with the same tunnel keys on both sides > > - check m_as $chassis ovn-nbctl lsp-add ts pod10 > > - check m_as $chassis ovn-nbctl lsp-set-addresses pod10 > > "00:00:00:00:10:10 10.100.200.10 10:200::10" > > - check m_as $chassis ovn-nbctl set logical_switch_port pod10 > > options:requested-tnl-key=10 > > - > > - check m_as $chassis ovn-nbctl lsp-add ts pod20 > > - check m_as $chassis ovn-nbctl lsp-set-addresses pod20 > > "00:00:00:00:10:20 10.100.200.20 10:200::20" > > - check m_as $chassis ovn-nbctl set logical_switch_port pod20 > > options:requested-tnl-key=20 > > - > > - # Add mgmt pod > > - check m_as $chassis ovn-nbctl lsp-add ts mgmt > > - check m_as $chassis ovn-nbctl lsp-set-addresses mgmt > > "00:00:00:00:10:11 10.100.200.11 10:200::11" > > - check m_as $chassis ovn-nbctl set logical_switch_port mgmt > > options:requested-tnl-key=11 > > done > > > > # Add SNAT for the GW router that corresponds to "gw-tr" LRP IP > > @@ -2499,18 +2487,6 @@ check m_as ovn-chassis-1 ovn-nbctl set > > logical_router tr options:ct-commit-all=" > > check m_as ovn-chassis-1 ovn-nbctl lsp-add public external > > check m_as ovn-chassis-1 ovn-nbctl lsp-set-addresses external > > "00:00:00:00:20:10 192.168.100.10 1000::10" > > > > -# Configure ports on the transit switch as remotes > > -check m_as ovn-chassis-1 ovn-nbctl lsp-set-type pod20 remote > > -check m_as ovn-chassis-1 ovn-nbctl lsp-set-options pod10 > > requested-chassis=ovn-chassis-1 > > -check m_as ovn-chassis-1 ovn-nbctl lsp-set-options mgmt > > requested-chassis=ovn-chassis-1 > > -check m_as ovn-chassis-1 ovn-nbctl lsp-set-options pod20 > > requested-chassis=ovn-chassis-2 > > - > > -check m_as ovn-chassis-2 ovn-nbctl lsp-set-type pod10 remote > > -check m_as ovn-chassis-2 ovn-nbctl lsp-set-type mgmt remote > > -check m_as ovn-chassis-2 ovn-nbctl lsp-set-options pod10 > > requested-chassis=ovn-chassis-1 > > -check m_as ovn-chassis-2 ovn-nbctl lsp-set-options mgmt > > requested-chassis=ovn-chassis-1 > > -check m_as ovn-chassis-2 ovn-nbctl lsp-set-options pod20 > > requested-chassis=ovn-chassis-2 > > - > > m_as ovn-chassis-1 /data/create_fake_vm.sh external external > > 00:00:00:00:20:10 1500 192.168.100.10 24 192.168.100.1 1000::10/64 1000::1 > > m_as ovn-chassis-1 /data/create_fake_vm.sh pod10 pod10 00:00:00:00:10:10 > > 1500 10.100.200.10 24 10.100.200.1 10:200::10/64 10:200::1 > > m_as ovn-chassis-1 /data/create_fake_vm.sh mgmt mgmt 00:00:00:00:10:11 > > 1500 10.100.200.11 24 10.100.200.1 10:200::11/64 10:200::1 > > diff --git a/tests/ovn-ic-nbctl.at b/tests/ovn-ic-nbctl.at > > index 4c5269784..eb333edec 100644 > > --- a/tests/ovn-ic-nbctl.at > > +++ b/tests/ovn-ic-nbctl.at > > @@ -61,6 +61,53 @@ AT_CHECK([ovn-ic-nbctl ts-del ts2], [1], [], > > > > AT_CHECK([ovn-ic-nbctl --if-exists ts-del ts2]) > > > > +AT_CHECK([ovn-ic-nbctl --may-exist ts-add ts0]) > > +AT_CHECK([ovn-ic-nbctl ts-list | uuidfilt], [0], [dnl > > +<0> (ts0) > > +]) > > + > > +AT_CHECK([ovn-ic-nbctl tsp-add], [1], [], > > + [ovn-ic-nbctl: 'tsp-add' command requires at least 2 arguments > > +]) > > + > > +AT_CHECK([ovn-ic-nbctl tsp-add ts0], [1], [], > > + [ovn-ic-nbctl: 'tsp-add' command requires at least 2 arguments > > +]) > > + > > +AT_CHECK([ovn-ic-nbctl tsp-add ts0 ts0-p0 chassis=chassis]) > > +AT_CHECK([ovn-ic-nbctl tsp-add ts0 ts0-p0], [1], [], > > + [ovn-ic-nbctl: ts0-p0: a port with this name already exists > > +]) > > + > > +AT_CHECK([ovn-ic-nbctl --may-exist tsp-add ts0 ts0-p0 chassis=chassis]) > > +AT_CHECK([ovn-ic-nbctl tsp-set-addr ts0-p0 "00:11:22:11:22:33 > > 192.168.10.10"]) > > +AT_CHECK([ovn-ic-nbctl tsp-set-addr ts0-p1 "00:11:22:11:22:34 > > 192.168.10.11"], [1], [], > > + [ovn-ic-nbctl: ts0-p1: switch port name not found > > +]) > > + > > +check_column "00:11:22:11:22:33 192.168.10.10" ic-nb:transit_switch_port > > addresses name=ts0-p0 > > +check_column "" ic-nb:transit_switch_port peer name=ts0-p0 > > +check_column "" ic-nb:transit_switch_port type name=ts0-p0 > > +check_column "chassis" ic-nb:transit_switch_port chassis name=ts0-p0 > > + > > +AT_CHECK([ovn-ic-nbctl tsp-set-addr ts0-p0 "aa:bb:cc:aa:bb:cc > > 192.168.11.11"]) > > +check_column "aa:bb:cc:aa:bb:cc 192.168.11.11" ic-nb:transit_switch_port > > addresses name=ts0-p0 > > +check_column "" ic-nb:transit_switch_port peer name=ts0-p0 > > +check_column "" ic-nb:transit_switch_port type name=ts0-p0 > > +check_column "chassis" ic-nb:transit_switch_port chassis name=ts0-p0 > > + > > +AT_CHECK([ovn-ic-nbctl tsp-set-addr ts0-p0]) > > +check_column "" ic-nb:transit_switch_port addresses name=ts0-p0 > > + > > +AT_CHECK([ovn-ic-nbctl tsp-del], [1], [], > > + [ovn-ic-nbctl: 'tsp-del' command requires at least 1 arguments > > +]) > > +AT_CHECK([ovn-ic-nbctl tsp-del ts0-p0]) > > +AT_CHECK([ovn-ic-nbctl tsp-del ts0-p0], [1], [], > > + [ovn-ic-nbctl: ts0-p0: switch port name not found > > +]) > > +AT_CHECK([ovn-ic-nbctl --if-exists trp-del tr0-p0]) > > + > > OVN_IC_NBCTL_TEST_STOP > > AT_CLEANUP > > > > diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at > > index 68d78d9e4..a9a1e83b7 100644 > > --- a/tests/ovn-ic.at > > +++ b/tests/ovn-ic.at > > @@ -177,6 +177,157 @@ OVN_CLEANUP_IC > > AT_CLEANUP > > ]) > > > > +OVN_FOR_EACH_NORTHD([ > > +AT_SETUP([ovn-ic -- transit port-bindings deletion upon TS deletion]) > > + > > +ovn_init_ic_db > > +net_add n1 > > + > > +# 1 GW per AZ > > +for i in 1 2; do > > + az=az$i > > + ovn_start $az > > + sim_add gw-$az > > + as gw-$az > > + check ovs-vsctl add-br br-phys > > + ovn_az_attach $az n1 br-phys 192.168.1.$i > > + check ovs-vsctl set open . external-ids:ovn-is-interconn=true > > +done > > + > > +ovn_as az1 > > + > > +# create transit switch and connect to LR > > +check ovn-ic-nbctl --wait=sb ts-add ts1 > > +check ovn-nbctl lr-add lr1 > > +check ovn-nbctl lrp-add lr1 lrp1 00:00:00:00:00:01 10.0.0.1/24 > > +check ovn-nbctl lrp-set-gateway-chassis lrp1 gw-az1 > > +check ovn-ic-nbctl tsp-add ts1 tsp1 type=router chassis=gw-az1 peer=lrp1 > > + > > +wait_row_count Datapath_Binding 1 external_ids:interconn-ts=ts1 > > + > > +# Sync ic-sb DB to see the TS changes. > > +check ovn-ic-nbctl --wait=sb sync > > +check_column "router" nb:Logical_Switch_Port type name=tsp1 > > +check_column "lrp1" nb:Logical_Switch_Port peer name=tsp1 > > +check_column "interconn-ts=tsp1 router-port=lrp1 requested-chassis=gw-az1 > > requested-tnl-key=1" nb:Logical_Switch_Port options name=tsp1 > > + > > +AT_CHECK([ovn-ic-sbctl show | grep -A2 tsp1], [0], [dnl > > + port tsp1 > > + transit switch: ts1 > > + address: [["00:00:00:00:00:01 10.0.0.1/24"]] > > +]) > > + > > +# remove transit switch and check if port_binding is deleted > > +check ovn-ic-nbctl --wait=sb ts-del ts1 > > +check_row_count ic-sb:Port_Binding 0 logical_port=tsp1 > > +for i in 1 2; do > > + az=az$i > > + ovn_as $az > > + OVN_CLEANUP_SBOX(gw-$az) > > + OVN_CLEANUP_AZ([$az]) > > +done > > +OVN_CLEANUP_IC > > +AT_CLEANUP > > +]) > > + > > +OVN_FOR_EACH_NORTHD([ > > +AT_SETUP([ovn-ic -- transit switch port type and address updates]) > > + > > +ovn_init_ic_db > > +net_add n1 > > + > > +# 1 GW per AZ > > +for i in 1 2; do > > + az=az$i > > + ovn_start $az > > + sim_add gw-$az > > + as gw-$az > > + check ovs-vsctl add-br br-phys > > + ovn_az_attach $az n1 br-phys 192.168.1.$i > > + check ovs-vsctl set open . external-ids:ovn-is-interconn=true > > +done > > + > > +ovn_as az1 > > + > > +# create transit switch and connect to LR > > +check ovn-ic-nbctl --wait=sb ts-add ts1 > > +check ovn-nbctl lr-add lr1 > > +check ovn-nbctl lrp-add lr1 lrp1 00:00:00:00:00:01 10.0.0.1/24 > > +check ovn-nbctl lrp-set-gateway-chassis lrp1 gw-az1 > > +check ovn-ic-nbctl tsp-add ts1 tsp1 type=router chassis=gw-az1 peer=lrp1 > > + > > +wait_row_count Datapath_Binding 1 external_ids:interconn-ts=ts1 > > + > > +# Sync ic-sb DB to see the TS changes. > > +check ovn-ic-nbctl --wait=sb sync > > + > > +AT_CHECK([ovn-ic-sbctl show | grep -A2 tsp1], [0], [dnl > > + port tsp1 > > + transit switch: ts1 > > + address: [["00:00:00:00:00:01 10.0.0.1/24"]] > > +]) > > + > > +# ICSB will ignore address updates as long as type=router. > > +check ovn-ic-nbctl tsp-set-addr tsp1 "00:11:22:11:22:33 192.168.10.10" > > +check ovn-ic-nbctl --wait=sb sync > > +AT_CHECK([ovn-ic-sbctl show | grep -A2 tsp1], [0], [dnl > > + port tsp1 > > + transit switch: ts1 > > + address: [["00:00:00:00:00:01 10.0.0.1/24"]] > > +]) > > +check_column "router" nb:Logical_Switch_Port type name=tsp1 > > +check_column "lrp1" nb:Logical_Switch_Port peer name=tsp1 > > +check_column "interconn-ts=tsp1 router-port=lrp1 requested-chassis=gw-az1 > > requested-tnl-key=1" nb:Logical_Switch_Port options name=tsp1 > > + > > +# Verify remote AZ sees the port as type "remote" > > +ovn_as az2 > > +wait_row_count nb:Logical_Switch_Port 1 name=tsp1 > > +check_column "remote" nb:Logical_Switch_Port type name=tsp1 > > +ovn_as az1 > > + > > +# Clear type and validate that address has been updated. > > +PORT_UUID=$(ovn-ic-nbctl --bare --columns=_uuid find Transit_Switch_Port > > name=tsp1) > > +check ovn-ic-nbctl set Transit_Switch_Port $PORT_UUID 'type=""' > > +check ovn-ic-nbctl --wait=sb sync > > +check_column "" nb:Logical_Switch_Port type name=tsp1 > > +check_column "interconn-ts=tsp1 requested-chassis=gw-az1 > > requested-tnl-key=1" nb:Logical_Switch_Port options name=tsp1 > > +AT_CHECK([ovn-ic-sbctl show | grep -A2 tsp1], [0], [dnl > > + port tsp1 > > + transit switch: ts1 > > + address: [["00:11:22:11:22:33 192.168.10.10"]] > > +]) > > + > > +# Remote AZ should still show type "remote" regardless of owner type > > change > > +ovn_as az2 > > +check_column "remote" nb:Logical_Switch_Port type name=tsp1 > > +ovn_as az1 > > + > > +# Reset type to router and check address. > > +PORT_UUID=$(ovn-ic-nbctl --bare --columns=_uuid find Transit_Switch_Port > > name=tsp1) > > +check ovn-ic-nbctl set Transit_Switch_Port $PORT_UUID 'type="router"' > > +check ovn-ic-nbctl --wait=sb sync > > +check_column "router" nb:Logical_Switch_Port type name=tsp1 > > +check_column "lrp1" nb:Logical_Switch_Port peer name=tsp1 > > +check_column "interconn-ts=tsp1 router-port=lrp1 requested-chassis=gw-az1 > > requested-tnl-key=1" nb:Logical_Switch_Port options name=tsp1 > > +AT_CHECK([ovn-ic-sbctl show | grep -A2 tsp1], [0], [dnl > > + port tsp1 > > + transit switch: ts1 > > + address: [["00:00:00:00:00:01 10.0.0.1/24"]] > > +]) > > + > > +# remove transit switch and check if port_binding is deleted > > +check ovn-ic-nbctl --wait=sb ts-del ts1 > > +check_row_count ic-sb:Port_Binding 0 logical_port=tsp1 > > +for i in 1 2; do > > + az=az$i > > + ovn_as $az > > + OVN_CLEANUP_SBOX(gw-$az) > > + OVN_CLEANUP_AZ([$az]) > > +done > > +OVN_CLEANUP_IC > > +AT_CLEANUP > > +]) > > + > > OVN_FOR_EACH_NORTHD([ > > AT_SETUP([ovn-ic -- route deletion upon TS deletion]) > > > > @@ -248,6 +399,170 @@ OVN_CLEANUP_IC > > AT_CLEANUP > > ]) > > > > +OVN_FOR_EACH_NORTHD([ > > +AT_SETUP([ovn-ic -- no-chassis transit switch port]) > > + > > +ovn_init_ic_db > > +net_add n1 > > + > > +for i in 1 2; do > > + az=az$i > > + ovn_start $az > > + sim_add gw-$az > > + as gw-$az > > + check ovs-vsctl add-br br-phys > > + ovn_az_attach $az n1 br-phys 192.168.1.$i > > + check ovs-vsctl set open . external-ids:ovn-is-interconn=true > > +done > > + > > +ovn_as az1 > > + > > +check ovn-ic-nbctl --wait=sb ts-add ts1 > > +check ovn-ic-nbctl tsp-add ts1 tsp1 > > +check ovn-ic-nbctl tsp-set-addr tsp1 "00:00:00:00:00:02 10.0.0.2" > > +check ovn-ic-nbctl --wait=sb sync > > + > > +# Leader AZ should have the NB LSP > > +wait_row_count nb:Logical_Switch_Port 1 name=tsp1 > > +check_column "" nb:Logical_Switch_Port type name=tsp1 > > + > > +# Non-leader AZ should also have it (found via index lookup) > > +ovn_as az2 > > +check ovn-ic-nbctl --wait=sb sync > > +wait_row_count nb:Logical_Switch_Port 1 name=tsp1 > > + > > +# Verify ISB port binding exists > > +check_row_count ic-sb:Port_Binding 1 logical_port=tsp1 > > + > > +# Cleanup > > +ovn_as az1 > > +check ovn-ic-nbctl --wait=sb ts-del ts1 > > +check_row_count ic-sb:Port_Binding 0 logical_port=tsp1 > > +for i in 1 2; do > > + az=az$i > > + ovn_as $az > > + OVN_CLEANUP_SBOX(gw-$az) > > + OVN_CLEANUP_AZ([$az]) > > +done > > +OVN_CLEANUP_IC > > +AT_CLEANUP > > +]) > > + > > +OVN_FOR_EACH_NORTHD([ > > +AT_SETUP([ovn-ic -- transit switch port chassis reassignment]) > > + > > +ovn_init_ic_db > > +net_add n1 > > + > > +for i in 1 2; do > > + az=az$i > > + ovn_start $az > > + sim_add gw-$az > > + as gw-$az > > + check ovs-vsctl add-br br-phys > > + ovn_az_attach $az n1 br-phys 192.168.1.$i > > + check ovs-vsctl set open . external-ids:ovn-is-interconn=true > > +done > > + > > +ovn_as az1 > > + > > +check ovn-ic-nbctl --wait=sb ts-add ts1 > > + > > +# Create a non-router TSP on az1 > > +check ovn-ic-nbctl tsp-add ts1 tsp1 chassis=gw-az1 > > +check ovn-ic-nbctl tsp-set-addr tsp1 "00:00:00:00:00:01 10.0.0.1" > > +check ovn-ic-nbctl --wait=sb sync > > + > > +# az1 owns it > > +wait_row_count nb:Logical_Switch_Port 1 name=tsp1 > > +check_column "" nb:Logical_Switch_Port type name=tsp1 > > + > > +# az2 sees it as remote > > +ovn_as az2 > > +wait_row_count nb:Logical_Switch_Port 1 name=tsp1 > > +check_column "remote" nb:Logical_Switch_Port type name=tsp1 > > + > > +# Reassign chassis to gw-az2 > > +ovn_as az1 > > +PORT_UUID=$(ovn-ic-nbctl --bare --columns=_uuid find Transit_Switch_Port > > name=tsp1) > > +check ovn-ic-nbctl set Transit_Switch_Port $PORT_UUID chassis=gw-az2 > > +check ovn-ic-nbctl --wait=sb sync > > + > > +# az1 now sees it as remote > > +wait_column "remote" nb:Logical_Switch_Port type name=tsp1 > > + > > +# az2 now owns it > > +ovn_as az2 > > +check ovn-ic-nbctl --wait=sb sync > > +wait_column "" nb:Logical_Switch_Port type name=tsp1 > > + > > +# Change type to router with peer on az2 > > +check ovn-nbctl lr-add lr1 > > +check ovn-nbctl lrp-add lr1 lrp1 00:00:00:00:00:02 10.0.0.2/24 > > +check ovn-nbctl lrp-set-gateway-chassis lrp1 gw-az2 > > +ovn_as az1 > > +check ovn-ic-nbctl set Transit_Switch_Port $PORT_UUID 'type=router' > > 'peer=lrp1' > > +check ovn-ic-nbctl --wait=sb sync > > + > > +# az2 owns it with type=router, address derived from LRP > > +ovn_as az2 > > +check ovn-ic-nbctl --wait=sb sync > > +wait_column "router" nb:Logical_Switch_Port type name=tsp1 > > +check_column "lrp1" nb:Logical_Switch_Port peer name=tsp1 > > +AT_CHECK([ovn-ic-sbctl show | grep -A2 tsp1], [0], [dnl > > + port tsp1 > > + transit switch: ts1 > > + address: [["00:00:00:00:00:02 10.0.0.2/24"]] > > +]) > > +# az1 still sees it as remote > > +ovn_as az1 > > +check_column "remote" nb:Logical_Switch_Port type name=tsp1 > > + > > +# Clear type back to "" while chassis stays gw-az2 > > +check ovn-ic-nbctl set Transit_Switch_Port $PORT_UUID 'type=""' 'peer=""' > > +check ovn-ic-nbctl --wait=sb sync > > + > > +# az2 owns it with empty type, address reverts to tsp-set-addr value > > +ovn_as az2 > > +check ovn-ic-nbctl --wait=sb sync > > +wait_column "" nb:Logical_Switch_Port type name=tsp1 > > + > > +AT_CHECK([ovn-ic-sbctl show | grep -A2 tsp1], [0], [dnl > > + port tsp1 > > + transit switch: ts1 > > + address: [["00:00:00:00:00:01 10.0.0.1"]] > > +]) > > + > > +# az1 still sees it as remote > > +ovn_as az1 > > +check_column "remote" nb:Logical_Switch_Port type name=tsp1 > > + > > +# Reassign back to gw-az1 > > +check ovn-ic-nbctl set Transit_Switch_Port $PORT_UUID chassis=gw-az1 > > +check ovn-ic-nbctl --wait=sb sync > > + > > +# az1 now owns it > > +wait_column "" nb:Logical_Switch_Port type name=tsp1 > > + > > +# az2 sees it as remote > > +ovn_as az2 > > +check ovn-ic-nbctl --wait=sb sync > > +wait_column "remote" nb:Logical_Switch_Port type name=tsp1 > > + > > +# Cleanup > > +ovn_as az1 > > +check ovn-ic-nbctl --wait=sb ts-del ts1 > > +check_row_count ic-sb:Port_Binding 0 logical_port=tsp1 > > +for i in 1 2; do > > + az=az$i > > + ovn_as $az > > + OVN_CLEANUP_SBOX(gw-$az) > > + OVN_CLEANUP_AZ([$az]) > > +done > > +OVN_CLEANUP_IC > > +AT_CLEANUP > > +]) > > + > > OVN_FOR_EACH_NORTHD([ > > AT_SETUP([ovn-ic -- duplicate NB route adv/learn]) > > > > diff --git a/utilities/ovn-ic-nbctl.8.xml b/utilities/ovn-ic-nbctl.8.xml > > index 633863294..308debd8e 100644 > > --- a/utilities/ovn-ic-nbctl.8.xml > > +++ b/utilities/ovn-ic-nbctl.8.xml > > @@ -50,6 +50,62 @@ > > <dd> > > Lists all existing switches on standard output, one per line. > > </dd> > > + > > + <dt>[<code>--may-exist</code>] <code>tsp-add</code> > > <var>switch</var> > > + <var>port</var> [<var>column</var>[<code>:</code><var>key</var>] > > + <code>=</code><var>value</var>]...</dt> > > + <dd> > > + <p> > > + Creates a new transit switch port named <var>port</var> > > + on <var>switch</var>. > > + </p> > > + > > + <p> > > + Transit switch port names must be unique. Adding a duplicated > > name > > + results in an error. With <code>--may-exist</code>, adding a > > + duplicate name succeeds but does not create a new transit switch > > + port. > > + </p> > > + </dd> > > + > > + <dt>[<code>--if-exists</code>] <code>tsp-del</code> > > <var>port</var></dt> > > + <dd> > > + Deletes <var>port</var>. It is an error if <var>port</var> does > > + not exist, unless <code>--if-exists</code> is specified. > > + </dd> > > + > > + <dt><code>tsp-set-addr</code> <var>port</var> > > + [<var>address</var>]...</dt> > > + <dd> > > + <p> > > + Sets the addresses associated with <var>port</var> to > > + <var>address</var>. Each <var>address</var> should be one of > > the > > + following: > > + </p> > > + > > + <dl> > > + <dt>an Ethernet address, optionally followed by a space and one > > or > > + more IP addresses</dt> > > + <dd> > > + OVN delivers packets for the Ethernet address to this port. > > + </dd> > > + > > + <dt><code>router</code></dt> > > + <dd> > > + Accepted only when the <code>type</code> of the transit switch > > + port is <code>router</code>. This indicates that the > > Ethernet, > > + IPv4, and IPv6 addresses for this transit switch port should > > be > > + obtained from the connected logical router port, as specified > > by > > + peer <ref column="peer" > > + table="Transit_Switch_Port" db="OVN_IC_Northbound"/> column. > > + </dd> > > + </dl> > > + > > + <p> > > + Multiple addresses may be set. If no <var>address</var> > > argument is > > + given, <var>port</var> will have no addresses associated with > > it. > > + </p> > > + </dd> > > </dl> > > > > <h1>Database Commands</h1> > > diff --git a/utilities/ovn-ic-nbctl.c b/utilities/ovn-ic-nbctl.c > > index 50e975283..4bf922212 100644 > > --- a/utilities/ovn-ic-nbctl.c > > +++ b/utilities/ovn-ic-nbctl.c > > @@ -336,6 +336,11 @@ Transit switch commands:\n\ > > ts-add SWITCH create a transit switch named SWITCH\n\ > > ts-del SWITCH delete SWITCH\n\ > > ts-list print all transit switches\n\ > > + tsp-add SWITCH PORT [COLUMN[:KEY]=VALUE]...\n\ > > + add a transit switch PORT\n\ > > + tsp-set-addr PORT ADDRESS...\n\ > > + set a transit switch PORT address\n\ > > + tsp-del PORT delete a transit switch PORT\n\ > > \n\ > > Transit router commands:\n\ > > tr-add ROUTER create a transit router named ROUTER\n\ > > @@ -400,6 +405,7 @@ struct ic_nbctl_context { > > * ic_nbctl_context_invalidate_cache() or manually update the cache to > > * maintain its correctness. */ > > bool cache_valid; > > + struct shash tsp_to_ts_map; > > }; > > > > static struct cmd_show_table cmd_show_tables[] = { > > @@ -617,6 +623,38 @@ trp_by_name_or_uuid(struct ctl_context *ctx, const > > char *id, bool must_exist, > > return NULL; > > } > > > > +static char * > > +tsp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool > > must_exist, > > + const struct icnbrec_transit_switch_port **tsp_p) > > +{ > > + const struct icnbrec_transit_switch_port *tsp = NULL; > > + *tsp_p = NULL; > > + struct uuid tsp_uuid; > > + bool is_uuid = uuid_from_string(&tsp_uuid, id); > > + if (is_uuid) { > > + tsp = icnbrec_transit_switch_port_get_for_uuid(ctx->idl, > > &tsp_uuid); > > + } > > + > > + if (!tsp) { > > + const struct icnbrec_transit_switch_port *iter; > > + > > + ICNBREC_TRANSIT_SWITCH_PORT_FOR_EACH (iter, ctx->idl) { > > + if (!strcmp(iter->name, id)) { > > + tsp = iter; > > + break; > > + } > > + } > > + } > > + > > + if (!tsp && must_exist) { > > + return xasprintf("%s: switch port %s not found", id, > > + is_uuid ? "UUID" : "name"); > > + } > > + > > + *tsp_p = tsp; > > + return NULL; > > +} > > + > > static void > > ic_nbctl_tr_del(struct ctl_context *ctx) > > { > > @@ -664,6 +702,74 @@ ic_nbctl_trp_del(struct ctl_context *ctx) > > icnbrec_transit_router_port_delete(trp); > > } > > > > +static struct ic_nbctl_context * > > +ic_nbctl_context_get(struct ctl_context *base) > > +{ > > + struct ic_nbctl_context *icnbctx > > + = CONTAINER_OF(base, struct ic_nbctl_context, base); > > + if (icnbctx->cache_valid) { > > + return icnbctx; > > + } > > + > > + const struct icnbrec_transit_switch *ts; > > + ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, base->idl) { > > + for (size_t i = 0; i < ts->n_ports; i++) { > > + shash_add_once(&icnbctx->tsp_to_ts_map, ts->ports[i]->name, > > ts); > > + } > > + } > > + > > + icnbctx->cache_valid = true; > > + return icnbctx; > > +} > > + > > +/* Returns the transit switch that contains 'tsp'. */ > > +static char * OVS_WARN_UNUSED_RESULT > > +tsp_to_ts(struct ctl_context *ctx, > > + const struct icnbrec_transit_switch_port *tsp, > > + const struct icnbrec_transit_switch **ts_p) > > +{ > > + struct ic_nbctl_context *icnbctx = ic_nbctl_context_get(ctx); > > + const struct icnbrec_transit_switch *ts; > > + *ts_p = NULL; > > + > > + ts = shash_find_data(&icnbctx->tsp_to_ts_map, tsp->name); > > + if (ts) { > > + *ts_p = ts; > > + return NULL; > > + } > > + /* Can't happen because of the database schema */ > > + return xasprintf("transit port %s is not part of any transit switch", > > + tsp->name); > > +} > > + > > +static void > > +ic_nbctl_tsp_del(struct ctl_context *ctx) > > +{ > > + bool must_exist = !shash_find(&ctx->options, "--if-exists"); > > + const char *tsp_name = ctx->argv[1]; > > + const struct icnbrec_transit_switch_port *tsp = NULL; > > + > > + char *error = tsp_by_name_or_uuid(ctx, tsp_name, must_exist, &tsp); > > + if (error) { > > + ctx->error = error; > > + return; > > + } > > + > > + if (!tsp) { > > + return; > > + } > > + > > + const struct icnbrec_transit_switch *ts = NULL; > > + error = tsp_to_ts(ctx, tsp, &ts); > > + if (error) { > > + ctx->error = error; > > + return; > > + } > > + > > + icnbrec_transit_switch_update_ports_delvalue(ts, tsp); > > + icnbrec_transit_switch_port_delete(tsp); > > +} > > + > > static void > > ic_nbctl_tr_list(struct ctl_context *ctx) > > { > > @@ -802,6 +908,139 @@ ic_nbctl_trp_add(struct ctl_context *ctx) > > icnbrec_transit_router_update_ports_addvalue(tr, trp); > > } > > > > +static void > > +ic_nbctl_tsp_add(struct ctl_context *ctx) > > +{ > > + bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; > > + const char *ts_name = ctx->argv[1]; > > + const char *tsp_name = ctx->argv[2]; > > + const struct icnbrec_transit_switch *ts; > > + > > + ctx->error = ts_by_name_or_uuid(ctx, ts_name, true, &ts); > > + if (ctx->error) { > > + return; > > + } > > + > > + const struct icnbrec_transit_switch_port *tsp; > > + ctx->error = tsp_by_name_or_uuid(ctx, tsp_name, false, &tsp); > > + if (ctx->error) { > > + return; > > + } > > + > > + if (tsp) { > > + if (!may_exist) { > > + ctl_error(ctx, "%s: a port with this name already exists", > > + tsp_name); > > + return; > > + } > > + } else { > > + tsp = icnbrec_transit_switch_port_insert(ctx->txn); > > + icnbrec_transit_switch_port_set_name(tsp, tsp_name); > > + } > > + > > + int n_settings = ctx->argc - 3; > > + char **settings = (char **) &ctx->argv[3]; > > + for (size_t i = 0; i < n_settings; i++) { > > + ctx->error = ctl_set_column("Transit_Switch_Port", &tsp->header_, > > + settings[i], ctx->symtab); > > + if (ctx->error) { > > + return; > > + } > > + } > > + > > + icnbrec_transit_switch_update_ports_addvalue(ts, tsp); > > +} > > + > > +static char * > > +tsp_contains_duplicates(const struct icnbrec_transit_switch *ts, > > + const struct icnbrec_transit_switch_port *tsp, > > + const char *address) > > +{ > > + char *sub_error = NULL; > > + struct lport_addresses laddrs; > > + if (!extract_lsp_addresses(address, &laddrs)) { > > + return NULL; > > + } > > + > > + for (size_t i = 0; i < ts->n_ports; i++) { > > + struct icnbrec_transit_switch_port *tsp_test = ts->ports[i]; > > + if (tsp_test == tsp) { > > + continue; > > + } > > + > > + for (size_t j = 0; j < tsp_test->n_addresses; j++) { > > + struct lport_addresses laddrs_test; > > + char *addr = tsp_test->addresses[j]; > > + if (extract_lsp_addresses(addr, &laddrs_test)) { > > + bool has_duplicate = > > + port_contains_duplicate_ip(&laddrs, &laddrs_test, > > + tsp_test->name, &sub_error); > > + destroy_lport_addresses(&laddrs_test); > > + if (has_duplicate) { > > + goto err_out; > > + } > > + } > > + } > > + } > > + > > +err_out: ; > > + char *error = NULL; > > + if (sub_error) { > > + error = xasprintf("Error on switch %s: %s", ts->name, sub_error); > > + free(sub_error); > > + } > > + destroy_lport_addresses(&laddrs); > > + return error; > > +} > > + > > +static void > > +ic_nbctl_tsp_set_addr(struct ctl_context *ctx) > > +{ > > + const char *tsp_name = ctx->argv[1]; > > + > > + const struct icnbrec_transit_switch_port *tsp; > > + ctx->error = tsp_by_name_or_uuid(ctx, tsp_name, true, &tsp); > > + if (ctx->error) { > > + return; > > + } > > + > > + const struct icnbrec_transit_switch *ts = NULL; > > + char *error = tsp_to_ts(ctx, tsp, &ts); > > + if (error) { > > + ctx->error = error; > > + return; > > + } > > + > > + for (size_t i = 2; i < ctx->argc; i++) { > > + char ipv6_s[IPV6_SCAN_LEN + 1]; > > + struct eth_addr ea; > > + ovs_be32 ip; > > + > > + if (strcmp(ctx->argv[i], "unknown") && strcmp(ctx->argv[i], > > "dynamic") > > + && strcmp(ctx->argv[i], "router") > > + && !ovs_scan(ctx->argv[i], ETH_ADDR_SCAN_FMT, > > + ETH_ADDR_SCAN_ARGS(ea)) > > + && !ovs_scan(ctx->argv[i], "dynamic "IPV6_SCAN_FMT, ipv6_s) > > + && !ovs_scan(ctx->argv[i], "dynamic "IP_SCAN_FMT, > > + IP_SCAN_ARGS(&ip))) { > > + ctl_error(ctx, "%s: Invalid address format. See ovn-nb(5). " > > + "Hint: An Ethernet address must be " > > + "listed before an IP address, together as a single " > > + "argument.", ctx->argv[i]); > > + return; > > + } > > + > > + ctx->error = tsp_contains_duplicates(ts, tsp, ctx->argv[i]); > > + if (ctx->error) { > > + return; > > + } > > + } > > + > > + icnbrec_transit_switch_port_set_addresses(tsp, > > + (const char **) ctx->argv + > > 2, > > + ctx->argc - 2); > > +} > > + > > static void > > verify_connections(struct ctl_context *ctx) > > { > > @@ -1036,6 +1275,7 @@ ic_nbctl_context_init(struct ic_nbctl_context > > *ic_nbctl_ctx, > > ctl_context_init(&ic_nbctl_ctx->base, command, idl, txn, symtab, > > NULL); > > ic_nbctl_ctx->cache_valid = false; > > + shash_init(&ic_nbctl_ctx->tsp_to_ts_map); > > } > > > > static void > > @@ -1077,6 +1317,7 @@ ic_nbctl_context_done(struct ic_nbctl_context > > *ic_nbctl_ctx, > > struct ctl_command *command) > > { > > ctl_context_done(&ic_nbctl_ctx->base, command); > > + shash_destroy(&ic_nbctl_ctx->tsp_to_ts_map); > > } > > > > static void > > @@ -1317,7 +1558,12 @@ static const struct ctl_command_syntax > > ic_nbctl_commands[] = { > > { "ts-add", 1, 1, "SWITCH", NULL, ic_nbctl_ts_add, NULL, > > "--may-exist", RW }, > > { "ts-del", 1, 1, "SWITCH", NULL, ic_nbctl_ts_del, NULL, > > "--if-exists", RW }, > > { "ts-list", 0, 0, "", NULL, ic_nbctl_ts_list, NULL, "", RO }, > > - > > + { "tsp-add", 2, INT_MAX, "SWITCH PORT [COLUMN[:KEY]=VALUE]...", > > + NULL, ic_nbctl_tsp_add, NULL, "--may-exist", RW }, > > + { "tsp-set-addr", 1, INT_MAX, "PORT ADDRESS...", > > + NULL, ic_nbctl_tsp_set_addr, NULL, "", RW }, > > + { "tsp-del", 1, 1, "PORT", NULL, ic_nbctl_tsp_del, NULL, > > "--if-exists", > > + RW }, > > /* transit router commands. */ > > { "tr-add", 1, 1, "ROUTER", NULL, ic_nbctl_tr_add, NULL, > > "--may-exist", > > RW }, > > diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c > > index fe71b06f3..fc0a60202 100644 > > --- a/utilities/ovn-nbctl.c > > +++ b/utilities/ovn-nbctl.c > > @@ -1514,50 +1514,6 @@ nbctl_pre_lsp_set_addresses(struct ctl_context *ctx) > > > > &nbrec_logical_switch_port_col_dynamic_addresses); > > } > > > > -static bool > > -lsp_contains_duplicate_ip(struct lport_addresses *laddrs1, > > - struct lport_addresses *laddrs2, > > - const struct nbrec_logical_switch_port > > *lsp_test, > > - char **error_str) > > -{ > > - for (size_t i = 0; i < laddrs1->n_ipv4_addrs; i++) { > > - for (size_t j = 0; j < laddrs2->n_ipv4_addrs; j++) { > > - if (laddrs1->ipv4_addrs[i].addr == > > laddrs2->ipv4_addrs[j].addr) { > > - if (error_str) { > > - *error_str = xasprintf("duplicate IPv4 address '%s' " > > - "found on logical switch " > > - "port '%s'", > > - laddrs1->ipv4_addrs[i].addr_s, > > - lsp_test->name); > > - } > > - return true; > > - } > > - } > > - } > > - > > - for (size_t i = 0; i < laddrs1->n_ipv6_addrs; i++) { > > - for (size_t j = 0; j < laddrs2->n_ipv6_addrs; j++) { > > - if (IN6_ARE_ADDR_EQUAL(&laddrs1->ipv6_addrs[i].addr, > > - &laddrs2->ipv6_addrs[j].addr)) { > > - if (error_str) { > > - *error_str = xasprintf("duplicate IPv6 address " > > - "'%s' found on logical " > > - "switch port '%s'", > > - laddrs1->ipv6_addrs[i].addr_s, > > - lsp_test->name); > > - } > > - return true; > > - } > > - } > > - } > > - > > - if (error_str) { > > - *error_str = NULL; > > - } > > - > > - return false; > > -} > > - > > static char * > > lsp_contains_duplicates(const struct nbrec_logical_switch *ls, > > const struct nbrec_logical_switch_port *lsp, > > @@ -1582,8 +1538,8 @@ lsp_contains_duplicates(const struct > > nbrec_logical_switch *ls, > > } > > if (extract_lsp_addresses(addr, &laddrs_test)) { > > bool has_duplicate = > > - lsp_contains_duplicate_ip(&laddrs, &laddrs_test, > > - lsp_test, &sub_error); > > + port_contains_duplicate_ip(&laddrs, &laddrs_test, > > + lsp_test->name, &sub_error); > > destroy_lport_addresses(&laddrs_test); > > if (has_duplicate) { > > goto err_out; > > @@ -8885,8 +8841,8 @@ lsp_health_check_parse_target_address( > > goto cleanup; > > } > > > > - if (lsp_contains_duplicate_ip(&target_address, > > - &lsp_address, lsp, NULL)) { > > + if (port_contains_duplicate_ip(&target_address, > > + &lsp_address, lsp->name, NULL)) { > > ip_found_on_port = true; > > } > > > > -- > > 2.54.0 > > > > > The testing additions look good. > > Regards, > Ales
Thanks for the review Ales, comments above. Regards, Mairtin _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
