Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2237920/?format=api
{ "id": 2237920, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2237920/?format=api", "web_url": "http://patchwork.ozlabs.org/project/ovn/patch/20260513154433.439593-1-moloings@redhat.com/", "project": { "id": 68, "url": "http://patchwork.ozlabs.org/api/1.1/projects/68/?format=api", "name": "Open Virtual Network development", "link_name": "ovn", "list_id": "ovs-dev.openvswitch.org", "list_email": "ovs-dev@openvswitch.org", "web_url": "http://openvswitch.org/", "scm_url": "", "webscm_url": "" }, "msgid": "<20260513154433.439593-1-moloings@redhat.com>", "date": "2026-05-13T15:44:33", "name": "[ovs-dev,v3,1/1] ic: Add transit switch port and schema.", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "0cfec52c7988ce285f85ec9b56a4cbf5056f0d49", "submitter": { "id": 91032, "url": "http://patchwork.ozlabs.org/api/1.1/people/91032/?format=api", "name": "Mairtin O'Loingsigh", "email": "moloings@redhat.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/ovn/patch/20260513154433.439593-1-moloings@redhat.com/mbox/", "series": [ { "id": 504181, "url": "http://patchwork.ozlabs.org/api/1.1/series/504181/?format=api", "web_url": "http://patchwork.ozlabs.org/project/ovn/list/?series=504181", "date": "2026-05-13T15:44:33", "name": "[ovs-dev,v3,1/1] ic: Add transit switch port and schema.", "version": 3, "mbox": "http://patchwork.ozlabs.org/series/504181/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2237920/comments/", "check": "warning", "checks": "http://patchwork.ozlabs.org/api/patches/2237920/checks/", "tags": {}, "headers": { "Return-Path": "<ovs-dev-bounces@openvswitch.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "dev@openvswitch.org" ], "Delivered-To": [ "patchwork-incoming@legolas.ozlabs.org", "ovs-dev@lists.linuxfoundation.org" ], "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=JIpLUI3u;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=140.211.166.136; helo=smtp3.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)", "smtp3.osuosl.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key)\n header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=JIpLUI3u", "smtp4.osuosl.org; dmarc=pass (p=quarantine dis=none)\n header.from=redhat.com", "smtp4.osuosl.org;\n dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com\n header.a=rsa-sha256 header.s=mimecast20190719 header.b=JIpLUI3u" ], "Received": [ "from smtp3.osuosl.org (smtp3.osuosl.org [140.211.166.136])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4gFyT93tD6z1yKH\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 14 May 2026 01:45:01 +1000 (AEST)", "from localhost (localhost [127.0.0.1])\n\tby smtp3.osuosl.org (Postfix) with ESMTP id 4C5E261259;\n\tWed, 13 May 2026 15:44:59 +0000 (UTC)", "from smtp3.osuosl.org ([127.0.0.1])\n by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id UZiqR6rbheCM; Wed, 13 May 2026 15:44:57 +0000 (UTC)", "from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56])\n\tby smtp3.osuosl.org (Postfix) with ESMTPS id 1197761252;\n\tWed, 13 May 2026 15:44:57 +0000 (UTC)", "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id EB56CC04EA;\n\tWed, 13 May 2026 15:44:56 +0000 (UTC)", "from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137])\n by lists.linuxfoundation.org (Postfix) with ESMTP id 5EC4AC04E9\n for <dev@openvswitch.org>; Wed, 13 May 2026 15:44:55 +0000 (UTC)", "from localhost (localhost [127.0.0.1])\n by smtp4.osuosl.org (Postfix) with ESMTP id 43FBB4158A\n for <dev@openvswitch.org>; Wed, 13 May 2026 15:44:55 +0000 (UTC)", "from smtp4.osuosl.org ([127.0.0.1])\n by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id aGN8wvB0kJLP for <dev@openvswitch.org>;\n Wed, 13 May 2026 15:44:53 +0000 (UTC)", "from us-smtp-delivery-124.mimecast.com\n (us-smtp-delivery-124.mimecast.com [170.10.133.124])\n by smtp4.osuosl.org (Postfix) with ESMTPS id 314E541588\n for <dev@openvswitch.org>; Wed, 13 May 2026 15:44:52 +0000 (UTC)", "from mail-qt1-f198.google.com (mail-qt1-f198.google.com\n [209.85.160.198]) by relay.mimecast.com with ESMTP with STARTTLS\n (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n us-mta-672-1HcHxnkHOFSFjR5RJrGAxQ-1; Wed, 13 May 2026 11:44:49 -0400", "by mail-qt1-f198.google.com with SMTP id\n d75a77b69052e-50edf01172bso12147991cf.2\n for <dev@openvswitch.org>; Wed, 13 May 2026 08:44:48 -0700 (PDT)", "from moloings-thinkpadp1gen7.rmtie.csb\n ([2001:bb6:2be4:f100:a13c:8a56:eb6f:ed3f])\n by smtp.gmail.com with ESMTPSA id\n d75a77b69052e-5148e836e32sm151663781cf.25.2026.05.13.08.44.43\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Wed, 13 May 2026 08:44:44 -0700 (PDT)" ], "X-Virus-Scanned": [ "amavis at osuosl.org", "amavis at osuosl.org" ], "X-Comment": "SPF check N/A for local connections - client-ip=140.211.9.56;\n helo=lists.linuxfoundation.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=<UNKNOWN> ", "DKIM-Filter": [ "OpenDKIM Filter v2.11.0 smtp3.osuosl.org 1197761252", "OpenDKIM Filter v2.11.0 smtp4.osuosl.org 314E541588" ], "Received-SPF": "Pass (mailfrom) identity=mailfrom; client-ip=170.10.133.124;\n helo=us-smtp-delivery-124.mimecast.com; envelope-from=moloings@redhat.com;\n receiver=<UNKNOWN>", "DMARC-Filter": "OpenDMARC Filter v1.4.2 smtp4.osuosl.org 314E541588", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1778687090;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc:cc:mime-version:mime-version:content-type:content-type:\n content-transfer-encoding:content-transfer-encoding;\n bh=2aBnNjieYieFeiAsUwIz9M2B/BW4Y8zVKkFFp2AANQ8=;\n b=JIpLUI3uv3URCnDb4BSMBe7PP10eUsxyeb+NjiwxG53bvBZjDhwaK3q75y9/ljhMk+W+Sh\n diCy1kLy5i409EMO5UHnAeTKolspDgcvHWxRpuLoWrlOScwt+Bgm6uqKFXk2Lm0JOSuWv1\n ERfL1UL3teKpRKaVkKKM3u8t2pufDMs=", "X-MC-Unique": "1HcHxnkHOFSFjR5RJrGAxQ-1", "X-Mimecast-MFC-AGG-ID": "1HcHxnkHOFSFjR5RJrGAxQ_1778687088", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1778687088; x=1779291888;\n h=content-transfer-encoding:mime-version:message-id:date:subject:cc\n :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date\n :message-id:reply-to;\n bh=2aBnNjieYieFeiAsUwIz9M2B/BW4Y8zVKkFFp2AANQ8=;\n b=LalEyQqDIH26Ml5t1R/O+w/nZ/6n9fJ4fzT1GPm5LCR6EkKVKoaWZm1OrZpFeLkTeR\n cawCj/LyXtkW+msidQCIekPK/zsP4fynmuHUBkCWFBweGJrPBo5qbsFy2KPseoGF5T98\n hKmZ4vAGD0nsbGGMzk/MK30PilEfBZVHpwk6oMZtvqDbFjfdYNtjXxFpJQaM1iqgxKQd\n SHaTJb14kwPf32iJ1WB1AQr+3Xjhv2fahhS7LeRxGUBsHgAXFdMuDTlEdcK2oyCHKBYK\n kpXCXUXIuwpYrmU4UiI8JyD1cob6t4LRI9gCS3m1a97scRbI7x4Vo0j+E6xkV8mqW1Xh\n l2gQ==", "X-Gm-Message-State": "AOJu0Yx8+DQfSF9MrHUGwNR0zLxw0PtOZfn4sld8DmGZPcw2WvUl7Nmy\n IxqQkUqGvtynbB3su7VyyTr3TW8S0aORfgL8vUUSc3/lWU1+AcETXMvG6n+i+vsma3Os05HeEKy\n 1mu47qLsCk0hPWgFSj/y+2hKW9V6iW7tbmyNApCDBoTfo7j/1X4QxnF8km7ojHL9kueBm8AtAI4\n bajJHlVCesjW7SqngJaY55FxDicdkYcNo0qS01/Q==", "X-Gm-Gg": "Acq92OE6MAAlqPPNtld7xHHOKLWs/sqyT02G8qNrUfqhWCAZBEwUPlBo3isdNaCLZYS\n rPpioIDF8xWRbQPqzBJFSmwinTe0JVTqV5lQYVxVidv8/Almt+ejoU5/A61BGW6Ucxa3LBBLojl\n Qjf4Huh5zVsdpNa4p+IITZzl0affJj4CAzW0ClYf8nRgtBmHZcOQgMXI7wVM6Uj+Qa7wUcTO82o\n pIB/QXO9Zjkj3hdDdDQPzT9KeE3Lh6uFOHmwSRUFy+4fZnEpmOxJrXPvmiYLKiwxMF1xsTDekZI\n AwDqHiPEx/j3XUoOEYjb4sWab161b3cnCBmxkkFIl6if5uIg9iTD1DQe8mAlfZRmlu6KgtyhVT3\n 2Ua3Pmy37QATmYj7wY93tSdVmZzRIrSGi/o8sIHQ3jHULq4IssAV6PNbOBYC86z4FHMxJldkNAw\n ql1kw0m3sO", "X-Received": [ "by 2002:a05:622a:5a9a:b0:50f:e0c0:9d92 with SMTP id\n d75a77b69052e-5162ffa5490mr49860951cf.54.1778687086400;\n Wed, 13 May 2026 08:44:46 -0700 (PDT)", "by 2002:a05:622a:5a9a:b0:50f:e0c0:9d92 with SMTP id\n d75a77b69052e-5162ffa5490mr49859791cf.54.1778687085254;\n Wed, 13 May 2026 08:44:45 -0700 (PDT)" ], "To": "dev@openvswitch.org", "Date": "Wed, 13 May 2026 16:44:33 +0100", "Message-ID": "<20260513154433.439593-1-moloings@redhat.com>", "X-Mailer": "git-send-email 2.54.0", "MIME-Version": "1.0", "X-Mimecast-Spam-Score": "0", "X-Mimecast-MFC-PROC-ID": "8ZV1UL3ZycK9dQCRZs2QOG5wt9Jd50YpMA7s0amk7nI_1778687088", "X-Mimecast-Originator": "redhat.com", "Subject": "[ovs-dev] [PATCH ovn v3 1/1] ic: Add transit switch port and schema.", "X-BeenThere": "ovs-dev@openvswitch.org", "X-Mailman-Version": "2.1.30", "Precedence": "list", "List-Id": "<ovs-dev.openvswitch.org>", "List-Unsubscribe": "<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>", "List-Archive": "<http://mail.openvswitch.org/pipermail/ovs-dev/>", "List-Post": "<mailto:ovs-dev@openvswitch.org>", "List-Help": "<mailto:ovs-dev-request@openvswitch.org?subject=help>", "List-Subscribe": "<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=subscribe>", "From": "Mairtin O'Loingsigh via dev <ovs-dev@openvswitch.org>", "Reply-To": "Mairtin O'Loingsigh <moloings@redhat.com>", "Cc": "dceara@redhat.com", "Content-Type": "text/plain; charset=\"us-ascii\"", "Content-Transfer-Encoding": "7bit", "Errors-To": "ovs-dev-bounces@openvswitch.org", "Sender": "\"dev\" <ovs-dev-bounces@openvswitch.org>" }, "content": "Enable the creation and management of transit switch ports, when\ncreated, these ports are available across multiple AZs and may share\nport binding depending on configuration. This patch also includes tests\nand a multinode test.\n\nCommands to add, set address and delete port\n ovn-ic-nbctl tsp-add ts0 ts0-p0 chassis=chassis\n ovn-ic-nbctl tsp-set-addr ts0-p0 \"00:11:22:11:22:34\n 192.168.10.11/24\"\n ovn-ic-nbctl tsp-del ts0-p0\n\nReported-at: https://issues.redhat.com/browse/FDP-2878\nSigned-off-by: Mairtin O'Loingsigh <moloings@redhat.com>\n---\n\nChanges since v2:\n - Remove function which was not needed.\n - Dont overwrite router-id.\n - Unify support functions.\n - Remove left over debug code from tests.\n - Add ovn-ic-nbctl.8.xml updates. \n - Add tsp-del test.\n\nChanges since v1:\n - Merge patches.\n - Bump OVNIC NB version number.\n - Add NEWS entry.\n - Remove option column from TSP.\n - Remove nb_uuid from TSP.\n - Clean up coding standard violations.\n - Fix missing port issue.\n\n NEWS | 4 +\n ic/ovn-ic.c | 217 +++++++++++++++++++++++-------\n lib/ovn-util.c | 44 +++++++\n lib/ovn-util.h | 4 +\n ovn-ic-nb.ovsschema | 23 +++-\n ovn-ic-nb.xml | 37 ++++++\n tests/multinode.at | 14 +-\n tests/ovn-ic-nbctl.at | 30 +++++\n tests/ovn-ic.at | 49 +++++++\n utilities/ovn-ic-nbctl.8.xml | 50 +++++++\n utilities/ovn-ic-nbctl.c | 247 ++++++++++++++++++++++++++++++++++-\n utilities/ovn-nbctl.c | 52 +-------\n 12 files changed, 664 insertions(+), 107 deletions(-)", "diff": "diff --git a/NEWS b/NEWS\nindex 9839d19b9..ad0851a1c 100644\n--- a/NEWS\n+++ b/NEWS\n@@ -15,6 +15,10 @@ Post v26.03.0\n \n OVN v26.03.0 - xxx xx xxxx\n --------------------------\n+ - Added Transit Switch port support:\n+ * Support the creation of Transit Switch Ports.\n+ * Added new ovn-ic-nbctl 'tsp-add', 'tsp-del' and 'tsp-set-addr' commands\n+ to manage Transit Switch Ports.\n - Added LSP/LRP option \"requested-encap-ip\" to let CMS request a specific\n SB Port_Binding encap IP (e.g., for transit switch ports in ovn-k8s\n interconnect mode).\ndiff --git a/ic/ovn-ic.c b/ic/ovn-ic.c\nindex ba9490658..d5f16b087 100644\n--- a/ic/ovn-ic.c\n+++ b/ic/ovn-ic.c\n@@ -733,6 +733,32 @@ get_lp_address_for_sb_pb(struct ic_context *ctx,\n return peer->n_mac ? *peer->mac : NULL;\n }\n \n+static const char *\n+get_lp_address_for_ts_pb(struct ic_context *ctx,\n+ struct icnbrec_transit_switch_port *tsp)\n+{\n+ const struct nbrec_logical_switch_port *nb_lsp;\n+\n+ nb_lsp = get_lsp_by_ts_port_name(ctx, tsp->name);\n+ if (!nb_lsp) {\n+ return NULL;\n+ }\n+\n+ if (!strcmp(nb_lsp->type, \"switch\")) {\n+ /* Switches always have implicit \"unknown\" address, and IC-SB port\n+ * binding can only have one address specified. */\n+ return \"unknown\";\n+ }\n+\n+ const struct sbrec_port_binding *peer =\n+ find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, tsp->peer);\n+ if (peer && peer->n_mac) {\n+ return *peer->mac;\n+ } else {\n+ return NULL;\n+ }\n+}\n+\n static const struct sbrec_chassis *\n find_sb_chassis(struct ic_context *ctx, const char *name)\n {\n@@ -818,27 +844,44 @@ update_isb_pb_external_ids(struct ic_context *ctx,\n }\n \n /* For each local port:\n- * - Sync from NB to ISB.\n- * - Sync gateway from SB to ISB.\n- * - Sync tunnel key from ISB to NB.\n+ * - Sync from ISB to NB/SB or from NB/SB to ISB.\n+ * Legacy TSP sync from SB/NB towards ICB.\n+ * TSP added using ICNB commands sync from ICB towards NB/SB.\n */\n static void\n-sync_local_port(struct ic_context *ctx,\n- const struct icsbrec_port_binding *isb_pb,\n- const struct sbrec_port_binding *sb_pb,\n- const struct nbrec_logical_switch_port *lsp)\n+sync_switch_port(struct ic_context *ctx,\n+ struct icnbrec_transit_switch_port *tsp,\n+ const struct icsbrec_port_binding *isb_pb,\n+ const struct nbrec_logical_switch_port *lsp,\n+ const struct sbrec_port_binding *sb_pb)\n {\n- /* Sync address from NB to ISB */\n- const char *address = get_lp_address_for_sb_pb(ctx, sb_pb);\n- if (!address) {\n- VLOG_DBG(\"Can't get router/switch port address for logical\"\n- \" switch port %s\", sb_pb->logical_port);\n- if (isb_pb->address[0]) {\n- icsbrec_port_binding_set_address(isb_pb, \"\");\n+ if (tsp) {\n+ const char *address = get_lp_address_for_ts_pb(ctx, tsp);\n+ if (!address) {\n+ VLOG_DBG(\"Can't get router/switch port address for transit \"\n+ \"switch port %s\", lsp->name);\n+ if (sb_pb->n_mac && sb_pb->mac[0]) {\n+ //sbrec_port_binding_set_mac(sb_pb, NULL, 0);\n+ icsbrec_port_binding_set_address(isb_pb, \"\");\n+ }\n+ } else {\n+ if (sb_pb->n_mac && strcmp(address, sb_pb->mac[0])) {\n+ sbrec_port_binding_set_mac(sb_pb, &address, 1);\n+ icsbrec_port_binding_set_address(isb_pb, address);\n+ }\n }\n } else {\n- if (strcmp(address, isb_pb->address)) {\n- icsbrec_port_binding_set_address(isb_pb, address);\n+ const char *address = get_lp_address_for_sb_pb(ctx, sb_pb);\n+ if (!address) {\n+ VLOG_DBG(\"Can't get router/switch port address for logical \"\n+ \"switch port %s\", lsp->name);\n+ if (isb_pb->address[0]) {\n+ icsbrec_port_binding_set_address(isb_pb, \"\");\n+ }\n+ } else {\n+ if (strcmp(address, isb_pb->address)) {\n+ icsbrec_port_binding_set_address(isb_pb, address);\n+ }\n }\n }\n \n@@ -1010,6 +1053,7 @@ create_isb_pb(struct ic_context *ctx, const char *logical_port,\n icsbrec_port_binding_set_tunnel_key(isb_pb, pb_tnl_key);\n icsbrec_port_binding_set_nb_ic_uuid(isb_pb, nb_ic_uuid, 1);\n icsbrec_port_binding_set_type(isb_pb, type);\n+\n return isb_pb;\n }\n \n@@ -1028,19 +1072,15 @@ get_lrp_by_lrp_name(struct ic_context *ctx, const char *lrp_name)\n }\n \n static bool\n-trp_is_remote(struct ic_context *ctx, const char *chassis_name)\n+get_chassis_remote_status(struct ic_context *ctx, const char *chassis_name)\n {\n- if (chassis_name) {\n- const struct sbrec_chassis *chassis =\n- find_sb_chassis(ctx, chassis_name);\n- if (chassis) {\n- return smap_get_bool(&chassis->other_config, \"is-remote\", false);\n- } else {\n- return true;\n- }\n+ const struct sbrec_chassis *chassis =\n+ find_sb_chassis(ctx, chassis_name);\n+ if (chassis) {\n+ return smap_get_bool(&chassis->other_config, \"is-remote\", false);\n+ } else {\n+ return false;\n }\n-\n- return false;\n }\n \n static struct nbrec_logical_router_port *\n@@ -1057,11 +1097,40 @@ lrp_create(struct ic_context *ctx, const struct nbrec_logical_router *lr,\n return lrp;\n }\n \n+static struct nbrec_logical_switch_port *\n+lsp_create(struct ic_context *ctx, const struct nbrec_logical_switch *ls,\n+ const struct icnbrec_transit_switch_port *tsp)\n+{\n+ bool router_port = !strcmp(tsp->type, \"router\");\n+ const char *type = router_port ? \"router\" : \"\";\n+\n+ struct nbrec_logical_switch_port *lsp =\n+ nbrec_logical_switch_port_insert(ctx->ovnnb_txn);\n+ nbrec_logical_switch_port_set_name(lsp, tsp->name);\n+\n+ nbrec_logical_switch_port_update_options_setkey(lsp, \"interconn-ts\",\n+ tsp->name);\n+ nbrec_logical_switch_port_set_type(lsp, type);\n+ nbrec_logical_switch_port_set_peer(lsp, tsp->peer);\n+\n+ if (router_port) {\n+ nbrec_logical_switch_port_update_options_setkey(\n+ lsp, \"router-port\", tsp->peer);\n+ }\n+\n+ const char *addresses[] = { router_port ? \"router\": \"unknown\" };\n+ nbrec_logical_switch_port_set_addresses(lsp, addresses, 1);\n+\n+ nbrec_logical_switch_update_ports_addvalue(ls, lsp);\n+ return lsp;\n+}\n+\n static void\n sync_ts_isb_pb(struct ic_context *ctx, const struct sbrec_port_binding *sb_pb,\n const struct icsbrec_port_binding *isb_pb)\n {\n const char *address = get_lp_address_for_sb_pb(ctx, sb_pb);\n+\n if (address) {\n icsbrec_port_binding_set_address(isb_pb, address);\n }\n@@ -1118,7 +1187,6 @@ port_binding_run(struct ic_context *ctx)\n }\n icsbrec_port_binding_index_destroy_row(isb_pb_key);\n \n- const struct sbrec_port_binding *sb_pb;\n const struct icnbrec_transit_switch *ts;\n ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) {\n const struct nbrec_logical_switch *ls = find_ts_in_nb(ctx, ts->name);\n@@ -1126,9 +1194,20 @@ port_binding_run(struct ic_context *ctx)\n VLOG_DBG(\"Transit switch %s not found in NB.\", ts->name);\n continue;\n }\n+ struct shash nb_ports = SHASH_INITIALIZER(&nb_ports);\n+ struct shash old_nb_ports = SHASH_INITIALIZER(&old_nb_ports);\n struct shash local_pbs = SHASH_INITIALIZER(&local_pbs);\n struct shash remote_pbs = SHASH_INITIALIZER(&remote_pbs);\n \n+ for (size_t i = 0; i < ls->n_ports; i++) {\n+ const struct nbrec_logical_switch_port *lsp = ls->ports[i];\n+ if (smap_get(&lsp->options, \"interconn-ts\")) {\n+ shash_add(&nb_ports, lsp->name, lsp);\n+ } else {\n+ shash_add(&old_nb_ports, lsp->name, lsp);\n+ }\n+ }\n+\n isb_pb_key = icsbrec_port_binding_index_init_row(\n ctx->icsbrec_port_binding_by_ts);\n icsbrec_port_binding_index_set_transit_switch(isb_pb_key, ts->name);\n@@ -1145,9 +1224,54 @@ port_binding_run(struct ic_context *ctx)\n }\n icsbrec_port_binding_index_destroy_row(isb_pb_key);\n \n+ for (size_t i = 0; i < ts->n_ports; i++) {\n+ struct icnbrec_transit_switch_port *tsp = ts->ports[i];\n+ if (!get_chassis_remote_status(ctx, tsp->chassis)) {\n+ isb_pb = shash_find_and_delete(&local_pbs, tsp->name);\n+ if (!isb_pb) {\n+ isb_pb = create_isb_pb(ctx, tsp->name, ctx->runned_az,\n+ ts->name, &ts->header_.uuid,\n+ \"transit-switch-port\", &pb_tnlids);\n+ if (!isb_pb) {\n+ continue;\n+ }\n+ }\n+\n+ const struct nbrec_logical_switch_port *lsp =\n+ shash_find_and_delete(&nb_ports, tsp->name);\n+ if (!lsp) {\n+ lsp = lsp_create(ctx, ls, tsp);\n+ }\n+\n+ const struct sbrec_port_binding *sb_pb;\n+ sb_pb = find_lsp_in_sb(ctx, lsp);\n+ if (sb_pb) {\n+ sync_switch_port(ctx, tsp, isb_pb, lsp, sb_pb);\n+ }\n+ } else {\n+ /* Remote port sync */\n+ isb_pb = shash_find_and_delete(&remote_pbs, tsp->name);\n+ if (isb_pb) {\n+ const struct nbrec_logical_switch_port *lsp =\n+ shash_find_and_delete(&nb_ports, tsp->name);\n+ if (!lsp) {\n+ lsp = lsp_create(ctx, ls, tsp);\n+ }\n+\n+ const struct sbrec_port_binding *sb_pb;\n+ sb_pb = find_lsp_in_sb(ctx, lsp);\n+ if (sb_pb) {\n+ sync_remote_port(ctx, isb_pb, lsp, sb_pb);\n+ }\n+ }\n+ }\n+ }\n+\n+ /* Support legacy way of adding transit switch ports. */\n+ const struct sbrec_port_binding *sb_pb;\n const struct nbrec_logical_switch_port *lsp;\n- for (int i = 0; i < ls->n_ports; i++) {\n- lsp = ls->ports[i];\n+ SHASH_FOR_EACH (node, &old_nb_ports) {\n+ lsp = node->data;\n \n if (!strcmp(lsp->type, \"router\")\n || !strcmp(lsp->type, \"switch\")) {\n@@ -1163,17 +1287,7 @@ port_binding_run(struct ic_context *ctx)\n &ts->header_.uuid, \"transit-switch-port\", &pb_tnlids);\n sync_ts_isb_pb(ctx, sb_pb, isb_pb);\n } else {\n- sync_local_port(ctx, isb_pb, sb_pb, lsp);\n- }\n-\n- if (isb_pb->type) {\n- icsbrec_port_binding_set_type(isb_pb,\n- \"transit-switch-port\");\n- }\n-\n- if (isb_pb->nb_ic_uuid) {\n- icsbrec_port_binding_set_nb_ic_uuid(isb_pb,\n- &ts->header_.uuid, 1);\n+ sync_switch_port(ctx, NULL, isb_pb, lsp, sb_pb);\n }\n } else if (!strcmp(lsp->type, \"remote\")) {\n /* The port is remote. */\n@@ -1193,6 +1307,11 @@ port_binding_run(struct ic_context *ctx)\n }\n }\n \n+ SHASH_FOR_EACH (node, &nb_ports) {\n+ nbrec_logical_switch_port_delete(node->data);\n+ nbrec_logical_switch_update_ports_delvalue(ls, node->data);\n+ }\n+\n /* Delete extra port-binding from ISB */\n SHASH_FOR_EACH (node, &local_pbs) {\n icsbrec_port_binding_delete(node->data);\n@@ -1203,8 +1322,10 @@ port_binding_run(struct ic_context *ctx)\n create_nb_lsp(ctx, node->data, ls);\n }\n \n+ shash_destroy(&nb_ports);\n shash_destroy(&local_pbs);\n shash_destroy(&remote_pbs);\n+ shash_destroy(&old_nb_ports);\n }\n \n SHASH_FOR_EACH (node, &switch_all_local_pbs) {\n@@ -1250,7 +1371,7 @@ port_binding_run(struct ic_context *ctx)\n for (size_t i = 0; i < tr->n_ports; i++) {\n const struct icnbrec_transit_router_port *trp = tr->ports[i];\n \n- if (trp_is_remote(ctx, trp->chassis)) {\n+ if (get_chassis_remote_status(ctx, trp->chassis)) {\n isb_pb = shash_find_and_delete(&remote_pbs, trp->name);\n } else {\n isb_pb = shash_find_and_delete(&local_pbs, trp->name);\n@@ -1275,7 +1396,7 @@ port_binding_run(struct ic_context *ctx)\n }\n }\n \n- SHASH_FOR_EACH(node, &nb_ports) {\n+ SHASH_FOR_EACH (node, &nb_ports) {\n nbrec_logical_router_port_delete(node->data);\n nbrec_logical_router_update_ports_delvalue(lr, node->data);\n }\n@@ -2580,10 +2701,12 @@ route_run(struct ic_context *ctx)\n const struct nbrec_logical_switch_port *nb_lsp;\n \n nb_lsp = get_lsp_by_ts_port_name(ctx, isb_pb->logical_port);\n- if (!strcmp(nb_lsp->type, \"switch\")) {\n- VLOG_DBG(\"IC-SB Port_Binding '%s' on ts '%s' corresponds to a \"\n- \"switch port, not considering for route collection.\",\n- isb_pb->logical_port, isb_pb->transit_switch);\n+ if (!strcmp(nb_lsp->type, \"switch\") || !strcmp(nb_lsp->type, \"\")) {\n+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);\n+ VLOG_DBG_RL(&rl,\n+ \"IC-SB Port_Binding '%s' on ts '%s' corresponds to a \"\n+ \"switch port, not considering for route collection.\",\n+ isb_pb->logical_port, isb_pb->transit_switch);\n continue;\n }\n \ndiff --git a/lib/ovn-util.c b/lib/ovn-util.c\nindex fb02825ac..d41ab366f 100644\n--- a/lib/ovn-util.c\n+++ b/lib/ovn-util.c\n@@ -1869,3 +1869,47 @@ eth_addr_parse_masked(const char *s, struct eth_addr *ea, unsigned int *plen)\n *ea = eth_addr_zero;\n return false;\n }\n+\n+bool\n+sp_contains_duplicate_ip(struct lport_addresses *laddrs1,\n+ struct lport_addresses *laddrs2,\n+ char *port_name,\n+ char **error_str)\n+{\n+ for (size_t i = 0; i < laddrs1->n_ipv4_addrs; i++) {\n+ for (size_t j = 0; j < laddrs2->n_ipv4_addrs; j++) {\n+ if (laddrs1->ipv4_addrs[i].addr == laddrs2->ipv4_addrs[j].addr) {\n+ if (error_str) {\n+ *error_str = xasprintf(\"duplicate IPv4 address '%s' \"\n+ \"found on logical switch \"\n+ \"port '%s'\",\n+ laddrs1->ipv4_addrs[i].addr_s,\n+ port_name);\n+ }\n+ return true;\n+ }\n+ }\n+ }\n+\n+ for (size_t i = 0; i < laddrs1->n_ipv6_addrs; i++) {\n+ for (size_t j = 0; j < laddrs2->n_ipv6_addrs; j++) {\n+ if (IN6_ARE_ADDR_EQUAL(&laddrs1->ipv6_addrs[i].addr,\n+ &laddrs2->ipv6_addrs[j].addr)) {\n+ if (error_str) {\n+ *error_str = xasprintf(\"duplicate IPv6 address \"\n+ \"'%s' found on logical \"\n+ \"switch port '%s'\",\n+ laddrs1->ipv6_addrs[i].addr_s,\n+ port_name);\n+ }\n+ return true;\n+ }\n+ }\n+ }\n+\n+ if (error_str) {\n+ *error_str = NULL;\n+ }\n+\n+ return false;\n+}\ndiff --git a/lib/ovn-util.h b/lib/ovn-util.h\nindex b44c9c770..001af3c3e 100644\n--- a/lib/ovn-util.h\n+++ b/lib/ovn-util.h\n@@ -790,6 +790,10 @@ char *normalize_ipv6_addr_str(const char *orig_addr);\n \n char *normalize_addr_str(const char *orig_addr);\n \n+bool sp_contains_duplicate_ip(struct lport_addresses *laddrs1,\n+ struct lport_addresses *laddrs2, char *name,\n+ char **error_str);\n+\n #define NEIGH_REDISTRIBUTE_MODES \\\n NEIGH_REDISTRIBUTE_MODE(FDB, 0) \\\n NEIGH_REDISTRIBUTE_MODE(IP, 1)\ndiff --git a/ovn-ic-nb.ovsschema b/ovn-ic-nb.ovsschema\nindex ca67a2fa9..ae7f38377 100644\n--- a/ovn-ic-nb.ovsschema\n+++ b/ovn-ic-nb.ovsschema\n@@ -1,7 +1,7 @@\n {\n \"name\": \"OVN_IC_Northbound\",\n- \"version\": \"1.3.0\",\n- \"cksum\": \"1918565391 5082\",\n+ \"version\": \"1.4.0\",\n+ \"cksum\": \"1916436818 6015\",\n \"tables\": {\n \"IC_NB_Global\": {\n \"columns\": {\n@@ -24,9 +24,28 @@\n \"min\": 0, \"max\": \"unlimited\"}}},\n \"maxRows\": 1,\n \"isRoot\": true},\n+ \"Transit_Switch_Port\": {\n+ \"columns\": {\n+ \"name\": {\"type\": \"string\"},\n+ \"other_config\": {\n+ \"type\": {\"key\": \"string\", \"value\": \"string\",\n+ \"min\": 0, \"max\": \"unlimited\"}},\n+ \"type\": {\"type\": \"string\"},\n+ \"chassis\": {\"type\": \"string\"},\n+ \"peer\": {\"type\": \"string\"},\n+ \"addresses\": {\"type\": {\"key\": \"string\",\n+ \"min\": 0,\n+ \"max\": \"unlimited\"}}},\n+ \"isRoot\": false,\n+ \"indexes\": [[\"name\"]]},\n \"Transit_Switch\": {\n \"columns\": {\n \"name\": {\"type\": \"string\"},\n+ \"ports\": {\"type\": {\"key\": {\"type\": \"uuid\",\n+ \"refTable\": \"Transit_Switch_Port\",\n+ \"refType\": \"strong\"},\n+ \"min\": 0,\n+ \"max\": \"unlimited\"}},\n \"other_config\": {\n \"type\": {\"key\": \"string\", \"value\": \"string\",\n \"min\": 0, \"max\": \"unlimited\"}},\ndiff --git a/ovn-ic-nb.xml b/ovn-ic-nb.xml\nindex a3a35baf2..75e03c20b 100644\n--- a/ovn-ic-nb.xml\n+++ b/ovn-ic-nb.xml\n@@ -112,6 +112,10 @@\n </column>\n </group>\n \n+ <column name=\"ports\">\n+ The switch's ports.\n+ </column>\n+\n <group title=\"Common Columns\">\n <column name=\"other_config\"/>\n <column name=\"external_ids\">\n@@ -190,6 +194,39 @@\n </group>\n </table>\n \n+ <table name=\"Transit_Switch_Port\" title=\"Transit logical switch port\">\n+ <p>\n+ Each row represents one transit logical switch port for interconnection\n+ between different OVN deployments (availability zones).\n+ </p>\n+\n+ <group title=\"Naming\">\n+ <column name=\"name\">\n+ A name that uniquely identifies the transit logical switch port.\n+ </column>\n+ </group>\n+\n+ <column name=\"type\">\n+ Specify if the port is of type router or vif.\n+ </column>\n+\n+ <column name=\"chassis\">\n+ The chassis this switch port should be bound to.\n+ </column>\n+\n+ <column name=\"peer\">\n+ Name of peer port.\n+ </column>\n+\n+ <column name=\"addresses\">\n+ Addresses to assign to port.\n+ </column>\n+\n+ <group title=\"Common Columns\">\n+ <column name=\"other_config\"/>\n+ </group>\n+ </table>\n+\n <table name=\"SSL\">\n SSL/TLS configuration for ovn-nb database access.\n \ndiff --git a/tests/multinode.at b/tests/multinode.at\nindex d07660797..ffe13eb6d 100644\n--- a/tests/multinode.at\n+++ b/tests/multinode.at\n@@ -2354,7 +2354,7 @@ fi\n \n AT_CLEANUP\n \n-AT_SETUP([ovn multinode - Transit Router])\n+AT_SETUP([ovn multinode - Transit Router + Transit Switch])\n \n # Check that ovn-fake-multinode setup is up and running\n check_fake_multinode_setup\n@@ -2424,7 +2424,7 @@ for i in 1 2; do\n check m_as $chassis ovs-vsctl set open . external_ids:ovn-is-interconn=true\n done\n \n-# Do the ovn-ic setup.\n+# Add TR and TS\n check m_central_as ovn-ic-nbctl tr-add tr\n check m_central_as ovn-ic-nbctl trp-add tr tr-gw1 \\\n 00:00:00:00:30:02 100.65.0.2/30 100:65::2/126 \\\n@@ -2434,6 +2434,9 @@ check m_central_as ovn-ic-nbctl trp-add tr tr-gw2 \\\n chassis=ovn-chassis-2\n check m_central_as ovn-ic-nbctl --wait=sb sync\n \n+check m_central_as ovn-ic-nbctl ts-add ts\n+check m_central_as ovn-ic-nbctl tsp-add ts ts-tr type=router peer=tr-ts\n+\n for i in 1 2; do\n chassis=\"ovn-chassis-$i\"\n \n@@ -2446,14 +2449,7 @@ for i in 1 2; do\n \n check m_as $chassis ovn-nbctl set logical_router gw options:chassis=$chassis\n \n- # Add TR and set the same tunnel key for both chassis\n- check m_as $chassis ovn-nbctl ls-add ts\n- check m_as $chassis ovn-nbctl set logical_switch ts other_config:requested-tnl-key=10\n-\n- check m_as $chassis ovn-nbctl lsp-add-router-port ts ts-tr tr-ts\n-\n 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\n- check m_as $chassis ovn-nbctl set logical_router tr options:requested-tnl-key=20\n check m_as $chassis ovn-nbctl lrp-set-gateway-chassis tr-ts $chassis\n \n # Add TS pods, with the same tunnel keys on both sides\ndiff --git a/tests/ovn-ic-nbctl.at b/tests/ovn-ic-nbctl.at\nindex 4c5269784..315a7ce1c 100644\n--- a/tests/ovn-ic-nbctl.at\n+++ b/tests/ovn-ic-nbctl.at\n@@ -61,6 +61,36 @@ AT_CHECK([ovn-ic-nbctl ts-del ts2], [1], [],\n \n AT_CHECK([ovn-ic-nbctl --if-exists ts-del ts2])\n \n+AT_CHECK([ovn-ic-nbctl --may-exist ts-add ts0])\n+AT_CHECK([ovn-ic-nbctl ts-list | uuidfilt], [0], [dnl\n+<0> (ts0)\n+])\n+\n+AT_CHECK([ovn-ic-nbctl tsp-add], [1], [],\n+ [ovn-ic-nbctl: 'tsp-add' command requires at least 2 arguments\n+])\n+\n+AT_CHECK([ovn-ic-nbctl tsp-add ts0], [1], [],\n+ [ovn-ic-nbctl: 'tsp-add' command requires at least 2 arguments\n+])\n+\n+AT_CHECK([ovn-ic-nbctl tsp-add ts0 ts0-p0 chassis=chassis])\n+\n+AT_CHECK([ovn-ic-nbctl --may-exist tsp-add ts0 ts0-p0 chassis=chassis])\n+AT_CHECK([ovn-ic-nbctl tsp-set-addr ts0-p0 \"00:11:22:11:22:33 192.168.10.10/24\"])\n+AT_CHECK([ovn-ic-nbctl tsp-set-addr ts0-p1 \"00:11:22:11:22:34 192.168.10.11/24\"], [1], [],\n+ [ovn-ic-nbctl: ts0-p1: switch port name not found\n+])\n+\n+AT_CHECK([ovn-ic-nbctl tsp-del], [1], [],\n+ [ovn-ic-nbctl: 'tsp-del' command requires at least 1 arguments\n+])\n+AT_CHECK([ovn-ic-nbctl tsp-del ts0-p0])\n+AT_CHECK([ovn-ic-nbctl tsp-del ts0-p0], [1], [],\n+ [ovn-ic-nbctl: ts0-p0: switch port name not found\n+])\n+AT_CHECK([ovn-ic-nbctl --if-exists trp-del tr0-p0])\n+\n OVN_IC_NBCTL_TEST_STOP\n AT_CLEANUP\n \ndiff --git a/tests/ovn-ic.at b/tests/ovn-ic.at\nindex 0fa7c4f29..44c49d17e 100644\n--- a/tests/ovn-ic.at\n+++ b/tests/ovn-ic.at\n@@ -164,6 +164,55 @@ AT_CHECK([ovn-ic-sbctl show | grep -A2 lsp1], [0], [dnl\n address: [[\"00:00:00:00:00:01 10.0.0.1/24\"]]\n ])\n \n+# remove transit switch and check if port_binding is deleted\n+check ovn-ic-nbctl --wait=sb ts-del ts1\n+check_row_count ic-sb:Port_Binding 0 logical_port=lsp1\n+for i in 1 2; do\n+ az=az$i\n+ ovn_as $az\n+ OVN_CLEANUP_SBOX(gw-$az)\n+ OVN_CLEANUP_AZ([$az])\n+done\n+OVN_CLEANUP_IC\n+AT_CLEANUP\n+])\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([ovn-ic -- transit port-bindings deletion upon TS deletion])\n+\n+ovn_init_ic_db\n+net_add n1\n+\n+# 1 GW per AZ\n+for i in 1 2; do\n+ az=az$i\n+ ovn_start $az\n+ sim_add gw-$az\n+ as gw-$az\n+ check ovs-vsctl add-br br-phys\n+ ovn_az_attach $az n1 br-phys 192.168.1.$i\n+ check ovs-vsctl set open . external-ids:ovn-is-interconn=true\n+done\n+\n+ovn_as az1\n+\n+# create transit switch and connect to LR\n+check ovn-ic-nbctl --wait=sb ts-add ts1\n+check ovn-nbctl lr-add lr1\n+check ovn-nbctl lrp-add lr1 lrp1 00:00:00:00:00:01 10.0.0.1/24\n+check ovn-nbctl lrp-set-gateway-chassis lrp1 gw-az1\n+check ovn-ic-nbctl tsp-add ts1 lsp1 type=router chassis=gw-az1 peer=lrp1\n+\n+wait_row_count Datapath_Binding 1 external_ids:interconn-ts=ts1\n+\n+# Sync ic-sb DB to see the TS changes.\n+check ovn-ic-nbctl --wait=sb sync\n+\n+AT_CHECK([ovn-ic-sbctl show | grep -A2 lsp1], [0], [dnl\n+ port lsp1\n+ transit switch: ts1\n+ address: [[\"00:00:00:00:00:01 10.0.0.1/24\"]]\n+])\n+\n # remove transit switch and check if port_binding is deleted\n check ovn-ic-nbctl --wait=sb ts-del ts1\n check_row_count ic-sb:Port_Binding 0 logical_port=lsp1\ndiff --git a/utilities/ovn-ic-nbctl.8.xml b/utilities/ovn-ic-nbctl.8.xml\nindex 633863294..0ffc9daa7 100644\n--- a/utilities/ovn-ic-nbctl.8.xml\n+++ b/utilities/ovn-ic-nbctl.8.xml\n@@ -50,6 +50,56 @@\n <dd>\n Lists all existing switches on standard output, one per line.\n </dd>\n+\n+ <dt>[<code>--may-exist</code>] <code>tsp-add</code> <var>switch</var> <var>port</var></dt>\n+ <dd>\n+ <p>\n+ Creates a new transit switch port named <var>port</var> on <var>switch</var>.\n+ </p>\n+\n+ <p>\n+ Transit switch ports names must be unique. Adding a duplicated name results\n+ in error. With <code>--may-exist</code>, adding a duplicate name\n+ succeeds but does not create a new transit switch port.\n+ </p>\n+ </dd>\n+\n+\n+ <dt>[<code>--if-exists</code>] <code>tsp-del</code> <var>port</var></dt>\n+ <dd>\n+ Deletes <var>port</var>. It is an error if <var>port</var> does\n+ not exist, unless <code>--if-exists</code> is specified.\n+ </dd>\n+\n+ <dt><code>tsp-set-addr</code> <var>port</var> [<var>address</var>]...</dt>\n+ <dd>\n+ <p>\n+ Sets the addresses associated with <var>port</var> to\n+ <var>address</var>. Each <var>address</var> should be one of the\n+ following:\n+ </p>\n+\n+ <dl>\n+ <dt>an Ethernet address, optionally followed by a space and one or more IP addresses</dt>\n+ <dd>\n+ OVN delivers packets for the Ethernet address to this port.\n+ </dd>\n+\n+ <dt><code>router</code></dt>\n+ <dd>\n+ Accepted only when the <code>type</code> of the logical switch\n+ port is <code>router</code>. This indicates that the Ethernet,\n+ IPv4, and IPv6 addresses for this logical switch port should be\n+ obtained from the connected logical router port, as specified by\n+ peer column.\n+ </dd>\n+ </dl>\n+\n+ <p>\n+ Multiple addresses may be set. If no <var>address</var> argument is\n+ given, <var>port</var> will have no addresses associated with it.\n+ </p>\n+ </dd>\n </dl>\n \n <h1>Database Commands</h1>\ndiff --git a/utilities/ovn-ic-nbctl.c b/utilities/ovn-ic-nbctl.c\nindex 50e975283..1e9327d4c 100644\n--- a/utilities/ovn-ic-nbctl.c\n+++ b/utilities/ovn-ic-nbctl.c\n@@ -336,6 +336,9 @@ Transit switch commands:\\n\\\n ts-add SWITCH create a transit switch named SWITCH\\n\\\n ts-del SWITCH delete SWITCH\\n\\\n ts-list print all transit switches\\n\\\n+ tsp-add SWITCH PORT add a transit switch PORT\\n\\\n+ tsp-set-addr PORT ADDRESS set a transit switch PORT address\\n\\\n+ tsp-del PORT delete a transit switch PORT\\n\\\n \\n\\\n Transit router commands:\\n\\\n tr-add ROUTER create a transit router named ROUTER\\n\\\n@@ -400,6 +403,7 @@ struct ic_nbctl_context {\n * ic_nbctl_context_invalidate_cache() or manually update the cache to\n * maintain its correctness. */\n bool cache_valid;\n+ struct shash tsp_to_ts_map;\n };\n \n static struct cmd_show_table cmd_show_tables[] = {\n@@ -617,6 +621,38 @@ trp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,\n return NULL;\n }\n \n+static char *\n+tsp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,\n+ const struct icnbrec_transit_switch_port **tsp_p)\n+{\n+ const struct icnbrec_transit_switch_port *tsp = NULL;\n+ *tsp_p = NULL;\n+ struct uuid tsp_uuid;\n+ bool is_uuid = uuid_from_string(&tsp_uuid, id);\n+ if (is_uuid) {\n+ tsp = icnbrec_transit_switch_port_get_for_uuid(ctx->idl, &tsp_uuid);\n+ }\n+\n+ if (!tsp) {\n+ const struct icnbrec_transit_switch_port *iter;\n+\n+ ICNBREC_TRANSIT_SWITCH_PORT_FOR_EACH (iter, ctx->idl) {\n+ if (!strcmp(iter->name, id)) {\n+ tsp = iter;\n+ break;\n+ }\n+ }\n+ }\n+\n+ if (!tsp && must_exist) {\n+ return xasprintf(\"%s: switch port %s not found\", id,\n+ is_uuid ? \"UUID\" : \"name\");\n+ }\n+\n+ *tsp_p = tsp;\n+ return NULL;\n+}\n+\n static void\n ic_nbctl_tr_del(struct ctl_context *ctx)\n {\n@@ -664,6 +700,74 @@ ic_nbctl_trp_del(struct ctl_context *ctx)\n icnbrec_transit_router_port_delete(trp);\n }\n \n+static struct ic_nbctl_context *\n+ic_nbctl_context_get(struct ctl_context *base)\n+{\n+ struct ic_nbctl_context *icnbctx\n+ = CONTAINER_OF(base, struct ic_nbctl_context, base);\n+ if (icnbctx->cache_valid) {\n+ return icnbctx;\n+ }\n+\n+ const struct icnbrec_transit_switch *ts;\n+ ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, base->idl) {\n+ for (size_t i = 0; i < ts->n_ports; i++) {\n+ shash_add_once(&icnbctx->tsp_to_ts_map, ts->ports[i]->name, ts);\n+ }\n+ }\n+\n+ icnbctx->cache_valid = true;\n+ return icnbctx;\n+}\n+\n+/* Returns the logical switch that contains 'lsp'. */\n+static char * OVS_WARN_UNUSED_RESULT\n+tsp_to_ts(struct ctl_context *ctx,\n+ const struct icnbrec_transit_switch_port *tsp,\n+ const struct icnbrec_transit_switch **ts_p)\n+{\n+ struct ic_nbctl_context *icnbctx = ic_nbctl_context_get(ctx);\n+ const struct icnbrec_transit_switch *ts;\n+ *ts_p = NULL;\n+\n+ ts = shash_find_data(&icnbctx->tsp_to_ts_map, tsp->name);\n+ if (ts) {\n+ *ts_p = ts;\n+ return NULL;\n+ }\n+ /* Can't happen because of the database schema */\n+ return xasprintf(\"transit port %s is not part of any transit switch\",\n+ tsp->name);\n+}\n+\n+static void\n+ic_nbctl_tsp_del(struct ctl_context *ctx)\n+{\n+ bool must_exist = !shash_find(&ctx->options, \"--if-exists\");\n+ const char *tsp_name = ctx->argv[1];\n+ const struct icnbrec_transit_switch_port *tsp = NULL;\n+\n+ char *error = tsp_by_name_or_uuid(ctx, tsp_name, must_exist, &tsp);\n+ if (error) {\n+ ctx->error = error;\n+ return;\n+ }\n+\n+ if (!tsp) {\n+ return;\n+ }\n+\n+ const struct icnbrec_transit_switch *ts = NULL;\n+ error = tsp_to_ts(ctx, tsp, &ts);\n+ if (error) {\n+ ctx->error = error;\n+ return;\n+ }\n+\n+ icnbrec_transit_switch_update_ports_delvalue(ts, tsp);\n+ icnbrec_transit_switch_port_delete(tsp);\n+}\n+\n static void\n ic_nbctl_tr_list(struct ctl_context *ctx)\n {\n@@ -802,6 +906,140 @@ ic_nbctl_trp_add(struct ctl_context *ctx)\n icnbrec_transit_router_update_ports_addvalue(tr, trp);\n }\n \n+static void\n+ic_nbctl_tsp_add(struct ctl_context *ctx)\n+{\n+ bool may_exist = shash_find(&ctx->options, \"--may-exist\") != NULL;\n+ const char *ts_name = ctx->argv[1];\n+ const char *tsp_name = ctx->argv[2];\n+ const struct icnbrec_transit_switch *ts;\n+\n+ ctx->error = ts_by_name_or_uuid(ctx, ts_name, true, &ts);\n+ if (ctx->error) {\n+ return;\n+ }\n+\n+ const struct icnbrec_transit_switch_port *tsp;\n+ ctx->error = tsp_by_name_or_uuid(ctx, tsp_name, false, &tsp);\n+ if (ctx->error) {\n+ return;\n+ }\n+\n+ if (tsp) {\n+ if (!may_exist) {\n+ ctl_error(ctx, \"%s: a port with this name already exists\",\n+ tsp_name);\n+ }\n+ return;\n+ }\n+\n+ tsp = icnbrec_transit_switch_port_insert(ctx->txn);\n+ icnbrec_transit_switch_port_set_name(tsp, tsp_name);\n+\n+ int n_settings = ctx->argc - 3;\n+ char **settings = (char **) &ctx->argv[3];\n+ for (size_t i = 0; i < n_settings; i++) {\n+ ctx->error = ctl_set_column(\"Transit_Switch_Port\", &tsp->header_,\n+ settings[i], ctx->symtab);\n+ if (ctx->error) {\n+ return;\n+ }\n+ }\n+\n+ icnbrec_transit_switch_update_ports_addvalue(ts, tsp);\n+}\n+\n+static char *\n+tsp_contains_duplicates(const struct icnbrec_transit_switch *ts,\n+ const struct icnbrec_transit_switch_port *tsp,\n+ const char *address)\n+{\n+ char *sub_error = NULL;\n+ struct lport_addresses laddrs;\n+ if (!extract_lsp_addresses(address, &laddrs)) {\n+ return NULL;\n+ }\n+\n+ for (size_t i = 0; i < ts->n_ports; i++) {\n+ struct icnbrec_transit_switch_port *tsp_test = ts->ports[i];\n+ if (tsp_test == tsp) {\n+ continue;\n+ }\n+\n+ for (size_t j = 0; j < tsp_test->n_addresses; j++) {\n+ struct lport_addresses laddrs_test;\n+ char *addr = tsp_test->addresses[j];\n+ if (extract_lsp_addresses(addr, &laddrs_test)) {\n+ bool has_duplicate =\n+ sp_contains_duplicate_ip(&laddrs, &laddrs_test,\n+ tsp_test->name, &sub_error);\n+ destroy_lport_addresses(&laddrs_test);\n+ if (has_duplicate) {\n+ goto err_out;\n+ }\n+ }\n+ }\n+ }\n+\n+err_out: ;\n+ char *error = NULL;\n+ if (sub_error) {\n+ error = xasprintf(\"Error on switch %s: %s\", ts->name, sub_error);\n+ free(sub_error);\n+ }\n+ destroy_lport_addresses(&laddrs);\n+ return error;\n+}\n+\n+static void\n+ic_nbctl_tsp_set_addr(struct ctl_context *ctx)\n+{\n+ const char *tsp_name = ctx->argv[1];\n+\n+ const struct icnbrec_transit_switch_port *tsp;\n+ ctx->error = tsp_by_name_or_uuid(ctx, tsp_name, true, &tsp);\n+ if (ctx->error) {\n+ return;\n+ }\n+\n+ const struct icnbrec_transit_switch *ts = NULL;\n+ char *error = tsp_to_ts(ctx, tsp, &ts);\n+ if (error) {\n+ ctx->error = error;\n+ return;\n+ }\n+\n+ int i;\n+ for (i = 2; i < ctx->argc; i++) {\n+ char ipv6_s[IPV6_SCAN_LEN + 1];\n+ struct eth_addr ea;\n+ ovs_be32 ip;\n+\n+ if (strcmp(ctx->argv[i], \"unknown\") && strcmp(ctx->argv[i], \"dynamic\")\n+ && strcmp(ctx->argv[i], \"router\")\n+ && !ovs_scan(ctx->argv[i], ETH_ADDR_SCAN_FMT,\n+ ETH_ADDR_SCAN_ARGS(ea))\n+ && !ovs_scan(ctx->argv[i], \"dynamic \"IPV6_SCAN_FMT, ipv6_s)\n+ && !ovs_scan(ctx->argv[i], \"dynamic \"IP_SCAN_FMT,\n+ IP_SCAN_ARGS(&ip))) {\n+ ctl_error(ctx, \"%s: Invalid address format. See ovn-nb(5). \"\n+ \"Hint: An Ethernet address must be \"\n+ \"listed before an IP address, together as a single \"\n+ \"argument.\", ctx->argv[i]);\n+ return;\n+ }\n+\n+ ctx->error = tsp_contains_duplicates(ts, tsp, ctx->argv[i]);\n+ if (ctx->error) {\n+ return;\n+ }\n+ }\n+\n+ icnbrec_transit_switch_port_set_addresses(tsp,\n+ (const char **) ctx->argv + 2,\n+ ctx->argc - 2);\n+}\n+\n static void\n verify_connections(struct ctl_context *ctx)\n {\n@@ -1036,6 +1274,7 @@ ic_nbctl_context_init(struct ic_nbctl_context *ic_nbctl_ctx,\n ctl_context_init(&ic_nbctl_ctx->base, command, idl, txn, symtab,\n NULL);\n ic_nbctl_ctx->cache_valid = false;\n+ shash_init(&ic_nbctl_ctx->tsp_to_ts_map);\n }\n \n static void\n@@ -1077,6 +1316,7 @@ ic_nbctl_context_done(struct ic_nbctl_context *ic_nbctl_ctx,\n struct ctl_command *command)\n {\n ctl_context_done(&ic_nbctl_ctx->base, command);\n+ shash_destroy(&ic_nbctl_ctx->tsp_to_ts_map);\n }\n \n static void\n@@ -1317,7 +1557,13 @@ static const struct ctl_command_syntax ic_nbctl_commands[] = {\n { \"ts-add\", 1, 1, \"SWITCH\", NULL, ic_nbctl_ts_add, NULL, \"--may-exist\", RW },\n { \"ts-del\", 1, 1, \"SWITCH\", NULL, ic_nbctl_ts_del, NULL, \"--if-exists\", RW },\n { \"ts-list\", 0, 0, \"\", NULL, ic_nbctl_ts_list, NULL, \"\", RO },\n+ { \"tsp-add\", 2, INT_MAX, \"SWITCH PORT COLUMN[:KEY]=VALUE]...\",\n+ NULL, ic_nbctl_tsp_add, NULL, \"--may-exist\", RW },\n+ { \"tsp-set-addr\", 2, INT_MAX, \"PORT ADDRESS...\",\n+ NULL, ic_nbctl_tsp_set_addr, NULL, \"\", RW },\n \n+ { \"tsp-del\", 1, 1, \"PORT\", NULL, ic_nbctl_tsp_del, NULL, \"--if-exists\",\n+ RW },\n /* transit router commands. */\n { \"tr-add\", 1, 1, \"ROUTER\", NULL, ic_nbctl_tr_add, NULL, \"--may-exist\",\n RW },\n@@ -1329,7 +1575,6 @@ static const struct ctl_command_syntax ic_nbctl_commands[] = {\n NULL, ic_nbctl_trp_add, NULL, \"--may-exist\", RW },\n { \"trp-del\", 1, 1, \"PORT\", NULL, ic_nbctl_trp_del, NULL, \"--if-exists\",\n RW },\n-\n /* Connection commands. */\n {\"get-connection\", 0, 0, \"\", pre_connection, cmd_get_connection, NULL, \"\",\n RO},\ndiff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c\nindex 9f6bb374b..861065371 100644\n--- a/utilities/ovn-nbctl.c\n+++ b/utilities/ovn-nbctl.c\n@@ -1509,50 +1509,6 @@ nbctl_pre_lsp_set_addresses(struct ctl_context *ctx)\n &nbrec_logical_switch_port_col_dynamic_addresses);\n }\n \n-static bool\n-lsp_contains_duplicate_ip(struct lport_addresses *laddrs1,\n- struct lport_addresses *laddrs2,\n- const struct nbrec_logical_switch_port *lsp_test,\n- char **error_str)\n-{\n- for (size_t i = 0; i < laddrs1->n_ipv4_addrs; i++) {\n- for (size_t j = 0; j < laddrs2->n_ipv4_addrs; j++) {\n- if (laddrs1->ipv4_addrs[i].addr == laddrs2->ipv4_addrs[j].addr) {\n- if (error_str) {\n- *error_str = xasprintf(\"duplicate IPv4 address '%s' \"\n- \"found on logical switch \"\n- \"port '%s'\",\n- laddrs1->ipv4_addrs[i].addr_s,\n- lsp_test->name);\n- }\n- return true;\n- }\n- }\n- }\n-\n- for (size_t i = 0; i < laddrs1->n_ipv6_addrs; i++) {\n- for (size_t j = 0; j < laddrs2->n_ipv6_addrs; j++) {\n- if (IN6_ARE_ADDR_EQUAL(&laddrs1->ipv6_addrs[i].addr,\n- &laddrs2->ipv6_addrs[j].addr)) {\n- if (error_str) {\n- *error_str = xasprintf(\"duplicate IPv6 address \"\n- \"'%s' found on logical \"\n- \"switch port '%s'\",\n- laddrs1->ipv6_addrs[i].addr_s,\n- lsp_test->name);\n- }\n- return true;\n- }\n- }\n- }\n-\n- if (error_str) {\n- *error_str = NULL;\n- }\n-\n- return false;\n-}\n-\n static char *\n lsp_contains_duplicates(const struct nbrec_logical_switch *ls,\n const struct nbrec_logical_switch_port *lsp,\n@@ -1577,8 +1533,8 @@ lsp_contains_duplicates(const struct nbrec_logical_switch *ls,\n }\n if (extract_lsp_addresses(addr, &laddrs_test)) {\n bool has_duplicate =\n- lsp_contains_duplicate_ip(&laddrs, &laddrs_test,\n- lsp_test, &sub_error);\n+ sp_contains_duplicate_ip(&laddrs, &laddrs_test,\n+ lsp_test->name, &sub_error);\n destroy_lport_addresses(&laddrs_test);\n if (has_duplicate) {\n goto err_out;\n@@ -8862,8 +8818,8 @@ lsp_health_check_parse_target_address(\n goto cleanup;\n }\n \n- if (lsp_contains_duplicate_ip(&target_address,\n- &lsp_address, lsp, NULL)) {\n+ if (sp_contains_duplicate_ip(&target_address,\n+ &lsp_address, lsp->name, NULL)) {\n ip_found_on_port = true;\n }\n \n", "prefixes": [ "ovs-dev", "v3", "1/1" ] }