get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/2225639/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2225639,
    "url": "http://patchwork.ozlabs.org/api/patches/2225639/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/ovn/patch/20260421101422.3608058-3-moloings@redhat.com/",
    "project": {
        "id": 68,
        "url": "http://patchwork.ozlabs.org/api/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": "",
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<20260421101422.3608058-3-moloings@redhat.com>",
    "list_archive_url": null,
    "date": "2026-04-21T10:14:21",
    "name": "[ovs-dev,2/3] ic: Add transit switch port.",
    "commit_ref": null,
    "pull_url": null,
    "state": "changes-requested",
    "archived": false,
    "hash": "bfb5cd4781db3c5e090f635f6064692c55685371",
    "submitter": {
        "id": 91032,
        "url": "http://patchwork.ozlabs.org/api/people/91032/?format=api",
        "name": "Mairtin O'Loingsigh",
        "email": "moloings@redhat.com"
    },
    "delegate": {
        "id": 94943,
        "url": "http://patchwork.ozlabs.org/api/users/94943/?format=api",
        "username": "dceara",
        "first_name": "Dumitru",
        "last_name": "Ceara",
        "email": "dceara@redhat.com"
    },
    "mbox": "http://patchwork.ozlabs.org/project/ovn/patch/20260421101422.3608058-3-moloings@redhat.com/mbox/",
    "series": [
        {
            "id": 500774,
            "url": "http://patchwork.ozlabs.org/api/series/500774/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/ovn/list/?series=500774",
            "date": "2026-04-21T10:14:20",
            "name": "Add support for Transit switch ports",
            "version": 1,
            "mbox": "http://patchwork.ozlabs.org/series/500774/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2225639/comments/",
    "check": "warning",
    "checks": "http://patchwork.ozlabs.org/api/patches/2225639/checks/",
    "tags": {},
    "related": [],
    "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=MM//RGk+;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=140.211.166.137; helo=smtp4.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)",
            "smtp4.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=MM//RGk+",
            "smtp3.osuosl.org; dmarc=pass (p=quarantine dis=none)\n header.from=redhat.com",
            "smtp3.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=MM//RGk+"
        ],
        "Received": [
            "from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137])\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 4g0JC16sYxz1yGt\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 21 Apr 2026 20:15:25 +1000 (AEST)",
            "from localhost (localhost [127.0.0.1])\n\tby smtp4.osuosl.org (Postfix) with ESMTP id 0EF9D40E13;\n\tTue, 21 Apr 2026 10:15:24 +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 VoLVeqWNSaAi; Tue, 21 Apr 2026 10:15:20 +0000 (UTC)",
            "from lists.linuxfoundation.org (lf-lists.osuosl.org\n [IPv6:2605:bc80:3010:104::8cd3:938])\n\tby smtp4.osuosl.org (Postfix) with ESMTPS id DFA4340DF1;\n\tTue, 21 Apr 2026 10:15:19 +0000 (UTC)",
            "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id BDFF5C058E;\n\tTue, 21 Apr 2026 10:15:19 +0000 (UTC)",
            "from smtp3.osuosl.org (smtp3.osuosl.org [140.211.166.136])\n by lists.linuxfoundation.org (Postfix) with ESMTP id 1217BC0591\n for <dev@openvswitch.org>; Tue, 21 Apr 2026 10:15:18 +0000 (UTC)",
            "from localhost (localhost [127.0.0.1])\n by smtp3.osuosl.org (Postfix) with ESMTP id 4BF5460689\n for <dev@openvswitch.org>; Tue, 21 Apr 2026 10:15:17 +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 ljxQbhwOKF0m for <dev@openvswitch.org>;\n Tue, 21 Apr 2026 10:15:14 +0000 (UTC)",
            "from us-smtp-delivery-124.mimecast.com\n (us-smtp-delivery-124.mimecast.com [170.10.129.124])\n by smtp3.osuosl.org (Postfix) with ESMTPS id CAB48611A7\n for <dev@openvswitch.org>; Tue, 21 Apr 2026 10:15:13 +0000 (UTC)",
            "from mail-qk1-f199.google.com (mail-qk1-f199.google.com\n [209.85.222.199]) by relay.mimecast.com with ESMTP with STARTTLS\n (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n us-mta-653-kbzX1OXdNa2pn-cnFk6VMg-1; Tue, 21 Apr 2026 06:15:10 -0400",
            "by mail-qk1-f199.google.com with SMTP id\n af79cd13be357-8eb21daf7ddso166480785a.1\n for <dev@openvswitch.org>; Tue, 21 Apr 2026 03:15:10 -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 af79cd13be357-8e7d92ce037sm997022285a.32.2026.04.21.03.15.06\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Tue, 21 Apr 2026 03:15:06 -0700 (PDT)"
        ],
        "X-Virus-Scanned": [
            "amavis at osuosl.org",
            "amavis at osuosl.org"
        ],
        "X-Comment": "SPF check N/A for local connections -\n client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=<UNKNOWN> ",
        "DKIM-Filter": [
            "OpenDKIM Filter v2.11.0 smtp4.osuosl.org DFA4340DF1",
            "OpenDKIM Filter v2.11.0 smtp3.osuosl.org CAB48611A7"
        ],
        "Received-SPF": "Pass (mailfrom) identity=mailfrom; client-ip=170.10.129.124;\n helo=us-smtp-delivery-124.mimecast.com; envelope-from=moloings@redhat.com;\n receiver=<UNKNOWN>",
        "DMARC-Filter": "OpenDMARC Filter v1.4.2 smtp3.osuosl.org CAB48611A7",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1776766512;\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 in-reply-to:in-reply-to:references:references;\n bh=Kk2e2UodINfEk4b84m0yA0J+fkSDg5hoeerTJYzeGx8=;\n b=MM//RGk+MYjHhuQrTRVhPIWB3TieOn8lZ6bA1RHopuRKjUvoxpgU7+JGNm5cUG8ye6ihdH\n XDgLOCYFj00PGppdC8KNj9RGRMRo1S1dk6kL7ccbaXV14CrC0YpXEuA0Y9ys2Ha+oAk2dY\n bvcCzlLg4lewULQmHVxr3hC6G1+VQuU=",
        "X-MC-Unique": "kbzX1OXdNa2pn-cnFk6VMg-1",
        "X-Mimecast-MFC-AGG-ID": "kbzX1OXdNa2pn-cnFk6VMg_1776766510",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1776766510; x=1777371310;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n :to:cc:subject:date:message-id:reply-to;\n bh=Kk2e2UodINfEk4b84m0yA0J+fkSDg5hoeerTJYzeGx8=;\n b=UmjRAEUfEpfOaze93b1KHWLetq7p0q6lcstOHPyQrkcvNEYeaEO4zkJHG4vXDXCODl\n ynGXLhl0itkqGdnauvH/jYum8iBrtvwnaUiOypj0NV2nf2sq8xHVCAO7D5/voqyvTHgW\n u6ZLEdr8TMl61kaiV9knbI9ME1qS8JRID18+lbOCNQSQeO+KBXzb6+a+XKW/7E3vkTCk\n b5/cm2+sAJ/vxLDPQNlMpG9EdoxpWg82HvovMLHwCxqt7GQ0gAeDK1q5Mb0/pasIdPIp\n noRJSwnNxgkTZ6oSednlK05HE1ERZLQX7CDUdsruGnK7/iDVpKlnjwPlXKOlR2TG0puE\n dxkw==",
        "X-Gm-Message-State": "AOJu0Yy74m8zLNq/CaEEQ5x8Sr/dv793szDS3KqK2agl5oJlA/VWSvNc\n wFSZodr626mIt0PgufJkJRqg0kyEMcLOVmHoZ9b0XTUU4vV+0Jxns2B+zXMBVRbwtIvr4lIxLuU\n gO4jyFeGQOSv0ouWpbxBzdqALTOLvlOwuolG5MMF9Q0OFck48oxZA3w8iX6gXK4JRtonMq93xrU\n sUmyqBsii4XZjhwd3Elrb8umlX9NwkQVpsIFGk2Q==",
        "X-Gm-Gg": "AeBDievXX/KIZfRwiwRNBJF+4m18MT/afAojhMVy7Q4tY8WveJuM2xgJhW3dkfQ15Qv\n hhCEL/Kdfs0UTNqaWKUPkR80b7FFz3Z6tV2Q8wk/WuQUIzQAw1H9z7tKeK6aDsSjF+B8B7EZBe4\n 5U7R4qZ+YvZY85Id+MyQ6gPNYn6nb4DSEV+nUkuouTiql8QN3s0D6g+6/fztCmwu56mkqZSO/Zu\n WxCgo7nmkVbOeGJz7l6O7dgN7hvXq4ZhfS3NsbfAmDudE+Ilad1W9Qxb6X3s5PSvb08v1G7mvQk\n ej8NwR1/YO6j1gaV2BW7kJZArurBWnwh8MtikBsZoWYpCZiYiZcjhOy6e1dHKwlQuIaq2rjichu\n z1poxwWL4KHjzhqf4FstHDldI5BVk8Asrk2OizKDlcugUqLetqWn+ORhO3mXJ3B+a",
        "X-Received": [
            "by 2002:a05:620a:2916:b0:8cf:cc52:635f with SMTP id\n af79cd13be357-8e7912c38b6mr2575162585a.29.1776766509259;\n Tue, 21 Apr 2026 03:15:09 -0700 (PDT)",
            "by 2002:a05:620a:2916:b0:8cf:cc52:635f with SMTP id\n af79cd13be357-8e7912c38b6mr2575151285a.29.1776766508389;\n Tue, 21 Apr 2026 03:15:08 -0700 (PDT)"
        ],
        "To": "dev@openvswitch.org",
        "Date": "Tue, 21 Apr 2026 11:14:21 +0100",
        "Message-ID": "<20260421101422.3608058-3-moloings@redhat.com>",
        "X-Mailer": "git-send-email 2.52.0",
        "In-Reply-To": "<20260421101422.3608058-1-moloings@redhat.com>",
        "References": "<20260421101422.3608058-1-moloings@redhat.com>",
        "MIME-Version": "1.0",
        "X-Mimecast-Spam-Score": "0",
        "X-Mimecast-MFC-PROC-ID": "CIKMzB3-aaxPzSFD0ylTG_UmvQHOONFk-5DS9vvom2A_1776766510",
        "X-Mimecast-Originator": "redhat.com",
        "Subject": "[ovs-dev] [PATCH ovn 2/3] ic: Add transit switch port.",
        "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>",
        "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.\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\nSigned-off-by: Mairtin O'Loingsigh <moloings@redhat.com>\n---\n ic/ovn-ic.c              | 223 +++++++++++++++++++++++++++++++++++++--\n lib/ovn-util.c           |  44 ++++++++\n lib/ovn-util.h           |   4 +\n utilities/ovn-ic-nbctl.c | 201 ++++++++++++++++++++++++++++++++++-\n utilities/ovn-nbctl.c    |  52 +--------\n 5 files changed, 468 insertions(+), 56 deletions(-)",
    "diff": "diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c\nindex ba9490658..f0312e71d 100644\n--- a/ic/ovn-ic.c\n+++ b/ic/ovn-ic.c\n@@ -631,6 +631,33 @@ find_tr_in_nb(struct ic_context *ctx, char *tr_name)\n     return NULL;\n }\n \n+static const struct nbrec_logical_router *\n+find_router_by_port(struct ic_context *ctx, const char *port_name)\n+{\n+    const struct nbrec_logical_router_port *key =\n+        nbrec_logical_router_port_index_init_row(ctx->nbrec_lrp_by_name);\n+    nbrec_logical_router_port_index_set_name(key, port_name);\n+\n+    const struct nbrec_logical_router *lr;\n+    const struct nbrec_logical_router_port *lrp;\n+    bool found = false;\n+    NBREC_LOGICAL_ROUTER_FOR_EACH (lr, ctx->ovnnb_idl) {\n+        for (size_t i = 0; i < lr->n_ports; i++) {\n+            lrp = lr->ports[i];\n+            if (!strcmp(lrp->name, port_name)) {\n+                found = true;\n+                break;\n+            }\n+        }\n+    }\n+\n+    nbrec_logical_router_port_index_destroy_row(key);\n+    if (found) {\n+        return lr;\n+    }\n+    return NULL;\n+}\n+\n static const struct sbrec_port_binding *\n find_sb_pb_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name,\n                    const char *name)\n@@ -733,6 +760,29 @@ 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_tr_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 (!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+    if (!tsp->option) {\n+        return NULL;\n+    }\n+\n+    const struct sbrec_port_binding *peer =\n+        find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, tsp->option);\n+\n+    return peer->n_mac ? *peer->mac : NULL;\n+}\n+\n static const struct sbrec_chassis *\n find_sb_chassis(struct ic_context *ctx, const char *name)\n {\n@@ -793,8 +843,8 @@ get_router_uuid_by_sb_pb(struct ic_context *ctx,\n }\n \n static void\n-update_isb_pb_external_ids(struct ic_context *ctx,\n-                           const struct sbrec_port_binding *sb_pb,\n+update_isb_pb_external_ids(struct ic_context *ctx OVS_UNUSED,\n+                           const struct sbrec_port_binding *sb_pb OVS_UNUSED,\n                            const struct icsbrec_port_binding *isb_pb)\n {\n     struct uuid lr_uuid;\n@@ -864,6 +914,52 @@ sync_local_port(struct ic_context *ctx,\n     /* Sync back tunnel key from ISB to NB */\n     sync_lsp_tnl_key(lsp, isb_pb->tunnel_key);\n }\n+/* For each local port:\n+ *   - Sync from ISB to NB.\n+ *   - Sync from ISB to SB.\n+ */\n+static void\n+sync_switch_port(struct ic_context *ctx OVS_UNUSED,\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+    const char *address = get_lp_address_for_tr_pb(ctx, tsp);\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+    /* Sync gateway from SB to ISB */\n+    const struct sbrec_port_binding *crp = find_crp_for_sb_pb(ctx, sb_pb);\n+    if (crp && crp->chassis) {\n+        if (strcmp(crp->chassis->name, isb_pb->gateway)) {\n+            icsbrec_port_binding_set_gateway(isb_pb, crp->chassis->name);\n+        }\n+    } else if (!strcmp(tsp->type, \"switch\") && sb_pb->chassis) {\n+        if (strcmp(sb_pb->chassis->name, isb_pb->gateway)) {\n+            icsbrec_port_binding_set_gateway(isb_pb, sb_pb->chassis->name);\n+        }\n+    } else {\n+        if (isb_pb->gateway[0]) {\n+            icsbrec_port_binding_set_gateway(isb_pb, \"\");\n+        }\n+    }\n+\n+    /* Sync external_ids:router-id to ISB */\n+    update_isb_pb_external_ids(ctx, sb_pb, isb_pb);\n+\n+    /* Sync back tunnel key from ISB to NB */\n+    sync_lsp_tnl_key(lsp, isb_pb->tunnel_key);\n+}\n \n /* For each remote port:\n  *   - Sync from ISB to NB\n@@ -991,6 +1087,21 @@ allocate_port_key(struct hmap *pb_tnlids)\n                               1, (1u << 15) - 1, &hint);\n }\n \n+static void\n+set_isb_pb_peer(struct ic_context *ctx,\n+                const struct icnbrec_transit_switch_port *tsp,\n+                const struct icsbrec_port_binding *isb_pb)\n+{\n+    const struct nbrec_logical_router *lr;\n+    lr = find_router_by_port(ctx, tsp->option);\n+    if (lr) {\n+        char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&lr->header_.uuid));\n+        icsbrec_port_binding_update_external_ids_setkey(isb_pb, \"router-id\",\n+                                                        uuid_s);\n+        free(uuid_s);\n+    }\n+}\n+\n static const struct icsbrec_port_binding *\n create_isb_pb(struct ic_context *ctx, const char *logical_port,\n               const struct icsbrec_availability_zone *az, const char *ts_name,\n@@ -1010,6 +1121,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,7 +1140,7 @@ 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+chassis_is_remote(struct ic_context *ctx, const char *chassis_name)\n {\n     if (chassis_name) {\n         const struct sbrec_chassis *chassis =\n@@ -1057,11 +1169,41 @@ 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 *lr,\n+           const struct icsbrec_port_binding *isb_pb,\n+           const struct icnbrec_transit_switch_port *tsp)\n+{\n+    bool router_port = !strcmp(tsp->type, \"router\");\n+    const char *type = router_port ? \"router\" : \"localnet\";\n+    const char *option_name = router_port ? \"router-port\" : \"network_name\";\n+    struct smap options = SMAP_CONST1(&options, option_name, tsp->option);\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+\n+    nbrec_logical_switch_port_set_options(lsp, &options);\n+\n+    nbrec_logical_switch_update_ports_addvalue(lr, lsp);\n+\n+    char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&lr->header_.uuid));\n+    icsbrec_port_binding_update_external_ids_setkey(isb_pb, \"router-id\",\n+                                                    uuid_s);\n+    free(uuid_s);\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 +1260,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 +1267,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_def(&lsp->options, \"interconn-ts\", NULL)) {\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 +1297,59 @@ port_binding_run(struct ic_context *ctx)\n         }\n         icsbrec_port_binding_index_destroy_row(isb_pb_key);\n \n+        for (int i = 0; i < ts->n_ports; i++) {\n+            struct icnbrec_transit_switch_port *tsp = ts->ports[i];\n+            if (!chassis_is_remote(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+                    set_isb_pb_peer(ctx, tsp, isb_pb);\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+                }\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, isb_pb, 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, isb_pb, tsp);\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+        /* 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@@ -1193,6 +1395,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 +1410,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 +1459,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 (chassis_is_remote(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);\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 bcb344de4..df78c7342 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/utilities/ovn-ic-nbctl.c b/utilities/ovn-ic-nbctl.c\nindex 50e975283..1ba11ddf9 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 NETWORK  add a transit switch PORT\\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@@ -617,6 +620,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 +699,34 @@ ic_nbctl_trp_del(struct ctl_context *ctx)\n     icnbrec_transit_router_port_delete(trp);\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+    ctx->error = tsp_by_name_or_uuid(ctx, tsp_name, must_exist, &tsp);\n+    if (ctx->error) {\n+        return;\n+    }\n+\n+    if (!tsp) {\n+        return;\n+    }\n+\n+    const struct icnbrec_transit_switch *ts = NULL;\n+    char *ts_uuid = uuid_to_string(&tsp->ts_uuid);\n+    ctx->error = ts_by_name_or_uuid(ctx, ts_uuid, true, &ts);\n+    free(ts_uuid);\n+    if (ctx->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 +865,137 @@ 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+            return;\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+    icnbrec_transit_switch_port_set_ts_uuid(tsp, ts->header_.uuid);\n+\n+    int n_settings = ctx->argc - 3;\n+    char **settings = (char **) &ctx->argv[3];\n+    for (int 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+        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+    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+        const struct icnbrec_transit_switch *ts;\n+        ts = icnbrec_transit_switch_get_for_uuid(ctx->idl, &tsp->ts_uuid);\n+        ctx->error = tsp_contains_duplicates(ts, tsp, ctx->argv[i]);\n+        if (ctx->error) {\n+            ctl_error(ctx, \"%s\", 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@@ -1317,7 +1511,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 +1529,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 0ef207272..56cfdf5b4 100644\n--- a/utilities/ovn-nbctl.c\n+++ b/utilities/ovn-nbctl.c\n@@ -1482,50 +1482,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@@ -1550,8 +1506,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@@ -8835,8 +8791,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",
        "2/3"
    ]
}