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.

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

Signed-off-by: Mairtin O'Loingsigh <[email protected]>
---
 ic/ovn-ic.c              | 223 +++++++++++++++++++++++++++++++++++++--
 lib/ovn-util.c           |  44 ++++++++
 lib/ovn-util.h           |   4 +
 utilities/ovn-ic-nbctl.c | 201 ++++++++++++++++++++++++++++++++++-
 utilities/ovn-nbctl.c    |  52 +--------
 5 files changed, 468 insertions(+), 56 deletions(-)

diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c
index ba9490658..f0312e71d 100644
--- a/ic/ovn-ic.c
+++ b/ic/ovn-ic.c
@@ -631,6 +631,33 @@ find_tr_in_nb(struct ic_context *ctx, char *tr_name)
     return NULL;
 }
 
+static const struct nbrec_logical_router *
+find_router_by_port(struct ic_context *ctx, const char *port_name)
+{
+    const struct nbrec_logical_router_port *key =
+        nbrec_logical_router_port_index_init_row(ctx->nbrec_lrp_by_name);
+    nbrec_logical_router_port_index_set_name(key, port_name);
+
+    const struct nbrec_logical_router *lr;
+    const struct nbrec_logical_router_port *lrp;
+    bool found = false;
+    NBREC_LOGICAL_ROUTER_FOR_EACH (lr, ctx->ovnnb_idl) {
+        for (size_t i = 0; i < lr->n_ports; i++) {
+            lrp = lr->ports[i];
+            if (!strcmp(lrp->name, port_name)) {
+                found = true;
+                break;
+            }
+        }
+    }
+
+    nbrec_logical_router_port_index_destroy_row(key);
+    if (found) {
+        return lr;
+    }
+    return NULL;
+}
+
 static const struct sbrec_port_binding *
 find_sb_pb_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                    const char *name)
@@ -733,6 +760,29 @@ get_lp_address_for_sb_pb(struct ic_context *ctx,
     return peer->n_mac ? *peer->mac : NULL;
 }
 
