Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/2194259/?format=api
{ "id": 2194259, "url": "http://patchwork.ozlabs.org/api/patches/2194259/?format=api", "web_url": "http://patchwork.ozlabs.org/project/ovn/patch/20260207214852.86991-1-arukomoinikova@k2.cloud/", "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": "<20260207214852.86991-1-arukomoinikova@k2.cloud>", "list_archive_url": null, "date": "2026-02-07T21:48:42", "name": "[ovs-dev,1/8,v2] ovn-nb, ovn-nbctl: Add Logical Switch Port Health Check support.", "commit_ref": null, "pull_url": null, "state": "changes-requested", "archived": false, "hash": "d27873007979fa77ca9b54a06481cc44c3367f82", "submitter": { "id": 89461, "url": "http://patchwork.ozlabs.org/api/people/89461/?format=api", "name": "Rukomoinikova Aleksandra", "email": "ARukomoinikova@k2.cloud" }, "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/20260207214852.86991-1-arukomoinikova@k2.cloud/mbox/", "series": [ { "id": 491397, "url": "http://patchwork.ozlabs.org/api/series/491397/?format=api", "web_url": "http://patchwork.ozlabs.org/project/ovn/list/?series=491397", "date": "2026-02-07T21:48:46", "name": "[ovs-dev,1/8,v2] ovn-nb, ovn-nbctl: Add Logical Switch Port Health Check support.", "version": 2, "mbox": "http://patchwork.ozlabs.org/series/491397/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2194259/comments/", "check": "warning", "checks": "http://patchwork.ozlabs.org/api/patches/2194259/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=k2.cloud header.i=@k2.cloud header.a=rsa-sha256\n header.s=cloudmail header.b=J43gT9dx;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=2605:bc80:3010::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=k2.cloud header.i=@k2.cloud header.a=rsa-sha256 header.s=cloudmail\n header.b=J43gT9dx", "smtp1.osuosl.org;\n dmarc=pass (p=none dis=none) header.from=k2.cloud", "smtp1.osuosl.org; dkim=pass (1024-bit key,\n unprotected) header.d=k2.cloud header.i=@k2.cloud header.a=rsa-sha256\n header.s=cloudmail header.b=J43gT9dx" ], "Received": [ "from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::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 4f7l3M3jHYz1xvW\n\tfor <incoming@patchwork.ozlabs.org>; Sun, 08 Feb 2026 08:49:19 +1100 (AEDT)", "from localhost (localhost [127.0.0.1])\n\tby smtp4.osuosl.org (Postfix) with ESMTP id 543D341BFB;\n\tSat, 7 Feb 2026 21:49:13 +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 S8cnCGUu36bg; Sat, 7 Feb 2026 21:49:06 +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 902FC41C01;\n\tSat, 7 Feb 2026 21:49:05 +0000 (UTC)", "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id 669DBC0780;\n\tSat, 7 Feb 2026 21:49:05 +0000 (UTC)", "from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138])\n by lists.linuxfoundation.org (Postfix) with ESMTP id 97D6BC02A4\n for <dev@openvswitch.org>; Sat, 7 Feb 2026 21:49:01 +0000 (UTC)", "from localhost (localhost [127.0.0.1])\n by smtp1.osuosl.org (Postfix) with ESMTP id 77B5983330\n for <dev@openvswitch.org>; Sat, 7 Feb 2026 21:49:01 +0000 (UTC)", "from smtp1.osuosl.org ([127.0.0.1])\n by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id 6sFXUcsK8_6o for <dev@openvswitch.org>;\n Sat, 7 Feb 2026 21:48:59 +0000 (UTC)", "from mail1.k2.cloud (mail1.k2.cloud [109.73.14.252])\n by smtp1.osuosl.org (Postfix) with ESMTPS id DD85380ED6\n for <dev@openvswitch.org>; Sat, 7 Feb 2026 21:48:57 +0000 (UTC)" ], "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 902FC41C01", "OpenDKIM Filter v2.11.0 smtp1.osuosl.org DD85380ED6" ], "Received-SPF": "Pass (mailfrom) identity=mailfrom; client-ip=109.73.14.252;\n helo=mail1.k2.cloud; envelope-from=arukomoinikova@k2.cloud;\n receiver=<UNKNOWN>", "DMARC-Filter": "OpenDMARC Filter v1.4.2 smtp1.osuosl.org DD85380ED6", "From": "Alexandra Rukomoinikova <arukomoinikova@k2.cloud>", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=k2.cloud;\n s=cloudmail; t=1770500934;\n bh=rltx91eR4UtHxYQitCzPL37yChgeydWxTjy7s7aLpGw=;\n h=From:To:Cc:Subject:Date;\n b=J43gT9dxDKIrbVzWDT77Ggigax6+3Y09oKE8JR2/4wd3ByARylCvyIZvAgjxpV0W5\n hecbuyaHQHAHkLpgFobE9XVVcR4uedXGh83/TpQYzDOWlV9m/m5tD9l56PQvkMQAZo\n y8pqUJ+8gCruCmMfxEiAJqhxRGBbkPE2czn6lq54=", "To": "dev@openvswitch.org", "Cc": "Alexandra Rukomoinikova <arukomoinikova@k2.cloud>", "Date": "Sun, 8 Feb 2026 00:48:42 +0300", "Message-Id": "<20260207214852.86991-1-arukomoinikova@k2.cloud>", "MIME-Version": "1.0", "Subject": "[ovs-dev] [PATCH ovn 1/8 v2] ovn-nb,\n ovn-nbctl: Add Logical Switch Port Health Check support.", "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>", "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": "Add schema and CLI support for health checks on logical switch\nports by introducing a new health check table and linking it\nfrom logical switch ports. Implement corresponding ovn-nbctl\ncommands to manage LSP health checks. Also extend service\nmonitoring to support the logical-switch-port type.\n\nSigned-off-by: Alexandra Rukomoinikova <arukomoinikova@k2.cloud>\n---\nv1 --> v2: corrected all the comments and made one service monitor bind to only one address\n---\n ovn-nb.ovsschema | 28 ++-\n ovn-nb.xml | 53 ++++\n ovn-sb.ovsschema | 7 +-\n ovn-sb.xml | 11 +\n tests/ovn-nbctl.at | 196 +++++++++++++++\n utilities/ovn-nbctl.8.xml | 52 ++++\n utilities/ovn-nbctl.c | 516 ++++++++++++++++++++++++++++++++++++--\n 7 files changed, 842 insertions(+), 21 deletions(-)", "diff": "diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema\nindex 8c2c1d861..538b37456 100644\n--- a/ovn-nb.ovsschema\n+++ b/ovn-nb.ovsschema\n@@ -1,7 +1,7 @@\n {\n \"name\": \"OVN_Northbound\",\n- \"version\": \"7.15.0\",\n- \"cksum\": \"4060410729 43708\",\n+ \"version\": \"7.16.0\",\n+ \"cksum\": \"3492890733 44939\",\n \"tables\": {\n \"NB_Global\": {\n \"columns\": {\n@@ -179,6 +179,11 @@\n \"refType\": \"strong\"},\n \"min\": 0,\n \"max\": 1}},\n+ \"health_checks\": {\"type\": {\"key\": {\"type\": \"uuid\",\n+ \"refTable\": \"Logical_Switch_Port_Health_Check\",\n+ \"refType\": \"strong\"},\n+ \"min\": 0,\n+ \"max\": \"unlimited\"}},\n \"external_ids\": {\n \"type\": {\"key\": \"string\", \"value\": \"string\",\n \"min\": 0, \"max\": \"unlimited\"}}},\n@@ -246,6 +251,25 @@\n \"min\": 0, \"max\": \"unlimited\"}}},\n \"indexes\": [[\"name\"], [\"id\"]],\n \"isRoot\": true},\n+ \"Logical_Switch_Port_Health_Check\": {\n+ \"columns\": {\n+ \"protocol\": {\n+ \"type\": {\"key\": {\"type\": \"string\",\n+ \"enum\": [\"set\", [\"tcp\", \"udp\", \"icmp\"]]},\n+ \"min\": 0, \"max\": 1}},\n+ \"src_ip\": {\"type\": \"string\"},\n+ \"port\": {\"type\": {\"key\": {\"type\": \"integer\",\n+ \"minInteger\": 0,\n+ \"maxInteger\": 65535}}},\n+ \"address\": {\"type\": {\"key\": \"string\",\n+ \"min\": 0,\n+ \"max\": 1}},\n+ \"options\": {\n+ \"type\": {\"key\": \"string\",\n+ \"value\": \"string\",\n+ \"min\": 0,\n+ \"max\": \"unlimited\"}}},\n+ \"isRoot\": false},\n \"Forwarding_Group\": {\n \"columns\": {\n \"name\": {\"type\": \"string\"},\ndiff --git a/ovn-nb.xml b/ovn-nb.xml\nindex 1acbf202b..e60edbd8d 100644\n--- a/ovn-nb.xml\n+++ b/ovn-nb.xml\n@@ -2136,6 +2136,11 @@\n Please see the <ref table=\"Mirror\"/> table.\n </column>\n \n+ <column name=\"health_checks\">\n+ Health checks configuration for this logical switch port.\n+ Please see the <ref table=\"Logical_Switch_Port_Health_Check\"/> table.\n+ </column>\n+\n <column name=\"ha_chassis_group\">\n References a row in the OVN Northbound database's\n <ref table=\"HA_Chassis_Group\" db=\"OVN_Northbound\"/> table.\n@@ -2192,6 +2197,54 @@\n </group>\n </table>\n \n+ <table name=\"Logical_Switch_Port_Health_Check\"\n+ title=\"logical switch port health check\">\n+ <p>\n+ Each row represents health check configuration for logical switch port.\n+ Health checks are used to monitor reachability and health of backend\n+ endpoints associated with logical switch port. Health check can be\n+ configured to use different protocols (TCP, UDP, or ICMP) to verify\n+ availability of target IP addresses.\n+ </p>\n+\n+ <column name=\"protocol\">\n+ Valid protocols are <code>tcp</code>, <code>udp</code>, or\n+ <code>icmp</code>. For <code>tcp</code> and <code>udp</code>\n+ protocols - destination port must be specified.\n+ </column>\n+\n+ <column name=\"src_ip\">\n+ Source IP address used when sending health check probes.\n+ </column>\n+\n+ <column name=\"port\">\n+ Destination port number for TCP and UDP health checks.\n+ </column>\n+\n+ <column name=\"address\">\n+ IP address to monitor for the health check.\n+ </column>\n+\n+ <column name=\"options\" key=\"interval\" type='{\"type\": \"integer\"}'>\n+ The interval, in seconds, between service monitor checks.\n+ </column>\n+\n+ <column name=\"options\" key=\"timeout\" type='{\"type\": \"integer\"}'>\n+ The time, in seconds, after which the service monitor check times\n+ out.\n+ </column>\n+\n+ <column name=\"options\" key=\"success_count\" type='{\"type\": \"integer\"}'>\n+ The number of successful checks after which the service is\n+ considered <code>online</code>.\n+ </column>\n+\n+ <column name=\"options\" key=\"failure_count\" type='{\"type\": \"integer\"}'>\n+ The number of failure checks after which the service is considered\n+ <code>offline</code>.\n+ </column>\n+ </table>\n+\n <table name=\"Forwarding_Group\" title=\"forwarding group\">\n <p>\n Each row represents one forwarding group.\ndiff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema\nindex cf33933da..dd9e32f06 100644\n--- a/ovn-sb.ovsschema\n+++ b/ovn-sb.ovsschema\n@@ -1,7 +1,7 @@\n {\n \"name\": \"OVN_Southbound\",\n- \"version\": \"21.7.0\",\n- \"cksum\": \"1383351379 36646\",\n+ \"version\": \"21.8.0\",\n+ \"cksum\": \"3491605797 36713\",\n \"tables\": {\n \"SB_Global\": {\n \"columns\": {\n@@ -516,7 +516,8 @@\n \"type\": {\"type\": {\"key\": {\n \"type\": \"string\",\n \"enum\": [\"set\", [\"load-balancer\",\n- \"network-function\"]]},\n+ \"network-function\",\n+ \"logical-switch-port\"]]},\n \"min\": 0, \"max\": 1}},\n \"ip\": {\"type\": \"string\"},\n \"mac\": {\"type\": \"string\"},\ndiff --git a/ovn-sb.xml b/ovn-sb.xml\nindex 00bae26bf..0ec7d6094 100644\n--- a/ovn-sb.xml\n+++ b/ovn-sb.xml\n@@ -5042,6 +5042,8 @@ tcp.flags = RST;\n icmp, and the health probe is done by injecting an icmp echo request\n packet into the <code>inport</code> of the Network_Function and then\n montoring the same packet coming out of the <code>outport</code>.\n+ For <code>type</code> \"logical-switch-port\", supported protocols are\n+ <code>tcp</code>, <code>udp</code> and <code>icmp</code>.\n </column>\n \n <column name=\"port\">\n@@ -5124,6 +5126,15 @@ tcp.flags = RST;\n the service and doesn't expect any reply. If it receives an ICMP\n reply, then it considers the service to be <code>offline</code>.\n </p>\n+\n+ <p>\n+ For ICMP service, <code>ovn-controller</code> sends an ICMP\n+ Echo Request and expects an ICMP Echo Reply to consider service\n+ to be <code>online</code>. If no reply is received or if an ICMP\n+ error message is received, the service is considered\n+ <code>offline</code>.\n+ </p>\n+\n </column>\n </group>\n \ndiff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at\nindex dccf30758..177e097fb 100644\n--- a/tests/ovn-nbctl.at\n+++ b/tests/ovn-nbctl.at\n@@ -3499,3 +3499,199 @@ AT_CHECK([ovn-nbctl --may-exist lsp-add-localnet-port ls ln_port net1], [1], [],\n check ovn-nbctl lsp-set-options ln_port network_name=net1\n check ovn-nbctl --may-exist lsp-add-localnet-port ls ln_port net1\n ])\n+\n+dnl ---------------------------------------------------------------------\n+\n+AT_SETUP([ovn-nbctl - Logical Switch Port Health Check])\n+OVN_NBCTL_TEST_START daemon\n+\n+# Create logical switch port\n+AT_CHECK([ovn-nbctl ls-add ls0])\n+AT_CHECK([ovn-nbctl lsp-add ls0 lport0])\n+AT_CHECK([ovn-nbctl lsp-set-addresses lport0 \"00:11:22:33:44:55 192.168.1.10\" \"00:11:22:33:44:56 192.168.1.11\"])\n+\n+# Create IPV4 ICMP health check\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 icmp 192.168.0.255 192.168.1.10])\n+AT_CHECK([ovn-nbctl lsp-hc-list lport0], [0], [dnl\n+Logical Switch Port lport0:\n+ Protocol : icmp\n+ Source IP : 192.168.0.255\n+ Addresses : 192.168.1.10\n+])\n+\n+# Check hc attaching to logical switch port\n+hc_icmp_uuid=$(fetch_column nb:logical_switch_port_health_check _uuid src_ip=\"192.168.0.255\")\n+check_column \"$hc_icmp_uuid\" nb:logical_switch_port health_checks\n+\n+# Create IPv4 TCP health check with the specified addresses\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 tcp 10.0.0.2 80 192.168.1.10])\n+AT_CHECK([ovn-nbctl lsp-hc-list lport0], [0], [dnl\n+Logical Switch Port lport0:\n+ Protocol : tcp\n+ Source IP : 10.0.0.2\n+ Port : 80\n+ Addresses : 192.168.1.10\n+\n+ Protocol : icmp\n+ Source IP : 192.168.0.255\n+ Addresses : 192.168.1.10\n+])\n+hc_tcp_uuid=$(fetch_column nb:logical_switch_port_health_check _uuid src_ip=\"10.0.0.2\")\n+\n+# Create UDP health check with the specified addresses\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 udp 10.0.0.3 53 192.168.1.11])\n+AT_CHECK([ovn-nbctl lsp-hc-list lport0], [0], [dnl\n+Logical Switch Port lport0:\n+ Protocol : tcp\n+ Source IP : 10.0.0.2\n+ Port : 80\n+ Addresses : 192.168.1.10\n+\n+ Protocol : udp\n+ Source IP : 10.0.0.3\n+ Port : 53\n+ Addresses : 192.168.1.11\n+\n+ Protocol : icmp\n+ Source IP : 192.168.0.255\n+ Addresses : 192.168.1.10\n+])\n+hc_udp_uuid=$(fetch_column nb:logical_switch_port_health_check _uuid src_ip=\"10.0.0.3\")\n+\n+check_column \"$hc_icmp_uuid $hc_tcp_uuid $hc_udp_uuid\" nb:logical_switch_port health_checks\n+\n+AT_CHECK([ovn-nbctl lsp-hc-del lport0 $hc_icmp_uuid])\n+# Check hcs detaching to logical switch port\n+check_column \"$hc_tcp_uuid $hc_udp_uuid\" nb:logical_switch_port health_checks\n+\n+AT_CHECK([ovn-nbctl lsp-hc-list lport0], [0], [dnl\n+Logical Switch Port lport0:\n+ Protocol : tcp\n+ Source IP : 10.0.0.2\n+ Port : 80\n+ Addresses : 192.168.1.10\n+\n+ Protocol : udp\n+ Source IP : 10.0.0.3\n+ Port : 53\n+ Addresses : 192.168.1.11\n+])\n+\n+AT_CHECK([ovn-nbctl lsp-hc-del lport0])\n+check_row_count nb:Logical_Switch_Port_Health_Check 0\n+AT_CHECK([ovn-nbctl lsp-hc-list lport0], [0], [dnl])\n+\n+# Check invalid protocol\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 stcp 10.0.0.1 50], [1], [], [stderr])\n+AT_CHECK([grep \"Type must be icmp, tcp or udp\" stderr], [0], [ignore])\n+\n+# Check invalid source IP\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 icmp invalid_ip invalid_ip], [1], [], [stderr])\n+AT_CHECK([grep \"Not a valid IPv4 or IPv6 address\" stderr], [0], [ignore])\n+\n+# Check TCP/UDP non-valid destination port\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 tcp 10.0.0.1 70000], [1], [], [stderr])\n+AT_CHECK([grep \"port must in range 0...65535\" stderr], [0], [ignore])\n+\n+# Check IP address not configured on port\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 icmp 10.0.0.1 192.168.99.99], [1], [], [stderr])\n+AT_CHECK([grep \"Address 192.168.99.99 not configured on port\" stderr], [0], [ignore])\n+\n+AT_CHECK([ovn-nbctl lsp-hc-del lport0])\n+\n+# Different source IP, same protocol, port and address - should be allowed\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 tcp 10.0.0.1 80 192.168.1.10])\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 tcp 10.0.0.2 80 192.168.1.10], [0], [], [ignore])\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 tcp 10.0.0.3 80 192.168.1.10], [0], [], [ignore])\n+\n+# Same source IP, different ports, same protocol and address - should be allowed\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 tcp 10.0.0.1 81 192.168.1.10], [0], [], [ignore])\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 tcp 10.0.0.1 82 192.168.1.10], [0], [], [ignore])\n+\n+# Same source IP and port, different protocols, same address - should be allowed\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 udp 10.0.0.1 80 192.168.1.10], [0], [], [ignore])\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 icmp 10.0.0.1 192.168.1.10], [0], [], [ignore])\n+\n+# Check duplication\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 tcp 10.0.0.5 90 192.168.1.10])\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 tcp 10.0.0.5 90 192.168.1.10], [1], [], [stderr])\n+AT_CHECK([grep \"Health check with address 192.168.1.10 already exists on port lport0\" stderr], [0], [ignore])\n+\n+# ICMP - same source IP, different addresses - should be allowed\n+AT_CHECK([ovn-nbctl lsp-hc-del lport0])\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 icmp 10.0.0.9 192.168.1.10])\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 icmp 10.0.0.9 192.168.1.11], [0], [], [ignore])\n+\n+# ICMP - different source IP, same address - should be allowed\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 icmp 10.0.0.10 192.168.1.10], [0], [], [ignore])\n+\n+# Check supported logical switch port type for monitoring\n+AT_CHECK([ovn-nbctl lsp-add ls0 lport1])\n+AT_CHECK([ovn-nbctl lsp-set-type lport1 router])\n+AT_CHECK([ovn-nbctl lsp-hc-add lport1 icmp 10.0.0.1 10.0.0.2], [1], [], [stderr])\n+AT_CHECK([grep \"Health check monitoring supported only for port with vif type\" stderr], [0], [ignore])\n+\n+AT_CHECK([ovn-nbctl lsp-hc-del lport0])\n+\n+# Test IPv6 addresses support: icmp, tcp, udp\n+AT_CHECK([ovn-nbctl lsp-add ls0 lport3])\n+AT_CHECK([ovn-nbctl lsp-set-addresses lport3 \"00:11:22:33:44:57 2001:db8::1\"])\n+AT_CHECK([ovn-nbctl lsp-hc-add lport3 icmp 2001:db8::2 2001:db8::1])\n+AT_CHECK([ovn-nbctl lsp-hc-add lport3 tcp 2001:db8::2 80 2001:db8::1])\n+AT_CHECK([ovn-nbctl lsp-hc-add lport3 udp 2001:db8::2 53 2001:db8::1])\n+AT_CHECK([ovn-nbctl lsp-hc-list lport3], [0], [dnl\n+Logical Switch Port lport3:\n+ Protocol : icmp\n+ Source IP : 2001:db8::2\n+ Addresses : 2001:db8::1\n+\n+ Protocol : tcp\n+ Source IP : 2001:db8::2\n+ Port : 80\n+ Addresses : 2001:db8::1\n+\n+ Protocol : udp\n+ Source IP : 2001:db8::2\n+ Port : 53\n+ Addresses : 2001:db8::1\n+])\n+\n+AT_CHECK([ovn-nbctl lsp-hc-del lport3])\n+\n+# Duplicate IPv6 health check - should be disabled\n+AT_CHECK([ovn-nbctl lsp-hc-add lport3 icmp 2001:db8::2 2001:db8::1])\n+AT_CHECK([ovn-nbctl lsp-hc-add lport3 icmp 2001:db8::2 2001:db8::1], [1], [], [stderr])\n+AT_CHECK([grep \"Health check with address 2001:db8::1 already exists on port lport3\" stderr], [0], [ignore])\n+\n+# Check options printing\n+AT_CHECK([ovn-nbctl lsp-hc-add lport0 icmp 10.0.0.4 192.168.1.10 192.168.1.11])\n+AT_CHECK([ovn-nbctl lsp-hc-list lport0], [0], [dnl\n+Logical Switch Port lport0:\n+ Protocol : icmp\n+ Source IP : 10.0.0.4\n+ Addresses : 192.168.1.10\n+])\n+hc_icmp_uuid=$(fetch_column nb:logical_switch_port_health_check _uuid src_ip=\"10.0.0.4\")\n+\n+AT_CHECK([ovn-nbctl set logical_switch_port_health_check $hc_icmp_uuid options:interval=3])\n+AT_CHECK([ovn-nbctl set logical_switch_port_health_check $hc_icmp_uuid options:timeout=30])\n+AT_CHECK([ovn-nbctl set logical_switch_port_health_check $hc_icmp_uuid options:success_count=1])\n+AT_CHECK([ovn-nbctl set logical_switch_port_health_check $hc_icmp_uuid options:failure_count=2])\n+\n+AT_CHECK([ovn-nbctl lsp-hc-list lport0], [0], [dnl\n+Logical Switch Port lport0:\n+ Protocol : icmp\n+ Source IP : 10.0.0.4\n+ Addresses : 192.168.1.10\n+ Interval : 3\n+ Timeout : 30\n+ Success count : 1\n+ Failure count : 2\n+])\n+\n+AT_CHECK([ovn-nbctl lsp-hc-del lport0])\n+AT_CHECK([ovn-nbctl lsp-hc-del lport3])\n+check_row_count nb:Logical_Switch_Port_Health_Check 0\n+\n+OVN_NBCTL_TEST_STOP \"/terminating with signal 15/d\"\n+AT_CLEANUP\ndiff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml\nindex 7df902944..c403fcfb6 100644\n--- a/utilities/ovn-nbctl.8.xml\n+++ b/utilities/ovn-nbctl.8.xml\n@@ -1822,6 +1822,58 @@\n </dd>\n </dl>\n \n+ <h2>Health Check commands</h2>\n+ <dl>\n+ <dt><code>lsp-hc-add</code> <var>PORT</var> <var>PROTOCOL</var>\n+ <var>SOURCE_IP</var> [<var>DST_PORT</var>] <var>ADDRESS</var></dt>\n+ <dd>\n+ <p>\n+ Creates a new health check configuration for the logical switch port\n+ <code>PORT</code> with the below mandatory arguments.\n+ </p>\n+\n+ <p>\n+ <var>PROTOCOL</var> specifies the health check protocol -\n+ <code>tcp</code>, <code>udp</code>, or <code>icmp</code>.\n+ </p>\n+\n+ <p>\n+ <var>SOURCE_IP</var> specifies the source IP address used when\n+ sending health check probes.\n+ </p>\n+\n+ <p>\n+ <var>DST_PORT</var> specifies the destination port number for\n+ <code>tcp</code> and <code>udp</code> health checks. This parameter\n+ is ignored for <code>icmp</code> protocol.\n+ </p>\n+\n+ <p>\n+ <var>ADDRESS</var> specifies the IP address to monitor.\n+ </p>\n+\n+ <p>\n+ Additional health check options such as interval, timeout,\n+ success count, and failure count can be configured separately.\n+ </p>\n+ </dd>\n+\n+ <dt><code>lsp-hc-del</code> <var>PORT</var> [<var>HC_UUID</var>]</dt>\n+ <dd>\n+ <p>\n+ Deletes health check configuration for the logical switch port\n+ <code>PORT</code>.\n+ </p>\n+ </dd>\n+\n+ <dt><code>lsp-hc-list</code> <var>PORT</var></dt>\n+ <dd>\n+ Lists all health check configurations for the logical switch port\n+ <code>PORT</code>, including protocol, source IP, destination port,\n+ monitored addresses, and current status.\n+ </dd>\n+ </dl>\n+\n <h2>Synchronization Commands</h2>\n \n <dl>\ndiff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c\nindex cdf6b578a..e58001148 100644\n--- a/utilities/ovn-nbctl.c\n+++ b/utilities/ovn-nbctl.c\n@@ -66,6 +66,18 @@ string_ptr(char *ptr)\n return (ptr) ? ptr : s;\n }\n \n+static char *OVS_WARN_UNUSED_RESULT\n+parse_l4_port_range(const char *arg, uint16_t *port_p)\n+{\n+ int64_t port;\n+ if (!ovs_scan(arg, \"%\"SCNd64, &port)\n+ || port < 0 || port > UINT16_MAX) {\n+ return xasprintf(\"%s: port must in range 0...65535\", arg);\n+ }\n+ *port_p = port;\n+ return NULL;\n+}\n+\n static void\n nbctl_add_base_prerequisites(struct ovsdb_idl *idl,\n enum nbctl_wait_type wait_type)\n@@ -544,6 +556,12 @@ MAC_Binding commands:\\n\\\n Delete Static_MAC_Binding entry\\n\\\n static-mac-binding-list List all Static_MAC_Binding entries\\n\\\n \\n\\\n+Logical Switch Port Health Check:\\n\\\n+ lsp-hc-add PORT PROTOCOL SOURCE_IP DST_PORT ADDRESS...\\n\\\n+ add health check monitoring for PORT\\n\\\n+ lsp-hc-del PORT HC_UUID delete health check monitoring for PORT\\n\\\n+ lsp-hc-list PORT print health check for PORT\\n\\\n+\\n\\\n %s\\\n %s\\\n \\n\\\n@@ -1464,18 +1482,23 @@ nbctl_pre_lsp_set_addresses(struct ctl_context *ctx)\n &nbrec_logical_switch_port_col_dynamic_addresses);\n }\n \n-static char *\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+ 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- return xasprintf(\"duplicate IPv4 address '%s' found on \"\n- \"logical switch port '%s'\",\n- laddrs1->ipv4_addrs[i].addr_s,\n- lsp_test->name);\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@@ -1484,15 +1507,23 @@ lsp_contains_duplicate_ip(struct lport_addresses *laddrs1,\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- return xasprintf(\"duplicate IPv6 address '%s' found on \"\n- \"logical switch port '%s'\",\n- laddrs1->ipv6_addrs[i].addr_s,\n- lsp_test->name);\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- return NULL;\n+ if (error_str) {\n+ *error_str = NULL;\n+ }\n+\n+ return false;\n }\n \n static char *\n@@ -1500,12 +1531,13 @@ lsp_contains_duplicates(const struct nbrec_logical_switch *ls,\n const struct nbrec_logical_switch_port *lsp,\n const char *address)\n {\n+ char *error = NULL;\n+ char *sub_error = NULL;\n struct lport_addresses laddrs;\n if (!extract_lsp_addresses(address, &laddrs)) {\n return NULL;\n }\n \n- char *sub_error = NULL;\n for (size_t i = 0; i < ls->n_ports; i++) {\n struct nbrec_logical_switch_port *lsp_test = ls->ports[i];\n if (lsp_test == lsp) {\n@@ -1518,10 +1550,11 @@ lsp_contains_duplicates(const struct nbrec_logical_switch *ls,\n addr = lsp_test->dynamic_addresses;\n }\n if (extract_lsp_addresses(addr, &laddrs_test)) {\n- sub_error = lsp_contains_duplicate_ip(&laddrs, &laddrs_test,\n- lsp_test);\n+ bool has_duplicate =\n+ lsp_contains_duplicate_ip(&laddrs, &laddrs_test,\n+ lsp_test, &sub_error);\n destroy_lport_addresses(&laddrs_test);\n- if (sub_error) {\n+ if (has_duplicate) {\n goto err_out;\n }\n }\n@@ -1529,7 +1562,6 @@ lsp_contains_duplicates(const struct nbrec_logical_switch *ls,\n }\n \n err_out: ;\n- char *error = NULL;\n if (sub_error) {\n error = xasprintf(\"Error on switch %s: %s\", ls->name, sub_error);\n free(sub_error);\n@@ -8677,6 +8709,448 @@ nbctl_lsp_add_misc_port(struct ctl_context *ctx)\n shash_add(&nbctx->lsp_to_ls_map, lsp_name, ls);\n }\n \n+/* Logical Switch Port Health Check Functions. */\n+enum health_check_protocol {\n+ LSP_ICMP_HEALTH_CHECK,\n+ LSP_TCP_HEALTH_CHECK,\n+ LSP_UDP_HEALTH_CHECK,\n+};\n+\n+static char *\n+lsp_health_check_parse_protocol(const char *type,\n+ enum health_check_protocol *protocol)\n+{\n+ char *error = NULL;\n+\n+ if (!strcmp(type, \"icmp\")) {\n+ *protocol = LSP_ICMP_HEALTH_CHECK;\n+ } else if (!strcmp(type, \"tcp\")) {\n+ *protocol = LSP_TCP_HEALTH_CHECK;\n+ } else if (!strcmp(type, \"udp\")) {\n+ *protocol = LSP_UDP_HEALTH_CHECK;\n+ } else {\n+ error = xasprintf(\"%s: Type must be icmp, tcp or udp.\", type);\n+ }\n+\n+ return error;\n+}\n+\n+static char *\n+lsp_health_check_parse_dst_port(struct ctl_context *ctx,\n+ enum health_check_protocol protocol,\n+ const char *protocol_str,\n+ uint16_t *destination_port,\n+ int *target_ip_index)\n+{\n+ char *error;\n+\n+ switch (protocol) {\n+ case LSP_TCP_HEALTH_CHECK:\n+ case LSP_UDP_HEALTH_CHECK:\n+ if (ctx->argc < 5) {\n+ error = xasprintf(\"Destination port required for \"\n+ \"%s health check.\", protocol_str);\n+ return error;\n+ }\n+\n+ error = parse_l4_port_range(ctx->argv[4], destination_port);\n+ if (error) {\n+ return error;\n+ }\n+\n+ *target_ip_index = 5;\n+ break;\n+\n+ case LSP_ICMP_HEALTH_CHECK:\n+ *target_ip_index = 4;\n+ break;\n+\n+ default:\n+ OVS_NOT_REACHED();\n+ }\n+\n+ if (*target_ip_index == ctx->argc) {\n+ error = xasprintf(\"No addresses specified for health checking.\");\n+ return error;\n+ }\n+\n+ return NULL;\n+}\n+\n+static bool\n+lsp_health_check_get_duplicate_record(\n+ int64_t destination_port, const char *protocol_str,\n+ const char *source_ip_str, const char *target_ip_str,\n+ const struct nbrec_logical_switch_port *lsp)\n+{\n+ for (size_t i = 0; i < lsp->n_health_checks; i++) {\n+ const struct nbrec_logical_switch_port_health_check *lsp_hc_p =\n+ lsp->health_checks[i];\n+\n+ if (!strcmp(lsp_hc_p->src_ip, source_ip_str) &&\n+ !strcmp(lsp_hc_p->protocol, protocol_str)) {\n+\n+ if (strcmp(protocol_str, \"icmp\") &&\n+ lsp_hc_p->port != destination_port) {\n+ continue;\n+ }\n+\n+ if (strcmp(lsp_hc_p->address, target_ip_str) == 0) {\n+ return true;\n+ }\n+ }\n+ }\n+\n+ return false;\n+}\n+\n+static char *\n+lsp_health_check_parse_target_address(\n+ const char *target_ip_str, const char *protocol_str,\n+ const char *source_ip_str, const int64_t destination_port,\n+ const struct nbrec_logical_switch_port *lsp)\n+{\n+ bool ip_found_on_port = false;\n+ char *error = NULL;\n+\n+ struct lport_addresses target_address;\n+ if (!extract_ip_address(target_ip_str, &target_address)) {\n+ error = xasprintf(\"Not a valid IPv4 or IPv6 address %s.\",\n+ target_ip_str);\n+ return error;\n+ }\n+\n+ struct lport_addresses lsp_address;\n+ for (size_t i = 0; i < lsp->n_addresses; i++) {\n+ if (!extract_lsp_addresses(lsp->addresses[i], &lsp_address)) {\n+ error = xasprintf(\"Error extracting logical switch port address.\");\n+ goto cleanup;\n+ }\n+\n+ if (lsp_contains_duplicate_ip(&target_address,\n+ &lsp_address, lsp, NULL)) {\n+ ip_found_on_port = true;\n+ }\n+\n+ destroy_lport_addresses(&lsp_address);\n+\n+ if (ip_found_on_port) {\n+ break;\n+ }\n+ }\n+\n+ if (!ip_found_on_port) {\n+ error = xasprintf(\"Address %s not configured on port %s.\",\n+ target_ip_str, lsp->name);\n+ goto cleanup;\n+ }\n+\n+ if (lsp_health_check_get_duplicate_record(destination_port,\n+ protocol_str,\n+ source_ip_str,\n+ target_ip_str, lsp)) {\n+ error = xasprintf(\"Health check with address %s already exists \"\n+ \"on port %s.\", target_ip_str, lsp->name);\n+ goto cleanup;\n+ }\n+\n+cleanup:\n+ destroy_lport_addresses(&target_address);\n+ return error;\n+}\n+\n+static void\n+nbctl_pre_lsp_health_check_add(struct ctl_context *ctx)\n+{\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_col_name);\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_col_addresses);\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_col_health_checks);\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_health_check_col_port);\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_health_check_col_protocol);\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_health_check_col_src_ip);\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_health_check_col_address);\n+}\n+\n+static void\n+nbctl_lsp_health_check_add(struct ctl_context *ctx)\n+{\n+ const struct nbrec_logical_switch_port *lsp = NULL;\n+ const struct nbrec_logical_switch_port_health_check *lsp_hc = NULL;\n+\n+ const char *lsp_port_name = ctx->argv[1];\n+ const char *protocol_type = ctx->argv[2];\n+ const char *src_ip = ctx->argv[3];\n+ char *error = NULL;\n+\n+ error = lsp_by_name_or_uuid(ctx, lsp_port_name, true, &lsp);\n+ if (error) {\n+ ctx->error = error;\n+ return;\n+ }\n+\n+ if (lsp->type && lsp->type[0]) {\n+ ctl_error(ctx, \"%s: Health check monitoring supported only for\"\n+ \" port with vif type.\", lsp->type);\n+ return;\n+ }\n+\n+ char *validated_src_ip = normalize_addr_str(src_ip);\n+ if (!validated_src_ip) {\n+ ctl_error(ctx, \"%s: Not a valid IPv4 or IPv6 address.\", src_ip);\n+ goto cleanup;\n+ }\n+\n+ enum health_check_protocol protocol;\n+ error = lsp_health_check_parse_protocol(protocol_type, &protocol);\n+ if (error) {\n+ ctx->error = error;\n+ goto cleanup;\n+ }\n+\n+ uint16_t destination_port = 0;\n+ int target_ip_index = 0;\n+ error = lsp_health_check_parse_dst_port(ctx, protocol,\n+ protocol_type,\n+ &destination_port,\n+ &target_ip_index);\n+ if (error) {\n+ ctx->error = error;\n+ goto cleanup;\n+ }\n+\n+ const char *target_address = ctx->argv[target_ip_index];\n+ error = lsp_health_check_parse_target_address(\n+ target_address, protocol_type,\n+ validated_src_ip, destination_port, lsp);\n+ if (error) {\n+ ctx->error = error;\n+ goto cleanup;\n+ }\n+\n+ lsp_hc = nbrec_logical_switch_port_health_check_insert(ctx->txn);\n+ nbrec_logical_switch_port_health_check_set_protocol(lsp_hc,\n+ protocol_type);\n+ nbrec_logical_switch_port_health_check_set_src_ip(lsp_hc,\n+ validated_src_ip);\n+ nbrec_logical_switch_port_health_check_set_port(lsp_hc,\n+ destination_port);\n+\n+ nbrec_logical_switch_port_health_check_set_address(lsp_hc,\n+ target_address);\n+\n+ nbrec_logical_switch_port_update_health_checks_addvalue(lsp, lsp_hc);\n+\n+cleanup:\n+ free(validated_src_ip);\n+}\n+\n+static char * OVS_WARN_UNUSED_RESULT\n+find_logical_switch_port_health_check_by_uuid(\n+ struct ctl_context *ctx,\n+ const char *id,\n+ const struct nbrec_logical_switch_port_health_check **lsp_hc_p)\n+{\n+ const struct nbrec_logical_switch_port_health_check *lsp_hc;\n+ *lsp_hc_p = NULL;\n+\n+ struct uuid lsp_hc_uuid;\n+ bool is_uuid = uuid_from_string(&lsp_hc_uuid, id);\n+\n+ if (!is_uuid) {\n+ return xasprintf(\"%s: Invalid UUID format.\", id);\n+ }\n+\n+ lsp_hc = nbrec_logical_switch_port_health_check_get_for_uuid(\n+ ctx->idl, &lsp_hc_uuid);\n+\n+ if (!lsp_hc) {\n+ return xasprintf(\"%s: Logical Switch Port Health Check \"\n+ \"not found.\", id);\n+ }\n+\n+ *lsp_hc_p = lsp_hc;\n+\n+ return NULL;\n+}\n+\n+static void\n+nbctl_pre_lsp_health_check_del(struct ctl_context *ctx)\n+{\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_col_name);\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_col_health_checks);\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_health_check_col_protocol);\n+}\n+\n+static void\n+nbctl_lsp_health_check_del(struct ctl_context *ctx)\n+{\n+ const struct nbrec_logical_switch_port_health_check *lsp_hc = NULL;\n+ const struct nbrec_logical_switch_port *lsp = NULL;\n+ const char *port_id = ctx->argv[1];\n+ const char *hc_uuid = ctx->argv[2];\n+\n+ char *error;\n+ error = lsp_by_name_or_uuid(ctx, port_id, true, &lsp);\n+ if (error) {\n+ ctx->error = error;\n+ return;\n+ }\n+\n+ if (hc_uuid) {\n+ error = find_logical_switch_port_health_check_by_uuid(ctx,\n+ hc_uuid,\n+ &lsp_hc);\n+ if (error) {\n+ ctx->error = error;\n+ return;\n+ }\n+\n+ nbrec_logical_switch_port_update_health_checks_delvalue(lsp, lsp_hc);\n+ nbrec_logical_switch_port_health_check_delete(lsp_hc);\n+ return;\n+ }\n+\n+ for (size_t i = 0; i < lsp->n_health_checks; i++) {\n+ nbrec_logical_switch_port_update_health_checks_delvalue(\n+ lsp, lsp->health_checks[i]);\n+ nbrec_logical_switch_port_health_check_delete(lsp->health_checks[i]);\n+ }\n+}\n+\n+static int\n+lsp_health_check_cmp(const void *lsp_hc_1_, const void *lsp_hc_2_)\n+{\n+ const struct nbrec_logical_switch_port_health_check *const *lsp_hc_1p =\n+ lsp_hc_1_;\n+ const struct nbrec_logical_switch_port_health_check *const *lsp_hc_2p =\n+ lsp_hc_2_;\n+ const struct nbrec_logical_switch_port_health_check *lsp_hc_1 =\n+ *lsp_hc_1p;\n+ const struct nbrec_logical_switch_port_health_check *lsp_hc_2 =\n+ *lsp_hc_2p;\n+\n+ int src_ip_cmp = strcmp(lsp_hc_1->src_ip, lsp_hc_2->src_ip);\n+ if (src_ip_cmp) {\n+ return src_ip_cmp;\n+ }\n+\n+ int protocol_cmp = strcmp(lsp_hc_1->protocol, lsp_hc_2->protocol);\n+ if (protocol_cmp) {\n+ return protocol_cmp;\n+ }\n+\n+ if (strcmp(lsp_hc_1->protocol, \"icmp\")) {\n+ if (lsp_hc_1->port != lsp_hc_2->port) {\n+ return lsp_hc_1->port < lsp_hc_2->port ? -1 : 1;\n+ }\n+ }\n+\n+ int ip_cmp = strcmp(lsp_hc_1->address, lsp_hc_2->address);\n+ if (ip_cmp) {\n+ return ip_cmp;\n+ }\n+\n+ return 0;\n+}\n+\n+static void\n+nbctl_pre_lsp_health_check_list(struct ctl_context *ctx)\n+{\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_col_name);\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_col_health_checks);\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_health_check_col_port);\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_health_check_col_protocol);\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_health_check_col_src_ip);\n+ ovsdb_idl_add_column(ctx->idl,\n+ &nbrec_logical_switch_port_health_check_col_address);\n+}\n+\n+static void\n+nbctl_lsp_health_check_list(struct ctl_context *ctx)\n+{\n+ const struct nbrec_logical_switch_port_health_check **lsp_hcs;\n+ const struct nbrec_logical_switch_port *lsp = NULL;\n+ const char *port = ctx->argv[1];\n+\n+ char *error;\n+ error = lsp_by_name_or_uuid(ctx, port, true, &lsp);\n+ if (error) {\n+ ctx->error = error;\n+ return;\n+ }\n+\n+ if (!lsp->n_health_checks) {\n+ return;\n+ }\n+\n+ lsp_hcs = xmalloc(sizeof *lsp_hcs * lsp->n_health_checks);\n+ for (size_t i = 0; i < lsp->n_health_checks; i++) {\n+ lsp_hcs[i] = lsp->health_checks[i];\n+ }\n+\n+ qsort(lsp_hcs, lsp->n_health_checks,\n+ sizeof *lsp_hcs, lsp_health_check_cmp);\n+\n+ ds_put_format(&ctx->output, \"Logical Switch Port %s:\\n\", port);\n+ for (size_t i = 0; i < lsp->n_health_checks; i++) {\n+ const struct nbrec_logical_switch_port_health_check *hc\n+\n+ = lsp_hcs[i];\n+ ds_put_format(&ctx->output, \" Protocol : %s\\n\",\n+ hc->protocol);\n+ ds_put_format(&ctx->output, \" Source IP : %s\\n\",\n+ hc->src_ip);\n+\n+ if (strcmp(hc->protocol, \"icmp\")) {\n+ ds_put_format(&ctx->output, \" Port : %\"PRId64\"\\n\",\n+ hc->port);\n+ }\n+\n+ ds_put_format(&ctx->output, \" Addresses : \");\n+ ds_put_format(&ctx->output, \"%s\", hc->address);\n+ ds_put_format(&ctx->output, \"\\n\");\n+\n+ int interval = smap_get_int(&hc->options, \"interval\", 0);\n+ int timeout = smap_get_int(&hc->options, \"timeout\", 0);\n+ int success_count = smap_get_int(&hc->options, \"success_count\", 0);\n+ int failure_count = smap_get_int(&hc->options, \"failure_count\", 0);\n+ if (interval) {\n+ ds_put_format(&ctx->output, \" Interval : %d\\n\",\n+ interval);\n+ }\n+ if (timeout) {\n+ ds_put_format(&ctx->output, \" Timeout : %d\\n\",\n+ timeout);\n+ }\n+ if (success_count) {\n+ ds_put_format(&ctx->output, \" Success count : %d\\n\",\n+ success_count);\n+ }\n+ if (failure_count) {\n+ ds_put_format(&ctx->output, \" Failure count : %d\\n\",\n+ failure_count);\n+ }\n+ if (i < lsp->n_health_checks - 1) {\n+ ds_put_format(&ctx->output, \"\\n\");\n+ }\n+ }\n+}\n+\n static const struct ctl_table_class tables[NBREC_N_TABLES] = {\n [NBREC_TABLE_DHCP_OPTIONS].row_ids\n = {{&nbrec_logical_switch_port_col_name, NULL,\n@@ -9064,6 +9538,16 @@ static const struct ctl_command_syntax nbctl_commands[] = {\n nbctl_pre_static_mac_binding, nbctl_static_mac_binding_list, NULL,\n \"\", RO },\n \n+ /* Health Check commands */\n+ {\"lsp-hc-add\", 4, 5, \"PORT TYPE SRC_IP [DST_PORT] ADDRESS\",\n+ nbctl_pre_lsp_health_check_add, nbctl_lsp_health_check_add,\n+ NULL, \"\", RW },\n+ {\"lsp-hc-del\", 1, INT_MAX, \"PORT [HC_UUID]\",\n+ nbctl_pre_lsp_health_check_del, nbctl_lsp_health_check_del,\n+ NULL, \"\", RW },\n+ {\"lsp-hc-list\", 1, 1, \"PORT\", nbctl_pre_lsp_health_check_list,\n+ nbctl_lsp_health_check_list, NULL, \"\", RW },\n+\n {NULL, 0, 0, NULL, NULL, NULL, NULL, \"\", RO},\n };\n \n", "prefixes": [ "ovs-dev", "1/8", "v2" ] }