+static const char *
+get_lp_address_for_tr_pb(struct ic_context *ctx,
+                         struct icnbrec_transit_switch_port *tsp)
+{
+    const struct nbrec_logical_switch_port *nb_lsp;
+
+    nb_lsp = get_lsp_by_ts_port_name(ctx, tsp->name);
+    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";
+    }
+
+    if (!tsp->option) {
+        return NULL;
+    }
+
+    const struct sbrec_port_binding *peer =
+        find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, tsp->option);
+
+    return peer->n_mac ? *peer->mac : NULL;
+}
+
 static const struct sbrec_chassis *
 find_sb_chassis(struct ic_context *ctx, const char *name)
 {
@@ -793,8 +843,8 @@ get_router_uuid_by_sb_pb(struct ic_context *ctx,
 }
 
 static void
-update_isb_pb_external_ids(struct ic_context *ctx,
-                           const struct sbrec_port_binding *sb_pb,
+update_isb_pb_external_ids(struct ic_context *ctx OVS_UNUSED,
+                           const struct sbrec_port_binding *sb_pb OVS_UNUSED,
                            const struct icsbrec_port_binding *isb_pb)
 {
     struct uuid lr_uuid;
@@ -864,6 +914,52 @@ sync_local_port(struct ic_context *ctx,
     /* Sync back tunnel key from ISB to NB */
     sync_lsp_tnl_key(lsp, isb_pb->tunnel_key);
 }
+/* For each local port:
+ *   - Sync from ISB to NB.
+ *   - Sync from ISB to SB.
+ */
+static void
+sync_switch_port(struct ic_context *ctx OVS_UNUSED,
+                 struct icnbrec_transit_switch_port *tsp,
+                 const struct icsbrec_port_binding *isb_pb,
+                 const struct nbrec_logical_switch_port *lsp,
+                 const struct sbrec_port_binding *sb_pb)
+{
+    const char *address = get_lp_address_for_tr_pb(ctx, tsp);
+    if (!address) {
+        VLOG_DBG("Can't get router/switch port address for logical"
+                 " switch port %s", lsp->name);
+        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(tsp->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);
+}
 
 /* For each remote port:
  *   - Sync from ISB to NB
@@ -991,6 +1087,21 @@ allocate_port_key(struct hmap *pb_tnlids)
                               1, (1u << 15) - 1, &hint);
 }
 
+static void
+set_isb_pb_peer(struct ic_context *ctx,
+                const struct icnbrec_transit_switch_port *tsp,
+                const struct icsbrec_port_binding *isb_pb)
+{
+    const struct nbrec_logical_router *lr;
+    lr = find_router_by_port(ctx, tsp->option);
+    if (lr) {
+        char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&lr->header_.uuid));
+        icsbrec_port_binding_update_external_ids_setkey(isb_pb, "router-id",
+                                                        uuid_s);
+        free(uuid_s);
+    }
+}
+
 static const struct icsbrec_port_binding *
 create_isb_pb(struct ic_context *ctx, const char *logical_port,
               const struct icsbrec_availability_zone *az, const char *ts_name,
@@ -1010,6 +1121,7 @@ create_isb_pb(struct ic_context *ctx, const char 
*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;
 }
 
@@ -1028,7 +1140,7 @@ get_lrp_by_lrp_name(struct ic_context *ctx, const char 
*lrp_name)
 }
 
 static bool
-trp_is_remote(struct ic_context *ctx, const char *chassis_name)
+chassis_is_remote(struct ic_context *ctx, const char *chassis_name)
 {
     if (chassis_name) {
         const struct sbrec_chassis *chassis =
@@ -1057,11 +1169,41 @@ lrp_create(struct ic_context *ctx, const struct 
nbrec_logical_router *lr,
     return lrp;
 }
 
+static struct nbrec_logical_switch_port *
+lsp_create(struct ic_context *ctx, const struct nbrec_logical_switch *lr,
+           const struct icsbrec_port_binding *isb_pb,
+           const struct icnbrec_transit_switch_port *tsp)
+{
+    bool router_port = !strcmp(tsp->type, "router");
+    const char *type = router_port ? "router" : "localnet";
+    const char *option_name = router_port ? "router-port" : "network_name";
+    struct smap options = SMAP_CONST1(&options, option_name, tsp->option);
+
+    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-ts",
+                                                    tsp->name);
+    nbrec_logical_switch_port_set_type(lsp, type);
+
+    nbrec_logical_switch_port_set_options(lsp, &options);
+
+    nbrec_logical_switch_update_ports_addvalue(lr, lsp);
+
+    char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&lr->header_.uuid));
+    icsbrec_port_binding_update_external_ids_setkey(isb_pb, "router-id",
+                                                    uuid_s);
+    free(uuid_s);
+    return lsp;
+}
+
 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);
     }
@@ -1118,7 +1260,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 +1267,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_def(&lsp->options, "interconn-ts", NULL)) {
+                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 +1297,59 @@ port_binding_run(struct ic_context *ctx)
         }
         icsbrec_port_binding_index_destroy_row(isb_pb_key);
 
+        for (int i = 0; i < ts->n_ports; i++) {
+            struct icnbrec_transit_switch_port *tsp = ts->ports[i];
+            if (!chassis_is_remote(ctx, tsp->chassis)) {
+                isb_pb = shash_find_and_delete(&local_pbs, tsp->name);
+                if (!isb_pb) {
+                    isb_pb = create_isb_pb(ctx, tsp->name, ctx->runned_az,
+                                           ts->name, &ts->header_.uuid,
+                                           "transit-switch-port", &pb_tnlids);
+                    set_isb_pb_peer(ctx, tsp, isb_pb);
+                }
+
+                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);
+                }
+                const struct nbrec_logical_switch_port *lsp =
+                    shash_find_and_delete(&nb_ports, tsp->name);
+                if (!lsp) {
+                    lsp = lsp_create(ctx, ls, isb_pb, tsp);
+                }
+
+                const struct sbrec_port_binding *sb_pb;
+                sb_pb = find_lsp_in_sb(ctx, lsp);
+                if (sb_pb) {
+                    sync_switch_port(ctx, tsp, isb_pb, lsp, sb_pb);
+                }
+            } else {
+                /* Remote port sync */
+                isb_pb = shash_find_and_delete(&remote_pbs, tsp->name);
+                if (isb_pb) {
+                    const struct nbrec_logical_switch_port *lsp =
+                        shash_find_and_delete(&nb_ports, tsp->name);
+                    if (!lsp) {
+                        lsp = lsp_create(ctx, ls, isb_pb, tsp);
+                    }
+                    const struct sbrec_port_binding *sb_pb;
+                    sb_pb = find_lsp_in_sb(ctx, lsp);
+                    if (sb_pb) {
+                        sync_remote_port(ctx, isb_pb, lsp, sb_pb);
+                    }
+                }
+            }
+        }
+        /* 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) {
+            lsp = node->data;
 
             if (!strcmp(lsp->type, "router")
                 || !strcmp(lsp->type, "switch")) {
@@ -1193,6 +1395,11 @@ port_binding_run(struct ic_context *ctx)
             }
         }
 
+        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 +1410,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) {
@@ -1250,7 +1459,7 @@ port_binding_run(struct ic_context *ctx)
         for (size_t i = 0; i < tr->n_ports; i++) {
             const struct icnbrec_transit_router_port *trp = tr->ports[i];
 
-            if (trp_is_remote(ctx, trp->chassis)) {
+            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);
diff --git a/lib/ovn-util.c b/lib/ovn-util.c
index fb02825ac..d41ab366f 100644
--- a/lib/ovn-util.c
+++ b/lib/ovn-util.c
@@ -1869,3 +1869,47 @@ eth_addr_parse_masked(const char *s, struct eth_addr 
*ea, unsigned int *plen)
     *ea = eth_addr_zero;
     return false;
 }
+
+bool
+sp_contains_duplicate_ip(struct lport_addresses *laddrs1,
+                          struct lport_addresses *laddrs2,
+                          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 bcb344de4..df78c7342 100644
--- a/lib/ovn-util.h
+++ b/lib/ovn-util.h
@@ -790,6 +790,10 @@ char *normalize_ipv6_addr_str(const char *orig_addr);
 
 char *normalize_addr_str(const char *orig_addr);
 
+bool sp_contains_duplicate_ip(struct lport_addresses *laddrs1,
+                              struct lport_addresses *laddrs2, char *name,
+                              char **error_str);
+
 #define NEIGH_REDISTRIBUTE_MODES    \
     NEIGH_REDISTRIBUTE_MODE(FDB, 0) \
     NEIGH_REDISTRIBUTE_MODE(IP, 1)
diff --git a/utilities/ovn-ic-nbctl.c b/utilities/ovn-ic-nbctl.c
index 50e975283..1ba11ddf9 100644
--- a/utilities/ovn-ic-nbctl.c
+++ b/utilities/ovn-ic-nbctl.c
@@ -336,6 +336,9 @@ 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        add a transit switch PORT\n\
+  tsp-set-addr PORT NETWORK  add a transit switch PORT\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\
@@ -617,6 +620,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 +699,34 @@ ic_nbctl_trp_del(struct ctl_context *ctx)
     icnbrec_transit_router_port_delete(trp);
 }
 
+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;
+
+    ctx->error = tsp_by_name_or_uuid(ctx, tsp_name, must_exist, &tsp);
+    if (ctx->error) {
+        return;
+    }
+
+    if (!tsp) {
+        return;
+    }
+
+    const struct icnbrec_transit_switch *ts = NULL;
+    char *ts_uuid = uuid_to_string(&tsp->ts_uuid);
+    ctx->error = ts_by_name_or_uuid(ctx, ts_uuid, true, &ts);
+    free(ts_uuid);
+    if (ctx->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 +865,137 @@ 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;
+        }
+        return;
+    }
+
+    tsp = icnbrec_transit_switch_port_insert(ctx->txn);
+    icnbrec_transit_switch_port_set_name(tsp, tsp_name);
+    icnbrec_transit_switch_port_set_ts_uuid(tsp, ts->header_.uuid);
+
+    int n_settings = ctx->argc - 3;
+    char **settings = (char **) &ctx->argv[3];
+    for (int 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 =
+                    sp_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;
+    }
+
+    int i;
+    for (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;
+        }
+
+        const struct icnbrec_transit_switch *ts;
+        ts = icnbrec_transit_switch_get_for_uuid(ctx->idl, &tsp->ts_uuid);
+        ctx->error = tsp_contains_duplicates(ts, tsp, ctx->argv[i]);
+        if (ctx->error) {
+            ctl_error(ctx, "%s", 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)
 {
@@ -1317,7 +1511,13 @@ 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", 2, 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 },
@@ -1329,7 +1529,6 @@ static const struct ctl_command_syntax 
ic_nbctl_commands[] = {
         NULL, ic_nbctl_trp_add, NULL, "--may-exist", RW },
     { "trp-del", 1, 1, "PORT", NULL, ic_nbctl_trp_del, NULL, "--if-exists",
         RW },
-
     /* Connection commands. */
     {"get-connection", 0, 0, "", pre_connection, cmd_get_connection, NULL, "",
         RO},
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index 0ef207272..56cfdf5b4 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -1482,50 +1482,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,
@@ -1550,8 +1506,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);
+                    sp_contains_duplicate_ip(&laddrs, &laddrs_test,
+                                             lsp_test->name, &sub_error);
                 destroy_lport_addresses(&laddrs_test);
                 if (has_duplicate) {
                     goto err_out;
@@ -8835,8 +8791,8 @@ lsp_health_check_parse_target_address(
             goto cleanup;
         }
 
-        if (lsp_contains_duplicate_ip(&target_address,
-                                      &lsp_address, lsp, NULL)) {
+        if (sp_contains_duplicate_ip(&target_address,
+                                      &lsp_address, lsp->name, NULL)) {
             ip_found_on_port = true;
         }
 
-- 
2.52.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to